pythonfmu3 0.3.2__py3-none-any.whl → 0.3.3__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/_version.py +1 -1
- pythonfmu3/builder.py +29 -7
- pythonfmu3/fmi3slave.py +20 -9
- pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3/tests/test_docs.py +98 -0
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/METADATA +2 -1
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/RECORD +11 -10
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/WHEEL +0 -0
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/entry_points.txt +0 -0
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {pythonfmu3-0.3.2.dist-info → pythonfmu3-0.3.3.dist-info}/top_level.txt +0 -0
pythonfmu3/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.3"
|
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
|
|
16
|
+
from .fmi3slave import FMI3_MODEL_OPTIONS_COMMON, FMI3_MODEL_OPTIONS_COSIM, FMI3_MODEL_OPTIONS_MX, Fmi3Slave, Fmi3SlaveBase
|
|
17
17
|
|
|
18
18
|
FilePath = Union[str, Path]
|
|
19
19
|
HERE = Path(__file__).parent
|
|
@@ -137,16 +137,20 @@ class FmuBuilder:
|
|
|
137
137
|
dest_file = dest / f"{model_identifier}.fmu"
|
|
138
138
|
|
|
139
139
|
type_node = xml.find("CoSimulation")
|
|
140
|
-
option_names = [opt.name for opt in
|
|
141
|
-
|
|
140
|
+
option_names = [opt.name for opt in FMI3_MODEL_OPTIONS_COMMON]
|
|
141
|
+
cosim_option_names = option_names.copy()
|
|
142
|
+
cosim_option_names.extend(opt.name for opt in FMI3_MODEL_OPTIONS_COSIM)
|
|
143
|
+
if type_node is not None:
|
|
142
144
|
for option, value in options.items():
|
|
143
|
-
if option in
|
|
145
|
+
if option in cosim_option_names:
|
|
144
146
|
type_node.set(option, str(value).lower())
|
|
145
147
|
|
|
146
148
|
type_node = xml.find("ModelExchange")
|
|
147
|
-
|
|
149
|
+
mx_option_names = option_names.copy()
|
|
150
|
+
mx_option_names.extend(opt.name for opt in FMI3_MODEL_OPTIONS_MX)
|
|
151
|
+
if type_node is not None:
|
|
148
152
|
for option, value in options.items():
|
|
149
|
-
if option in
|
|
153
|
+
if option in mx_option_names:
|
|
150
154
|
type_node.set(option, str(value).lower())
|
|
151
155
|
|
|
152
156
|
with zipfile.ZipFile(dest_file, "w") as zip_fmu:
|
|
@@ -247,7 +251,25 @@ def create_command_parser(parser: argparse.ArgumentParser):
|
|
|
247
251
|
default=None
|
|
248
252
|
)
|
|
249
253
|
|
|
250
|
-
for option in
|
|
254
|
+
for option in FMI3_MODEL_OPTIONS_COMMON:
|
|
255
|
+
action = "store_false" if option.value else "store_true"
|
|
256
|
+
parser.add_argument(
|
|
257
|
+
f"--{option.cli}",
|
|
258
|
+
dest=option.name,
|
|
259
|
+
help=f"If given, {option.name}={action[6:]}",
|
|
260
|
+
action=action
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
for option in FMI3_MODEL_OPTIONS_COSIM:
|
|
264
|
+
action = "store_false" if option.value else "store_true"
|
|
265
|
+
parser.add_argument(
|
|
266
|
+
f"--{option.cli}",
|
|
267
|
+
dest=option.name,
|
|
268
|
+
help=f"If given, {option.name}={action[6:]}",
|
|
269
|
+
action=action
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
for option in FMI3_MODEL_OPTIONS_MX:
|
|
251
273
|
action = "store_false" if option.value else "store_true"
|
|
252
274
|
parser.add_argument(
|
|
253
275
|
f"--{option.cli}",
|
pythonfmu3/fmi3slave.py
CHANGED
|
@@ -21,14 +21,22 @@ from .unit import Unit
|
|
|
21
21
|
|
|
22
22
|
ModelOptions = namedtuple("ModelOptions", ["name", "value", "cli"])
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
FMI3_MODEL_OPTIONS_COMMON: List[ModelOptions] = [
|
|
25
25
|
ModelOptions("needsExecutionTool", True, "no-external-tool"),
|
|
26
|
-
ModelOptions("canHandleVariableCommunicationStepSize", True, "no-variable-step"),
|
|
27
26
|
ModelOptions("canBeInstantiatedOnlyOncePerProcess", False, "only-one-per-process"),
|
|
28
27
|
ModelOptions("canGetAndSetFMUState", False, "handle-state"),
|
|
29
28
|
ModelOptions("canSerializeFMUState", False, "serialize-state")
|
|
30
29
|
]
|
|
31
30
|
|
|
31
|
+
FMI3_MODEL_OPTIONS_COSIM: List[ModelOptions] = [
|
|
32
|
+
ModelOptions("canHandleVariableCommunicationStepSize", True, "no-variable-step"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
FMI3_MODEL_OPTIONS_MX: List[ModelOptions] = [
|
|
36
|
+
ModelOptions("needsCompletedIntegratorStep", False, "needs-completed-step"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
32
40
|
class Fmi3StepResult(NamedTuple):
|
|
33
41
|
status: Fmi3Status = Fmi3Status.ok
|
|
34
42
|
eventHandlingNeeded: bool = False
|
|
@@ -103,23 +111,26 @@ class Fmi3SlaveBase(object):
|
|
|
103
111
|
root = Element("fmiModelDescription", attrib)
|
|
104
112
|
|
|
105
113
|
options = dict()
|
|
106
|
-
for option in
|
|
114
|
+
for option in FMI3_MODEL_OPTIONS_COMMON:
|
|
107
115
|
value = model_options.get(option.name, option.value)
|
|
108
116
|
options[option.name] = str(value).lower()
|
|
117
|
+
|
|
109
118
|
options["modelIdentifier"] = self.modelName
|
|
110
|
-
|
|
119
|
+
|
|
120
|
+
options_cs = options.copy()
|
|
121
|
+
for option in FMI3_MODEL_OPTIONS_COSIM:
|
|
122
|
+
options_cs[option.name] = str(value).lower()
|
|
111
123
|
|
|
112
|
-
options_me =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
options_me["needsCompletedIntegratorStep"] = "false"
|
|
124
|
+
options_me = options.copy()
|
|
125
|
+
for option in FMI3_MODEL_OPTIONS_MX:
|
|
126
|
+
options_me[option.name] = str(value).lower()
|
|
116
127
|
|
|
117
128
|
# check if we have cosim mixin or model exchange mixin
|
|
118
129
|
if isinstance(self, ModelExchange):
|
|
119
130
|
SubElement(root, "ModelExchange", attrib=options_me)
|
|
120
131
|
|
|
121
132
|
if isinstance(self, CoSimulation):
|
|
122
|
-
SubElement(root, "CoSimulation", attrib=
|
|
133
|
+
SubElement(root, "CoSimulation", attrib=options_cs)
|
|
123
134
|
|
|
124
135
|
if self.units:
|
|
125
136
|
unit_defs = SubElement(root, "UnitDefinitions")
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pathlib
|
|
3
|
+
import re
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
def extract_python_code_from_md(md_file):
|
|
7
|
+
"""Extract Python code blocks from markdown, excluding skip blocks"""
|
|
8
|
+
content = pathlib.Path(md_file).read_text()
|
|
9
|
+
|
|
10
|
+
# Find all python code blocks that aren't marked as skip
|
|
11
|
+
python_blocks = []
|
|
12
|
+
|
|
13
|
+
# Pattern to match python blocks but not python skip
|
|
14
|
+
pattern = r'```python(?!\s+skip)(?:[^\n]*)\n(.*?)\n```'
|
|
15
|
+
matches = re.findall(pattern, content, re.DOTALL)
|
|
16
|
+
|
|
17
|
+
for match in matches:
|
|
18
|
+
# Skip blocks that are wrapped in skip-test comments
|
|
19
|
+
if '<!-- skip-test -->' not in content.split(match)[0][-100:]:
|
|
20
|
+
python_blocks.append(match)
|
|
21
|
+
|
|
22
|
+
return python_blocks
|
|
23
|
+
|
|
24
|
+
@pytest.mark.doc
|
|
25
|
+
def test_readme():
|
|
26
|
+
test_dir = pathlib.Path(__file__).parent
|
|
27
|
+
fpath = test_dir.parent.parent / "README.md"
|
|
28
|
+
|
|
29
|
+
# extract Python code
|
|
30
|
+
code_blocks = extract_python_code_from_md(fpath)
|
|
31
|
+
|
|
32
|
+
namespace = {}
|
|
33
|
+
exec("from pythonfmu3 import *", namespace)
|
|
34
|
+
|
|
35
|
+
for i, code in enumerate(code_blocks):
|
|
36
|
+
try:
|
|
37
|
+
exec(code, namespace)
|
|
38
|
+
print(f"✅ Code block {i+1} executed successfully")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
pytest.fail(f"Code block {i+1} failed: {e}\nCode:\n{code}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.doc
|
|
46
|
+
def test_cosimulation_examples():
|
|
47
|
+
"""Test that all Python examples in usage.md actually work"""
|
|
48
|
+
test_dir = pathlib.Path(__file__).parent
|
|
49
|
+
usage_file = test_dir.parent.parent / "docs" / "usage.md"
|
|
50
|
+
|
|
51
|
+
# Extract Python code
|
|
52
|
+
code_blocks = extract_python_code_from_md(usage_file)
|
|
53
|
+
|
|
54
|
+
# Create a test namespace
|
|
55
|
+
namespace = {}
|
|
56
|
+
exec("from pythonfmu3 import *", namespace)
|
|
57
|
+
|
|
58
|
+
# Execute each code block
|
|
59
|
+
for i, code in enumerate(code_blocks):
|
|
60
|
+
try:
|
|
61
|
+
exec(code, namespace)
|
|
62
|
+
print(f"✅ Code block {i+1} executed successfully")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
pytest.fail(f"Code block {i+1} failed: {e}\nCode:\n{code}")
|
|
65
|
+
|
|
66
|
+
assert 'LinearTransform' in namespace, "LinearTransform class not found"
|
|
67
|
+
assert 'PIDController' in namespace, "PIDController class not found"
|
|
68
|
+
|
|
69
|
+
# Test instantiation
|
|
70
|
+
lt = namespace['LinearTransform'](instance_name="test")
|
|
71
|
+
pc = namespace['PIDController'](instance_name="test")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.doc
|
|
75
|
+
def test_model_exchange_examples():
|
|
76
|
+
"""Test Model Exchange examples"""
|
|
77
|
+
test_dir = pathlib.Path(__file__).parent
|
|
78
|
+
mx_file = test_dir.parent.parent / "docs" / "usageMX.md"
|
|
79
|
+
|
|
80
|
+
code_blocks = extract_python_code_from_md(mx_file)
|
|
81
|
+
|
|
82
|
+
namespace = {}
|
|
83
|
+
exec("from pythonfmu3 import *", namespace)
|
|
84
|
+
|
|
85
|
+
for i, code in enumerate(code_blocks):
|
|
86
|
+
try:
|
|
87
|
+
exec(code, namespace)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
pytest.fail(f"MX Code block {i+1} failed: {e}")
|
|
90
|
+
|
|
91
|
+
# Test that classes were actually created
|
|
92
|
+
assert 'VanDerPol' in namespace, "VanDerPol class not found"
|
|
93
|
+
assert 'BouncingBall' in namespace, "BouncingBall class not found"
|
|
94
|
+
|
|
95
|
+
# Test instantiation
|
|
96
|
+
vdp = namespace['VanDerPol'](instance_name="test")
|
|
97
|
+
|
|
98
|
+
bb = namespace['BouncingBall'](instance_name="test")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonfmu3
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -173,6 +173,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of
|
|
|
173
173
|
```python
|
|
174
174
|
|
|
175
175
|
from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
|
|
176
|
+
from typing import List
|
|
176
177
|
|
|
177
178
|
|
|
178
179
|
class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
pythonfmu3/__init__.py,sha256=VJ4QqOZTU8sBI8xhiRVD540Hsp381ma24xGWHABZ724,551
|
|
2
2
|
pythonfmu3/__main__.py,sha256=gC1T8x2d02hhqshyNQGSS70SyCk-3I3t8zPqoRo2Xd8,1543
|
|
3
|
-
pythonfmu3/_version.py,sha256=
|
|
4
|
-
pythonfmu3/builder.py,sha256=
|
|
3
|
+
pythonfmu3/_version.py,sha256=8KcCYTXH99C2-gCLuPILJvtT9YftRWJsartIx6TQ2ZY,22
|
|
4
|
+
pythonfmu3/builder.py,sha256=DLpy6Hd9ZmbeQLVMw_FcxnS5LB_-yRXzkPjlupyiYsg,11060
|
|
5
5
|
pythonfmu3/cosimulation.py,sha256=EZ5pa_5hy1AwoaZRzZqNz_IX8kjxoFM9sPgeqrUiRuw,179
|
|
6
6
|
pythonfmu3/default_experiment.py,sha256=FJ0l0FpU7pT4Pox2RiA2y-Cb_e7DE2XlbWKySRYQaVk,293
|
|
7
7
|
pythonfmu3/deploy.py,sha256=eqFWVxZUkJCLvmq1-GuMNjn2j0VCGoaXPhRWrCk2Vxg,3741
|
|
8
8
|
pythonfmu3/enums.py,sha256=ZmC6vGeBTGt7HarPQ4uYV10ezPsvIxAHH1ypjNq7G3E,729
|
|
9
|
-
pythonfmu3/fmi3slave.py,sha256=
|
|
9
|
+
pythonfmu3/fmi3slave.py,sha256=LmVDNKJjuUuzepBMijudqsjMFZoZAhAAPxlcgKGz_So,19688
|
|
10
10
|
pythonfmu3/logmsg.py,sha256=0wn11GLwq0LLWqyC2ktLRjQFWlpj07wWliZ8rUHx1gg,359
|
|
11
11
|
pythonfmu3/modelexchange.py,sha256=pdC3gvr-vCXA8qvJ_urWplJZWEi1YKjMcNczme7GOOo,1884
|
|
12
12
|
pythonfmu3/osutil.py,sha256=1qN6qudN4zCupZwc15pzp52WObkqi6jGy9a-MehNt9Q,656
|
|
@@ -30,11 +30,12 @@ pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=Dmnvn7CprYx
|
|
|
30
30
|
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=kdLzY-vrRyRnJfqZ0ZQiz7g7i3gP1MLzmvUD7u0ylPM,5392
|
|
31
31
|
pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp,sha256=CjFEIrZu8b1jxQAIgfI_o36hyItW_SdQ5VSkImf7JNs,2977
|
|
32
32
|
pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=MYOFAGHaNBGS-o7DFQPmdYrfSgG1QXkhznwlh7fsRfY,367904
|
|
33
|
-
pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=
|
|
33
|
+
pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=MTUolw2jI9rDWEQmqt0XyI_xmu2F880RHfcxpb85Fok,427008
|
|
34
34
|
pythonfmu3/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
pythonfmu3/tests/conftest.py,sha256=B6XxTnBdeF_tEJ3w2DlfPKUfvlk-oULgIhQtXkgOJIc,171
|
|
36
36
|
pythonfmu3/tests/test_builder.py,sha256=GrD4ziin3RLOe_36rBQCMnKrSkhc3IqnxE1a6kCFPok,6842
|
|
37
37
|
pythonfmu3/tests/test_deploy.py,sha256=JCDo-O93-izxrP9Q4K0njBcfZ-gAXYx9aZ4plgctz7k,1783
|
|
38
|
+
pythonfmu3/tests/test_docs.py,sha256=VZZFUj2j35mBKvWATIUzJnWNDfiETsLdHZnRZGutOVM,3134
|
|
38
39
|
pythonfmu3/tests/test_fmi3slave.py,sha256=VSd3o3ejOcTG6TlnqxVK7YQc4QCKFTCQnpIgleaxJl4,4801
|
|
39
40
|
pythonfmu3/tests/test_integration.py,sha256=34rus-OU9S5FlAVAfpZyoHrXoziG14d4_yw4Wdt5uVU,12513
|
|
40
41
|
pythonfmu3/tests/test_logger.py,sha256=OcHQhxUtNBPH-0zdmheC4I9OWz1yu48W8MYDshNVkf8,5765
|
|
@@ -50,9 +51,9 @@ pythonfmu3/tests/slaves/pythonslaveMX.py,sha256=rmtiKWj-7DNCEGKAzCoS-6ybkWKabYMh
|
|
|
50
51
|
pythonfmu3/tests/slaves/pythonslaveMXCS.py,sha256=P1aKY92obd-y9VGwDRin9fwmnAlSmdHguFW8E5-LU4I,1417
|
|
51
52
|
pythonfmu3/tests/slaves/pythonslave_read_file.py,sha256=t2sDaZSr2veD91gdqCO9W9CN-AN3TyXABXXfV9Rt0Xs,791
|
|
52
53
|
pythonfmu3/tests/slaves/slavewithdep.py,sha256=hHDFB0dw8wkHiGzxeAVbz9O5LR6ghsFeONgRFdiK_t0,812
|
|
53
|
-
pythonfmu3-0.3.
|
|
54
|
-
pythonfmu3-0.3.
|
|
55
|
-
pythonfmu3-0.3.
|
|
56
|
-
pythonfmu3-0.3.
|
|
57
|
-
pythonfmu3-0.3.
|
|
58
|
-
pythonfmu3-0.3.
|
|
54
|
+
pythonfmu3-0.3.3.dist-info/licenses/LICENSE,sha256=RL2018BU2DKcTi8Eb4ODF-XF3wnr7SDKvMP5S49x4rQ,1188
|
|
55
|
+
pythonfmu3-0.3.3.dist-info/METADATA,sha256=IXN1NfJJ8MAqrE9a7wOmHD95jpATxsMaCLMi3TTayyM,9375
|
|
56
|
+
pythonfmu3-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
57
|
+
pythonfmu3-0.3.3.dist-info/entry_points.txt,sha256=GgkWYs0DKGHLcijMEYZGkhkHOK2RVUpjGwvl1fwyDwQ,60
|
|
58
|
+
pythonfmu3-0.3.3.dist-info/top_level.txt,sha256=6ne7bh_cME1hepxCx1BG8N5QVhVAufrp7LLok6vq7nE,11
|
|
59
|
+
pythonfmu3-0.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|