Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions examples/bouncingBall_me.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from pythonfmu3 import Fmi3Causality, Fmi3Variability, Fmi3SlaveBase, ModelExchange, Fmi3Status, Float64, Fmi3Initial, Unit, Fmi3UpdateDiscreteStatesResult

from typing import List

import sys

EVENT_EPS = 1e-12

class BouncingBall(Fmi3SlaveBase, ModelExchange):

def __init__(self, **kwargs):
super().__init__(**kwargs)

self.author = "..."
self.description = "Bouncing Ball"

self.time = 0.0
self.h = 1.0
self.v = 0.0
self.derh = 0.0
self.derv = 0.0
self.g = -9.81
self.e = 0.7
self.v_min = 0.1

# define units
unit1 = Unit(name="m", m=1)
unit2 = Unit(name="m/s", m=1, s=-1)
unit3 = Unit(name="m/s2", m=1, s=-2)
self.register_units([unit1, unit2, unit3])


self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))

self.register_variable(Float64("ball.h", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact, unit=unit1.name),
nested=False, has_event_indicator=True)
self.register_variable(Float64("ball.derh", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1, unit=unit2.name),
nested=False)
self.register_variable(Float64("ball.v", causality=Fmi3Causality.output, start=0, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact, unit=unit2.name),
nested=False)
self.register_variable(Float64("ball.derv", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=3, unit=unit3.name),
nested=False)

self.register_variable(Float64("g", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed, unit=unit3.name))
self.register_variable(Float64("e", causality=Fmi3Causality.parameter, variability=Fmi3Variability.tunable))
self.register_variable(Float64("v_min", variability=Fmi3Variability.constant, start=0.1))



def get_continuous_state_derivatives(self) -> List[float]:
self.derh = self.v
self.derv = self.g
return [self.derh, self.derv]

def get_event_indicators(self) -> List[float]:
z = [self.h]
if self.h > -EVENT_EPS and self.h <=0 and self.v > 0:
z[0] = -EVENT_EPS

return z

def update_discrete_states(self):
fdsr = Fmi3UpdateDiscreteStatesResult()

if self.h <= 0 and self.v < 0:
self.h = sys.float_info.min
self.v = -self.v * self.e

if self.v < self.v_min:
self.v = 0.0;
self.g = 0.0;

fdsr.valuesOfContinuousStatesChanged = True

return fdsr
2 changes: 1 addition & 1 deletion pythonfmu3/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ._version import __version__
from .builder import FmuBuilder
from .cosimulation import CoSimulation
from .modelexchange import ModelExchange
from .modelexchange import ModelExchange, Fmi3UpdateDiscreteStatesResult
from .enums import Fmi3Causality, Fmi3Initial, Fmi3Status, Fmi3Variability
from .fmi3slave import Fmi3Slave, Fmi3SlaveBase, Fmi3StepResult
from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, String, Dimension
Expand Down
15 changes: 13 additions & 2 deletions pythonfmu3/fmi3slave.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Fmi3SlaveBase(object):

def __init__(self, **kwargs):
self.vars = OrderedDict()
self.event_indicators: List[int] = []
self.instance_name = kwargs["instance_name"]
self.resources = kwargs.get("resources", None)
self.visible = kwargs.get("visible", False)
Expand Down Expand Up @@ -184,7 +185,8 @@ def to_xml(self, model_options: Dict[str, str] = dict()) -> Element:
for v in initial_unknown:
SubElement(structure, "InitialUnknown", attrib=dict(valueReference=str(v.value_reference)))


for v in self.event_indicators:
SubElement(structure, "EventIndicator", attrib=dict(valueReference=str(v)))

return root

Expand All @@ -206,7 +208,7 @@ def __apply_start_value(self, var: ModelVariable):
raise Exception(f"Unsupported type {type(var)}!")
var.start = refs if len(getattr(var, "dimensions", [])) > 0 else refs[0]

def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None):
def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None, has_event_indicator: bool = False):
"""Register a variable as FMU interface.

Args:
Expand Down Expand Up @@ -238,6 +240,12 @@ def register_variable(self, var: ModelVariable, nested: bool = True, var_type: A
if var_type:
self.type_definitions[var_type.name] = var_type
var.declared_type = var_type.name

if has_event_indicator:
self.register_event_indicator(var.value_reference)

def register_event_indicator(self, vr):
self.event_indicators.append(vr)

def setup_experiment(self, start_time: float):
pass
Expand Down Expand Up @@ -416,6 +424,9 @@ def _set_fmu_state(self, state: Dict[str, Any]):
if v.setter is not None:
v.setter(value)

def get_number_of_event_indicators(self) -> int:
return len(self.event_indicators)

def set_continuous_states(self, values: List[float]):
offset = 0
continuous_state_derivatives = list(
Expand Down
27 changes: 26 additions & 1 deletion pythonfmu3/modelexchange.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
from abc import ABC, ABCMeta, abstractmethod
from dataclasses import dataclass

@dataclass
class Fmi3UpdateDiscreteStatesResult:
discreteStateNeedsUpdate: bool = False
terminateSimulation: bool = False
nominalsOfContinuousStatesChanged: bool = False
valuesOfContinuousStatesChanged: bool = False
nextEventTimeDefined: bool = False
nextEventTime: float = 0.0

class RequireTimeMeta(ABCMeta):
def __init__(cls, name, bases, namespace):
Expand All @@ -15,9 +25,24 @@ def new_init(self, *args, **kwargs):
class ModelExchange(ABC, metaclass=RequireTimeMeta):
"""
Classes derived from ModelExchange must define a 'self.time' member variable.

Required methods to override:
- `get_continuous_state_derivatives`: Must return a list of continuous state derivatives.

Optional methods:
- `get_event_indicators`: Should return a list of event indicators.
- `update_discrete_states`: Signify converged solution at current super-dense time instant.
"""

@abstractmethod
def get_continuous_state_derivatives(self):
"""Return the continuous state derivatives of the model."""
pass
pass

def get_event_indicators(self):
"""Return the event indicators of the model."""
return []

def update_discrete_states(self):
"""Update the discrete states of the model."""
return Fmi3UpdateDiscreteStatesResult()
1 change: 1 addition & 0 deletions pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class SlaveInstance
virtual void SetTime(cppfmu::FMIFloat64 time) = 0;
virtual void GetNumberOfContinuousStates(std::size_t& nStates) const = 0;
virtual void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const = 0;
virtual void GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nEventIndicators) const = 0;
virtual void UpdateDiscreteStates(
cppfmu::FMIBoolean* discreteStatesNeedUpdate,
cppfmu::FMIBoolean* terminateSimulation,
Expand Down
12 changes: 11 additions & 1 deletion pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,17 @@ fmi3Status fmi3GetEventIndicators(
fmi3Float64 eventIndicators[],
size_t nEventIndicators)
{
return fmi3OK;
const auto component = reinterpret_cast<Component*>(c);
try {
component->slave->GetEventIndicators(eventIndicators, nEventIndicators);
return fmi3OK;
} catch (const cppfmu::FatalError& e) {
component->logger.Log(fmi3Fatal, "", e.what());
return fmi3Fatal;
} catch (const std::exception& e) {
component->logger.Log(fmi3Error, "", e.what());
return fmi3Error;
}
}

fmi3Status fmi3GetNominalsOfContinuousStates(
Expand Down
78 changes: 71 additions & 7 deletions pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ void PySlaveInstance::GetContinuousStates(cppfmu::FMIFloat64* continuousStates,
PyObject* item = PyList_GetItem(f, i);
continuousStates[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
}
Py_DECREF(f);
clearLogBuffer();
});
}
Expand All @@ -551,13 +552,15 @@ void PySlaveInstance::GetContinuousStateDerivatives(cppfmu::FMIFloat64* continuo
py_safe_run([this, &continuousStateDerivatives, nStates](PyGILState_STATE gilState) {
auto f = PyObject_CallMethod(pInstance_, "get_continuous_state_derivatives", nullptr);
if (f == nullptr) {
PyErr_Print();
handle_py_exception("[get_continuous_state_derivatives] PyObject_CallMethod", gilState);
}
// Assuming f is a list of floats
for (std::size_t i = 0; i < nStates; i++) {
PyObject* item = PyList_GetItem(f, i);
continuousStateDerivatives[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
}
Py_DECREF(f);
clearLogBuffer();
});
}
Expand Down Expand Up @@ -602,9 +605,36 @@ void PySlaveInstance::GetNumberOfContinuousStates(std::size_t& nStates) const

void PySlaveInstance::GetNumberOfEventIndicators(std::size_t& nIndicators) const
{
nIndicators= 0u;
py_safe_run([this, &nIndicators](PyGILState_STATE gilState) {
auto f = PyObject_CallMethod(pInstance_, "get_number_of_event_indicators", nullptr);
if (f == nullptr) {
handle_py_exception("[getNumberOfEventIndicators] PyObject_CallMethod", gilState);
}
nIndicators = static_cast<std::size_t>(PyLong_AsLong(f));
Py_DECREF(f);
clearLogBuffer();
});
}

void PySlaveInstance::GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nIndicators) const
{
py_safe_run([this, &eventIndicators, nIndicators](PyGILState_STATE gilState) {
auto f = PyObject_CallMethod(pInstance_, "get_event_indicators", nullptr);
if (f == nullptr) {
handle_py_exception("[getEventIndicators] PyObject_CallMethod", gilState);
}
// Assuming f is a list of floats
for (std::size_t i = 0; i < nIndicators; i++) {
PyObject* item = PyList_GetItem(f, i);
eventIndicators[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
}
Py_DECREF(f);
clearLogBuffer();
});

}


void PySlaveInstance::SetTime(cppfmu::FMIFloat64 time)
{
py_safe_run([this, time](PyGILState_STATE gilState) {
Expand All @@ -625,12 +655,46 @@ void PySlaveInstance::UpdateDiscreteStates(
cppfmu::FMIBoolean* nextEventTimeDefined,
cppfmu::FMIFloat64* nextEventTime)
{
*discreteStatesNeedUpdate = false;
*terminateSimulation = false;
*nominalContinuousStatesChanged = false;
*valuesOfContinuousStatesChanged = false;
*nextEventTimeDefined = false;
*nextEventTime = 0.0;
py_safe_run([this, discreteStatesNeedUpdate, terminateSimulation, nominalContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime](PyGILState_STATE gilState) {
auto f = PyObject_CallMethod(pInstance_, "update_discrete_states", nullptr);
if (f == nullptr) {
handle_py_exception("[updateDiscreteStates] PyObject_CallMethod", gilState);
}

if (PyObject_HasAttrString(f, "discreteStatesNeedUpdate")) {
PyObject * pyDiscreteStatesNeedUpdate = PyObject_GetAttrString(f, "discreteStatesNeedUpdate");
*discreteStatesNeedUpdate = static_cast<bool>(PyObject_IsTrue(pyDiscreteStatesNeedUpdate));
Py_DECREF(pyDiscreteStatesNeedUpdate);
}
if (PyObject_HasAttrString(f, "terminateSimulation")) {
PyObject* pyTerminateSimulation = PyObject_GetAttrString(f, "terminateSimulation");
*terminateSimulation = static_cast<bool>(PyObject_IsTrue(pyTerminateSimulation));
Py_DECREF(pyTerminateSimulation);
}
if (PyObject_HasAttrString(f, "nominalContinuousStatesChanged")) {
PyObject* pyNominalContinuousStatesChanged = PyObject_GetAttrString(f, "nominalContinuousStatesChanged");
*nominalContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyNominalContinuousStatesChanged));
Py_DECREF(pyNominalContinuousStatesChanged);
}
if (PyObject_HasAttrString(f, "valuesOfContinuousStatesChanged")) {
PyObject* pyValuesOfContinuousStatesChanged = PyObject_GetAttrString(f, "valuesOfContinuousStatesChanged");
*valuesOfContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyValuesOfContinuousStatesChanged));
Py_DECREF(pyValuesOfContinuousStatesChanged);
}
if (PyObject_HasAttrString(f, "nextEventTimeDefined")) {
PyObject* pyNextEventTimeDefined = PyObject_GetAttrString(f, "nextEventTimeDefined");
*nextEventTimeDefined = static_cast<bool>(PyObject_IsTrue(pyNextEventTimeDefined));
Py_DECREF(pyNextEventTimeDefined);
}
if (nextEventTimeDefined && *nextEventTimeDefined) {
PyObject* pyNextEventTime = PyObject_GetAttrString(f, "nextEventTime");
*nextEventTime = PyFloat_AsDouble(pyNextEventTime);
Py_DECREF(pyNextEventTime);
}

Py_DECREF(f);
clearLogBuffer();
});
}

void PySlaveInstance::GetFMUstate(fmi3FMUState& state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const override;
void GetContinuousStateDerivatives(cppfmu::FMIFloat64* derivatives, std::size_t nStates) const override;
void GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalContinuousStates, std::size_t nStates) const override;
void GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nEventIndicators) const override;
void SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates) override;
void SetTime(cppfmu::FMIFloat64 time) override;
void UpdateDiscreteStates(
Expand Down