pythonfmu3 0.3.4__tar.gz → 1.1.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.
Files changed (103) hide show
  1. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/MANIFEST.in +6 -0
  2. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/PKG-INFO +69 -25
  3. pythonfmu3-0.3.4/pythonfmu3.egg-info/PKG-INFO → pythonfmu3-1.1.0/README.md +51 -42
  4. pythonfmu3-1.1.0/pyproject.toml +72 -0
  5. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/__main__.py +8 -0
  6. pythonfmu3-1.1.0/pythonfmu3/_version.py +1 -0
  7. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/builder.py +96 -24
  8. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/fmi3slave.py +23 -19
  9. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/modelexchange.py +17 -2
  10. pythonfmu3-1.1.0/pythonfmu3/osutil.py +47 -0
  11. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/CMakeLists.txt +36 -4
  12. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/generate_shim.py +88 -0
  13. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/src/CMakeLists.txt +73 -0
  14. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_common.hpp +3 -0
  15. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +6 -4
  16. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +922 -0
  17. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +20 -7
  18. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/src/shim/fmi3_forwarders.cpp +612 -0
  19. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/src/shim/shim_loader.cpp +157 -0
  20. pythonfmu3-1.1.0/pythonfmu3/pythonfmu-export/src/shim/shim_loader.hpp +29 -0
  21. pythonfmu3-1.1.0/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-backend.so +0 -0
  22. pythonfmu3-1.1.0/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
  23. pythonfmu3-1.1.0/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
  24. pythonfmu3-1.1.0/pythonfmu3/studio/__init__.py +1 -0
  25. pythonfmu3-1.1.0/pythonfmu3/studio/server.py +358 -0
  26. pythonfmu3-1.1.0/pythonfmu3/studio/static/app.css +179 -0
  27. pythonfmu3-1.1.0/pythonfmu3/studio/static/app.js +613 -0
  28. pythonfmu3-1.1.0/pythonfmu3/studio/static/index.html +141 -0
  29. pythonfmu3-1.1.0/pythonfmu3/studio/static/logo.svg +8 -0
  30. pythonfmu3-1.1.0/pythonfmu3/studio/templates/bouncing_ball.py +75 -0
  31. pythonfmu3-1.1.0/pythonfmu3/studio/templates/dahlquist.py +31 -0
  32. pythonfmu3-1.1.0/pythonfmu3/studio/templates/pid_controller.py +42 -0
  33. pythonfmu3-1.1.0/pythonfmu3/studio/templates/signal_generator.py +40 -0
  34. pythonfmu3-1.1.0/pythonfmu3/studio/templates/surrogate.py +37 -0
  35. pythonfmu3-1.1.0/pythonfmu3/studio/templates/thermostat.py +45 -0
  36. pythonfmu3-1.1.0/pythonfmu3/studio/templates/van_der_pol.py +34 -0
  37. pythonfmu3-1.1.0/pythonfmu3/studio/worker.py +519 -0
  38. pythonfmu3-1.1.0/pythonfmu3/tests/data/fmi3_host.c +31 -0
  39. pythonfmu3-1.1.0/pythonfmu3/tests/fmi3_ctypes_driver.py +137 -0
  40. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_builder.py +48 -2
  41. pythonfmu3-1.1.0/pythonfmu3/tests/test_docs.py +107 -0
  42. pythonfmu3-1.1.0/pythonfmu3/tests/test_example_pipe_cht.py +200 -0
  43. pythonfmu3-1.1.0/pythonfmu3/tests/test_migration_doc.py +34 -0
  44. pythonfmu3-1.1.0/pythonfmu3/tests/test_non_python_host.py +75 -0
  45. pythonfmu3-1.1.0/pythonfmu3/tests/test_osutil.py +23 -0
  46. pythonfmu3-1.1.0/pythonfmu3/tests/test_studio.py +149 -0
  47. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_variables.py +1 -1
  48. pythonfmu3-1.1.0/pythonfmu3/tests/test_wrapper_ctypes.py +61 -0
  49. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/variables.py +4 -3
  50. pythonfmu3-0.3.4/README.md → pythonfmu3-1.1.0/pythonfmu3.egg-info/PKG-INFO +86 -15
  51. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3.egg-info/SOURCES.txt +28 -3
  52. pythonfmu3-1.1.0/setup.cfg +4 -0
  53. pythonfmu3-0.3.4/pythonfmu3/_version.py +0 -1
  54. pythonfmu3-0.3.4/pythonfmu3/osutil.py +0 -22
  55. pythonfmu3-0.3.4/pythonfmu3/pythonfmu-export/src/CMakeLists.txt +0 -45
  56. pythonfmu3-0.3.4/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +0 -933
  57. pythonfmu3-0.3.4/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
  58. pythonfmu3-0.3.4/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
  59. pythonfmu3-0.3.4/pythonfmu3/tests/test_docs.py +0 -98
  60. pythonfmu3-0.3.4/pythonfmu3.egg-info/not-zip-safe +0 -1
  61. pythonfmu3-0.3.4/setup.cfg +0 -57
  62. pythonfmu3-0.3.4/setup.py +0 -4
  63. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/LICENSE +0 -0
  64. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/__init__.py +0 -0
  65. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/cosimulation.py +0 -0
  66. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/default_experiment.py +0 -0
  67. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/deploy.py +0 -0
  68. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/enums.py +0 -0
  69. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/logmsg.py +0 -0
  70. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/.clang-format +0 -0
  71. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/build_unix.sh +0 -0
  72. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/build_win.sh +0 -0
  73. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp +0 -0
  74. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +0 -0
  75. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h +0 -0
  76. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h +0 -0
  77. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h +0 -0
  78. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp +0 -0
  79. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp +0 -0
  80. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/__init__.py +0 -0
  81. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/conftest.py +0 -0
  82. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/data/csvdemo.csv +0 -0
  83. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/data/hello.txt +0 -0
  84. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/PythonSlaveWithException.py +0 -0
  85. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/localmodule.py +0 -0
  86. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/pythonslave.py +0 -0
  87. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/pythonslaveMX.py +0 -0
  88. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/pythonslaveMXCS.py +0 -0
  89. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/pythonslave_arraytypes.py +0 -0
  90. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/pythonslave_read_file.py +0 -0
  91. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/slaves/slavewithdep.py +0 -0
  92. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_deploy.py +0 -0
  93. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_fmi3slave.py +0 -0
  94. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_integration.py +0 -0
  95. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_logger.py +0 -0
  96. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/test_multiple_fmus.py +0 -0
  97. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/tests/utils.py +0 -0
  98. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/unit.py +0 -0
  99. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3/variable_types.py +0 -0
  100. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3.egg-info/dependency_links.txt +0 -0
  101. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3.egg-info/entry_points.txt +0 -0
  102. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3.egg-info/requires.txt +0 -0
  103. {pythonfmu3-0.3.4 → pythonfmu3-1.1.0}/pythonfmu3.egg-info/top_level.txt +0 -0
@@ -6,3 +6,9 @@ recursive-include pythonfmu3/tests/slaves *
6
6
 
7
7
  recursive-include pythonfmu3/resources *
8
8
  recursive-include pythonfmu3/pythonfmu-export *
9
+
10
+ recursive-include pythonfmu3/studio/static *
11
+ recursive-include pythonfmu3/studio/templates *.py
12
+
13
+ prune pythonfmu3/pythonfmu-export/build
14
+ global-exclude __pycache__ *.py[cod]
@@ -1,23 +1,31 @@
1
1
  Metadata-Version: 2.4
2
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
3
+ Version: 1.1.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
- Keywords: FMI
9
- Classifier: Development Status :: 3 - Alpha
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.6
18
- Classifier: Programming Language :: Python :: 3.7
19
- Classifier: Programming Language :: Python :: 3.8
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,38 @@ This project is a fork of the original PythonFMU repository available at https:/
41
49
 
42
50
  ### Support:
43
51
 
44
- Please take a look at the examples to see the supported features.
45
- We currently support both the Model exchange and Cosimulation Interfaces.
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.
76
+
77
+ ### PythonFMU3 Studio
78
+
79
+ `pythonfmu3 studio` opens a local web app to write, check, simulate, build and inspect
80
+ FMUs, with live plots, a model-description preview, an FMU inspector and templates. It
81
+ needs no extra dependencies. See the [Studio docs](docs/studio.md).
82
+
83
+ ![PythonFMU3 Studio](docs/assets/studio-simulate.png)
46
84
 
47
85
  ### How do I build an FMU from python code?
48
86
 
@@ -53,35 +91,37 @@ pip install pythonfmu3
53
91
  ```
54
92
 
55
93
  2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
94
+ Keep a single FMU class per script.
56
95
  3. Run `pythonfmu3 build` to create the fmu.
57
96
 
58
97
  ```
59
- usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS_FILE] [--no-external-tool]
60
- [--no-variable-step] [--interpolate-inputs] [--only-one-per-process] [--handle-state]
61
- [--serialize-state] [--use-memory-management]
62
- [Project files [Project files ...]]
98
+ usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS]
99
+ [--no-external-tool] [--only-one-per-process] [--handle-state] [--serialize-state]
100
+ [--no-variable-step] [--needs-completed-step]
101
+ [Project files ...]
63
102
 
64
103
  Build an FMU from a Python script.
65
104
 
66
105
  positional arguments:
67
106
  Project files Additional project files required by the Python script.
68
107
 
69
- optional arguments:
108
+ options:
70
109
  -h, --help show this help message and exit
71
110
  -f SCRIPT_FILE, --file SCRIPT_FILE
72
111
  Path to the Python script.
73
112
  -d DEST, --dest DEST Where to save the FMU.
74
113
  --doc DOCUMENTATION_FOLDER
75
114
  Documentation folder to include in the FMU.
76
- --terminals TERMINALS_FILE
115
+ --terminals TERMINALS
77
116
  Terminals file (terminalsAndIcons.xml) to include in the FMU.
78
117
  --no-external-tool If given, needsExecutionTool=false
79
- --no-variable-step If given, canHandleVariableCommunicationStepSize=false
80
- --interpolate-inputs If given, canInterpolateInputs=true
81
118
  --only-one-per-process
82
119
  If given, canBeInstantiatedOnlyOncePerProcess=true
83
- --handle-state If given, canGetAndSetFMUstate=true
84
- --serialize-state If given, canSerializeFMUstate=true
120
+ --handle-state If given, canGetAndSetFMUState=true
121
+ --serialize-state If given, canSerializeFMUState=true
122
+ --no-variable-step If given, canHandleVariableCommunicationStepSize=false
123
+ --needs-completed-step
124
+ If given, needsCompletedIntegratorStep=true
85
125
  ```
86
126
 
87
127
  ### How do I build an FMU from python code with third-party dependencies?
@@ -91,7 +131,7 @@ _PythonFMU_ does not package a full environment within the FMU.
91
131
  However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:
92
132
 
93
133
  1. Install _pythonfmu_ package: `pip install pythonfmu3`
94
- 2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
134
+ 2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the examples below).
95
135
  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
136
  4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.
97
137
 
@@ -133,6 +173,8 @@ class PythonSlave(Fmi3Slave):
133
173
  self.author = "John Doe"
134
174
  self.description = "A simple description"
135
175
 
176
+ # Create attributes before registering them: register_variable only adds a setter
177
+ # (needed for inputs and parameters) for attributes that already exist.
136
178
  self.time = 0.0
137
179
  self.intOut = 1
138
180
  self.realOut = 3.0
@@ -172,7 +214,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of
172
214
 
173
215
  ```python
174
216
 
175
- from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
217
+ from pythonfmu3 import Fmi3Causality, Fmi3Initial, Fmi3SlaveBase, Fmi3Variability, Float64, ModelExchange
176
218
  from typing import List
177
219
 
178
220
 
@@ -207,7 +249,9 @@ class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
207
249
 
208
250
  ```
209
251
 
210
- Note: There is a hard requirement that we have a `self.time` member in the derived class.
252
+ Note: Model Exchange classes must define a `self.time` member. Derivative variables give the
253
+ value reference of their state with `derivative=`; value references are assigned in
254
+ registration order starting at 0 (here `time` = 0, `x0` = 1, `x1` = 2).
211
255
 
212
256
  #### Create the FMU
213
257
 
@@ -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,38 @@ This project is a fork of the original PythonFMU repository available at https:/
41
14
 
42
15
  ### Support:
43
16
 
44
- Please take a look at the examples to see the supported features.
45
- We currently support both the Model exchange and Cosimulation Interfaces.
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.
41
+
42
+ ### PythonFMU3 Studio
43
+
44
+ `pythonfmu3 studio` opens a local web app to write, check, simulate, build and inspect
45
+ FMUs, with live plots, a model-description preview, an FMU inspector and templates. It
46
+ needs no extra dependencies. See the [Studio docs](docs/studio.md).
47
+
48
+ ![PythonFMU3 Studio](docs/assets/studio-simulate.png)
46
49
 
47
50
  ### How do I build an FMU from python code?
48
51
 
@@ -53,35 +56,37 @@ pip install pythonfmu3
53
56
  ```
54
57
 
55
58
  2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
59
+ Keep a single FMU class per script.
56
60
  3. Run `pythonfmu3 build` to create the fmu.
57
61
 
58
62
  ```
59
- usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS_FILE] [--no-external-tool]
60
- [--no-variable-step] [--interpolate-inputs] [--only-one-per-process] [--handle-state]
61
- [--serialize-state] [--use-memory-management]
62
- [Project files [Project files ...]]
63
+ usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS]
64
+ [--no-external-tool] [--only-one-per-process] [--handle-state] [--serialize-state]
65
+ [--no-variable-step] [--needs-completed-step]
66
+ [Project files ...]
63
67
 
64
68
  Build an FMU from a Python script.
65
69
 
66
70
  positional arguments:
67
71
  Project files Additional project files required by the Python script.
68
72
 
69
- optional arguments:
73
+ options:
70
74
  -h, --help show this help message and exit
71
75
  -f SCRIPT_FILE, --file SCRIPT_FILE
72
76
  Path to the Python script.
73
77
  -d DEST, --dest DEST Where to save the FMU.
74
78
  --doc DOCUMENTATION_FOLDER
75
79
  Documentation folder to include in the FMU.
76
- --terminals TERMINALS_FILE
80
+ --terminals TERMINALS
77
81
  Terminals file (terminalsAndIcons.xml) to include in the FMU.
78
82
  --no-external-tool If given, needsExecutionTool=false
79
- --no-variable-step If given, canHandleVariableCommunicationStepSize=false
80
- --interpolate-inputs If given, canInterpolateInputs=true
81
83
  --only-one-per-process
82
84
  If given, canBeInstantiatedOnlyOncePerProcess=true
83
- --handle-state If given, canGetAndSetFMUstate=true
84
- --serialize-state If given, canSerializeFMUstate=true
85
+ --handle-state If given, canGetAndSetFMUState=true
86
+ --serialize-state If given, canSerializeFMUState=true
87
+ --no-variable-step If given, canHandleVariableCommunicationStepSize=false
88
+ --needs-completed-step
89
+ If given, needsCompletedIntegratorStep=true
85
90
  ```
86
91
 
87
92
  ### How do I build an FMU from python code with third-party dependencies?
@@ -91,7 +96,7 @@ _PythonFMU_ does not package a full environment within the FMU.
91
96
  However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:
92
97
 
93
98
  1. Install _pythonfmu_ package: `pip install pythonfmu3`
94
- 2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
99
+ 2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the examples below).
95
100
  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
101
  4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.
97
102
 
@@ -133,6 +138,8 @@ class PythonSlave(Fmi3Slave):
133
138
  self.author = "John Doe"
134
139
  self.description = "A simple description"
135
140
 
141
+ # Create attributes before registering them: register_variable only adds a setter
142
+ # (needed for inputs and parameters) for attributes that already exist.
136
143
  self.time = 0.0
137
144
  self.intOut = 1
138
145
  self.realOut = 3.0
@@ -172,7 +179,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of
172
179
 
173
180
  ```python
174
181
 
175
- from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
182
+ from pythonfmu3 import Fmi3Causality, Fmi3Initial, Fmi3SlaveBase, Fmi3Variability, Float64, ModelExchange
176
183
  from typing import List
177
184
 
178
185
 
@@ -207,7 +214,9 @@ class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):
207
214
 
208
215
  ```
209
216
 
210
- Note: There is a hard requirement that we have a `self.time` member in the derived class.
217
+ Note: Model Exchange classes must define a `self.time` member. Derivative variables give the
218
+ value reference of their state with `derivative=`; value references are assigned in
219
+ registration order starting at 0 (here `time` = 0, `x0` = 1, `x1` = 2).
211
220
 
212
221
  #### Create the FMU
213
222
 
@@ -0,0 +1,72 @@
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
+ "studio/static/*",
62
+ "studio/templates/*.py",
63
+ ]
64
+
65
+ [tool.setuptools.exclude-package-data]
66
+ pythonfmu3 = ["pythonfmu-export/build/**", "**/__pycache__/*"]
67
+
68
+ [tool.pytest.ini_options]
69
+ markers = [
70
+ "integration: marks integration tests",
71
+ "doc: marks documentation tests",
72
+ ]
@@ -43,6 +43,14 @@ def cli_main():
43
43
  )
44
44
  deploy.create_command_parser(deploy_parser)
45
45
 
46
+ studio_parser = subparsers.add_parser(
47
+ "studio",
48
+ description="Open PythonFMU3 Studio, a local web app to edit, check, simulate, build and inspect FMUs.",
49
+ help="Open the PythonFMU3 Studio web app."
50
+ )
51
+ from .studio.server import create_command_parser as create_studio_parser
52
+ create_studio_parser(studio_parser)
53
+
46
54
  options = vars(parser.parse_args())
47
55
  execute = options.pop("execute")
48
56
  execute(**options)
@@ -0,0 +1 @@
1
+ __version__ = "1.1.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
- with open(str(file_name), 'r') as file:
26
- data = file.read()
27
- return re.search(r'class (\w+)\(([^)]*\bFmi3Slave(?:Base)?\b[^)]*)\)\s*:', data).group(1)
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
- def get_model_description(filepath: Path, module_name: str) -> Tuple[str, Element]:
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
- class_name = get_class_name(filepath)
49
- instance = getattr(fmu_interface, class_name)(instance_name="dummyInstance", resources=str(filepath.parent))
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] = set(),
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
- # Add information for the Python loader
167
- zip_fmu.writestr(str(resource.joinpath("slavemodule.txt")), module_name)
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
- arcname = (
190
- binaries
191
- / relative_f.parent
192
- / f"{model_identifier}{relative_f.suffix}"
193
- )
194
- zip_fmu.write(f, arcname=arcname)
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
- relative_f = terminals.relative_to(terminals.parent)
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"))