pythonfmu3 0.2.4__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonfmu3/__init__.py +3 -1
- pythonfmu3/_version.py +1 -1
- pythonfmu3/builder.py +14 -7
- pythonfmu3/cosimulation.py +8 -0
- pythonfmu3/fmi3slave.py +63 -4
- pythonfmu3/modelexchange.py +23 -0
- pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +17 -0
- pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +215 -82
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +105 -2
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +17 -0
- pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
- pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3/tests/slaves/pythonslaveMX.py +27 -0
- pythonfmu3/tests/slaves/pythonslaveMXCS.py +32 -0
- pythonfmu3/tests/test_fmi3slave.py +16 -1
- pythonfmu3/tests/test_integration.py +61 -0
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/METADATA +61 -8
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/RECORD +22 -18
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/WHEEL +0 -0
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/entry_points.txt +0 -0
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {pythonfmu3-0.2.4.dist-info → pythonfmu3-0.3.0.dist-info}/top_level.txt +0 -0
pythonfmu3/__init__.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from ._version import __version__
|
|
2
2
|
from .builder import FmuBuilder
|
|
3
|
+
from .cosimulation import CoSimulation
|
|
4
|
+
from .modelexchange import ModelExchange
|
|
3
5
|
from .enums import Fmi3Causality, Fmi3Initial, Fmi3Status, Fmi3Variability
|
|
4
|
-
from .fmi3slave import Fmi3Slave, Fmi3StepResult
|
|
6
|
+
from .fmi3slave import Fmi3Slave, Fmi3SlaveBase, Fmi3StepResult
|
|
5
7
|
from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, String, Dimension
|
|
6
8
|
from .default_experiment import DefaultExperiment
|
|
7
9
|
from .variable_types import Float64Type, EnumerationType
|
pythonfmu3/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
pythonfmu3/builder.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import Iterable, Optional, Tuple, Union
|
|
|
13
13
|
from xml.dom.minidom import parseString
|
|
14
14
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
|
15
15
|
from .osutil import get_lib_extension, get_platform
|
|
16
|
-
from .fmi3slave import FMI3_MODEL_OPTIONS, Fmi3Slave
|
|
16
|
+
from .fmi3slave import FMI3_MODEL_OPTIONS, Fmi3Slave, Fmi3SlaveBase
|
|
17
17
|
|
|
18
18
|
FilePath = Union[str, Path]
|
|
19
19
|
HERE = Path(__file__).parent
|
|
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|
|
24
24
|
def get_class_name(file_name: Path) -> str:
|
|
25
25
|
with open(str(file_name), 'r') as file:
|
|
26
26
|
data = file.read()
|
|
27
|
-
return re.search(r'class (\w+)\(
|
|
27
|
+
return re.search(r'class (\w+)\(([^)]*\bFmi3Slave(?:Base)?\b[^)]*)\)\s*:', data).group(1)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Element]:
|
|
@@ -50,9 +50,9 @@ def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Elemen
|
|
|
50
50
|
finally:
|
|
51
51
|
sys.path.remove(str(filepath.parent)) # remove inserted temporary path
|
|
52
52
|
|
|
53
|
-
if not isinstance(instance,
|
|
53
|
+
if not isinstance(instance, Fmi3SlaveBase):
|
|
54
54
|
raise TypeError(
|
|
55
|
-
f"The provided class '{class_name}' does not inherit from {
|
|
55
|
+
f"The provided class '{class_name}' does not inherit from {Fmi3SlaveBase.__qualname__}"
|
|
56
56
|
)
|
|
57
57
|
# Produce the xml
|
|
58
58
|
return instance.modelName, instance.to_xml()
|
|
@@ -138,9 +138,16 @@ class FmuBuilder:
|
|
|
138
138
|
|
|
139
139
|
type_node = xml.find("CoSimulation")
|
|
140
140
|
option_names = [opt.name for opt in FMI3_MODEL_OPTIONS]
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
if type_node:
|
|
142
|
+
for option, value in options.items():
|
|
143
|
+
if option in option_names:
|
|
144
|
+
type_node.set(option, str(value).lower())
|
|
145
|
+
|
|
146
|
+
type_node = xml.find("ModelExchange")
|
|
147
|
+
if type_node:
|
|
148
|
+
for option, value in options.items():
|
|
149
|
+
if option in option_names:
|
|
150
|
+
type_node.set(option, str(value).lower())
|
|
144
151
|
|
|
145
152
|
with zipfile.ZipFile(dest_file, "w") as zip_fmu:
|
|
146
153
|
|
pythonfmu3/fmi3slave.py
CHANGED
|
@@ -11,6 +11,8 @@ from xml.etree.ElementTree import Element, SubElement
|
|
|
11
11
|
|
|
12
12
|
from .logmsg import LogMsg
|
|
13
13
|
from .default_experiment import DefaultExperiment
|
|
14
|
+
from .cosimulation import CoSimulation
|
|
15
|
+
from .modelexchange import ModelExchange
|
|
14
16
|
from ._version import __version__ as VERSION
|
|
15
17
|
from .enums import Fmi3Type, Fmi3Status, Fmi3Causality, Fmi3Initial, Fmi3Variability
|
|
16
18
|
from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, ModelVariable, String
|
|
@@ -33,7 +35,8 @@ class Fmi3StepResult(NamedTuple):
|
|
|
33
35
|
terminateSimulation: bool = False
|
|
34
36
|
earlyReturn: bool = False
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
|
|
39
|
+
class Fmi3SlaveBase(object):
|
|
37
40
|
"""Abstract facade class to execute Python through FMI standard."""
|
|
38
41
|
|
|
39
42
|
# Dictionary of (category, description) entries
|
|
@@ -104,8 +107,18 @@ class Fmi3Slave(ABC):
|
|
|
104
107
|
options[option.name] = str(value).lower()
|
|
105
108
|
options["modelIdentifier"] = self.modelName
|
|
106
109
|
options["canNotUseMemoryManagementFunctions"] = "true"
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
|
|
111
|
+
options_me = dict()
|
|
112
|
+
options_me["canGetAndSetFMUState"] = "true"
|
|
113
|
+
options_me["modelIdentifier"] = self.modelName
|
|
114
|
+
options_me["needsCompletedIntegratorStep"] = "false"
|
|
115
|
+
|
|
116
|
+
# check if we have cosim mixin or model exchange mixin
|
|
117
|
+
if isinstance(self, ModelExchange):
|
|
118
|
+
SubElement(root, "ModelExchange", attrib=options_me)
|
|
119
|
+
|
|
120
|
+
if isinstance(self, CoSimulation):
|
|
121
|
+
SubElement(root, "CoSimulation", attrib=options)
|
|
109
122
|
|
|
110
123
|
if self.units:
|
|
111
124
|
unit_defs = SubElement(root, "UnitDefinitions")
|
|
@@ -239,7 +252,6 @@ class Fmi3Slave(ABC):
|
|
|
239
252
|
def exit_initialization_mode(self):
|
|
240
253
|
pass
|
|
241
254
|
|
|
242
|
-
@abstractmethod
|
|
243
255
|
def do_step(self, current_time: float, step_size: float) -> Fmi3StepResult:
|
|
244
256
|
pass
|
|
245
257
|
|
|
@@ -404,6 +416,50 @@ class Fmi3Slave(ABC):
|
|
|
404
416
|
if v.setter is not None:
|
|
405
417
|
v.setter(value)
|
|
406
418
|
|
|
419
|
+
def set_continuous_states(self, values: List[float]):
|
|
420
|
+
offset = 0
|
|
421
|
+
continuous_state_derivatives = list(
|
|
422
|
+
filter(lambda v: v.variability == Fmi3Variability.continuous and (isinstance(v, Float64) and v.derivative is not None), self.vars.values())
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
vrs = [v.derivative for v in continuous_state_derivatives]
|
|
426
|
+
|
|
427
|
+
for vr in vrs:
|
|
428
|
+
var = self.vars[vr]
|
|
429
|
+
size = var.size(self.vars)
|
|
430
|
+
if size > 1:
|
|
431
|
+
var.setter(values[offset:offset+size])
|
|
432
|
+
else:
|
|
433
|
+
var.setter(values[offset])
|
|
434
|
+
offset += size
|
|
435
|
+
|
|
436
|
+
def get_continuous_states(self) -> List[float]:
|
|
437
|
+
offset = 0
|
|
438
|
+
continuous_state_derivatives = list(
|
|
439
|
+
filter(lambda v: v.variability == Fmi3Variability.continuous and (isinstance(v, Float64) and v.derivative is not None), self.vars.values())
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
vrs = [v.derivative for v in continuous_state_derivatives]
|
|
443
|
+
|
|
444
|
+
refs = list()
|
|
445
|
+
for vr in vrs:
|
|
446
|
+
var = self.vars[vr]
|
|
447
|
+
if len(var.dimensions) == 0:
|
|
448
|
+
refs.append(float(var.getter()))
|
|
449
|
+
else:
|
|
450
|
+
refs.extend(var.getter())
|
|
451
|
+
|
|
452
|
+
return refs
|
|
453
|
+
|
|
454
|
+
def get_number_of_continuous_states(self) -> int:
|
|
455
|
+
continuous_state_derivatives = list(
|
|
456
|
+
filter(lambda v: v.variability == Fmi3Variability.continuous and (isinstance(v, Float64) and v.derivative is not None), self.vars.values())
|
|
457
|
+
)
|
|
458
|
+
return len(continuous_state_derivatives)
|
|
459
|
+
|
|
460
|
+
def set_time(self, time: float):
|
|
461
|
+
self.time = time
|
|
462
|
+
|
|
407
463
|
@staticmethod
|
|
408
464
|
def _fmu_state_to_bytes(state: Dict[str, Any]) -> bytes:
|
|
409
465
|
return json.dumps(state).encode("utf-8")
|
|
@@ -436,3 +492,6 @@ class Fmi3Slave(ABC):
|
|
|
436
492
|
category = "logAll"
|
|
437
493
|
log_msg = LogMsg(status, category, msg, debug)
|
|
438
494
|
self.log_queue.append(log_msg)
|
|
495
|
+
|
|
496
|
+
class Fmi3Slave(Fmi3SlaveBase, CoSimulation):
|
|
497
|
+
pass
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
2
|
+
|
|
3
|
+
class RequireTimeMeta(ABCMeta):
|
|
4
|
+
def __init__(cls, name, bases, namespace):
|
|
5
|
+
super().__init__(name, bases, namespace)
|
|
6
|
+
orig_init = cls.__init__
|
|
7
|
+
def new_init(self, *args, **kwargs):
|
|
8
|
+
orig_init(self, *args, **kwargs)
|
|
9
|
+
if not hasattr(self, 'time'):
|
|
10
|
+
raise AttributeError(f"{cls.__name__} must define 'self.time' in __init__")
|
|
11
|
+
cls.__init__ = new_init
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# model exchange mixin
|
|
15
|
+
class ModelExchange(ABC, metaclass=RequireTimeMeta):
|
|
16
|
+
"""
|
|
17
|
+
Classes derived from ModelExchange must define a 'self.time' member variable.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_continuous_state_derivatives(self):
|
|
22
|
+
"""Return the continuous state derivatives of the model."""
|
|
23
|
+
pass
|
|
@@ -133,6 +133,23 @@ public:
|
|
|
133
133
|
FMIBoolean* earlyReturn,
|
|
134
134
|
FMIFloat64& endOfStep) = 0;
|
|
135
135
|
|
|
136
|
+
// Model Exchange methods
|
|
137
|
+
virtual void GetContinuousStates(cppfmu::FMIFloat64* continuousStates, std::size_t nStates) const = 0;
|
|
138
|
+
virtual void GetContinuousStateDerivatives(cppfmu::FMIFloat64* derivatives, std::size_t nStates) const = 0;
|
|
139
|
+
virtual void GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalContinuousStates, std::size_t nStates) const = 0;
|
|
140
|
+
virtual void SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates) = 0;
|
|
141
|
+
virtual void SetTime(cppfmu::FMIFloat64 time) = 0;
|
|
142
|
+
virtual void GetNumberOfContinuousStates(std::size_t& nStates) const = 0;
|
|
143
|
+
virtual void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const = 0;
|
|
144
|
+
virtual void UpdateDiscreteStates(
|
|
145
|
+
cppfmu::FMIBoolean* discreteStatesNeedUpdate,
|
|
146
|
+
cppfmu::FMIBoolean* terminateSimulation,
|
|
147
|
+
cppfmu::FMIBoolean* nominalContinuousStatesChanged,
|
|
148
|
+
cppfmu::FMIBoolean* valuesOfContinuousStatesChanged,
|
|
149
|
+
cppfmu::FMIBoolean* nextEventTimeDefined,
|
|
150
|
+
cppfmu::FMIFloat64* nextEventTime) = 0;
|
|
151
|
+
|
|
152
|
+
|
|
136
153
|
virtual void GetFMUstate(fmi3FMUState& state) = 0;
|
|
137
154
|
virtual void SetFMUstate(const fmi3FMUState& state) = 0;
|
|
138
155
|
virtual void FreeFMUstate(fmi3FMUState& state) = 0;
|
|
@@ -110,10 +110,16 @@ fmi3Instance fmi3InstantiateCoSimulation(
|
|
|
110
110
|
component->logger);
|
|
111
111
|
return component;
|
|
112
112
|
} catch (const cppfmu::FatalError& e) {
|
|
113
|
-
|
|
113
|
+
if (logMessage)
|
|
114
|
+
{
|
|
115
|
+
logMessage(environment, fmi3Fatal, "", e.what());
|
|
116
|
+
}
|
|
114
117
|
return nullptr;
|
|
115
118
|
} catch (const std::exception& e) {
|
|
116
|
-
|
|
119
|
+
if (logMessage)
|
|
120
|
+
{
|
|
121
|
+
logMessage(environment, fmi3Error, "", e.what());
|
|
122
|
+
}
|
|
117
123
|
return nullptr;
|
|
118
124
|
}
|
|
119
125
|
}
|
|
@@ -124,10 +130,38 @@ fmi3Instance fmi3InstantiateModelExchange(
|
|
|
124
130
|
fmi3String resourcePath,
|
|
125
131
|
fmi3Boolean visible,
|
|
126
132
|
fmi3Boolean loggingOn,
|
|
127
|
-
fmi3InstanceEnvironment
|
|
133
|
+
fmi3InstanceEnvironment environment,
|
|
128
134
|
fmi3LogMessageCallback logMessage)
|
|
129
135
|
{
|
|
130
|
-
|
|
136
|
+
try {
|
|
137
|
+
auto component = new Component(
|
|
138
|
+
environment,
|
|
139
|
+
logMessage,
|
|
140
|
+
loggingOn);
|
|
141
|
+
component->slave = CppfmuInstantiateSlave(
|
|
142
|
+
instanceName,
|
|
143
|
+
instantiationToken,
|
|
144
|
+
resourcePath,
|
|
145
|
+
"application/x-fmu-sharedlibrary",
|
|
146
|
+
0.0,
|
|
147
|
+
visible,
|
|
148
|
+
cppfmu::FMIFalse,
|
|
149
|
+
component->logger);
|
|
150
|
+
return component;
|
|
151
|
+
} catch (const cppfmu::FatalError& e) {
|
|
152
|
+
if (logMessage)
|
|
153
|
+
{
|
|
154
|
+
logMessage(environment, fmi3Fatal, "", e.what());
|
|
155
|
+
}
|
|
156
|
+
return nullptr;
|
|
157
|
+
} catch (const std::exception& e) {
|
|
158
|
+
if (logMessage)
|
|
159
|
+
{
|
|
160
|
+
logMessage(environment, fmi3Error, "", e.what());
|
|
161
|
+
}
|
|
162
|
+
return nullptr;
|
|
163
|
+
}
|
|
164
|
+
|
|
131
165
|
}
|
|
132
166
|
|
|
133
167
|
fmi3Instance fmi3InstantiateScheduledExecution(
|
|
@@ -512,6 +546,183 @@ fmi3Status fmi3SetString(
|
|
|
512
546
|
}
|
|
513
547
|
}
|
|
514
548
|
|
|
549
|
+
fmi3Status fmi3GetContinuousStates(
|
|
550
|
+
fmi3Instance c,
|
|
551
|
+
fmi3Float64 continuousStates[],
|
|
552
|
+
size_t nStates)
|
|
553
|
+
{
|
|
554
|
+
auto component = reinterpret_cast<Component*>(c);
|
|
555
|
+
try {
|
|
556
|
+
component->slave->GetContinuousStates(continuousStates, nStates);
|
|
557
|
+
return fmi3OK;
|
|
558
|
+
} catch (const cppfmu::FatalError& e) {
|
|
559
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
560
|
+
return fmi3Fatal;
|
|
561
|
+
} catch (const std::exception& e) {
|
|
562
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
563
|
+
return fmi3Error;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
fmi3Status fmi3GetContinuousStateDerivatives(
|
|
568
|
+
fmi3Instance c,
|
|
569
|
+
fmi3Float64 derivatives[],
|
|
570
|
+
size_t nStates)
|
|
571
|
+
{
|
|
572
|
+
auto component = reinterpret_cast<Component*>(c);
|
|
573
|
+
try {
|
|
574
|
+
component->slave->GetContinuousStateDerivatives(derivatives, nStates);
|
|
575
|
+
return fmi3OK;
|
|
576
|
+
} catch (const cppfmu::FatalError& e) {
|
|
577
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
578
|
+
return fmi3Fatal;
|
|
579
|
+
} catch (const std::exception& e) {
|
|
580
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
581
|
+
return fmi3Error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
fmi3Status fmi3SetTime(
|
|
586
|
+
fmi3Instance c,
|
|
587
|
+
fmi3Float64 time)
|
|
588
|
+
{
|
|
589
|
+
auto component = reinterpret_cast<Component*>(c);
|
|
590
|
+
try {
|
|
591
|
+
component->slave->SetTime(time);
|
|
592
|
+
return fmi3OK;
|
|
593
|
+
} catch (const cppfmu::FatalError& e) {
|
|
594
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
595
|
+
return fmi3Fatal;
|
|
596
|
+
} catch (const std::exception& e) {
|
|
597
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
598
|
+
return fmi3Error;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
fmi3Status fmi3SetContinuousStates(
|
|
603
|
+
fmi3Instance c,
|
|
604
|
+
const fmi3Float64 continuousStates[],
|
|
605
|
+
size_t nStates)
|
|
606
|
+
{
|
|
607
|
+
auto component = reinterpret_cast<Component*>(c);
|
|
608
|
+
try {
|
|
609
|
+
component->slave->SetContinuousStates(continuousStates, nStates);
|
|
610
|
+
return fmi3OK;
|
|
611
|
+
} catch (const cppfmu::FatalError& e) {
|
|
612
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
613
|
+
return fmi3Fatal;
|
|
614
|
+
} catch (const std::exception& e) {
|
|
615
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
616
|
+
return fmi3Error;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
fmi3Status fmi3GetEventIndicators(
|
|
621
|
+
fmi3Instance c,
|
|
622
|
+
fmi3Float64 eventIndicators[],
|
|
623
|
+
size_t nEventIndicators)
|
|
624
|
+
{
|
|
625
|
+
return fmi3OK;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
fmi3Status fmi3GetNominalsOfContinuousStates(
|
|
629
|
+
fmi3Instance c,
|
|
630
|
+
fmi3Float64 nominalContinuousStates[],
|
|
631
|
+
size_t nStates)
|
|
632
|
+
{
|
|
633
|
+
const auto component = reinterpret_cast<Component*>(c);
|
|
634
|
+
try {
|
|
635
|
+
component->slave->GetNominalsOfContinuousStates(nominalContinuousStates, nStates);
|
|
636
|
+
return fmi3OK;
|
|
637
|
+
} catch (const cppfmu::FatalError& e) {
|
|
638
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
639
|
+
return fmi3Fatal;
|
|
640
|
+
} catch (const std::exception& e) {
|
|
641
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
642
|
+
return fmi3Error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
fmi3Status fmi3GetNumberOfContinuousStates(
|
|
647
|
+
fmi3Instance c,
|
|
648
|
+
std::size_t * nContinuousStates)
|
|
649
|
+
{
|
|
650
|
+
const auto component = reinterpret_cast<Component*>(c);
|
|
651
|
+
try {
|
|
652
|
+
component->slave->GetNumberOfContinuousStates(*nContinuousStates);
|
|
653
|
+
return fmi3OK;
|
|
654
|
+
} catch (const cppfmu::FatalError& e) {
|
|
655
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
656
|
+
return fmi3Fatal;
|
|
657
|
+
} catch (const std::exception& e) {
|
|
658
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
659
|
+
return fmi3Error;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
fmi3Status fmi3GetNumberOfEventIndicators(
|
|
664
|
+
fmi3Instance c,
|
|
665
|
+
std::size_t * nEventIndicators)
|
|
666
|
+
{
|
|
667
|
+
const auto component = reinterpret_cast<Component*>(c);
|
|
668
|
+
try {
|
|
669
|
+
component->slave->GetNumberOfEventIndicators(*nEventIndicators);
|
|
670
|
+
return fmi3OK;
|
|
671
|
+
} catch (const cppfmu::FatalError& e) {
|
|
672
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
673
|
+
return fmi3Fatal;
|
|
674
|
+
} catch (const std::exception& e) {
|
|
675
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
676
|
+
return fmi3Error;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
fmi3Status fmi3UpdateDiscreteStates(fmi3Instance c,
|
|
682
|
+
fmi3Boolean* discreteStatesNeedUpdate,
|
|
683
|
+
fmi3Boolean* terminateSimulation,
|
|
684
|
+
fmi3Boolean* nominalContinuousStatesChanged,
|
|
685
|
+
fmi3Boolean* valuesOfContinuousStatesChanged,
|
|
686
|
+
fmi3Boolean* nextEventTimeDefined,
|
|
687
|
+
fmi3Float64* nextEventTime)
|
|
688
|
+
{
|
|
689
|
+
const auto component = reinterpret_cast<Component*>(c);
|
|
690
|
+
try {
|
|
691
|
+
component->slave->UpdateDiscreteStates(
|
|
692
|
+
discreteStatesNeedUpdate,
|
|
693
|
+
terminateSimulation,
|
|
694
|
+
nominalContinuousStatesChanged,
|
|
695
|
+
valuesOfContinuousStatesChanged,
|
|
696
|
+
nextEventTimeDefined,
|
|
697
|
+
nextEventTime);
|
|
698
|
+
return fmi3OK;
|
|
699
|
+
} catch (const cppfmu::FatalError& e) {
|
|
700
|
+
component->logger.Log(fmi3Fatal, "", e.what());
|
|
701
|
+
return fmi3Fatal;
|
|
702
|
+
} catch (const std::exception& e) {
|
|
703
|
+
component->logger.Log(fmi3Error, "", e.what());
|
|
704
|
+
return fmi3Error;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fmi3Status fmi3EnterContinuousTimeMode(fmi3Instance c)
|
|
709
|
+
{
|
|
710
|
+
return fmi3OK;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
fmi3Status fmi3CompletedIntegratorStep(
|
|
714
|
+
fmi3Instance c,
|
|
715
|
+
fmi3Boolean noSetFMUStatePriorToCurrentPoint,
|
|
716
|
+
fmi3Boolean* enterEventMode,
|
|
717
|
+
fmi3Boolean* terminateSimulation)
|
|
718
|
+
{
|
|
719
|
+
*enterEventMode = false;
|
|
720
|
+
*terminateSimulation = false;
|
|
721
|
+
|
|
722
|
+
return fmi3OK;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
|
|
515
726
|
|
|
516
727
|
fmi3Status fmi3GetFMUState(
|
|
517
728
|
fmi3Instance c,
|
|
@@ -850,15 +1061,6 @@ fmi3EvaluateDiscreteStates(fmi3Instance instance)
|
|
|
850
1061
|
NOT_IMPLEMENTED;
|
|
851
1062
|
}
|
|
852
1063
|
|
|
853
|
-
fmi3Status
|
|
854
|
-
fmi3UpdateDiscreteStates(fmi3Instance instance, fmi3Boolean* discreteStatesNeedUpdate,
|
|
855
|
-
fmi3Boolean* terminateSimulation,
|
|
856
|
-
fmi3Boolean* nominalsOfContinuousStatesChanged,
|
|
857
|
-
fmi3Boolean* valuesOfContinuousStatesChanged,
|
|
858
|
-
fmi3Boolean* nextEventTimeDefined, fmi3Float64* nextEventTime)
|
|
859
|
-
{
|
|
860
|
-
NOT_IMPLEMENTED;
|
|
861
|
-
}
|
|
862
1064
|
|
|
863
1065
|
fmi3Status fmi3ActivateModelPartition(fmi3Instance instance, fmi3ValueReference clockReference,
|
|
864
1066
|
fmi3Float64 activationTime)
|
|
@@ -866,72 +1068,3 @@ fmi3Status fmi3ActivateModelPartition(fmi3Instance instance, fmi3ValueReference
|
|
|
866
1068
|
NOT_IMPLEMENTED;
|
|
867
1069
|
}
|
|
868
1070
|
|
|
869
|
-
|
|
870
|
-
// model exchange functions
|
|
871
|
-
fmi3Status
|
|
872
|
-
fmi3EnterContinuousTimeMode(fmi3Instance instance)
|
|
873
|
-
{
|
|
874
|
-
NOT_IMPLEMENTED;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
fmi3Status
|
|
878
|
-
fmi3CompletedIntegratorStep(fmi3Instance instance, fmi3Boolean noSetFMUStatePriorToCurrentPoint,
|
|
879
|
-
fmi3Boolean* enterEventMode, fmi3Boolean* terminateSimulation)
|
|
880
|
-
{
|
|
881
|
-
NOT_IMPLEMENTED;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/* Providing independent variables and re-initialization of caching */
|
|
885
|
-
fmi3Status
|
|
886
|
-
fmi3SetTime(fmi3Instance instance, fmi3Float64 time)
|
|
887
|
-
{
|
|
888
|
-
NOT_IMPLEMENTED;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
fmi3Status
|
|
892
|
-
fmi3SetContinuousStates(fmi3Instance instance, const fmi3Float64 continuousStates[],
|
|
893
|
-
size_t nContinuousStates)
|
|
894
|
-
{
|
|
895
|
-
NOT_IMPLEMENTED;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
/* Evaluation of the model equations */
|
|
899
|
-
fmi3Status
|
|
900
|
-
fmi3GetContinuousStateDerivatives(fmi3Instance instance, fmi3Float64 derivatives[],
|
|
901
|
-
size_t nContinuousStates)
|
|
902
|
-
{
|
|
903
|
-
NOT_IMPLEMENTED;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
fmi3Status
|
|
907
|
-
fmi3GetEventIndicators(fmi3Instance instance, fmi3Float64 eventIndicators[],
|
|
908
|
-
size_t nEventIndicators)
|
|
909
|
-
{
|
|
910
|
-
NOT_IMPLEMENTED;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
fmi3Status
|
|
914
|
-
fmi3GetContinuousStates(fmi3Instance instance, fmi3Float64 continuousStates[],
|
|
915
|
-
size_t nContinuousStates)
|
|
916
|
-
{
|
|
917
|
-
NOT_IMPLEMENTED;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
fmi3Status
|
|
921
|
-
fmi3GetNominalsOfContinuousStates(fmi3Instance instance, fmi3Float64 nominals[],
|
|
922
|
-
size_t nContinuousStates)
|
|
923
|
-
{
|
|
924
|
-
NOT_IMPLEMENTED;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
fmi3Status
|
|
928
|
-
fmi3GetNumberOfEventIndicators(fmi3Instance instance, size_t* nEventIndicators)
|
|
929
|
-
{
|
|
930
|
-
NOT_IMPLEMENTED;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
fmi3Status
|
|
934
|
-
fmi3GetNumberOfContinuousStates(fmi3Instance instance, size_t* nContinuousStates)
|
|
935
|
-
{
|
|
936
|
-
NOT_IMPLEMENTED;
|
|
937
|
-
}
|
|
@@ -28,7 +28,7 @@ inline std::string findClassName(const std::string& fileName)
|
|
|
28
28
|
{
|
|
29
29
|
std::string line;
|
|
30
30
|
std::ifstream infile(fileName);
|
|
31
|
-
std::string regexStr(R"(^class (\w+)\(
|
|
31
|
+
std::string regexStr(R"(^class (\w+)\(([^)]*\bFmi3Slave(?:Base)?\b[^)]*)\)\s*:)");
|
|
32
32
|
while (getline(infile, line)) {
|
|
33
33
|
std::smatch m;
|
|
34
34
|
std::regex re(regexStr);
|
|
@@ -80,7 +80,7 @@ PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources
|
|
|
80
80
|
std::string className = findClassName(resources_ + "/" + moduleName + ".py");
|
|
81
81
|
if (className.empty()) {
|
|
82
82
|
cleanPyObject();
|
|
83
|
-
throw cppfmu::FatalError("Unable to find class extending
|
|
83
|
+
throw cppfmu::FatalError("Unable to find class extending Fmi3SlaveBase!");
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
PyObject* pyClassName = Py_BuildValue("s", className.c_str());
|
|
@@ -530,6 +530,109 @@ void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t
|
|
|
530
530
|
});
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
void PySlaveInstance::GetContinuousStates(cppfmu::FMIFloat64* continuousStates, std::size_t nStates) const
|
|
534
|
+
{
|
|
535
|
+
py_safe_run([this, &continuousStates, nStates](PyGILState_STATE gilState) {
|
|
536
|
+
auto f = PyObject_CallMethod(pInstance_, "get_continuous_states", nullptr);
|
|
537
|
+
if (f == nullptr) {
|
|
538
|
+
handle_py_exception("[get_continuous_states] PyObject_CallMethod", gilState);
|
|
539
|
+
}
|
|
540
|
+
// Assuming f is a list of floats
|
|
541
|
+
for (std::size_t i = 0; i < nStates; i++) {
|
|
542
|
+
PyObject* item = PyList_GetItem(f, i);
|
|
543
|
+
continuousStates[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
544
|
+
}
|
|
545
|
+
clearLogBuffer();
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
void PySlaveInstance::GetContinuousStateDerivatives(cppfmu::FMIFloat64* continuousStateDerivatives, std::size_t nStates) const
|
|
550
|
+
{
|
|
551
|
+
py_safe_run([this, &continuousStateDerivatives, nStates](PyGILState_STATE gilState) {
|
|
552
|
+
auto f = PyObject_CallMethod(pInstance_, "get_continuous_state_derivatives", nullptr);
|
|
553
|
+
if (f == nullptr) {
|
|
554
|
+
handle_py_exception("[get_continuous_state_derivatives] PyObject_CallMethod", gilState);
|
|
555
|
+
}
|
|
556
|
+
// Assuming f is a list of floats
|
|
557
|
+
for (std::size_t i = 0; i < nStates; i++) {
|
|
558
|
+
PyObject* item = PyList_GetItem(f, i);
|
|
559
|
+
continuousStateDerivatives[i] = static_cast<cppfmu::FMIFloat64>(PyFloat_AsDouble(item));
|
|
560
|
+
}
|
|
561
|
+
clearLogBuffer();
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void PySlaveInstance::GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalsOfContinuousStates, std::size_t nStates) const
|
|
566
|
+
{
|
|
567
|
+
for (size_t i = 0u; i < nStates; i++) {
|
|
568
|
+
nominalsOfContinuousStates[i] = 1.0;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
void PySlaveInstance::SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates)
|
|
573
|
+
{
|
|
574
|
+
py_safe_run([this, &continuousStates, nStates](PyGILState_STATE gilState) {
|
|
575
|
+
PyObject* refs = PyList_New(nStates);
|
|
576
|
+
for (int i = 0; i < nStates; i++) {
|
|
577
|
+
PyList_SetItem(refs, i, Py_BuildValue("d", continuousStates[i]));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
auto f = PyObject_CallMethod(pInstance_, "set_continuous_states", "(O)", refs);
|
|
581
|
+
Py_DECREF(refs);
|
|
582
|
+
if (f == nullptr) {
|
|
583
|
+
handle_py_exception("[setContinuousStates] PyObject_CallMethod", gilState);
|
|
584
|
+
}
|
|
585
|
+
Py_DECREF(f);
|
|
586
|
+
clearLogBuffer();
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
void PySlaveInstance::GetNumberOfContinuousStates(std::size_t& nStates) const
|
|
591
|
+
{
|
|
592
|
+
py_safe_run([this, &nStates](PyGILState_STATE gilState) {
|
|
593
|
+
auto f = PyObject_CallMethod(pInstance_, "get_number_of_continuous_states", nullptr);
|
|
594
|
+
if (f == nullptr) {
|
|
595
|
+
handle_py_exception("[getNumberOfContinuousStates] PyObject_CallMethod", gilState);
|
|
596
|
+
}
|
|
597
|
+
nStates = static_cast<std::size_t>(PyLong_AsLong(f));
|
|
598
|
+
Py_DECREF(f);
|
|
599
|
+
clearLogBuffer();
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
void PySlaveInstance::GetNumberOfEventIndicators(std::size_t& nIndicators) const
|
|
604
|
+
{
|
|
605
|
+
nIndicators= 0u;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
void PySlaveInstance::SetTime(cppfmu::FMIFloat64 time)
|
|
609
|
+
{
|
|
610
|
+
py_safe_run([this, time](PyGILState_STATE gilState) {
|
|
611
|
+
auto f = PyObject_CallMethod(pInstance_, "set_time", "(d)", time);
|
|
612
|
+
if (f == nullptr) {
|
|
613
|
+
handle_py_exception("[setTime] PyObject_CallMethod", gilState);
|
|
614
|
+
}
|
|
615
|
+
Py_DECREF(f);
|
|
616
|
+
clearLogBuffer();
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
void PySlaveInstance::UpdateDiscreteStates(
|
|
621
|
+
cppfmu::FMIBoolean* discreteStatesNeedUpdate,
|
|
622
|
+
cppfmu::FMIBoolean* terminateSimulation,
|
|
623
|
+
cppfmu::FMIBoolean* nominalContinuousStatesChanged,
|
|
624
|
+
cppfmu::FMIBoolean* valuesOfContinuousStatesChanged,
|
|
625
|
+
cppfmu::FMIBoolean* nextEventTimeDefined,
|
|
626
|
+
cppfmu::FMIFloat64* nextEventTime)
|
|
627
|
+
{
|
|
628
|
+
*discreteStatesNeedUpdate = false;
|
|
629
|
+
*terminateSimulation = false;
|
|
630
|
+
*nominalContinuousStatesChanged = false;
|
|
631
|
+
*valuesOfContinuousStatesChanged = false;
|
|
632
|
+
*nextEventTimeDefined = false;
|
|
633
|
+
*nextEventTime = 0.0;
|
|
634
|
+
}
|
|
635
|
+
|
|
533
636
|
void PySlaveInstance::GetFMUstate(fmi3FMUState& state)
|
|
534
637
|
{
|
|
535
638
|
py_safe_run([this, &state](PyGILState_STATE gilState) {
|
|
@@ -50,6 +50,23 @@ public:
|
|
|
50
50
|
void GetFMUstate(fmi3FMUState& State) override;
|
|
51
51
|
void SetFMUstate(const fmi3FMUState& State) override;
|
|
52
52
|
void FreeFMUstate(fmi3FMUState& State) override;
|
|
53
|
+
|
|
54
|
+
// Model Exchange methods
|
|
55
|
+
void GetContinuousStates(cppfmu::FMIFloat64* continuousStates, std::size_t nStates) const override;
|
|
56
|
+
void GetNumberOfContinuousStates(std::size_t& nStates) const override;
|
|
57
|
+
void GetNumberOfEventIndicators(std::size_t& nEventIndicators) const override;
|
|
58
|
+
void GetContinuousStateDerivatives(cppfmu::FMIFloat64* derivatives, std::size_t nStates) const override;
|
|
59
|
+
void GetNominalsOfContinuousStates(cppfmu::FMIFloat64* nominalContinuousStates, std::size_t nStates) const override;
|
|
60
|
+
void SetContinuousStates(const cppfmu::FMIFloat64* continuousStates, std::size_t nStates) override;
|
|
61
|
+
void SetTime(cppfmu::FMIFloat64 time) override;
|
|
62
|
+
void UpdateDiscreteStates(
|
|
63
|
+
cppfmu::FMIBoolean* discreteStatesNeedUpdate,
|
|
64
|
+
cppfmu::FMIBoolean* terminateSimulation,
|
|
65
|
+
cppfmu::FMIBoolean* nominalContinuousStatesChanged,
|
|
66
|
+
cppfmu::FMIBoolean* valuesOfContinuousStatesChanged,
|
|
67
|
+
cppfmu::FMIBoolean* nextEventTimeDefined,
|
|
68
|
+
cppfmu::FMIFloat64* nextEventTime) override;
|
|
69
|
+
|
|
53
70
|
|
|
54
71
|
size_t SerializedFMUstateSize(const fmi3FMUState& State) override;
|
|
55
72
|
void SerializeFMUstate(const fmi3FMUState& State, fmi3Byte bytes[], size_t size) override;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pythonfmu3 import Fmi3Causality, ModelExchange, Fmi3Variability, Fmi3SlaveBase, Fmi3Status, Float64, Fmi3Initial, Unit, Float64Type, Fmi3StepResult
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
class Dahlquist(Fmi3SlaveBase, ModelExchange):
|
|
6
|
+
|
|
7
|
+
def __init__(self, **kwargs):
|
|
8
|
+
super().__init__(**kwargs)
|
|
9
|
+
|
|
10
|
+
self.author = "Stephen Smith"
|
|
11
|
+
self.description = "Dahlquist's test problem for model exchange FMUs"
|
|
12
|
+
|
|
13
|
+
self.time = 0.0
|
|
14
|
+
self.k = 1.0
|
|
15
|
+
self.x = 1.0
|
|
16
|
+
self.derx = 0.0
|
|
17
|
+
|
|
18
|
+
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
|
|
19
|
+
self.register_variable(Float64("x", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
|
|
20
|
+
self.register_variable(Float64("derx", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1))
|
|
21
|
+
self.register_variable(Float64("k", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_continuous_state_derivatives(self) -> List[float]:
|
|
25
|
+
self.derx = -self.k * self.x
|
|
26
|
+
return [self.derx]
|
|
27
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from pythonfmu3 import Fmi3Causality, CoSimulation, ModelExchange, Fmi3Variability, Fmi3SlaveBase, Fmi3Status, Float64, Fmi3Initial, Unit, Float64Type, Fmi3StepResult
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
class Dahlquist(Fmi3SlaveBase, ModelExchange, CoSimulation):
|
|
6
|
+
|
|
7
|
+
def __init__(self, **kwargs):
|
|
8
|
+
super().__init__(**kwargs)
|
|
9
|
+
|
|
10
|
+
self.author = "Stephen Smith"
|
|
11
|
+
self.description = "Dahlquist's test problem for model exchange FMUs"
|
|
12
|
+
|
|
13
|
+
self.time = 0.0
|
|
14
|
+
self.k = 1.0
|
|
15
|
+
self.x = 1.0
|
|
16
|
+
self.derx = 0.0
|
|
17
|
+
|
|
18
|
+
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
|
|
19
|
+
self.register_variable(Float64("x", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
|
|
20
|
+
self.register_variable(Float64("derx", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1))
|
|
21
|
+
self.register_variable(Float64("k", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_continuous_state_derivatives(self) -> List[float]:
|
|
25
|
+
self.derx = -self.k * self.x
|
|
26
|
+
return [self.derx]
|
|
27
|
+
|
|
28
|
+
def do_step(self, current_time: float, step_size: float) -> Fmi3StepResult:
|
|
29
|
+
self.time = current_time + step_size
|
|
30
|
+
self.derx = -self.k * self.x
|
|
31
|
+
self.x += self.derx * step_size
|
|
32
|
+
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from pythonfmu3 import Fmi3Slave
|
|
3
|
+
from pythonfmu3 import Fmi3Slave, ModelExchange
|
|
4
4
|
from pythonfmu3 import __version__ as VERSION
|
|
5
5
|
|
|
6
6
|
from .utils import FMI2PY, PY2FMI
|
|
@@ -150,3 +150,18 @@ def test_Fmi3Slave_customized_log_categories(new_categories):
|
|
|
150
150
|
assert categories.find(f"Category[@name='{category}'][@description='{description}']") is not None
|
|
151
151
|
else:
|
|
152
152
|
assert categories is None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_Fmi3Slave_model_exchange():
|
|
156
|
+
class Slave(Fmi3Slave, ModelExchange):
|
|
157
|
+
|
|
158
|
+
def get_continuous_state_derivatives(self, vals):
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
slave = Slave(instance_name="instance")
|
|
162
|
+
xml = slave.to_xml()
|
|
163
|
+
|
|
164
|
+
categories = xml.find("ModelExchange")
|
|
165
|
+
|
|
166
|
+
assert categories is not None, "ModelExchange category should be present in the XML"
|
|
167
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
import ctypes
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
|
|
@@ -30,6 +31,66 @@ def test_integration_demo(tmp_path):
|
|
|
30
31
|
assert res["realOut"][-1] == pytest.approx(res["time"][-1], rel=1e-7)
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
@pytest.mark.integration
|
|
35
|
+
def test_integration_demo_MX(tmp_path):
|
|
36
|
+
script_file = Path(__file__).parent / "slaves/pythonslaveMX.py"
|
|
37
|
+
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false")
|
|
38
|
+
assert fmu.exists()
|
|
39
|
+
res = fmpy.simulate_fmu(str(fmu), stop_time=0.5)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.integration
|
|
43
|
+
def test_integration_solve_MX(tmp_path):
|
|
44
|
+
script_file = Path(__file__).parent / "slaves/pythonslaveMX.py"
|
|
45
|
+
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false")
|
|
46
|
+
assert fmu.exists()
|
|
47
|
+
|
|
48
|
+
md = fmpy.read_model_description(str(fmu))
|
|
49
|
+
unzipdir = fmpy.extract(str(fmu))
|
|
50
|
+
model = fmpy.fmi3.FMU3Model(guid=md.guid,
|
|
51
|
+
unzipDirectory=unzipdir,
|
|
52
|
+
modelIdentifier=md.modelExchange.modelIdentifier,
|
|
53
|
+
instanceName="instance"
|
|
54
|
+
)
|
|
55
|
+
model.instantiate()
|
|
56
|
+
model.enterInitializationMode()
|
|
57
|
+
model.exitInitializationMode()
|
|
58
|
+
|
|
59
|
+
# forward euler
|
|
60
|
+
t = 0.0
|
|
61
|
+
step_size = 0.1
|
|
62
|
+
|
|
63
|
+
values = (ctypes.c_double * 1)(0.0)
|
|
64
|
+
states = (ctypes.c_double *1)(0.0)
|
|
65
|
+
|
|
66
|
+
model.getContinuousStateDerivatives(values, 1)
|
|
67
|
+
model.getContinuousStates(states, 1)
|
|
68
|
+
|
|
69
|
+
assert values[0] == pytest.approx(-1.0, rel=1e-7)
|
|
70
|
+
|
|
71
|
+
der = values[0]
|
|
72
|
+
new_state = states[0] + der * step_size
|
|
73
|
+
|
|
74
|
+
model.setContinuousStates((ctypes.c_double * 1)(new_state), 1)
|
|
75
|
+
model.setTime(t + step_size)
|
|
76
|
+
|
|
77
|
+
model.getContinuousStates(states, 1)
|
|
78
|
+
|
|
79
|
+
assert states[0] == pytest.approx(new_state, rel=1e-7)
|
|
80
|
+
|
|
81
|
+
@pytest.mark.integration
|
|
82
|
+
def test_integration_demo_CS_MX(tmp_path):
|
|
83
|
+
script_file = Path(__file__).parent / "slaves/pythonslaveMXCS.py"
|
|
84
|
+
fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false")
|
|
85
|
+
assert fmu.exists()
|
|
86
|
+
res = fmpy.simulate_fmu(str(fmu), stop_time=0.5, fmi_type="ModelExchange")
|
|
87
|
+
|
|
88
|
+
res1 = fmpy.simulate_fmu(str(fmu), stop_time=0.5, fmi_type="CoSimulation")
|
|
89
|
+
|
|
90
|
+
assert res[-1][0] == pytest.approx(res1[-1][0], rel=1e-7)
|
|
91
|
+
assert res[-1][1] == pytest.approx(res1[-1][1], rel=0.1)
|
|
92
|
+
|
|
93
|
+
|
|
33
94
|
@pytest.mark.integration
|
|
34
95
|
def test_integration_reset(tmp_path):
|
|
35
96
|
script_file = Path(__file__).parent / "slaves/pythonslave.py"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonfmu3
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A lightweight framework that enables the packaging of Python3.x code as co-simulation FMUs.
|
|
5
5
|
Home-page: https://github.com/stephensmith25/PythonFMU3
|
|
6
6
|
Author: Stephen Smith
|
|
@@ -27,7 +27,7 @@ Dynamic: license-file
|
|
|
27
27
|
|
|
28
28
|
# PythonFMU3
|
|
29
29
|
|
|
30
|
-
> A lightweight framework that enables the packaging of Python 3 code as
|
|
30
|
+
> A lightweight framework that enables the packaging of Python 3 code as FMUs (following FMI version 3.0).
|
|
31
31
|
|
|
32
32
|
[](https://opensource.org/licenses/MIT)
|
|
33
33
|
[](https://github.com/StephenSmith25/PythonFMU3/issues)
|
|
@@ -42,14 +42,16 @@ This project is a fork of the original PythonFMU repository available at https:/
|
|
|
42
42
|
### Support:
|
|
43
43
|
|
|
44
44
|
Please take a look at the examples to see the supported features.
|
|
45
|
+
We currently support both the Model exchange and Cosimulation Interfaces.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
For both interfaces event mode is not supported.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
We currently do not support the following methods (beyond a default, trivial implementation)
|
|
49
50
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
51
|
+
- fmi3UpdateDiscreteStates
|
|
52
|
+
- fmi3GetNumberOfEventindicators
|
|
53
|
+
- fmi3GetNominalsOfContinuousStates
|
|
54
|
+
- fmi3EvaluateDiscreteStates
|
|
53
55
|
|
|
54
56
|
### How do I build an FMU from python code?
|
|
55
57
|
|
|
@@ -123,7 +125,7 @@ optional arguments:
|
|
|
123
125
|
Requirements or environment file.
|
|
124
126
|
```
|
|
125
127
|
|
|
126
|
-
### Example:
|
|
128
|
+
### Cosimulation Example:
|
|
127
129
|
|
|
128
130
|
#### Write the script
|
|
129
131
|
|
|
@@ -169,3 +171,54 @@ pythonfmu3 build -f pythonslave.py myproject
|
|
|
169
171
|
In this example a python class named `PythonSlave` that extends `Fmi3Slave` is declared in a file named `pythonslave.py`,
|
|
170
172
|
where `myproject` is an optional folder containing additional project files required by the python script.
|
|
171
173
|
Project folders such as this will be recursively copied into the FMU. Multiple project files/folders may be added.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### Model Exchange Example
|
|
177
|
+
|
|
178
|
+
To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of (or both) mixin classes `ModelExchange` or `CoSimulation`.
|
|
179
|
+
|
|
180
|
+
#### Write the script
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
|
|
184
|
+
from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
|
|
188
|
+
|
|
189
|
+
def __init__(self, **kwargs):
|
|
190
|
+
super().__init__(**kwargs)
|
|
191
|
+
|
|
192
|
+
self.author = "John Doe"
|
|
193
|
+
self.description = "A simple description"
|
|
194
|
+
|
|
195
|
+
self.time = 0.0
|
|
196
|
+
self.mu = 1.0
|
|
197
|
+
self.x0 = 2
|
|
198
|
+
self.x1 = 0
|
|
199
|
+
self.derx0 = 0.0
|
|
200
|
+
self.derx1 = 0.0
|
|
201
|
+
|
|
202
|
+
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
|
|
203
|
+
self.register_variable(Float64("x0", causality=Fmi3Causality.output, start=2, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
|
|
204
|
+
self.register_variable(Float64("x1", causality=Fmi3Causality.output, start=0, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
|
|
205
|
+
self.register_variable(Float64("derx0", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1))
|
|
206
|
+
self.register_variable(Float64("derx1", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=2))
|
|
207
|
+
self.register_variable(Float64("mu", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_continuous_state_derivatives(self) -> List[float]:
|
|
211
|
+
self.derx0 = self.x1
|
|
212
|
+
self.derx1 = self.mu * ((1 - self.x0**2) * self.x1) - self.x0
|
|
213
|
+
return [self.derx0, self.derx1]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Note: There is a hard requirement that we have a `self.time` member in the derived class.
|
|
219
|
+
|
|
220
|
+
#### Create the FMU
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
pythonfmu3 build -f pythonslaveMX.py
|
|
224
|
+
```
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
pythonfmu3/__init__.py,sha256=
|
|
1
|
+
pythonfmu3/__init__.py,sha256=ZftV413VIf6iwjwRkjR0on_r3ufKIwMkp25TBN9APhY,519
|
|
2
2
|
pythonfmu3/__main__.py,sha256=gC1T8x2d02hhqshyNQGSS70SyCk-3I3t8zPqoRo2Xd8,1543
|
|
3
|
-
pythonfmu3/_version.py,sha256=
|
|
4
|
-
pythonfmu3/builder.py,sha256=
|
|
3
|
+
pythonfmu3/_version.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
|
|
4
|
+
pythonfmu3/builder.py,sha256=w7c9ZFv1Udz99cb7rj2NpD0CgrcHRu1SR_axm0ew__Y,10103
|
|
5
|
+
pythonfmu3/cosimulation.py,sha256=EZ5pa_5hy1AwoaZRzZqNz_IX8kjxoFM9sPgeqrUiRuw,179
|
|
5
6
|
pythonfmu3/default_experiment.py,sha256=FJ0l0FpU7pT4Pox2RiA2y-Cb_e7DE2XlbWKySRYQaVk,293
|
|
6
7
|
pythonfmu3/deploy.py,sha256=eqFWVxZUkJCLvmq1-GuMNjn2j0VCGoaXPhRWrCk2Vxg,3741
|
|
7
8
|
pythonfmu3/enums.py,sha256=ZmC6vGeBTGt7HarPQ4uYV10ezPsvIxAHH1ypjNq7G3E,729
|
|
8
|
-
pythonfmu3/fmi3slave.py,sha256=
|
|
9
|
+
pythonfmu3/fmi3slave.py,sha256=5myd4EzlkLdeyNXGYDddrORFUCiKJnzBtmcoHJtmfA4,18960
|
|
9
10
|
pythonfmu3/logmsg.py,sha256=0wn11GLwq0LLWqyC2ktLRjQFWlpj07wWliZ8rUHx1gg,359
|
|
11
|
+
pythonfmu3/modelexchange.py,sha256=riF9YkiAY1WN27jhYnUOHwwkajrFUR6Yu6038ClxYho,792
|
|
10
12
|
pythonfmu3/osutil.py,sha256=1qN6qudN4zCupZwc15pzp52WObkqi6jGy9a-MehNt9Q,656
|
|
11
13
|
pythonfmu3/unit.py,sha256=SjOaywb5lVQbAO_iLjc_7Ly3nYrmAvIW2_hAGt12UFU,1475
|
|
12
14
|
pythonfmu3/variable_types.py,sha256=Q0D4wlGi3i3zE5UsmGJEpNh7SV_W8mn_8p_WIkwrLQA,2881
|
|
@@ -18,23 +20,23 @@ pythonfmu3/pythonfmu-export/build_win.sh,sha256=UzmAcWqFmwBu6mvYne2Dx3UK8iPwTtP4
|
|
|
18
20
|
pythonfmu3/pythonfmu-export/src/CMakeLists.txt,sha256=fOVlV7hy9c0wx_Qp4K51tQaQ5xy9lL4qtyGl15ZXfGs,1208
|
|
19
21
|
pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_common.hpp,sha256=cgrlZa5L-bbjZeVi0pmy4JKE5UJ2GszzuyOqTdRcBRQ,4353
|
|
20
22
|
pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp,sha256=EsUIevwmCzX9hsui3i8x1crSnSOG8dEK15Zm1ZIavYQ,3843
|
|
21
|
-
pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp,sha256=
|
|
22
|
-
pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp,sha256=
|
|
23
|
+
pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp,sha256=yI_iktqM2vh2OQXj5Ul6WNsRO591QPXWefSPfXXzXFg,7330
|
|
24
|
+
pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp,sha256=_vZcoG4EzeIjSqY28JtTwJBMZiGepruAg1r8nbp1_BU,29606
|
|
23
25
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h,sha256=Tk1WY0nAYBGiOAQ8zVbmqRAfdQttITWsLba7udhBwG4,30050
|
|
24
26
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h,sha256=NsHuclp20wcuUInYf-91UXZbljTeu2cPXENqNEORdgU,16262
|
|
25
27
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h,sha256=ZCQLJgSbRGrMsBiQ4auaYCY4J3Hck6g3t1iZp5NEguw,4251
|
|
26
28
|
pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp,sha256=GLiOjhKLDVEICersnqVwezwEshbxfdh_cyMxBJGFbu8,456
|
|
27
|
-
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=
|
|
28
|
-
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=
|
|
29
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=wwsp1N2tuCxdZzA6tbgcqI-wXPLnjXnQn3Tkdmxfxyo,29284
|
|
30
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=mMNKbzP0pZYMJQbqwa_dybPAQtTslElRvoAqaRf6_ww,5281
|
|
29
31
|
pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp,sha256=CjFEIrZu8b1jxQAIgfI_o36hyItW_SdQ5VSkImf7JNs,2977
|
|
30
|
-
pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=
|
|
31
|
-
pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=
|
|
32
|
+
pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=hKu5soPg6GEieC-H5_Tnd9PIRmU_PazfWOX3Y9mAmdY,356232
|
|
33
|
+
pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=iNB6sT7BEg2B2Cy2Mvhi6hHfQ5ctyHnr96Qix9iRBF0,421376
|
|
32
34
|
pythonfmu3/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
35
|
pythonfmu3/tests/conftest.py,sha256=B6XxTnBdeF_tEJ3w2DlfPKUfvlk-oULgIhQtXkgOJIc,171
|
|
34
36
|
pythonfmu3/tests/test_builder.py,sha256=GrD4ziin3RLOe_36rBQCMnKrSkhc3IqnxE1a6kCFPok,6842
|
|
35
37
|
pythonfmu3/tests/test_deploy.py,sha256=JCDo-O93-izxrP9Q4K0njBcfZ-gAXYx9aZ4plgctz7k,1783
|
|
36
|
-
pythonfmu3/tests/test_fmi3slave.py,sha256=
|
|
37
|
-
pythonfmu3/tests/test_integration.py,sha256=
|
|
38
|
+
pythonfmu3/tests/test_fmi3slave.py,sha256=7nPT5UPQdJu7T3SSQggIi2ZL_hEgs5iEX3KNc-UYX-c,4695
|
|
39
|
+
pythonfmu3/tests/test_integration.py,sha256=34rus-OU9S5FlAVAfpZyoHrXoziG14d4_yw4Wdt5uVU,12513
|
|
38
40
|
pythonfmu3/tests/test_logger.py,sha256=OcHQhxUtNBPH-0zdmheC4I9OWz1yu48W8MYDshNVkf8,5765
|
|
39
41
|
pythonfmu3/tests/test_multiple_fmus.py,sha256=-sthCm2YUZiRkdfvpLQNgquPBForUKQ7HGqfFBdrRas,3942
|
|
40
42
|
pythonfmu3/tests/test_variables.py,sha256=FL7eUCXH4RmH0rK0k6qXDnyDzthW_wAWJBHEmC-u8SI,10384
|
|
@@ -44,11 +46,13 @@ pythonfmu3/tests/data/hello.txt,sha256=wFNeS-K3n_2TKRMFQ2v4iTFOSj-uwF7P_Lt98xrZ5
|
|
|
44
46
|
pythonfmu3/tests/slaves/PythonSlaveWithException.py,sha256=lTTMMnD5Cu09HzA8G2masKyf-ntEMfJ0a3M8btdYVLo,644
|
|
45
47
|
pythonfmu3/tests/slaves/localmodule.py,sha256=-q5Lmh2I88MG7nLTscms0n5xhHLkUDed1IuQ5wj49lo,79
|
|
46
48
|
pythonfmu3/tests/slaves/pythonslave.py,sha256=uzrTQmfk0QBalxNTviGMZNph_YgVmcbxPqKgX5rSvXg,2172
|
|
49
|
+
pythonfmu3/tests/slaves/pythonslaveMX.py,sha256=rmtiKWj-7DNCEGKAzCoS-6ybkWKabYMhatpn14r7UpA,1167
|
|
50
|
+
pythonfmu3/tests/slaves/pythonslaveMXCS.py,sha256=P1aKY92obd-y9VGwDRin9fwmnAlSmdHguFW8E5-LU4I,1417
|
|
47
51
|
pythonfmu3/tests/slaves/pythonslave_read_file.py,sha256=t2sDaZSr2veD91gdqCO9W9CN-AN3TyXABXXfV9Rt0Xs,791
|
|
48
52
|
pythonfmu3/tests/slaves/slavewithdep.py,sha256=hHDFB0dw8wkHiGzxeAVbz9O5LR6ghsFeONgRFdiK_t0,812
|
|
49
|
-
pythonfmu3-0.
|
|
50
|
-
pythonfmu3-0.
|
|
51
|
-
pythonfmu3-0.
|
|
52
|
-
pythonfmu3-0.
|
|
53
|
-
pythonfmu3-0.
|
|
54
|
-
pythonfmu3-0.
|
|
53
|
+
pythonfmu3-0.3.0.dist-info/licenses/LICENSE,sha256=RL2018BU2DKcTi8Eb4ODF-XF3wnr7SDKvMP5S49x4rQ,1188
|
|
54
|
+
pythonfmu3-0.3.0.dist-info/METADATA,sha256=MfS9nNJe_1MHA7fs7mlu92rsPd6bbXpKM-oTIjGU8q4,9621
|
|
55
|
+
pythonfmu3-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
56
|
+
pythonfmu3-0.3.0.dist-info/entry_points.txt,sha256=GgkWYs0DKGHLcijMEYZGkhkHOK2RVUpjGwvl1fwyDwQ,60
|
|
57
|
+
pythonfmu3-0.3.0.dist-info/top_level.txt,sha256=6ne7bh_cME1hepxCx1BG8N5QVhVAufrp7LLok6vq7nE,11
|
|
58
|
+
pythonfmu3-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|