pythonfmu3 0.3.4__tar.gz → 1.0.0__tar.gz
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-0.3.4 → pythonfmu3-1.0.0}/MANIFEST.in +3 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/PKG-INFO +61 -25
- pythonfmu3-0.3.4/pythonfmu3.egg-info/PKG-INFO → pythonfmu3-1.0.0/README.md +43 -42
- pythonfmu3-1.0.0/pyproject.toml +70 -0
- pythonfmu3-1.0.0/pythonfmu3/_version.py +1 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/builder.py +96 -24
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/fmi3slave.py +23 -19
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/modelexchange.py +17 -2
- pythonfmu3-1.0.0/pythonfmu3/osutil.py +47 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/CMakeLists.txt +36 -4
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/generate_shim.py +88 -0
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/src/CMakeLists.txt +73 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_common.hpp +3 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +6 -4
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +922 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +20 -7
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/src/shim/fmi3_forwarders.cpp +612 -0
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/src/shim/shim_loader.cpp +157 -0
- pythonfmu3-1.0.0/pythonfmu3/pythonfmu-export/src/shim/shim_loader.hpp +29 -0
- pythonfmu3-1.0.0/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-backend.so +0 -0
- pythonfmu3-1.0.0/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
- pythonfmu3-1.0.0/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3-1.0.0/pythonfmu3/tests/data/fmi3_host.c +31 -0
- pythonfmu3-1.0.0/pythonfmu3/tests/fmi3_ctypes_driver.py +137 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_builder.py +48 -2
- pythonfmu3-1.0.0/pythonfmu3/tests/test_docs.py +107 -0
- pythonfmu3-1.0.0/pythonfmu3/tests/test_non_python_host.py +75 -0
- pythonfmu3-1.0.0/pythonfmu3/tests/test_osutil.py +23 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_variables.py +1 -1
- pythonfmu3-1.0.0/pythonfmu3/tests/test_wrapper_ctypes.py +61 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/variables.py +4 -3
- pythonfmu3-0.3.4/README.md → pythonfmu3-1.0.0/pythonfmu3.egg-info/PKG-INFO +78 -15
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3.egg-info/SOURCES.txt +11 -3
- pythonfmu3-1.0.0/setup.cfg +4 -0
- pythonfmu3-0.3.4/pythonfmu3/_version.py +0 -1
- pythonfmu3-0.3.4/pythonfmu3/osutil.py +0 -22
- pythonfmu3-0.3.4/pythonfmu3/pythonfmu-export/src/CMakeLists.txt +0 -45
- pythonfmu3-0.3.4/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +0 -933
- pythonfmu3-0.3.4/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
- pythonfmu3-0.3.4/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3-0.3.4/pythonfmu3/tests/test_docs.py +0 -98
- pythonfmu3-0.3.4/pythonfmu3.egg-info/not-zip-safe +0 -1
- pythonfmu3-0.3.4/setup.cfg +0 -57
- pythonfmu3-0.3.4/setup.py +0 -4
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/LICENSE +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/__init__.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/__main__.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/cosimulation.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/default_experiment.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/deploy.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/enums.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/logmsg.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/.clang-format +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/build_unix.sh +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/build_win.sh +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/__init__.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/conftest.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/data/csvdemo.csv +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/data/hello.txt +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/PythonSlaveWithException.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/localmodule.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/pythonslave.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/pythonslaveMX.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/pythonslaveMXCS.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/pythonslave_arraytypes.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/pythonslave_read_file.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/slaves/slavewithdep.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_deploy.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_fmi3slave.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_integration.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_logger.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/test_multiple_fmus.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/tests/utils.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/unit.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3/variable_types.py +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3.egg-info/dependency_links.txt +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3.egg-info/entry_points.txt +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3.egg-info/requires.txt +0 -0
- {pythonfmu3-0.3.4 → pythonfmu3-1.0.0}/pythonfmu3.egg-info/top_level.txt +0 -0
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonfmu3
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A lightweight framework that enables the packaging of
|
|
5
|
-
Home-page: https://github.com/stephensmith25/PythonFMU3
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A lightweight framework that enables the packaging of Python 3 code as FMI 3.0 co-simulation and model exchange FMUs.
|
|
6
5
|
Author: Stephen Smith
|
|
7
|
-
License: MIT
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/StephenSmith25/PythonFMU3
|
|
8
|
+
Project-URL: Documentation, https://pythonfmu3.readthedocs.io/
|
|
9
|
+
Project-URL: Issues, https://github.com/StephenSmith25/PythonFMU3/issues
|
|
10
|
+
Keywords: FMI,FMI3,FMU,co-simulation,model exchange,simulation
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
12
|
Classifier: Intended Audience :: End Users/Desktop
|
|
11
13
|
Classifier: Intended Audience :: Developers
|
|
12
14
|
Classifier: Intended Audience :: Science/Research
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
15
|
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
15
19
|
Classifier: Programming Language :: C++
|
|
16
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
27
|
Classifier: Topic :: Scientific/Engineering
|
|
28
|
+
Requires-Python: >=3.9
|
|
21
29
|
Description-Content-Type: text/markdown
|
|
22
30
|
License-File: LICENSE
|
|
23
31
|
Provides-Extra: tests
|
|
@@ -41,8 +49,30 @@ This project is a fork of the original PythonFMU repository available at https:/
|
|
|
41
49
|
|
|
42
50
|
### Support:
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
Both the Model Exchange and Co-simulation interfaces are supported, with Float64, Int32,
|
|
53
|
+
Int64, UInt64, Boolean, String and Enumeration variables (plus NumPy arrays of the numeric
|
|
54
|
+
types). See the [documentation](https://pythonfmu3.readthedocs.io/) for the full list of
|
|
55
|
+
supported features and current limitations, and the `examples` folder for more models.
|
|
56
|
+
|
|
57
|
+
See the [changelog](docs/changelog.md) for what changed in each release.
|
|
58
|
+
|
|
59
|
+
### Where can I run the FMU?
|
|
60
|
+
|
|
61
|
+
In any FMI 3.0 importer: Python-based tools such as FMPy, and native tools such as
|
|
62
|
+
OMSimulator, `fmusim` or Simulink.
|
|
63
|
+
|
|
64
|
+
The machine running the simulation needs Python 3 with a **shared** Python library, plus
|
|
65
|
+
the packages your model imports (see `pythonfmu3 deploy` below). Inside a Python-based
|
|
66
|
+
importer the FMU uses that interpreter. Otherwise, on Linux and macOS the FMU finds and
|
|
67
|
+
loads `libpython` itself; if it picks the wrong one, or none, point it at the right library:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
export PYTHONFMU_PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
On Windows, `python3.dll` must be on the `PATH`. See
|
|
74
|
+
[Running FMUs outside Python](docs/install.md#running-fmus-outside-python-linux-and-macos)
|
|
75
|
+
for the full lookup order.
|
|
46
76
|
|
|
47
77
|
### How do I build an FMU from python code?
|
|
48
78
|
|
|
@@ -53,35 +83,37 @@ pip install pythonfmu3
|
|
|
53
83
|
```
|
|
54
84
|
|
|
55
85
|
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
|
|
86
|
+
Keep a single FMU class per script.
|
|
56
87
|
3. Run `pythonfmu3 build` to create the fmu.
|
|
57
88
|
|
|
58
89
|
```
|
|
59
|
-
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS]
|
|
91
|
+
[--no-external-tool] [--only-one-per-process] [--handle-state] [--serialize-state]
|
|
92
|
+
[--no-variable-step] [--needs-completed-step]
|
|
93
|
+
[Project files ...]
|
|
63
94
|
|
|
64
95
|
Build an FMU from a Python script.
|
|
65
96
|
|
|
66
97
|
positional arguments:
|
|
67
98
|
Project files Additional project files required by the Python script.
|
|
68
99
|
|
|
69
|
-
|
|
100
|
+
options:
|
|
70
101
|
-h, --help show this help message and exit
|
|
71
102
|
-f SCRIPT_FILE, --file SCRIPT_FILE
|
|
72
103
|
Path to the Python script.
|
|
73
104
|
-d DEST, --dest DEST Where to save the FMU.
|
|
74
105
|
--doc DOCUMENTATION_FOLDER
|
|
75
106
|
Documentation folder to include in the FMU.
|
|
76
|
-
--terminals
|
|
107
|
+
--terminals TERMINALS
|
|
77
108
|
Terminals file (terminalsAndIcons.xml) to include in the FMU.
|
|
78
109
|
--no-external-tool If given, needsExecutionTool=false
|
|
79
|
-
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
|
|
80
|
-
--interpolate-inputs If given, canInterpolateInputs=true
|
|
81
110
|
--only-one-per-process
|
|
82
111
|
If given, canBeInstantiatedOnlyOncePerProcess=true
|
|
83
|
-
--handle-state If given,
|
|
84
|
-
--serialize-state If given,
|
|
112
|
+
--handle-state If given, canGetAndSetFMUState=true
|
|
113
|
+
--serialize-state If given, canSerializeFMUState=true
|
|
114
|
+
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
|
|
115
|
+
--needs-completed-step
|
|
116
|
+
If given, needsCompletedIntegratorStep=true
|
|
85
117
|
```
|
|
86
118
|
|
|
87
119
|
### How do I build an FMU from python code with third-party dependencies?
|
|
@@ -91,7 +123,7 @@ _PythonFMU_ does not package a full environment within the FMU.
|
|
|
91
123
|
However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:
|
|
92
124
|
|
|
93
125
|
1. Install _pythonfmu_ package: `pip install pythonfmu3`
|
|
94
|
-
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see
|
|
126
|
+
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the examples below).
|
|
95
127
|
3. Create a `requirements.txt` file (to use _pip_ manager) and/or a `environment.yml` file (to use _conda_ manager) that defines your dependencies.
|
|
96
128
|
4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.
|
|
97
129
|
|
|
@@ -133,6 +165,8 @@ class PythonSlave(Fmi3Slave):
|
|
|
133
165
|
self.author = "John Doe"
|
|
134
166
|
self.description = "A simple description"
|
|
135
167
|
|
|
168
|
+
# Create attributes before registering them: register_variable only adds a setter
|
|
169
|
+
# (needed for inputs and parameters) for attributes that already exist.
|
|
136
170
|
self.time = 0.0
|
|
137
171
|
self.intOut = 1
|
|
138
172
|
self.realOut = 3.0
|
|
@@ -172,7 +206,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of
|
|
|
172
206
|
|
|
173
207
|
```python
|
|
174
208
|
|
|
175
|
-
from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
|
|
209
|
+
from pythonfmu3 import Fmi3Causality, Fmi3Initial, Fmi3SlaveBase, Fmi3Variability, Float64, ModelExchange
|
|
176
210
|
from typing import List
|
|
177
211
|
|
|
178
212
|
|
|
@@ -207,7 +241,9 @@ class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
|
|
|
207
241
|
|
|
208
242
|
```
|
|
209
243
|
|
|
210
|
-
Note:
|
|
244
|
+
Note: Model Exchange classes must define a `self.time` member. Derivative variables give the
|
|
245
|
+
value reference of their state with `derivative=`; value references are assigned in
|
|
246
|
+
registration order starting at 0 (here `time` = 0, `x0` = 1, `x1` = 2).
|
|
211
247
|
|
|
212
248
|
#### Create the FMU
|
|
213
249
|
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pythonfmu3
|
|
3
|
-
Version: 0.3.4
|
|
4
|
-
Summary: A lightweight framework that enables the packaging of Python3.x code as co-simulation FMUs.
|
|
5
|
-
Home-page: https://github.com/stephensmith25/PythonFMU3
|
|
6
|
-
Author: Stephen Smith
|
|
7
|
-
License: MIT
|
|
8
|
-
Keywords: FMI
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Intended Audience :: Science/Research
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Natural Language :: English
|
|
15
|
-
Classifier: Programming Language :: C++
|
|
16
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Topic :: Scientific/Engineering
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
License-File: LICENSE
|
|
23
|
-
Provides-Extra: tests
|
|
24
|
-
Requires-Dist: pytest; extra == "tests"
|
|
25
|
-
Requires-Dist: fmpy; extra == "tests"
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
1
|
# PythonFMU3
|
|
29
2
|
|
|
30
3
|
> A lightweight framework that enables the packaging of Python 3 code as FMUs (following FMI version 3.0).
|
|
@@ -41,8 +14,30 @@ This project is a fork of the original PythonFMU repository available at https:/
|
|
|
41
14
|
|
|
42
15
|
### Support:
|
|
43
16
|
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
Both the Model Exchange and Co-simulation interfaces are supported, with Float64, Int32,
|
|
18
|
+
Int64, UInt64, Boolean, String and Enumeration variables (plus NumPy arrays of the numeric
|
|
19
|
+
types). See the [documentation](https://pythonfmu3.readthedocs.io/) for the full list of
|
|
20
|
+
supported features and current limitations, and the `examples` folder for more models.
|
|
21
|
+
|
|
22
|
+
See the [changelog](docs/changelog.md) for what changed in each release.
|
|
23
|
+
|
|
24
|
+
### Where can I run the FMU?
|
|
25
|
+
|
|
26
|
+
In any FMI 3.0 importer: Python-based tools such as FMPy, and native tools such as
|
|
27
|
+
OMSimulator, `fmusim` or Simulink.
|
|
28
|
+
|
|
29
|
+
The machine running the simulation needs Python 3 with a **shared** Python library, plus
|
|
30
|
+
the packages your model imports (see `pythonfmu3 deploy` below). Inside a Python-based
|
|
31
|
+
importer the FMU uses that interpreter. Otherwise, on Linux and macOS the FMU finds and
|
|
32
|
+
loads `libpython` itself; if it picks the wrong one, or none, point it at the right library:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export PYTHONFMU_PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
On Windows, `python3.dll` must be on the `PATH`. See
|
|
39
|
+
[Running FMUs outside Python](docs/install.md#running-fmus-outside-python-linux-and-macos)
|
|
40
|
+
for the full lookup order.
|
|
46
41
|
|
|
47
42
|
### How do I build an FMU from python code?
|
|
48
43
|
|
|
@@ -53,35 +48,37 @@ pip install pythonfmu3
|
|
|
53
48
|
```
|
|
54
49
|
|
|
55
50
|
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
|
|
51
|
+
Keep a single FMU class per script.
|
|
56
52
|
3. Run `pythonfmu3 build` to create the fmu.
|
|
57
53
|
|
|
58
54
|
```
|
|
59
|
-
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS]
|
|
56
|
+
[--no-external-tool] [--only-one-per-process] [--handle-state] [--serialize-state]
|
|
57
|
+
[--no-variable-step] [--needs-completed-step]
|
|
58
|
+
[Project files ...]
|
|
63
59
|
|
|
64
60
|
Build an FMU from a Python script.
|
|
65
61
|
|
|
66
62
|
positional arguments:
|
|
67
63
|
Project files Additional project files required by the Python script.
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
options:
|
|
70
66
|
-h, --help show this help message and exit
|
|
71
67
|
-f SCRIPT_FILE, --file SCRIPT_FILE
|
|
72
68
|
Path to the Python script.
|
|
73
69
|
-d DEST, --dest DEST Where to save the FMU.
|
|
74
70
|
--doc DOCUMENTATION_FOLDER
|
|
75
71
|
Documentation folder to include in the FMU.
|
|
76
|
-
--terminals
|
|
72
|
+
--terminals TERMINALS
|
|
77
73
|
Terminals file (terminalsAndIcons.xml) to include in the FMU.
|
|
78
74
|
--no-external-tool If given, needsExecutionTool=false
|
|
79
|
-
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
|
|
80
|
-
--interpolate-inputs If given, canInterpolateInputs=true
|
|
81
75
|
--only-one-per-process
|
|
82
76
|
If given, canBeInstantiatedOnlyOncePerProcess=true
|
|
83
|
-
--handle-state If given,
|
|
84
|
-
--serialize-state If given,
|
|
77
|
+
--handle-state If given, canGetAndSetFMUState=true
|
|
78
|
+
--serialize-state If given, canSerializeFMUState=true
|
|
79
|
+
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
|
|
80
|
+
--needs-completed-step
|
|
81
|
+
If given, needsCompletedIntegratorStep=true
|
|
85
82
|
```
|
|
86
83
|
|
|
87
84
|
### How do I build an FMU from python code with third-party dependencies?
|
|
@@ -91,7 +88,7 @@ _PythonFMU_ does not package a full environment within the FMU.
|
|
|
91
88
|
However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:
|
|
92
89
|
|
|
93
90
|
1. Install _pythonfmu_ package: `pip install pythonfmu3`
|
|
94
|
-
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see
|
|
91
|
+
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the examples below).
|
|
95
92
|
3. Create a `requirements.txt` file (to use _pip_ manager) and/or a `environment.yml` file (to use _conda_ manager) that defines your dependencies.
|
|
96
93
|
4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.
|
|
97
94
|
|
|
@@ -133,6 +130,8 @@ class PythonSlave(Fmi3Slave):
|
|
|
133
130
|
self.author = "John Doe"
|
|
134
131
|
self.description = "A simple description"
|
|
135
132
|
|
|
133
|
+
# Create attributes before registering them: register_variable only adds a setter
|
|
134
|
+
# (needed for inputs and parameters) for attributes that already exist.
|
|
136
135
|
self.time = 0.0
|
|
137
136
|
self.intOut = 1
|
|
138
137
|
self.realOut = 3.0
|
|
@@ -172,7 +171,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of
|
|
|
172
171
|
|
|
173
172
|
```python
|
|
174
173
|
|
|
175
|
-
from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
|
|
174
|
+
from pythonfmu3 import Fmi3Causality, Fmi3Initial, Fmi3SlaveBase, Fmi3Variability, Float64, ModelExchange
|
|
176
175
|
from typing import List
|
|
177
176
|
|
|
178
177
|
|
|
@@ -207,7 +206,9 @@ class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
|
|
|
207
206
|
|
|
208
207
|
```
|
|
209
208
|
|
|
210
|
-
Note:
|
|
209
|
+
Note: Model Exchange classes must define a `self.time` member. Derivative variables give the
|
|
210
|
+
value reference of their state with `derivative=`; value references are assigned in
|
|
211
|
+
registration order starting at 0 (here `time` = 0, `x0` = 1, `x1` = 2).
|
|
211
212
|
|
|
212
213
|
#### Create the FMU
|
|
213
214
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# >= 77 for the SPDX `license` expression (PEP 639)
|
|
3
|
+
requires = ["setuptools>=77"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "pythonfmu3"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
description = "A lightweight framework that enables the packaging of Python 3 code as FMI 3.0 co-simulation and model exchange FMUs."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
authors = [{ name = "Stephen Smith" }]
|
|
15
|
+
keywords = ["FMI", "FMI3", "FMU", "co-simulation", "model exchange", "simulation"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Operating System :: Microsoft :: Windows",
|
|
23
|
+
"Operating System :: POSIX :: Linux",
|
|
24
|
+
"Operating System :: MacOS",
|
|
25
|
+
"Programming Language :: C++",
|
|
26
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
33
|
+
"Topic :: Scientific/Engineering",
|
|
34
|
+
]
|
|
35
|
+
dependencies = []
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
tests = ["pytest", "fmpy"]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/StephenSmith25/PythonFMU3"
|
|
42
|
+
Documentation = "https://pythonfmu3.readthedocs.io/"
|
|
43
|
+
Issues = "https://github.com/StephenSmith25/PythonFMU3/issues"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
pythonfmu3 = "pythonfmu3.__main__:cli_main"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.dynamic]
|
|
49
|
+
version = { attr = "pythonfmu3._version.__version__" }
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
include = ["pythonfmu3*"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
# Prebuilt wrapper libraries, the wrapper sources (embedded in every FMU) and test data
|
|
56
|
+
pythonfmu3 = [
|
|
57
|
+
"resources/binaries/*/*",
|
|
58
|
+
"pythonfmu-export/**/*",
|
|
59
|
+
"tests/data/*",
|
|
60
|
+
"tests/slaves/*",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.exclude-package-data]
|
|
64
|
+
pythonfmu3 = ["pythonfmu-export/build/**", "**/__pycache__/*"]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
markers = [
|
|
68
|
+
"integration: marks integration tests",
|
|
69
|
+
"doc: marks documentation tests",
|
|
70
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Python FMU builder"""
|
|
2
2
|
import argparse
|
|
3
|
-
import importlib
|
|
3
|
+
import importlib.util
|
|
4
4
|
import itertools
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
7
|
import shutil
|
|
8
8
|
import sys
|
|
9
|
+
import sysconfig
|
|
9
10
|
import tempfile
|
|
10
11
|
import zipfile
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import Iterable, Optional, Tuple, Union
|
|
13
|
+
from typing import Iterable, List, Optional, Tuple, Union
|
|
13
14
|
from xml.dom.minidom import parseString
|
|
14
15
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
|
15
16
|
from .osutil import get_lib_extension, get_platform
|
|
@@ -21,13 +22,49 @@ HERE = Path(__file__).parent
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
_SLAVE_CLASS_RE = re.compile(r'^class (\w+)\(([^)]*\bFmi3Slave(?:Base)?\b[^)]*)\)\s*:', re.MULTILINE)
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
def get_class_name(file_name: Path) -> str:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
"""Find the name of the first class in *file_name* that textually extends Fmi3Slave/Fmi3SlaveBase."""
|
|
30
|
+
data = Path(file_name).read_text(encoding="utf-8")
|
|
31
|
+
match = _SLAVE_CLASS_RE.search(data)
|
|
32
|
+
if match is None:
|
|
33
|
+
raise ValueError(f"Unable to find a class extending Fmi3Slave or Fmi3SlaveBase in {file_name!s}")
|
|
34
|
+
return match.group(1)
|
|
35
|
+
|
|
28
36
|
|
|
37
|
+
def _find_slave_class(module, filepath: Path) -> type:
|
|
38
|
+
"""Locate the FMU class in an imported module.
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
Classes defined in the module that inherit from Fmi3SlaveBase are candidates. If the
|
|
41
|
+
regex-based lookup (also used by the native wrapper as a fallback) matches one of them
|
|
42
|
+
it wins, so behaviour stays identical for existing scripts; otherwise a single
|
|
43
|
+
candidate is accepted (this handles multi-line base lists and indirect inheritance).
|
|
44
|
+
"""
|
|
45
|
+
candidates = {
|
|
46
|
+
name: obj for name, obj in vars(module).items()
|
|
47
|
+
if isinstance(obj, type)
|
|
48
|
+
and obj.__module__ == module.__name__
|
|
49
|
+
and any(base.__name__ == Fmi3SlaveBase.__name__ for base in obj.__mro__)
|
|
50
|
+
}
|
|
51
|
+
try:
|
|
52
|
+
preferred = get_class_name(filepath)
|
|
53
|
+
except ValueError:
|
|
54
|
+
preferred = None
|
|
55
|
+
if preferred in candidates:
|
|
56
|
+
return candidates[preferred]
|
|
57
|
+
if len(candidates) == 1:
|
|
58
|
+
return next(iter(candidates.values()))
|
|
59
|
+
if not candidates:
|
|
60
|
+
raise ValueError(f"No class inheriting from {Fmi3SlaveBase.__qualname__} found in {filepath!s}")
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Several FMU classes found in {filepath!s} ({', '.join(sorted(candidates))}); "
|
|
63
|
+
"keep a single Fmi3Slave subclass per script."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Element, str]:
|
|
31
68
|
"""Extract the FMU model description as XML.
|
|
32
69
|
|
|
33
70
|
Args:
|
|
@@ -35,7 +72,7 @@ def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Elemen
|
|
|
35
72
|
module_name (str) : python module to load
|
|
36
73
|
|
|
37
74
|
Returns:
|
|
38
|
-
Tuple[str, xml.etree.TreeElement.Element] : FMU model name, model description
|
|
75
|
+
Tuple[str, xml.etree.TreeElement.Element, str] : FMU model name, model description, class name
|
|
39
76
|
"""
|
|
40
77
|
# Add current folder to handle local dependencies
|
|
41
78
|
sys.path.insert(0, str(filepath.parent))
|
|
@@ -45,8 +82,9 @@ def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Elemen
|
|
|
45
82
|
fmu_interface = importlib.util.module_from_spec(spec)
|
|
46
83
|
spec.loader.exec_module(fmu_interface)
|
|
47
84
|
# Instantiate the interface
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
slave_class = _find_slave_class(fmu_interface, filepath)
|
|
86
|
+
class_name = slave_class.__name__
|
|
87
|
+
instance = slave_class(instance_name="dummyInstance", resources=str(filepath.parent))
|
|
50
88
|
finally:
|
|
51
89
|
sys.path.remove(str(filepath.parent)) # remove inserted temporary path
|
|
52
90
|
|
|
@@ -55,7 +93,30 @@ def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Elemen
|
|
|
55
93
|
f"The provided class '{class_name}' does not inherit from {Fmi3SlaveBase.__qualname__}"
|
|
56
94
|
)
|
|
57
95
|
# Produce the xml
|
|
58
|
-
return instance.modelName, instance.to_xml()
|
|
96
|
+
return instance.modelName, instance.to_xml(), class_name
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _is_fmu_entry_library(path: Path) -> bool:
|
|
100
|
+
"""True for the library exporting the FMI functions (renamed to the model identifier)."""
|
|
101
|
+
return path.stem in ("pythonfmu-export", "libpythonfmu-export")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _libpython_candidates() -> List[str]:
|
|
105
|
+
"""Paths of the shared libpython of the running interpreter, if it has one."""
|
|
106
|
+
if sys.platform == "win32":
|
|
107
|
+
return [] # the Windows library links python3.dll directly
|
|
108
|
+
libdir = sysconfig.get_config_var("LIBDIR") or ""
|
|
109
|
+
framework_prefix = sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX") or ""
|
|
110
|
+
candidates = []
|
|
111
|
+
for name in (sysconfig.get_config_var("INSTSONAME"), sysconfig.get_config_var("LDLIBRARY")):
|
|
112
|
+
if not name or name.endswith(".a"):
|
|
113
|
+
continue # statically linked interpreter: nothing to point at
|
|
114
|
+
for base in (libdir, framework_prefix):
|
|
115
|
+
if base:
|
|
116
|
+
candidate = Path(base) / name
|
|
117
|
+
if candidate.is_file() and str(candidate) not in candidates:
|
|
118
|
+
candidates.append(str(candidate))
|
|
119
|
+
return candidates
|
|
59
120
|
|
|
60
121
|
|
|
61
122
|
class FmuBuilder:
|
|
@@ -64,7 +125,7 @@ class FmuBuilder:
|
|
|
64
125
|
def build_FMU(
|
|
65
126
|
script_file: FilePath,
|
|
66
127
|
dest: FilePath = ".",
|
|
67
|
-
project_files: Iterable[FilePath] =
|
|
128
|
+
project_files: Iterable[FilePath] = (),
|
|
68
129
|
documentation_folder: Optional[FilePath] = None,
|
|
69
130
|
terminals : Optional[FilePath] = None,
|
|
70
131
|
**options,
|
|
@@ -130,7 +191,7 @@ class FmuBuilder:
|
|
|
130
191
|
else:
|
|
131
192
|
shutil.copy2(file_, temp_dir)
|
|
132
193
|
|
|
133
|
-
model_identifier, xml = get_model_description(
|
|
194
|
+
model_identifier, xml, class_name = get_model_description(
|
|
134
195
|
temp_dir.absolute() / script_file.name, module_name
|
|
135
196
|
)
|
|
136
197
|
|
|
@@ -163,8 +224,20 @@ class FmuBuilder:
|
|
|
163
224
|
relative_f = f.relative_to(temp_dir)
|
|
164
225
|
zip_fmu.write(f, arcname=(resource / relative_f))
|
|
165
226
|
|
|
166
|
-
#
|
|
167
|
-
|
|
227
|
+
# Hint for the Linux/macOS shim: where the Python that built this FMU
|
|
228
|
+
# keeps its shared library (used when the importer is not Python itself).
|
|
229
|
+
libpython_hints = _libpython_candidates()
|
|
230
|
+
if libpython_hints:
|
|
231
|
+
zip_fmu.writestr(
|
|
232
|
+
str(resource.joinpath("pythonfmu_libpython.txt")),
|
|
233
|
+
"\n".join(libpython_hints) + "\n",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Add information for the Python loader: line 1 is the module, line 2 the class.
|
|
237
|
+
# Older wrappers only read the first line, so this stays backward compatible.
|
|
238
|
+
zip_fmu.writestr(
|
|
239
|
+
str(resource.joinpath("slavemodule.txt")), f"{module_name}\n{class_name}\n"
|
|
240
|
+
)
|
|
168
241
|
|
|
169
242
|
# Add FMI API wrapping Python class source
|
|
170
243
|
sources = Path("sources")
|
|
@@ -174,7 +247,7 @@ class FmuBuilder:
|
|
|
174
247
|
):
|
|
175
248
|
relative_f = f.relative_to(src)
|
|
176
249
|
if "build" in relative_f.parts:
|
|
177
|
-
continue
|
|
250
|
+
continue
|
|
178
251
|
zip_fmu.write(f, arcname=(sources / relative_f))
|
|
179
252
|
|
|
180
253
|
# Add FMI API wrapping Python class library
|
|
@@ -186,12 +259,13 @@ class FmuBuilder:
|
|
|
186
259
|
src_binaries.rglob("*.dylib"),
|
|
187
260
|
):
|
|
188
261
|
relative_f = f.relative_to(src_binaries)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
262
|
+
# Only the FMI entry-point library takes the model identifier's name;
|
|
263
|
+
# helper libraries (the Linux/macOS backend) keep their own name.
|
|
264
|
+
if _is_fmu_entry_library(f):
|
|
265
|
+
name = f"{model_identifier}{relative_f.suffix}"
|
|
266
|
+
else:
|
|
267
|
+
name = relative_f.name
|
|
268
|
+
zip_fmu.write(f, arcname=(binaries / relative_f.parent / name))
|
|
195
269
|
|
|
196
270
|
# Add the documentation folder
|
|
197
271
|
if documentation_folder is not None:
|
|
@@ -203,9 +277,7 @@ class FmuBuilder:
|
|
|
203
277
|
|
|
204
278
|
if terminals is not None:
|
|
205
279
|
terminalsFolder = Path("terminalsAndIcons")
|
|
206
|
-
|
|
207
|
-
zip_fmu.write(f, arcname=(terminalsFolder / relative_f))
|
|
208
|
-
|
|
280
|
+
zip_fmu.write(terminals, arcname=(terminalsFolder / terminals.name))
|
|
209
281
|
|
|
210
282
|
# Add the model description
|
|
211
283
|
xml_str = parseString(tostring(xml, "UTF-8"))
|