Skip to content

Commit 9fabf47

Browse files
Add event model exchange support (#31)
1 parent 28fe6d3 commit 9fabf47

8 files changed

Lines changed: 199 additions & 12 deletions

File tree

examples/bouncingBall_me.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from pythonfmu3 import Fmi3Causality, Fmi3Variability, Fmi3SlaveBase, ModelExchange, Fmi3Status, Float64, Fmi3Initial, Unit, Fmi3UpdateDiscreteStatesResult
2+
3+
from typing import List
4+
5+
import sys
6+
7+
EVENT_EPS = 1e-12
8+
9+
class BouncingBall(Fmi3SlaveBase, ModelExchange):
10+
11+
def __init__(self, **kwargs):
12+
super().__init__(**kwargs)
13+
14+
self.author = "..."
15+
self.description = "Bouncing Ball"
16+
17+
self.time = 0.0
18+
self.h = 1.0
19+
self.v = 0.0
20+
self.derh = 0.0
21+
self.derv = 0.0
22+
self.g = -9.81
23+
self.e = 0.7
24+
self.v_min = 0.1
25+
26+
# define units
27+
unit1 = Unit(name="m", m=1)
28+
unit2 = Unit(name="m/s", m=1, s=-1)
29+
unit3 = Unit(name="m/s2", m=1, s=-2)
30+
self.register_units([unit1, unit2, unit3])
31+
32+
33+
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
34+
35+
self.register_variable(Float64("ball.h", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact, unit=unit1.name),
36+
nested=False, has_event_indicator=True)
37+
self.register_variable(Float64("ball.derh", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1, unit=unit2.name),
38+
nested=False)
39+
self.register_variable(Float64("ball.v", causality=Fmi3Causality.output, start=0, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact, unit=unit2.name),
40+
nested=False)
41+
self.register_variable(Float64("ball.derv", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=3, unit=unit3.name),
42+
nested=False)
43+
44+
self.register_variable(Float64("g", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed, unit=unit3.name))
45+
self.register_variable(Float64("e", causality=Fmi3Causality.parameter, variability=Fmi3Variability.tunable))
46+
self.register_variable(Float64("v_min", variability=Fmi3Variability.constant, start=0.1))
47+
48+
49+
50+
def get_continuous_state_derivatives(self) -> List[float]:
51+
self.derh = self.v
52+
self.derv = self.g
53+
return [self.derh, self.derv]
54+
55+
def get_event_indicators(self) -> List[float]:
56+
z = [self.h]
57+
if self.h > -EVENT_EPS and self.h <=0 and self.v > 0:
58+
z[0] = -EVENT_EPS
59+
60+
return z
61+
62+
def update_discrete_states(self):
63+
fdsr = Fmi3UpdateDiscreteStatesResult()
64+
65+
if self.h <= 0 and self.v < 0:
66+
self.h = sys.float_info.min
67+
self.v = -self.v * self.e
68+
69+
if self.v < self.v_min:
70+
self.v = 0.0;
71+
self.g = 0.0;
72+
73+
fdsr.valuesOfContinuousStatesChanged = True
74+
75+
return fdsr

pythonfmu3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ._version import __version__
22
from .builder import FmuBuilder
33
from .cosimulation import CoSimulation
4-
from .modelexchange import ModelExchange
4+
from .modelexchange import ModelExchange, Fmi3UpdateDiscreteStatesResult
55
from .enums import Fmi3Causality, Fmi3Initial, Fmi3Status, Fmi3Variability
66
from .fmi3slave import Fmi3Slave, Fmi3SlaveBase, Fmi3StepResult
77
from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, String, Dimension

pythonfmu3/fmi3slave.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Fmi3SlaveBase(object):
5050

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

187-
188+
for v in self.event_indicators:
189+
SubElement(structure, "EventIndicator", attrib=dict(valueReference=str(v)))
188190

189191
return root
190192

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

209-
def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None):
211+
def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None, has_event_indicator: bool = False):
210212
"""Register a variable as FMU interface.
211213
212214
Args:
@@ -238,6 +240,12 @@ def register_variable(self, var: ModelVariable, nested: bool = True, var_type: A
238240
if var_type:
239241
self.type_definitions[var_type.name] = var_type
240242
var.declared_type = var_type.name
243+
244+
if has_event_indicator:
245+
self.register_event_indicator(var.value_reference)
246+
247+
def register_event_indicator(self, vr):
248+
self.event_indicators.append(vr)
241249

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

427+
def get_number_of_event_indicators(self) -> int:
428+
return len(self.event_indicators)
429+
419430
def set_continuous_states(self, values: List[float]):
420431
offset = 0
421432
continuous_state_derivatives = list(

pythonfmu3/modelexchange.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
from abc import ABC, ABCMeta, abstractmethod
2+
from dataclasses import dataclass
3+
4+
@dataclass
5+
class Fmi3UpdateDiscreteStatesResult:
6+
discreteStateNeedsUpdate: bool = False
7+
terminateSimulation: bool = False
8+
nominalsOfContinuousStatesChanged: bool = False
9+
valuesOfContinuousStatesChanged: bool = False
10+
nextEventTimeDefined: bool = False
11+
nextEventTime: float = 0.0
212

313
class RequireTimeMeta(ABCMeta):
414
def __init__(cls, name, bases, namespace):
@@ -15,9 +25,24 @@ def new_init(self, *args, **kwargs):
1525
class ModelExchange(ABC, metaclass=RequireTimeMeta):
1626
"""
1727
Classes derived from ModelExchange must define a 'self.time' member variable.
28+
29+
Required methods to override:
30+
- `get_continuous_state_derivatives`: Must return a list of continuous state derivatives.
31+
32+
Optional methods:
33+
- `get_event_indicators`: Should return a list of event indicators.
34+
- `update_discrete_states`: Signify converged solution at current super-dense time instant.
1835
"""
1936

2037
@abstractmethod
2138
def get_continuous_state_derivatives(self):
2239
"""Return the continuous state derivatives of the model."""
23-
pass
40+
pass
41+
42+
def get_event_indicators(self):
43+
"""Return the event indicators of the model."""
44+
return []
45+
46+
def update_discrete_states(self):
47+
"""Update the discrete states of the model."""
48+
return Fmi3UpdateDiscreteStatesResult()

pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class SlaveInstance
141141
virtual void SetTime(cppfmu::FMIFloat64 time) = 0;
142142
virtual void GetNumberOfContinuousStates(std::size_t& nStates) const = 0;
143143
virtual void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const = 0;
144+
virtual void GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nEventIndicators) const = 0;
144145
virtual void UpdateDiscreteStates(
145146
cppfmu::FMIBoolean* discreteStatesNeedUpdate,
146147
cppfmu::FMIBoolean* terminateSimulation,

pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,17 @@ fmi3Status fmi3GetEventIndicators(
622622
fmi3Float64 eventIndicators[],
623623
size_t nEventIndicators)
624624
{
625-
return fmi3OK;
625+
const auto component = reinterpret_cast<Component*>(c);
626+
try {
627+
component->slave->GetEventIndicators(eventIndicators, nEventIndicators);
628+
return fmi3OK;
629+
} catch (const cppfmu::FatalError& e) {
630+
component->logger.Log(fmi3Fatal, "", e.what());
631+
return fmi3Fatal;
632+
} catch (const std::exception& e) {
633+
component->logger.Log(fmi3Error, "", e.what());
634+
return fmi3Error;
635+
}
626636
}
627637

628638
fmi3Status fmi3GetNominalsOfContinuousStates(

pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ void PySlaveInstance::GetContinuousStates(cppfmu::FMIFloat64* continuousStates,
542542
PyObject* item = PyList_GetItem(f, i);
543543
continuousStates[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
544544
}
545+
Py_DECREF(f);
545546
clearLogBuffer();
546547
});
547548
}
@@ -551,13 +552,15 @@ void PySlaveInstance::GetContinuousStateDerivatives(cppfmu::FMIFloat64* continuo
551552
py_safe_run([this, &continuousStateDerivatives, nStates](PyGILState_STATE gilState) {
552553
auto f = PyObject_CallMethod(pInstance_, "get_continuous_state_derivatives", nullptr);
553554
if (f == nullptr) {
555+
PyErr_Print();
554556
handle_py_exception("[get_continuous_state_derivatives] PyObject_CallMethod", gilState);
555557
}
556558
// Assuming f is a list of floats
557559
for (std::size_t i = 0; i < nStates; i++) {
558560
PyObject* item = PyList_GetItem(f, i);
559561
continuousStateDerivatives[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
560562
}
563+
Py_DECREF(f);
561564
clearLogBuffer();
562565
});
563566
}
@@ -602,9 +605,36 @@ void PySlaveInstance::GetNumberOfContinuousStates(std::size_t& nStates) const
602605

603606
void PySlaveInstance::GetNumberOfEventIndicators(std::size_t& nIndicators) const
604607
{
605-
nIndicators= 0u;
608+
py_safe_run([this, &nIndicators](PyGILState_STATE gilState) {
609+
auto f = PyObject_CallMethod(pInstance_, "get_number_of_event_indicators", nullptr);
610+
if (f == nullptr) {
611+
handle_py_exception("[getNumberOfEventIndicators] PyObject_CallMethod", gilState);
612+
}
613+
nIndicators = static_cast<std::size_t>(PyLong_AsLong(f));
614+
Py_DECREF(f);
615+
clearLogBuffer();
616+
});
617+
}
618+
619+
void PySlaveInstance::GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nIndicators) const
620+
{
621+
py_safe_run([this, &eventIndicators, nIndicators](PyGILState_STATE gilState) {
622+
auto f = PyObject_CallMethod(pInstance_, "get_event_indicators", nullptr);
623+
if (f == nullptr) {
624+
handle_py_exception("[getEventIndicators] PyObject_CallMethod", gilState);
625+
}
626+
// Assuming f is a list of floats
627+
for (std::size_t i = 0; i < nIndicators; i++) {
628+
PyObject* item = PyList_GetItem(f, i);
629+
eventIndicators[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
630+
}
631+
Py_DECREF(f);
632+
clearLogBuffer();
633+
});
634+
606635
}
607636

637+
608638
void PySlaveInstance::SetTime(cppfmu::FMIFloat64 time)
609639
{
610640
py_safe_run([this, time](PyGILState_STATE gilState) {
@@ -625,12 +655,46 @@ void PySlaveInstance::UpdateDiscreteStates(
625655
cppfmu::FMIBoolean* nextEventTimeDefined,
626656
cppfmu::FMIFloat64* nextEventTime)
627657
{
628-
*discreteStatesNeedUpdate = false;
629-
*terminateSimulation = false;
630-
*nominalContinuousStatesChanged = false;
631-
*valuesOfContinuousStatesChanged = false;
632-
*nextEventTimeDefined = false;
633-
*nextEventTime = 0.0;
658+
py_safe_run([this, discreteStatesNeedUpdate, terminateSimulation, nominalContinuousStatesChanged, valuesOfContinuousStatesChanged, nextEventTimeDefined, nextEventTime](PyGILState_STATE gilState) {
659+
auto f = PyObject_CallMethod(pInstance_, "update_discrete_states", nullptr);
660+
if (f == nullptr) {
661+
handle_py_exception("[updateDiscreteStates] PyObject_CallMethod", gilState);
662+
}
663+
664+
if (PyObject_HasAttrString(f, "discreteStatesNeedUpdate")) {
665+
PyObject * pyDiscreteStatesNeedUpdate = PyObject_GetAttrString(f, "discreteStatesNeedUpdate");
666+
*discreteStatesNeedUpdate = static_cast<bool>(PyObject_IsTrue(pyDiscreteStatesNeedUpdate));
667+
Py_DECREF(pyDiscreteStatesNeedUpdate);
668+
}
669+
if (PyObject_HasAttrString(f, "terminateSimulation")) {
670+
PyObject* pyTerminateSimulation = PyObject_GetAttrString(f, "terminateSimulation");
671+
*terminateSimulation = static_cast<bool>(PyObject_IsTrue(pyTerminateSimulation));
672+
Py_DECREF(pyTerminateSimulation);
673+
}
674+
if (PyObject_HasAttrString(f, "nominalContinuousStatesChanged")) {
675+
PyObject* pyNominalContinuousStatesChanged = PyObject_GetAttrString(f, "nominalContinuousStatesChanged");
676+
*nominalContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyNominalContinuousStatesChanged));
677+
Py_DECREF(pyNominalContinuousStatesChanged);
678+
}
679+
if (PyObject_HasAttrString(f, "valuesOfContinuousStatesChanged")) {
680+
PyObject* pyValuesOfContinuousStatesChanged = PyObject_GetAttrString(f, "valuesOfContinuousStatesChanged");
681+
*valuesOfContinuousStatesChanged = static_cast<bool>(PyObject_IsTrue(pyValuesOfContinuousStatesChanged));
682+
Py_DECREF(pyValuesOfContinuousStatesChanged);
683+
}
684+
if (PyObject_HasAttrString(f, "nextEventTimeDefined")) {
685+
PyObject* pyNextEventTimeDefined = PyObject_GetAttrString(f, "nextEventTimeDefined");
686+
*nextEventTimeDefined = static_cast<bool>(PyObject_IsTrue(pyNextEventTimeDefined));
687+
Py_DECREF(pyNextEventTimeDefined);
688+
}
689+
if (nextEventTimeDefined && *nextEventTimeDefined) {
690+
PyObject* pyNextEventTime = PyObject_GetAttrString(f, "nextEventTime");
691+
*nextEventTime = PyFloat_AsDouble(pyNextEventTime);
692+
Py_DECREF(pyNextEventTime);
693+
}
694+
695+
Py_DECREF(f);
696+
clearLogBuffer();
697+
});
634698
}
635699

636700
void PySlaveInstance::GetFMUstate(fmi3FMUState& state)

pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
5757
void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const override;
5858
void GetContinuousStateDerivatives(cppfmu::FMIFloat64* derivatives, std::size_t nStates) const override;
5959
void GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalContinuousStates, std::size_t nStates) const override;
60+
void GetEventIndicators(cppfmu::FMIFloat64* eventIndicators, std::size_t nEventIndicators) const override;
6061
void SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates) override;
6162
void SetTime(cppfmu::FMIFloat64 time) override;
6263
void UpdateDiscreteStates(

0 commit comments

Comments
 (0)