pythonfmu3 0.1.10__tar.gz → 0.1.16__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 (65) hide show
  1. {pythonfmu3-0.1.10/pythonfmu3.egg-info → pythonfmu3-0.1.16}/PKG-INFO +11 -14
  2. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/README.md +6 -11
  3. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/__init__.py +2 -1
  4. pythonfmu3-0.1.16/pythonfmu3/_version.py +1 -0
  5. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/fmi3slave.py +23 -5
  6. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/osutil.py +2 -2
  7. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_common.hpp +0 -3
  8. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +4 -3
  9. pythonfmu3-0.1.16/pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp +18 -0
  10. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +67 -8
  11. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +3 -1
  12. pythonfmu3-0.1.16/pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp +85 -0
  13. pythonfmu3-0.1.16/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
  14. pythonfmu3-0.1.16/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
  15. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/slaves/PythonSlaveWithException.py +4 -1
  16. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/slaves/pythonslave.py +3 -0
  17. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/slaves/pythonslave_read_file.py +4 -1
  18. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/slaves/slavewithdep.py +4 -1
  19. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_integration.py +41 -47
  20. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_multiple_fmus.py +12 -10
  21. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_variables.py +3 -1
  22. pythonfmu3-0.1.16/pythonfmu3/unit.py +62 -0
  23. pythonfmu3-0.1.10/pythonfmu3/type_definitions.py → pythonfmu3-0.1.16/pythonfmu3/variable_types.py +41 -25
  24. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/variables.py +46 -5
  25. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16/pythonfmu3.egg-info}/PKG-INFO +11 -14
  26. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/SOURCES.txt +3 -1
  27. pythonfmu3-0.1.10/pythonfmu3/_version.py +0 -1
  28. pythonfmu3-0.1.10/pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp +0 -41
  29. pythonfmu3-0.1.10/pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
  30. pythonfmu3-0.1.10/pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
  31. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/LICENSE +0 -0
  32. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/MANIFEST.in +0 -0
  33. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/__main__.py +0 -0
  34. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/builder.py +0 -0
  35. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/default_experiment.py +0 -0
  36. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/deploy.py +0 -0
  37. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/enums.py +0 -0
  38. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/logmsg.py +0 -0
  39. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/.clang-format +0 -0
  40. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/CMakeLists.txt +0 -0
  41. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/build_unix.sh +0 -0
  42. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/build_win.sh +0 -0
  43. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/CMakeLists.txt +0 -0
  44. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp +0 -0
  45. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +0 -0
  46. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h +0 -0
  47. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h +0 -0
  48. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h +0 -0
  49. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/__init__.py +0 -0
  50. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/conftest.py +0 -0
  51. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/data/csvdemo.csv +0 -0
  52. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/data/hello.txt +0 -0
  53. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/slaves/localmodule.py +0 -0
  54. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_builder.py +0 -0
  55. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_deploy.py +0 -0
  56. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_fmi3slave.py +0 -0
  57. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/test_logger.py +0 -0
  58. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3/tests/utils.py +0 -0
  59. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/dependency_links.txt +0 -0
  60. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/entry_points.txt +0 -0
  61. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/not-zip-safe +0 -0
  62. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/requires.txt +0 -0
  63. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/pythonfmu3.egg-info/top_level.txt +0 -0
  64. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/setup.cfg +0 -0
  65. {pythonfmu3-0.1.10 → pythonfmu3-0.1.16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pythonfmu3
3
- Version: 0.1.10
3
+ Version: 0.1.16
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
@@ -19,8 +19,10 @@ Classifier: Programming Language :: Python :: 3.7
19
19
  Classifier: Programming Language :: Python :: 3.8
20
20
  Classifier: Topic :: Scientific/Engineering
21
21
  Description-Content-Type: text/markdown
22
- Provides-Extra: tests
23
22
  License-File: LICENSE
23
+ Provides-Extra: tests
24
+ Requires-Dist: pytest; extra == "tests"
25
+ Requires-Dist: fmpy; extra == "tests"
24
26
 
25
27
  # PythonFMU3
26
28
 
@@ -38,13 +40,7 @@ This project is a fork of the original PythonFMU repository available at https:/
38
40
 
39
41
  ### Support:
40
42
 
41
- We currently support a basic FMU which follows the FMI3 standard. This includes support for:
42
-
43
- - Float64, Int32, UInt64, String, Boolean variables
44
- - Step Mode
45
- - Arrays, which requires the numpy package
46
-
47
- Please take a look at the examples bouncingBall.py and linearTransform.py for a demonstration of these features.
43
+ Please take a look at the examples to see the supported features.
48
44
 
49
45
  ### Future
50
46
 
@@ -53,7 +49,6 @@ In no particular order, we plan to add support for:
53
49
  - Support more variable types from FMI3
54
50
  - Improve array support
55
51
  - Add event mode
56
- - Include FMI3 related testing
57
52
 
58
53
  ### How do I build an FMU from python code?
59
54
 
@@ -136,16 +131,18 @@ from pythonfmu3 import Fmi3Causality, Fmi3Slave, Boolean, Int32, Float64, String
136
131
 
137
132
  class PythonSlave(Fmi3Slave):
138
133
 
139
- author = "John Doe"
140
- description = "A simple description"
141
-
142
134
  def __init__(self, **kwargs):
143
135
  super().__init__(**kwargs)
144
136
 
137
+ self.author = "John Doe"
138
+ self.description = "A simple description"
139
+
140
+ self.time = 0.0
145
141
  self.intOut = 1
146
142
  self.realOut = 3.0
147
143
  self.booleanVariable = True
148
144
  self.stringVariable = "Hello World!"
145
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent))
149
146
  self.register_variable(Int32("intOut", causality=Fmi3Causality.output))
150
147
  self.register_variable(Float64("realOut", causality=Fmi3Causality.output))
151
148
  self.register_variable(Boolean("booleanVariable", causality=Fmi3Causality.local))
@@ -14,13 +14,7 @@ This project is a fork of the original PythonFMU repository available at https:/
14
14
 
15
15
  ### Support:
16
16
 
17
- We currently support a basic FMU which follows the FMI3 standard. This includes support for:
18
-
19
- - Float64, Int32, UInt64, String, Boolean variables
20
- - Step Mode
21
- - Arrays, which requires the numpy package
22
-
23
- Please take a look at the examples bouncingBall.py and linearTransform.py for a demonstration of these features.
17
+ Please take a look at the examples to see the supported features.
24
18
 
25
19
  ### Future
26
20
 
@@ -29,7 +23,6 @@ In no particular order, we plan to add support for:
29
23
  - Support more variable types from FMI3
30
24
  - Improve array support
31
25
  - Add event mode
32
- - Include FMI3 related testing
33
26
 
34
27
  ### How do I build an FMU from python code?
35
28
 
@@ -112,16 +105,18 @@ from pythonfmu3 import Fmi3Causality, Fmi3Slave, Boolean, Int32, Float64, String
112
105
 
113
106
  class PythonSlave(Fmi3Slave):
114
107
 
115
- author = "John Doe"
116
- description = "A simple description"
117
-
118
108
  def __init__(self, **kwargs):
119
109
  super().__init__(**kwargs)
120
110
 
111
+ self.author = "John Doe"
112
+ self.description = "A simple description"
113
+
114
+ self.time = 0.0
121
115
  self.intOut = 1
122
116
  self.realOut = 3.0
123
117
  self.booleanVariable = True
124
118
  self.stringVariable = "Hello World!"
119
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent))
125
120
  self.register_variable(Int32("intOut", causality=Fmi3Causality.output))
126
121
  self.register_variable(Float64("realOut", causality=Fmi3Causality.output))
127
122
  self.register_variable(Boolean("booleanVariable", causality=Fmi3Causality.local))
@@ -4,4 +4,5 @@ from .enums import Fmi3Causality, Fmi3Initial, Fmi3Variability
4
4
  from .fmi3slave import Fmi3Slave
5
5
  from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, String, Dimension
6
6
  from .default_experiment import DefaultExperiment
7
- from .type_definitions import TypeDefinitions, Item, EnumerationType
7
+ from .variable_types import Float64Type, EnumerationType
8
+ from .unit import BaseUnit, Unit
@@ -0,0 +1 @@
1
+ __version__ = "0.1.16"
@@ -11,10 +11,11 @@ from xml.etree.ElementTree import Element, SubElement
11
11
 
12
12
  from .logmsg import LogMsg
13
13
  from .default_experiment import DefaultExperiment
14
- from .type_definitions import TypeDefinitions
15
14
  from ._version import __version__ as VERSION
16
15
  from .enums import Fmi3Type, Fmi3Status, Fmi3Causality, Fmi3Initial, Fmi3Variability
17
16
  from .variables import Boolean, Enumeration, Int32, Int64, UInt64, Float64, ModelVariable, String
17
+ from .variable_types import VariableType
18
+ from .unit import Unit
18
19
 
19
20
  ModelOptions = namedtuple("ModelOptions", ["name", "value", "cli"])
20
21
 
@@ -54,7 +55,9 @@ class Fmi3Slave(ABC):
54
55
  self.modelName: Optional[str] = self.__class__.__name__
55
56
  self.description: Optional[str] = None
56
57
  self.default_experiment: Optional[DefaultExperiment] = None
57
- self.type_definitions: Optional[TypeDefinitions] = None
58
+
59
+ self.type_definitions: Dict[str, VariableType] = {}
60
+ self.units: Dict[str, Unit] = {}
58
61
 
59
62
  def to_xml(self, model_options: Dict[str, str] = dict()) -> Element:
60
63
  """Build the XML representation of the model.
@@ -99,8 +102,15 @@ class Fmi3Slave(ABC):
99
102
 
100
103
  SubElement(root, "CoSimulation", attrib=options)
101
104
 
102
- if self.type_definitions is not None:
103
- root.append(self.type_definitions.to_xml())
105
+ if self.units:
106
+ unit_defs = SubElement(root, "UnitDefinitions")
107
+ for _, unit in self.units.items():
108
+ unit_defs.append(unit.to_xml())
109
+
110
+ if self.type_definitions:
111
+ type_defs = SubElement(root, "TypeDefinitions")
112
+ for _, val in self.type_definitions.items():
113
+ type_defs.append(val.to_xml())
104
114
 
105
115
  if len(self.log_categories) > 0:
106
116
  categories = SubElement(root, "LogCategories")
@@ -178,7 +188,7 @@ class Fmi3Slave(ABC):
178
188
  raise Exception(f"Unsupported type {type(var)}!")
179
189
  var.start = refs if len(getattr(var, "dimensions", [])) > 0 else refs[0]
180
190
 
181
- def register_variable(self, var: ModelVariable, nested: bool = True):
191
+ def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None):
182
192
  """Register a variable as FMU interface.
183
193
 
184
194
  Args:
@@ -206,10 +216,18 @@ class Fmi3Slave(ABC):
206
216
  var.setter = lambda v: setattr(owner, var.local_name, np.reshape(v, newshape=getattr(owner, var.local_name).shape))
207
217
  else:
208
218
  var.setter = lambda v: setattr(owner, var.local_name, v)
219
+
220
+ if var_type:
221
+ self.type_definitions[var_type.name] = var_type
222
+ var.declared_type = var_type.name
209
223
 
210
224
  def setup_experiment(self, start_time: float):
211
225
  pass
212
226
 
227
+ def register_units(self, units: List[Unit]):
228
+ for unit in units:
229
+ self.units[unit.name] = unit
230
+
213
231
  def enter_initialization_mode(self):
214
232
  pass
215
233
 
@@ -1,5 +1,4 @@
1
1
  import sys
2
- import platform
3
2
 
4
3
  import platform
5
4
 
@@ -8,7 +7,8 @@ def get_fmu_arch():
8
7
  system = platform.system()
9
8
  is_x86 = platform.machine() in ["i386", "AMD64", "x86_64"]
10
9
  platforms = {"Windows": "windows", "Linux": "linux", "Darwin": "darwin"}
11
- return "x86_64" if is_x86 else "x86" + "-" + platforms.get(system, "unknown")
10
+ arch = "x86_64" if is_x86 else "x86"
11
+ return arch + "-" + platforms.get(system, "unknown")
12
12
 
13
13
  def get_platform() -> str:
14
14
  """Get FMU binary platform folder name."""
@@ -105,11 +105,9 @@ public:
105
105
 
106
106
  Logger(
107
107
  FMIComponentEnvironment component,
108
- String instanceName,
109
108
  fmi3LogMessageCallback logCallback,
110
109
  std::shared_ptr<Settings> settings)
111
110
  : m_component{component}
112
- , m_instanceName(std::move(instanceName))
113
111
  , m_fmiLogger{logCallback}
114
112
  , m_settings{settings}
115
113
  {
@@ -154,7 +152,6 @@ public:
154
152
 
155
153
  private:
156
154
  const FMIComponentEnvironment m_component;
157
- const String m_instanceName;
158
155
  const FMICallbackLogger m_fmiLogger;
159
156
  std::shared_ptr<Settings> m_settings;
160
157
  };
@@ -47,10 +47,10 @@ struct Component
47
47
  modelError = 1 << 12,
48
48
  modelFatal = 1 << 13,
49
49
  };
50
- Component(cppfmu::FMIString instanceName,
50
+ Component(cppfmu::FMIComponentEnvironment instanceEnvironment,
51
51
  cppfmu::FMICallbackLogger logCallback,
52
52
  cppfmu::FMIBoolean loggingOn) : loggerSettings{std::make_shared<cppfmu::Logger::Settings>()},
53
- logger{this, instanceName, logCallback, loggerSettings}, lastSuccessfulTime{std::numeric_limits<cppfmu::FMIFloat64>::quiet_NaN()}
53
+ logger{instanceEnvironment, logCallback, loggerSettings}, lastSuccessfulTime{std::numeric_limits<cppfmu::FMIFloat64>::quiet_NaN()}
54
54
  {
55
55
  loggerSettings->debugLoggingEnabled = (loggingOn == cppfmu::FMITrue);
56
56
  }
@@ -96,7 +96,8 @@ fmi3Instance fmi3InstantiateCoSimulation(
96
96
  fmi3IntermediateUpdateCallback intermediateUpdate )
97
97
  {
98
98
  try {
99
- auto component = new Component(instanceName,
99
+ auto component = new Component(
100
+ environment,
100
101
  logMessage,
101
102
  loggingOn);
102
103
  component->slave = CppfmuInstantiateSlave(
@@ -0,0 +1,18 @@
1
+ #ifndef PYTHONFMU_IPYTHONSTATE_HPP
2
+ #define PYTHONFMU_IPYTHONSTATE_HPP
3
+
4
+ namespace pythonfmu
5
+ {
6
+ class IPyState
7
+ {
8
+ public:
9
+ IPyState() = default;
10
+ IPyState(IPyState const& other) = delete;
11
+ IPyState(IPyState&& other) = delete;
12
+ IPyState& operator=(IPyState const& other) = delete;
13
+ IPyState& operator=(IPyState&& other) = delete;
14
+ virtual ~IPyState() = default;
15
+ };
16
+ }
17
+
18
+ #endif //PYTHONFMU_IPYTHONSTATE_HPP
@@ -1,4 +1,5 @@
1
1
 
2
+ #include "pythonfmu/IPyState.hpp"
2
3
  #include "pythonfmu/PySlaveInstance.hpp"
3
4
 
4
5
  #include "pythonfmu/PyState.hpp"
@@ -7,6 +8,7 @@
7
8
 
8
9
  #include <fstream>
9
10
  #include <functional>
11
+ #include <mutex>
10
12
  #include <regex>
11
13
  #include <sstream>
12
14
  #include <utility>
@@ -44,8 +46,9 @@ inline void py_safe_run(const std::function<void(PyGILState_STATE gilState)>& f)
44
46
  PyGILState_Release(gil_state);
45
47
  }
46
48
 
47
- PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible)
48
- : instanceName_(std::move(instanceName))
49
+ PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr<IPyState> pyState)
50
+ : pyState_{ std::move(pyState) }
51
+ , instanceName_(std::move(instanceName))
49
52
  , resources_(std::move(resources))
50
53
  , logger_(logger)
51
54
  , visible_(visible)
@@ -441,9 +444,14 @@ void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t
441
444
  }
442
445
 
443
446
  for (int i = 0; i < nvr; i++) {
444
- PyObject* value = PyList_GetItem(refs, i);
445
- values[i] = static_cast<cppfmu::FMIUInt64>(PyLong_AsUnsignedLongLong(value));
447
+ PyObject* item = PyList_GetItem(refs, i);
448
+ PyObject* value = PyObject_GetAttrString(item, "value");
449
+ if (value && PyLong_Check(value))
450
+ {
451
+ values[i] = static_cast<cppfmu::FMIUInt64>(PyLong_AsUnsignedLongLong(value));
452
+ }
446
453
  }
454
+
447
455
  Py_DECREF(refs);
448
456
  clearLogBuffer();
449
457
  });
@@ -625,7 +633,10 @@ PySlaveInstance::~PySlaveInstance()
625
633
 
626
634
  } // namespace pythonfmu
627
635
 
628
- std::unique_ptr<pythonfmu::PyState> pyState = nullptr;
636
+ namespace {
637
+ std::mutex pyStateMutex{};
638
+ std::shared_ptr<pythonfmu::PyState> pyState{};
639
+ }
629
640
 
630
641
  std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
631
642
  cppfmu::FMIString instanceName,
@@ -650,9 +661,57 @@ std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
650
661
  #endif
651
662
  }
652
663
 
653
- if (pyState == nullptr) {
654
- pyState = std::make_unique<pythonfmu::PyState>();
664
+ {
665
+ auto const ensurePyStateAlive = [&]() {
666
+ auto const lock = std::lock_guard{pyStateMutex};
667
+ if (nullptr == pyState) pyState = std::make_shared<pythonfmu::PyState>();
668
+ };
669
+
670
+ ensurePyStateAlive();
671
+ return std::make_unique<pythonfmu::PySlaveInstance>(
672
+ instanceName, resources, logger, visible, pyState);
655
673
  }
674
+ }
675
+
676
+ extern "C" {
656
677
 
657
- return std::make_unique<pythonfmu::PySlaveInstance>(instanceName, resources, logger, visible);
678
+ // The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread.
679
+ // Creation of an std::thread increments ref counter of a shared library. So, when the client unloads the library
680
+ // the library won't be freed, as std::thread is alive, and the std::thread itself waits for de-initialization request.
681
+ // Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization.
682
+ void finalizePythonInterpreter()
683
+ {
684
+ pyState = nullptr;
685
+ }
686
+ }
687
+
688
+ namespace
689
+ {
690
+ #ifdef _WIN32
691
+ #include <windows.h>
692
+
693
+ BOOL APIENTRY DllMain(HMODULE hModule,
694
+ DWORD ul_reason_for_call,
695
+ LPVOID lpReserved)
696
+ {
697
+ switch (ul_reason_for_call) {
698
+ case DLL_PROCESS_ATTACH:
699
+ break;
700
+ case DLL_THREAD_ATTACH:
701
+ case DLL_THREAD_DETACH:
702
+ break;
703
+ case DLL_PROCESS_DETACH:
704
+ finalizePythonInterpreter();
705
+ break;
706
+ }
707
+ return TRUE;
708
+ }
709
+ #elif defined(__linux__)
710
+ __attribute__((destructor)) void onLibraryUnload()
711
+ {
712
+ finalizePythonInterpreter();
713
+ }
714
+ #else
715
+ #error port the code
716
+ #endif
658
717
  }
@@ -3,6 +3,7 @@
3
3
  #define PYTHONFMU_SLAVEINSTANCE_HPP
4
4
 
5
5
  #include "cppfmu/cppfmu_cs.hpp"
6
+ #include "pythonfmu/IPyState.hpp"
6
7
 
7
8
  #include <Python.h>
8
9
  #include <string>
@@ -15,7 +16,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
15
16
  {
16
17
 
17
18
  public:
18
- PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible);
19
+ PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible, std::shared_ptr<IPyState> pyState);
19
20
 
20
21
  void initialize(PyGILState_STATE gilState);
21
22
 
@@ -53,6 +54,7 @@ public:
53
54
  ~PySlaveInstance() override;
54
55
 
55
56
  private:
57
+ std::shared_ptr<IPyState> pyState_;
56
58
  PyObject* pClass_;
57
59
  PyObject* pInstance_{};
58
60
  PyObject* pMessages_{};
@@ -0,0 +1,85 @@
1
+
2
+ #ifndef PYTHONFMU_PYTHONSTATE_HPP
3
+ #define PYTHONFMU_PYTHONSTATE_HPP
4
+
5
+ #include "IPyState.hpp"
6
+ #include <Python.h>
7
+ #include <condition_variable>
8
+ #include <iostream>
9
+ #include <mutex>
10
+ #include <thread>
11
+
12
+ namespace pythonfmu
13
+ {
14
+ class PyState : public IPyState
15
+ {
16
+ public:
17
+ PyState()
18
+ : _initDeinitPyThread_{ &PyState::Worker, this }
19
+ {
20
+ auto lock = std::unique_lock{ mutex_ };
21
+ conditionalVariable_.wait(lock, [&] { return consructed_; });
22
+ }
23
+
24
+ ~PyState() override
25
+ {
26
+ {
27
+ auto const lock = std::lock_guard{ mutex_ };
28
+ destroyRequested_ = true;
29
+ }
30
+ conditionalVariable_.notify_one();
31
+ if (_initDeinitPyThread_.joinable()) _initDeinitPyThread_.join();
32
+ }
33
+
34
+ private:
35
+
36
+ // In accordance to the documentation https://docs.python.org/3/c-api/init.html#c.Py_FinalizeEx
37
+ // the Py_Initialize/Py_Finalize should be called from the same
38
+ // thread. The FMI standard allows to call fmi functions from different threads, also different threads
39
+ // could be used for loading unloading the FMU libraries. There is a deadlock, when the simulation tool
40
+ // unloads the FMU library from the thread which is differs from the thread where the Py_Initialize was called.
41
+ // Create a new thread which is used for calling Py_Initialize and Py_Deinitialize. The thread waits for
42
+ // notification from destructor and then calls Py_Deinitialize.
43
+
44
+ void Worker()
45
+ {
46
+ // It will be nullptr in case when some other tool already called Py_IsInitialized.
47
+ // There is no need to call Py_Finalize thus, this thread can exit ASAP.
48
+ auto const mainPyThread = []() -> PyThreadState* {
49
+ auto const justInitialized = !Py_IsInitialized();
50
+ if (justInitialized) {
51
+ Py_SetProgramName(L"./PythonFMU");
52
+ Py_Initialize();
53
+ #if PY_VERSION_HEX < 0x03070000
54
+ PyEval_InitThreads();
55
+ #endif
56
+ return PyEval_SaveThread();
57
+ }
58
+ return nullptr;
59
+ }();
60
+
61
+ {
62
+ auto const lock = std::lock_guard{ mutex_ };
63
+ consructed_ = true;
64
+ }
65
+ conditionalVariable_.notify_one();
66
+
67
+ if (nullptr != mainPyThread) {
68
+ auto lock = std::unique_lock{ mutex_ };
69
+ conditionalVariable_.wait(lock, [&] { return destroyRequested_; });
70
+
71
+ PyEval_RestoreThread(mainPyThread);
72
+ Py_Finalize();
73
+ }
74
+ }
75
+
76
+ bool consructed_ = false;
77
+ bool destroyRequested_ = false;
78
+ std::condition_variable conditionalVariable_;
79
+ std::mutex mutex_;
80
+ std::thread _initDeinitPyThread_;
81
+ };
82
+
83
+ } // namespace pythonfmu
84
+
85
+ #endif //PYTHONFMU_PYTHONSTATE_HPP
@@ -1,4 +1,4 @@
1
- from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Float64
1
+ from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Float64, Fmi3Variability
2
2
 
3
3
 
4
4
  class PythonSlaveWithException(Fmi3Slave):
@@ -8,6 +8,9 @@ class PythonSlaveWithException(Fmi3Slave):
8
8
 
9
9
  self.realIn = 22.0
10
10
  self.realOut = 0.0
11
+ self.time = 0
12
+
13
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
11
14
  self.register_variable(Float64("realIn", causality=Fmi3Causality.input))
12
15
  self.register_variable(Float64("realOut", causality=Fmi3Causality.output))
13
16
 
@@ -21,6 +21,9 @@ class PythonSlave(Fmi3Slave):
21
21
  self.realIn = 2. / 3.
22
22
  self.booleanParameter = False
23
23
  self.stringParameter = "dog"
24
+ self.time = 0
25
+
26
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
24
27
  self.register_variable(
25
28
  Int32("intParam", causality=Fmi3Causality.parameter, variability=Fmi3Variability.tunable))
26
29
  self.register_variable(Float64("realIn", causality=Fmi3Causality.input))
@@ -1,4 +1,4 @@
1
- from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Fmi3Variability, String, DefaultExperiment
1
+ from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Fmi3Variability, String, DefaultExperiment, Float64
2
2
 
3
3
 
4
4
  class PythonSlaveReadFile(Fmi3Slave):
@@ -11,6 +11,9 @@ class PythonSlaveReadFile(Fmi3Slave):
11
11
  with (open(f'{self.resources}/hello.txt', 'r')) as file:
12
12
  data = file.read()
13
13
 
14
+ self.time = 0
15
+
16
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
14
17
  self.register_variable(
15
18
  String("file_content", getter=lambda: data,
16
19
  causality=Fmi3Causality.output,
@@ -1,5 +1,5 @@
1
1
  import math
2
- from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Float64
2
+ from pythonfmu3.fmi3slave import Fmi3Slave, Fmi3Causality, Float64, Fmi3Variability
3
3
  from localmodule import get_amplitude, get_time_constant
4
4
 
5
5
 
@@ -10,6 +10,9 @@ class PythonSlaveWithDep(Fmi3Slave):
10
10
 
11
11
  self.realIn = 22.0
12
12
  self.realOut = 0.0
13
+ self.time = 0
14
+
15
+ self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
13
16
  self.register_variable(Float64("realIn", causality=Fmi3Causality.input))
14
17
  self.register_variable(Float64("realOut", causality=Fmi3Causality.output))
15
18