pythonfmu3 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pythonfmu3/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.11"
1
+ __version__ = "0.1.13"
pythonfmu3/fmi3slave.py CHANGED
@@ -55,6 +55,7 @@ class Fmi3Slave(ABC):
55
55
  self.modelName: Optional[str] = self.__class__.__name__
56
56
  self.description: Optional[str] = None
57
57
  self.default_experiment: Optional[DefaultExperiment] = None
58
+
58
59
  self.type_definitions: Dict[str, VariableType] = {}
59
60
  self.units: Dict[str, Unit] = {}
60
61
 
@@ -223,7 +224,7 @@ class Fmi3Slave(ABC):
223
224
  def setup_experiment(self, start_time: float):
224
225
  pass
225
226
 
226
- def add_units(self, units: List[Unit]):
227
+ def register_units(self, units: List[Unit]):
227
228
  for unit in units:
228
229
  self.units[unit.name] = unit
229
230
 
@@ -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)
@@ -625,7 +628,10 @@ PySlaveInstance::~PySlaveInstance()
625
628
 
626
629
  } // namespace pythonfmu
627
630
 
628
- std::unique_ptr<pythonfmu::PyState> pyState = nullptr;
631
+ namespace {
632
+ std::mutex pyStateMutex{};
633
+ std::shared_ptr<pythonfmu::PyState> pyState{};
634
+ }
629
635
 
630
636
  std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
631
637
  cppfmu::FMIString instanceName,
@@ -650,9 +656,57 @@ std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
650
656
  #endif
651
657
  }
652
658
 
653
- if (pyState == nullptr) {
654
- pyState = std::make_unique<pythonfmu::PyState>();
659
+ {
660
+ auto const ensurePyStateAlive = [&]() {
661
+ auto const lock = std::lock_guard{pyStateMutex};
662
+ if (nullptr == pyState) pyState = std::make_shared<pythonfmu::PyState>();
663
+ };
664
+
665
+ ensurePyStateAlive();
666
+ return std::make_unique<pythonfmu::PySlaveInstance>(
667
+ instanceName, resources, logger, visible, pyState);
655
668
  }
669
+ }
670
+
671
+ extern "C" {
672
+
673
+ // The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread.
674
+ // Creation of an std::thread increments ref counter of a shared library. So, when the client unloads the library
675
+ // the library won't be freed, as std::thread is alive, and the std::thread itself waits for de-initialization request.
676
+ // Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization.
677
+ void finalizePythonInterpreter()
678
+ {
679
+ pyState = nullptr;
680
+ }
681
+ }
656
682
 
657
- return std::make_unique<pythonfmu::PySlaveInstance>(instanceName, resources, logger, visible);
683
+ namespace
684
+ {
685
+ #ifdef _WIN32
686
+ #include <windows.h>
687
+
688
+ BOOL APIENTRY DllMain(HMODULE hModule,
689
+ DWORD ul_reason_for_call,
690
+ LPVOID lpReserved)
691
+ {
692
+ switch (ul_reason_for_call) {
693
+ case DLL_PROCESS_ATTACH:
694
+ break;
695
+ case DLL_THREAD_ATTACH:
696
+ case DLL_THREAD_DETACH:
697
+ break;
698
+ case DLL_PROCESS_DETACH:
699
+ finalizePythonInterpreter();
700
+ break;
701
+ }
702
+ return TRUE;
703
+ }
704
+ #elif defined(__linux__)
705
+ __attribute__((destructor)) void onLibraryUnload()
706
+ {
707
+ finalizePythonInterpreter();
708
+ }
709
+ #else
710
+ #error port the code
711
+ #endif
658
712
  }
@@ -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_{};
@@ -2,40 +2,84 @@
2
2
  #ifndef PYTHONFMU_PYTHONSTATE_HPP
3
3
  #define PYTHONFMU_PYTHONSTATE_HPP
4
4
 
5
+ #include "IPyState.hpp"
5
6
  #include <Python.h>
7
+ #include <condition_variable>
6
8
  #include <iostream>
9
+ #include <mutex>
10
+ #include <thread>
7
11
 
8
12
  namespace pythonfmu
9
13
  {
10
-
11
- class PyState
12
- {
13
- public:
14
- PyState()
14
+ class PyState : public IPyState
15
15
  {
16
- was_initialized_ = Py_IsInitialized();
16
+ public:
17
+ PyState()
18
+ : _initDeinitPyThread_{ &PyState::Worker, this }
19
+ {
20
+ auto lock = std::unique_lock{ mutex_ };
21
+ conditionalVariable_.wait(lock, [&] { return consructed_; });
22
+ }
17
23
 
18
- if (!was_initialized_) {
19
- Py_SetProgramName(L"./PythonFMU");
20
- Py_Initialize();
21
- PyEval_InitThreads();
22
- _mainPyThread = PyEval_SaveThread();
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();
23
32
  }
24
- }
25
33
 
26
- ~PyState()
27
- {
28
- if (!was_initialized_) {
29
- PyEval_RestoreThread(_mainPyThread);
30
- Py_Finalize();
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
+ }
31
74
  }
32
- }
33
75
 
34
- private:
35
- bool was_initialized_;
36
- PyThreadState* _mainPyThread;
37
- };
76
+ bool consructed_ = false;
77
+ bool destroyRequested_ = false;
78
+ std::condition_variable conditionalVariable_;
79
+ std::mutex mutex_;
80
+ std::thread _initDeinitPyThread_;
81
+ };
38
82
 
39
83
  } // namespace pythonfmu
40
84
 
41
- #endif //PYTHONFMU_PYTHONSTATE_HPP
85
+ #endif //PYTHONFMU_PYTHONSTATE_HPP
pythonfmu3/variables.py CHANGED
@@ -144,7 +144,7 @@ class Dimension(object):
144
144
  if self.start:
145
145
  return self.start
146
146
  else:
147
- result = next((var for var in vars if var.value_reference == self.value_reference), None)
147
+ result = next((value for vr, value in vars.items() if vr == int(self.value_reference)), None)
148
148
  return result.getter()
149
149
 
150
150
  def to_xml(self) -> Element:
@@ -158,10 +158,11 @@ class Dimension(object):
158
158
  return ele
159
159
 
160
160
  class Float64(ModelVariable):
161
- def __init__(self, name: str, start: Optional[Any] = None, derivative: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs):
161
+ def __init__(self, name: str, start: Optional[Any] = None, derivative: Optional[Any] = None, dimensions: List[Dimension] = [], unit: Optional[str] = None, **kwargs):
162
162
  super().__init__(name, **kwargs)
163
163
  self.__attrs = {"start": start, "derivative": derivative}
164
164
  self._type = "Float64"
165
+ self._unit = unit
165
166
  if dimensions:
166
167
  check_numpy()
167
168
  self.__dimensions = dimensions
@@ -173,6 +174,14 @@ class Float64(ModelVariable):
173
174
  @start.setter
174
175
  def start(self, value: float):
175
176
  self.__attrs["start"] = value
177
+
178
+ @property
179
+ def unit(self) -> Optional[Any]:
180
+ return self._unit
181
+
182
+ @unit.setter
183
+ def unit(self, value: str):
184
+ self._unit = value
176
185
 
177
186
  @property
178
187
  def derivative(self):
@@ -182,6 +191,7 @@ class Float64(ModelVariable):
182
191
  def dimensions(self) -> List[Dimension]:
183
192
  return self.__dimensions
184
193
 
194
+
185
195
  def to_xml(self) -> Element:
186
196
  attrib = dict()
187
197
  for key, value in self.__attrs.items():
@@ -194,6 +204,10 @@ class Float64(ModelVariable):
194
204
  else:
195
205
  output = f"{value:.16g}"
196
206
  attrib[key] = output
207
+
208
+ if self.unit:
209
+ attrib["unit"] = self.unit
210
+
197
211
  self._extras = attrib
198
212
  parent = super().to_xml()
199
213
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pythonfmu3
3
- Version: 0.1.11
3
+ Version: 0.1.13
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
@@ -21,8 +21,8 @@ Classifier: Topic :: Scientific/Engineering
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Provides-Extra: tests
24
- Requires-Dist: pytest ; extra == 'tests'
25
- Requires-Dist: fmpy ; extra == 'tests'
24
+ Requires-Dist: pytest; extra == "tests"
25
+ Requires-Dist: fmpy; extra == "tests"
26
26
 
27
27
  # PythonFMU3
28
28
 
@@ -1,16 +1,16 @@
1
1
  pythonfmu3/__init__.py,sha256=PT1R0LpSInyVdXGJbkWMTQym6_SEU7CqUkG7OH5j1f0,396
2
2
  pythonfmu3/__main__.py,sha256=gC1T8x2d02hhqshyNQGSS70SyCk-3I3t8zPqoRo2Xd8,1543
3
- pythonfmu3/_version.py,sha256=nllDrH0jyChMuuYrK0CC55iTBKUNTUjejtcwxyUF2EQ,23
3
+ pythonfmu3/_version.py,sha256=khDKUuWafURKVs5EAZkpOMiUHI2-V7axlqrWLPUpuZo,23
4
4
  pythonfmu3/builder.py,sha256=YLTeLLpiFOkoDYwnJ55GEhb3ham0xxROMjMN6bvaw5Y,8784
5
5
  pythonfmu3/default_experiment.py,sha256=FJ0l0FpU7pT4Pox2RiA2y-Cb_e7DE2XlbWKySRYQaVk,293
6
6
  pythonfmu3/deploy.py,sha256=eqFWVxZUkJCLvmq1-GuMNjn2j0VCGoaXPhRWrCk2Vxg,3741
7
7
  pythonfmu3/enums.py,sha256=ZmC6vGeBTGt7HarPQ4uYV10ezPsvIxAHH1ypjNq7G3E,729
8
- pythonfmu3/fmi3slave.py,sha256=gLjwxTOV2UHuKNK0iFT9LUtUr5AQtZUrHLd7-QCcawQ,16563
8
+ pythonfmu3/fmi3slave.py,sha256=7rDp_5TCQnkFLV11UF55mKEvUDXbpZPfn8sOjYCNCSw,16569
9
9
  pythonfmu3/logmsg.py,sha256=0wn11GLwq0LLWqyC2ktLRjQFWlpj07wWliZ8rUHx1gg,359
10
10
  pythonfmu3/osutil.py,sha256=KMAoMf_BpqFQQUy3WzHnt9jiklkS1xy0syIbj7kNH6A,656
11
11
  pythonfmu3/unit.py,sha256=SjOaywb5lVQbAO_iLjc_7Ly3nYrmAvIW2_hAGt12UFU,1475
12
12
  pythonfmu3/variable_types.py,sha256=Q0D4wlGi3i3zE5UsmGJEpNh7SV_W8mn_8p_WIkwrLQA,2881
13
- pythonfmu3/variables.py,sha256=n0qfwqeRBvaEnSpgbGzw2qturYUSwt4y9BCKFdyK1yc,11285
13
+ pythonfmu3/variables.py,sha256=jLVaT9Qirw08QBHo4cpMg6lng7tEwD3Tk3Czi6TpL2I,11566
14
14
  pythonfmu3/pythonfmu-export/.clang-format,sha256=ApbsHWYwilCXJ1iBux4nqOjLkaqgbGLsVrc_U3hX7WI,944
15
15
  pythonfmu3/pythonfmu-export/CMakeLists.txt,sha256=p0-4Xh_sAaOI9AVb8lsUsMxJPc6bg9b_6CF6NJt5FHE,1847
16
16
  pythonfmu3/pythonfmu-export/build_unix.sh,sha256=I5RrF2O6y0hRG_SN4ExKN_CCFYaa2l-ngKzqmBXnHmA,73
@@ -23,11 +23,12 @@ pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp,sha256=sTHQVUCH_dwIxRl6
23
23
  pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h,sha256=Tk1WY0nAYBGiOAQ8zVbmqRAfdQttITWsLba7udhBwG4,30050
24
24
  pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h,sha256=NsHuclp20wcuUInYf-91UXZbljTeu2cPXENqNEORdgU,16262
25
25
  pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h,sha256=ZCQLJgSbRGrMsBiQ4auaYCY4J3Hck6g3t1iZp5NEguw,4251
26
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=uVGq0QcTySLjgTd3_fe-csPenT89DukCPL4u_j4rDew,22610
27
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=XvRaA0OvB9MRSxFUP64TvHsLEP4CSOn0WcfImegS2_8,3954
28
- pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp,sha256=PApY1VOpEJbzrnQjKB1-UoTKKCzaDbIGF-dZt6f3Uls,717
29
- pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=-xeuSKeqjB5_96yRhInnWCPh6kBXmlOHNeRzZZfOG9M,353232
30
- pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=fRxBPluHxbDDGmnUi0NMeaZX8qB4zFA_8Sn_6Ff4VHA,417280
26
+ pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp,sha256=GLiOjhKLDVEICersnqVwezwEshbxfdh_cyMxBJGFbu8,456
27
+ pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=FnzyEPNPFM8X49NwPi_BuC61Y10iv-lgZ_DlzWIkIts,24186
28
+ pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=Q8eulMAkv7YkfzCi2KSwxvYngv0I_nRbmfTJR9vT92M,4063
29
+ pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp,sha256=CjFEIrZu8b1jxQAIgfI_o36hyItW_SdQ5VSkImf7JNs,2977
30
+ pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=zB2OzZ-BQAPaGVm2R1up5Qp7S4JT2RTXdDm61iax0v0,365120
31
+ pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=Pg1pi8wTFVwcS0jLJYUfBRC3ZHjbHw_yIKU-ykhbG6w,431104
31
32
  pythonfmu3/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
33
  pythonfmu3/tests/conftest.py,sha256=B6XxTnBdeF_tEJ3w2DlfPKUfvlk-oULgIhQtXkgOJIc,171
33
34
  pythonfmu3/tests/test_builder.py,sha256=StR9oNmF6dtuxxKtPFlncLQN-O7n97nvHm6EFH4Qn14,6252
@@ -45,9 +46,9 @@ pythonfmu3/tests/slaves/localmodule.py,sha256=-q5Lmh2I88MG7nLTscms0n5xhHLkUDed1I
45
46
  pythonfmu3/tests/slaves/pythonslave.py,sha256=H75vLhqshSWWvMj2NQt6a_aQhA6BGSkBVJstjjVE0AU,2024
46
47
  pythonfmu3/tests/slaves/pythonslave_read_file.py,sha256=NIAmyZZGNAcnqf8R5jb1yFcQxj6iSMOAc13pY0GFtco,634
47
48
  pythonfmu3/tests/slaves/slavewithdep.py,sha256=4yqxa0vsOTLLvtaUQFkjfU9OcwjDUtuUe-qIjnD-jrw,647
48
- pythonfmu3-0.1.11.dist-info/LICENSE,sha256=24Cw_MPouLK-G_t8Hgjm59IS9BnPoL6SKkyuZ0xesXY,1155
49
- pythonfmu3-0.1.11.dist-info/METADATA,sha256=dM3wszQmsIZlzlXCI_49kVB6bQPgGMiJc38CB10TmCM,7317
50
- pythonfmu3-0.1.11.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
51
- pythonfmu3-0.1.11.dist-info/entry_points.txt,sha256=GgkWYs0DKGHLcijMEYZGkhkHOK2RVUpjGwvl1fwyDwQ,60
52
- pythonfmu3-0.1.11.dist-info/top_level.txt,sha256=6ne7bh_cME1hepxCx1BG8N5QVhVAufrp7LLok6vq7nE,11
53
- pythonfmu3-0.1.11.dist-info/RECORD,,
49
+ pythonfmu3-0.1.13.dist-info/LICENSE,sha256=24Cw_MPouLK-G_t8Hgjm59IS9BnPoL6SKkyuZ0xesXY,1155
50
+ pythonfmu3-0.1.13.dist-info/METADATA,sha256=0hn7POMD7cxS8WKqb_FicfAsYXxqPXNswAxL0MKBATk,7315
51
+ pythonfmu3-0.1.13.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
52
+ pythonfmu3-0.1.13.dist-info/entry_points.txt,sha256=GgkWYs0DKGHLcijMEYZGkhkHOK2RVUpjGwvl1fwyDwQ,60
53
+ pythonfmu3-0.1.13.dist-info/top_level.txt,sha256=6ne7bh_cME1hepxCx1BG8N5QVhVAufrp7LLok6vq7nE,11
54
+ pythonfmu3-0.1.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5