From 58babdadd6b1515bc312b666f74dfb62c8dd14eb Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Fri, 26 Sep 2025 16:33:10 +0100 Subject: [PATCH 1/7] expand array var possibilities --- pythonfmu3/fmi3slave.py | 10 +++++-- .../pythonfmu-export/src/cppfmu/cppfmu_cs.cpp | 30 ++++++++++++------- .../pythonfmu-export/src/cppfmu/cppfmu_cs.hpp | 30 ++++++++++++------- .../src/cppfmu/fmi_functions.cpp | 20 ++++++------- .../src/pythonfmu/PySlaveInstance.cpp | 30 +++++++++---------- .../src/pythonfmu/PySlaveInstance.hpp | 20 ++++++------- pythonfmu3/variables.py | 28 +++++++++++++++-- 7 files changed, 108 insertions(+), 60 deletions(-) diff --git a/pythonfmu3/fmi3slave.py b/pythonfmu3/fmi3slave.py index ebdbaa9..0e6fbff 100644 --- a/pythonfmu3/fmi3slave.py +++ b/pythonfmu3/fmi3slave.py @@ -306,11 +306,15 @@ def get_uint64(self, vrs: List[int]) -> List[ctypes.c_uint64]: for vr in vrs: var = self.vars[vr] if isinstance(var, UInt64): - val = var.getter() - refs.append(val if isinstance(val, ctypes.c_uint64) else ctypes.c_uint64(val)) + if len(var.dimensions) == 0: + val = var.getter() + refs.append(ctypes.c_uint64(val) if not isinstance(val, ctypes.c_uint64) else val) + else: + vals = [ctypes.c_uint64(v) for v in var.getter()] + refs.extend(vals) else: raise TypeError( - f"Variable with valueReference={vr} is not of type UInt64!" + f"Variable with valueReference={vr} is not of type Real!" ) return refs diff --git a/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp b/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp index afe899e..ecc0855 100644 --- a/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp +++ b/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.cpp @@ -66,7 +66,8 @@ void SlaveInstance::SetFloat64( void SlaveInstance::SetInt32( const FMIValueReference /*vr*/[], std::size_t nvr, - const FMIInt32 /*value*/[]) + const FMIInt32 /*value*/[], + std::size_t nValues) { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -76,7 +77,8 @@ void SlaveInstance::SetInt32( void SlaveInstance::SetInt64( const FMIValueReference /*vr*/[], std::size_t nvr, - const FMIInt64 /*value*/[]) + const FMIInt64 /*value*/[], + std::size_t nValues) { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -86,7 +88,8 @@ void SlaveInstance::SetInt64( void SlaveInstance::SetUInt64( const FMIValueReference /*vr*/[], std::size_t nvr, - const FMIUInt64 /*value*/[]) + const FMIUInt64 /*value*/[], + std::size_t nValues) { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -97,7 +100,8 @@ void SlaveInstance::SetUInt64( void SlaveInstance::SetBoolean( const FMIValueReference /*vr*/[], std::size_t nvr, - const FMIBoolean /*value*/[]) + const FMIBoolean /*value*/[], + std::size_t nValues) { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -108,7 +112,8 @@ void SlaveInstance::SetBoolean( void SlaveInstance::SetString( const FMIValueReference /*vr*/[], std::size_t nvr, - const FMIString /*value*/[]) + const FMIString /*value*/[], + std::size_t nValues) { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -131,7 +136,8 @@ void SlaveInstance::GetFloat64( void SlaveInstance::GetInt32( const FMIValueReference /*vr*/[], std::size_t nvr, - FMIInt32 /*value*/[]) const + FMIInt32 /*value*/[], + std::size_t nValues) const { if (nvr != 0) { throw std::logic_error("Attempted to get nonexistent variable"); @@ -142,7 +148,8 @@ void SlaveInstance::GetInt32( void SlaveInstance::GetInt64( const FMIValueReference /*vr*/[], std::size_t nvr, - FMIInt64 /*value*/[]) const + FMIInt64 /*value*/[], + std::size_t nValues) const { if (nvr != 0) { throw std::logic_error("Attempted to get nonexistent variable"); @@ -153,7 +160,8 @@ void SlaveInstance::GetInt64( void SlaveInstance::GetUInt64( const FMIValueReference /*vr*/[], std::size_t nvr, - FMIUInt64 /*value*/[]) const + FMIUInt64 /*value*/[], + std::size_t nValues) const { if (nvr != 0) { throw std::logic_error("Attempted to get nonexistent variable"); @@ -164,7 +172,8 @@ void SlaveInstance::GetUInt64( void SlaveInstance::GetBoolean( const FMIValueReference /*vr*/[], std::size_t nvr, - FMIBoolean /*value*/[]) const + FMIBoolean /*value*/[], + std::size_t nValues) const { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); @@ -175,7 +184,8 @@ void SlaveInstance::GetBoolean( void SlaveInstance::GetString( const FMIValueReference /*vr*/[], std::size_t nvr, - FMIString /*value*/[]) const + FMIString /*value*/[], + std::size_t nValues) const { if (nvr != 0) { throw std::logic_error("Attempted to set nonexistent variable"); diff --git a/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp b/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp index 6b83fb4..61bc2cb 100644 --- a/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp +++ b/pythonfmu3/pythonfmu-export/src/cppfmu/cppfmu_cs.hpp @@ -76,23 +76,28 @@ class SlaveInstance virtual void SetInt32( const FMIValueReference vr[], std::size_t nvr, - const FMIInt32 value[]); + const FMIInt32 value[], + std::size_t nValues); virtual void SetInt64( const FMIValueReference vr[], std::size_t nvr, - const FMIInt64 value[]); + const FMIInt64 value[], + std::size_t nValues); virtual void SetUInt64( const FMIValueReference vr[], std::size_t nvr, - const FMIUInt64 value[]); + const FMIUInt64 value[], + std::size_t nValues); virtual void SetBoolean( const FMIValueReference vr[], std::size_t nvr, - const FMIBoolean value[]); + const FMIBoolean value[], + std::size_t nValues); virtual void SetString( const FMIValueReference vr[], std::size_t nvr, - const FMIString value[]); + const FMIString value[], + std::size_t nValues); /* Called from fmi3GetXxx()/fmiGetXxx(). * Throws std::logic_error by default. @@ -105,23 +110,28 @@ class SlaveInstance virtual void GetInt32( const FMIValueReference vr[], std::size_t nvr, - FMIInt32 value[]) const; + FMIInt32 value[], + std::size_t nValues) const; virtual void GetInt64( const FMIValueReference vr[], std::size_t nvr, - FMIInt64 value[]) const; + FMIInt64 value[], + std::size_t nValues) const; virtual void GetUInt64( const FMIValueReference vr[], std::size_t nvr, - FMIUInt64 value[]) const; + FMIUInt64 value[], + std::size_t nValues) const; virtual void GetBoolean( const FMIValueReference vr[], std::size_t nvr, - FMIBoolean value[]) const; + FMIBoolean value[], + std::size_t nValues) const; virtual void GetString( const FMIValueReference vr[], std::size_t nvr, - FMIString value[]) const; + FMIString value[], + std::size_t nValues) const; // Called from fmi3DoStep()/fmiDoStep(). Must be implemented in model code. virtual FMIStatus DoStep( diff --git a/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp b/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp index 9501db3..6cd61d4 100644 --- a/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp +++ b/pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp @@ -334,7 +334,7 @@ fmi3Status fmi3GetInt32( { const auto component = reinterpret_cast(c); try { - component->slave->GetInt32(vr, nvr, values); + component->slave->GetInt32(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -354,7 +354,7 @@ fmi3Status fmi3GetInt64( { const auto component = reinterpret_cast(c); try { - component->slave->GetInt64(vr, nvr, values); + component->slave->GetInt64(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -374,7 +374,7 @@ fmi3Status fmi3GetUInt64( { const auto component = reinterpret_cast(c); try { - component->slave->GetUInt64(vr, nvr, values); + component->slave->GetUInt64(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -394,7 +394,7 @@ fmi3Status fmi3GetBoolean( { const auto component = reinterpret_cast(c); try { - component->slave->GetBoolean(vr, nvr, values); + component->slave->GetBoolean(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -414,7 +414,7 @@ fmi3Status fmi3GetString( { const auto component = reinterpret_cast(c); try { - component->slave->GetString(vr, nvr, values); + component->slave->GetString(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -455,7 +455,7 @@ fmi3Status fmi3SetInt32( { const auto component = reinterpret_cast(c); try { - component->slave->SetInt32(vr, nvr, values); + component->slave->SetInt32(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -475,7 +475,7 @@ fmi3Status fmi3SetInt64( { const auto component = reinterpret_cast(c); try { - component->slave->SetInt64(vr, nvr, values); + component->slave->SetInt64(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -495,7 +495,7 @@ fmi3Status fmi3SetUInt64( { const auto component = reinterpret_cast(c); try { - component->slave->SetUInt64(vr, nvr, values); + component->slave->SetUInt64(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -515,7 +515,7 @@ fmi3Status fmi3SetBoolean( { const auto component = reinterpret_cast(c); try { - component->slave->SetBoolean(vr, nvr, values); + component->slave->SetBoolean(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); @@ -535,7 +535,7 @@ fmi3Status fmi3SetString( { const auto component = reinterpret_cast(c); try { - component->slave->SetString(vr, nvr, values); + component->slave->SetString(vr, nvr, values, nValues); return fmi3OK; } catch (const cppfmu::FatalError& e) { component->logger.Log(fmi3Fatal, "", e.what()); diff --git a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index b8c3f4c..edace62 100644 --- a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -285,7 +285,7 @@ void PySlaveInstance::SetFloat64(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* values) +void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* values, std::size_t nValues) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -306,7 +306,7 @@ void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* values) +void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* values, std::size_t nValues) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -327,7 +327,7 @@ void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* values) +void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* values, std::size_t nValues) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -348,7 +348,7 @@ void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* values) +void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* values, std::size_t nValues) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -369,7 +369,7 @@ void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* values) +void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* values, std::size_t nValues) { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); @@ -413,9 +413,9 @@ void PySlaveInstance::GetFloat64(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::GetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt32* values) const +void PySlaveInstance::GetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt32* values, std::size_t nValues) const { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); @@ -435,9 +435,9 @@ void PySlaveInstance::GetInt32(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::GetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt64* values) const +void PySlaveInstance::GetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt64* values, std::size_t nValues) const { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); @@ -457,9 +457,9 @@ void PySlaveInstance::GetInt64(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIUInt64* values) const +void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIUInt64* values, std::size_t nValues) const { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); @@ -470,7 +470,7 @@ void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t handle_py_exception("[getUInt64] PyObject_CallMethod", gilState); } - for (int i = 0; i < nvr; i++) { + for (int i = 0; i < nValues; i++) { PyObject* item = PyList_GetItem(refs, i); PyObject* value = PyObject_GetAttrString(item, "value"); if (value && PyLong_Check(value)) @@ -484,9 +484,9 @@ void PySlaveInstance::GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t }); } -void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* values) const +void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* values, std::size_t nValues) const { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); @@ -506,7 +506,7 @@ void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_ }); } -void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values) const +void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values, std::size_t nValues) const { py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { clearStrBuffer(); diff --git a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp index 8f7c204..609ca61 100644 --- a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +++ b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp @@ -34,18 +34,18 @@ class PySlaveInstance : public cppfmu::SlaveInstance cppfmu::FMIFloat64& endOfStep) override; void SetFloat64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIFloat64* value, std::size_t nValues) override; - void SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* value) override; - void SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* value) override; - void SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* value) override; - void SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* value) override; - void SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* value) override; + void SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* value, std::size_t nValues) override; + void SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* value, std::size_t nValues) override; + void SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* value, std::size_t nValues) override; + void SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* value, std::size_t nValues) override; + void SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* value, std::size_t nValues) override; void GetFloat64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIFloat64* value, std::size_t nValues) const override; - void GetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt32* value) const override; - void GetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt64* value) const override; - void GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIUInt64* value) const override; - void GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* value) const override; - void GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* value) const override; + void GetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt32* value, std::size_t nValues) const override; + void GetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInt64* value, std::size_t nValues) const override; + void GetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIUInt64* value, std::size_t nValues) const override; + void GetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* value, std::size_t nValues) const override; + void GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* value, std::size_t nValues) const override; void GetFMUstate(fmi3FMUState& State) override; void SetFMUstate(const fmi3FMUState& State) override; diff --git a/pythonfmu3/variables.py b/pythonfmu3/variables.py index c476f7f..42a8766 100644 --- a/pythonfmu3/variables.py +++ b/pythonfmu3/variables.py @@ -50,6 +50,11 @@ def __init__( self.setter = setter self._type = None self.local_name = name.split(".")[-1] + + # demanagle names + self.local_name = self.local_name.lstrip('_') + + self.__attrs = { "name": name, "valueReference": None, @@ -291,15 +296,23 @@ def to_xml(self) -> Element: return parent class UInt64(ModelVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): + def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): super().__init__(name, **kwargs) self.__attrs = {"start": start} self._type = "UInt64"; + + if dimensions: + check_numpy() + self.__dimensions = dimensions @property def start(self) -> Optional[Any]: return self.__attrs["start"] + @property + def dimensions(self) -> List[Dimension]: + return self.__dimensions + @start.setter def start(self, value: int): self.__attrs["start"] = value @@ -308,11 +321,22 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value) + if len(self.dimensions) > 0: + output = " ".join([str(val) for val in flatten(value)]) + if len(output) > MAX_LENGTH: + output = output[0] + else: + attrib[key] = str(value) self._extras = attrib parent = super().to_xml() + + for dimension in self.__dimensions: + parent.append(dimension.to_xml()) return parent + + def size(self, vars): + return reduce(lambda x, dim: x * int(dim.size(vars)), self.__dimensions, 1) class Boolean(ModelVariable): def __init__(self, name: str, start: Optional[Any] = None, **kwargs): From 4e522064aa23ce7fe9470bc50cebd2a252c47960 Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Fri, 26 Sep 2025 17:13:06 +0100 Subject: [PATCH 2/7] further progress towards more array support --- pythonfmu3/fmi3slave.py | 6 +- .../src/pythonfmu/PySlaveInstance.cpp | 31 +++-- pythonfmu3/variables.py | 111 ++++++++++-------- 3 files changed, 90 insertions(+), 58 deletions(-) diff --git a/pythonfmu3/fmi3slave.py b/pythonfmu3/fmi3slave.py index 0e6fbff..6432262 100644 --- a/pythonfmu3/fmi3slave.py +++ b/pythonfmu3/fmi3slave.py @@ -338,7 +338,11 @@ def get_boolean(self, vrs: List[int]) -> List[bool]: for vr in vrs: var = self.vars[vr] if isinstance(var, Boolean): - refs.append(bool(var.getter())) + if len(var.dimensions == 0): + refs.append(bool(var.getter())) + else: + refs.extend(var.getter()) + else: raise TypeError( f"Variable with valueReference={vr} is not of type Boolean!" diff --git a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index edace62..8f09eb1 100644 --- a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -287,11 +287,14 @@ void PySlaveInstance::SetFloat64(const cppfmu::FMIValueReference* vr, std::size_ void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt32* values, std::size_t nValues) { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); PyObject* refs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + + for (int i = 0; i < nValues; i++) { PyList_SetItem(refs, i, Py_BuildValue("i", values[i])); } @@ -308,11 +311,13 @@ void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIInt64* values, std::size_t nValues) { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); PyObject* refs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + for (int i = 0; i < nValues; i++) { PyList_SetItem(refs, i, Py_BuildValue("L", values[i])); } @@ -329,11 +334,13 @@ void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIUInt64* values, std::size_t nValues) { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); PyObject* refs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + for (int i = 0; i < nValues; i++) { PyList_SetItem(refs, i, Py_BuildValue("K", values[i])); } @@ -350,11 +357,13 @@ void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, const cppfmu::FMIBoolean* values, std::size_t nValues) { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); PyObject* refs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + for (int i = 0; i < nValues; i++) { PyList_SetItem(refs, i, PyBool_FromLong(values[i])); } @@ -371,11 +380,13 @@ void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_ void PySlaveInstance::SetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString const* values, std::size_t nValues) { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); PyObject* refs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); + } + for (int i = 0; i < nValues; i++) { PyList_SetItem(refs, i, Py_BuildValue("s", values[i])); } @@ -426,7 +437,7 @@ void PySlaveInstance::GetInt32(const cppfmu::FMIValueReference* vr, std::size_t handle_py_exception("[getInt32] PyObject_CallMethod", gilState); } - for (int i = 0; i < nvr; i++) { + for (int i = 0; i < nValues; i++) { PyObject* value = PyList_GetItem(refs, i); values[i] = static_cast(PyLong_AsLong(value)); } @@ -448,7 +459,7 @@ void PySlaveInstance::GetInt64(const cppfmu::FMIValueReference* vr, std::size_t handle_py_exception("[getInt64] PyObject_CallMethod", gilState); } - for (int i = 0; i < nvr; i++) { + for (int i = 0; i < nValues; i++) { PyObject* value = PyList_GetItem(refs, i); values[i] = static_cast(PyLong_AsLongLong(value)); } @@ -497,7 +508,7 @@ void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_ handle_py_exception("[getBoolean] PyObject_CallMethod", gilState); } - for (int i = 0; i < nvr; i++) { + for (int i = 0; i < nValues; i++) { PyObject* value = PyList_GetItem(refs, i); values[i] = PyObject_IsTrue(value); } @@ -508,7 +519,7 @@ void PySlaveInstance::GetBoolean(const cppfmu::FMIValueReference* vr, std::size_ void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values, std::size_t nValues) const { - py_safe_run([this, &vr, nvr, &values](PyGILState_STATE gilState) { + py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { clearStrBuffer(); PyObject* vrs = PyList_New(nvr); for (int i = 0; i < nvr; i++) { @@ -520,7 +531,7 @@ void PySlaveInstance::GetString(const cppfmu::FMIValueReference* vr, std::size_t handle_py_exception("[getString] PyObject_CallMethod", gilState); } - for (int i = 0; i < nvr; i++) { + for (int i = 0; i < nValues; i++) { PyObject* value = PyUnicode_AsEncodedString(PyList_GetItem(refs, i), "utf-8", nullptr); values[i] = PyBytes_AsString(value); strBuffer.emplace_back(value); diff --git a/pythonfmu3/variables.py b/pythonfmu3/variables.py index 42a8766..b63cac5 100644 --- a/pythonfmu3/variables.py +++ b/pythonfmu3/variables.py @@ -156,7 +156,6 @@ def to_xml(self) -> Element: attrib["value"] = self.value return Element("Start", attrib) - class Dimension(object): def __init__(self, start: str = "", valueReference: str = ""): if start and valueReference and any((start, valueReference)): @@ -182,15 +181,38 @@ def to_xml(self) -> Element: ele = Element("Dimension", attrib) return ele -class Float64(ModelVariable): +class Arrayable(object): + def __init__(self, dimensions : List[Dimension] = [], **kwargs): + if dimensions: + check_numpy() + self._dimensions = dimensions + + @property + def dimensions(self) -> List[Dimension]: + return self._dimensions + + def get_start_str(self, value, formatter): + if len(self.dimensions) > 0: + output = "".join([formatter(val) for val in flatten(value)]) + if len(output) > MAX_LENGTH: + output = output[0] + return output + else: + return formatter(value) + + def dimensions_xml(self) -> List[Element]: + return [dim.to_xml() for dim in self._dimensions] + + def size(self, vars): + return reduce(lambda x, dim: x * int(dim.size(vars)), self._dimensions, 1) + +class Float64(ModelVariable, Arrayable): def __init__(self, name: str, start: Optional[Any] = None, derivative: Optional[Any] = None, dimensions: List[Dimension] = [], unit: Optional[str] = None, **kwargs): - super().__init__(name, **kwargs) + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions) self.__attrs = {"start": start, "derivative": derivative} self._type = "Float64" self._unit = unit - if dimensions: - check_numpy() - self.__dimensions = dimensions @property def start(self) -> Optional[Any]: @@ -211,11 +233,6 @@ def unit(self, value: str): @property def derivative(self): return self.__attrs["derivative"] - - @property - def dimensions(self) -> List[Dimension]: - return self.__dimensions - def to_xml(self) -> Element: attrib = dict() @@ -223,13 +240,7 @@ def to_xml(self) -> Element: if value is not None: # In order to not loose precision, a number of this type should be # stored on an XML file with at least 16 significant digits - output = "" - if len(self.dimensions) > 0: - output = " ".join([f"{val:.16g}" for val in flatten(value)]) - if len(output) > MAX_LENGTH: - output = output[0] - else: - output = f"{value:.16g}" + output = self.get_start_str(value, lambda v: f"{v:.16g}") attrib[key] = output if self.unit: @@ -238,8 +249,7 @@ def to_xml(self) -> Element: self._extras = attrib parent = super().to_xml() - for dimension in self.__dimensions: - parent.append(dimension.to_xml()) + parent.extend(self.dimensions_xml()) return parent @@ -247,9 +257,10 @@ def size(self, vars): return reduce(lambda x, dim: x * int(dim.size(vars)), self.__dimensions, 1) -class Int32(ModelVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): - super().__init__(name, **kwargs) +class Int32(ModelVariable, Arrayable): + def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = {"start": start} self._type = "Int32"; @@ -265,15 +276,18 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value) + attrib[key] = self.get_start_str(value, lambda v: str(v)) self._extras = attrib parent = super().to_xml() + + parent.extend(self.dimensions_xml()) return parent -class Int64(ModelVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): - super().__init__(name, **kwargs) +class Int64(ModelVariable, Arrayable): + def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = {"start": start} self._type = "Int64"; @@ -289,15 +303,18 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value) + attrib[key] = self.get_start_str(value, lambda v: str(v)) self._extras = attrib parent = super().to_xml() + parent.extend(self.dimensions_xml()) + return parent -class UInt64(ModelVariable): +class UInt64(ModelVariable, Arrayable): def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): - super().__init__(name, **kwargs) + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = {"start": start} self._type = "UInt64"; @@ -321,26 +338,21 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - if len(self.dimensions) > 0: - output = " ".join([str(val) for val in flatten(value)]) - if len(output) > MAX_LENGTH: - output = output[0] - else: - attrib[key] = str(value) + attrib[key] = self.get_start_str(value, lambda v: str(v)) self._extras = attrib parent = super().to_xml() - - for dimension in self.__dimensions: - parent.append(dimension.to_xml()) + + parent.extend(self.dimensions_xml()) return parent def size(self, vars): return reduce(lambda x, dim: x * int(dim.size(vars)), self.__dimensions, 1) -class Boolean(ModelVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): - super().__init__(name, **kwargs) +class Boolean(ModelVariable, Arrayable): + def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = {"start": start} self._type = "Boolean" @@ -356,16 +368,19 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value).lower() + attrib[key] = self.get_start_str(value, lambda v: str(v).lower()) self._extras = attrib parent = super().to_xml() + + parent.extend(self.dimensions_xml()) return parent -class String(ModelVariable): - def __init__(self, name: str, start: Optional[Any] = None, **kwargs): - super().__init__(name, **kwargs) +class String(ModelVariable, Arrayable): + def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): + ModelVariable.__init__(self, name, **kwargs) + Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = dict() self._type = "String" self._start = Start(start) @@ -382,12 +397,14 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = str(value) + attrib[key] = self.get_start_str(value, lambda v: str(v)) self._extras = attrib parent = super().to_xml() if self.start is not None: parent.append(self._start.to_xml()) + + parent.extend(self.dimensions_xml()) return parent From 19765f7981c850d7c72dd3aca2eca27be347579d Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Mon, 29 Sep 2025 16:34:06 +0100 Subject: [PATCH 3/7] fix for additional vars --- examples/alltypes.py | 75 +++++++++++++++++++++++++++++++++++++++++ pythonfmu3/fmi3slave.py | 18 ++++++---- pythonfmu3/variables.py | 13 ++----- 3 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 examples/alltypes.py diff --git a/examples/alltypes.py b/examples/alltypes.py new file mode 100644 index 0000000..a3525f6 --- /dev/null +++ b/examples/alltypes.py @@ -0,0 +1,75 @@ + +from pythonfmu3 import Fmi3Causality, Fmi3Variability, Dimension, Fmi3Slave, Fmi3Status, Float64, Int32, Int64, UInt64, String, Boolean + +import numpy as np + +SIZE = 10 + +TYPES = [UInt64, Float64, Boolean, Int32, Int64] +CAUSALITY = [Fmi3Causality.output, Fmi3Causality.input] + +TYPE_MAP = { + UInt64: np.uint64, + Float64: np.float64, + Int32: np.int32, + Int64: np.int64, + Boolean: bool +} + +def var_names(var_type, causality): + return f"{var_type.__name__.lower()}_{causality.name.lower()}" + + +def init_var(var_type, causality, array=True): + if array: + return np.zeros(SIZE, dtype=TYPE_MAP[var_type]) + else: + return TYPE_MAP[var_type]() + + +def create_vars(self): + dimensions = [Dimension(start=str(SIZE))] + for var_type in TYPES: + for causality in CAUSALITY: + name = var_names(var_type, causality) + if var_type == Float64: + var = var_type(name, causality=causality, variability=Fmi3Variability.continuous, dimensions=dimensions) + else: + var = var_type(name, causality=causality, variability=Fmi3Variability.discrete, dimensions=dimensions) + setattr(self, name, init_var(var_type, causality)) + self.register_variable(var) + + +def generate_random_data(self): + for var_type in TYPES: + causality = Fmi3Causality.output + name = var_names(var_type, causality) + var = getattr(self, name) + if var_type == Boolean: + setattr(self, name, np.random.choice(a=[False, True], size=SIZE).astype(bool)) + elif var_type == Int32 or var_type == Int64 or var_type == UInt64: + setattr(self, name, np.random.randint(0, 100, size=SIZE).astype(TYPE_MAP[var_type])) + else: + setattr(self, name, np.random.rand(SIZE).astype(TYPE_MAP[var_type])) + +class AllTypes(Fmi3Slave): + + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.author = "Stephen Smith" + self.description = "All types example" + + self.time = 0.0 + + self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous)) + + create_vars(self) + + + def do_step(self, current_time: float, step_size: float) -> Fmi3Status: + generate_random_data(self) + return True + + diff --git a/pythonfmu3/fmi3slave.py b/pythonfmu3/fmi3slave.py index 6432262..43e38ed 100644 --- a/pythonfmu3/fmi3slave.py +++ b/pythonfmu3/fmi3slave.py @@ -183,8 +183,7 @@ def to_xml(self, model_options: Dict[str, str] = dict()) -> Element: initial_unknown = list( filter(lambda v: (v.causality == Fmi3Causality.output and (v.initial in allowed_variability)) or v.causality == Fmi3Causality.calculatedParameter - or v in continuous_state_derivatives and v.initial in allowed_variability - or v.variability == Fmi3Variability.continuous and v.initial in allowed_variability and v.causality != Fmi3Causality.independent, self.vars.values()) + or v in continuous_state_derivatives and v.initial in allowed_variability, self.vars.values()) ) for v in outputs: @@ -282,7 +281,10 @@ def get_int32(self, vrs: List[int]) -> List[int]: for vr in vrs: var = self.vars[vr] if isinstance(var, Int32): - refs.append(int(var.getter())) + if len(var.dimensions) == 0: + refs.append(int(var.getter())) + else: + refs.extend(map(int, var.getter())) else: raise TypeError( f"Variable with valueReference={vr} is not of type Integer!" @@ -294,7 +296,10 @@ def get_int64(self, vrs: List[int]) -> List[int]: for vr in vrs: var = self.vars[vr] if isinstance(var, (Enumeration, Int64)): - refs.append(int(var.getter())) + if len(var.dimensions) == 0: + refs.append(int(var.getter())) + else: + refs.extend(map(int, var.getter())) else: raise TypeError( f"Variable with valueReference={vr} is not of type Int64!" @@ -310,8 +315,7 @@ def get_uint64(self, vrs: List[int]) -> List[ctypes.c_uint64]: val = var.getter() refs.append(ctypes.c_uint64(val) if not isinstance(val, ctypes.c_uint64) else val) else: - vals = [ctypes.c_uint64(v) for v in var.getter()] - refs.extend(vals) + refs.extend(map(ctypes.c_uint64, var.getter())) else: raise TypeError( f"Variable with valueReference={vr} is not of type Real!" @@ -338,7 +342,7 @@ def get_boolean(self, vrs: List[int]) -> List[bool]: for vr in vrs: var = self.vars[vr] if isinstance(var, Boolean): - if len(var.dimensions == 0): + if len(var.dimensions) == 0: refs.append(bool(var.getter())) else: refs.extend(var.getter()) diff --git a/pythonfmu3/variables.py b/pythonfmu3/variables.py index b63cac5..62294f2 100644 --- a/pythonfmu3/variables.py +++ b/pythonfmu3/variables.py @@ -193,7 +193,7 @@ def dimensions(self) -> List[Dimension]: def get_start_str(self, value, formatter): if len(self.dimensions) > 0: - output = "".join([formatter(val) for val in flatten(value)]) + output = " ".join([formatter(val) for val in flatten(value)]) if len(output) > MAX_LENGTH: output = output[0] return output @@ -317,19 +317,11 @@ def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dime Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = {"start": start} self._type = "UInt64"; - - if dimensions: - check_numpy() - self.__dimensions = dimensions @property def start(self) -> Optional[Any]: return self.__attrs["start"] - @property - def dimensions(self) -> List[Dimension]: - return self.__dimensions - @start.setter def start(self, value: int): self.__attrs["start"] = value @@ -377,10 +369,9 @@ def to_xml(self) -> Element: return parent -class String(ModelVariable, Arrayable): +class String(ModelVariable): def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): ModelVariable.__init__(self, name, **kwargs) - Arrayable.__init__(self, dimensions, **kwargs) self.__attrs = dict() self._type = "String" self._start = Start(start) From d4d68887a48a24f7e798c425203d83bbbc8c514c Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Mon, 29 Sep 2025 16:39:33 +0100 Subject: [PATCH 4/7] fix string --- pythonfmu3/variables.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pythonfmu3/variables.py b/pythonfmu3/variables.py index 62294f2..c3aba08 100644 --- a/pythonfmu3/variables.py +++ b/pythonfmu3/variables.py @@ -253,9 +253,6 @@ def to_xml(self) -> Element: return parent - def size(self, vars): - return reduce(lambda x, dim: x * int(dim.size(vars)), self.__dimensions, 1) - class Int32(ModelVariable, Arrayable): def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): @@ -338,9 +335,6 @@ def to_xml(self) -> Element: return parent - def size(self, vars): - return reduce(lambda x, dim: x * int(dim.size(vars)), self.__dimensions, 1) - class Boolean(ModelVariable, Arrayable): def __init__(self, name: str, start: Optional[Any] = None, dimensions: List[Dimension] = [], **kwargs): ModelVariable.__init__(self, name, **kwargs) @@ -388,15 +382,13 @@ def to_xml(self) -> Element: attrib = dict() for key, value in self.__attrs.items(): if value is not None: - attrib[key] = self.get_start_str(value, lambda v: str(v)) + attrib[key] = str(value, lambda v: str(v)) self._extras = attrib parent = super().to_xml() if self.start is not None: parent.append(self._start.to_xml()) - parent.extend(self.dimensions_xml()) - return parent class Enumeration(ModelVariable): From 684d6084a1cdbacae7ecf2a8786198aa0d289e8d Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Tue, 30 Sep 2025 15:16:58 +0100 Subject: [PATCH 5/7] fix array usage --- examples/{alltypes.py => arraytypes.py} | 3 +- pythonfmu3/fmi3slave.py | 40 +++++-- .../src/pythonfmu/PySlaveInstance.cpp | 8 +- .../tests/slaves/pythonslave_arraytypes.py | 80 +++++++++++++ pythonfmu3/tests/test_integration.py | 106 ++++++++++++++++++ 5 files changed, 223 insertions(+), 14 deletions(-) rename examples/{alltypes.py => arraytypes.py} (98%) create mode 100644 pythonfmu3/tests/slaves/pythonslave_arraytypes.py diff --git a/examples/alltypes.py b/examples/arraytypes.py similarity index 98% rename from examples/alltypes.py rename to examples/arraytypes.py index a3525f6..b04b4ff 100644 --- a/examples/alltypes.py +++ b/examples/arraytypes.py @@ -52,8 +52,7 @@ def generate_random_data(self): else: setattr(self, name, np.random.rand(SIZE).astype(TYPE_MAP[var_type])) -class AllTypes(Fmi3Slave): - +class ArrayTypes(Fmi3Slave): def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/pythonfmu3/fmi3slave.py b/pythonfmu3/fmi3slave.py index 43e38ed..17063ab 100644 --- a/pythonfmu3/fmi3slave.py +++ b/pythonfmu3/fmi3slave.py @@ -366,30 +366,48 @@ def get_string(self, vrs: List[int]) -> List[str]: return refs def set_int32(self, vrs: List[int], values: List[int]): - for vr, value in zip(vrs, values): + offset = 0 + for vr in vrs: var = self.vars[vr] if isinstance(var, Int32): - var.setter(value) + size = var.size(self.vars) + if size > 1: + var.setter(values[offset:offset+size]) + else: + var.setter(values[offset]) + offset += size else: raise TypeError( f"Variable with valueReference={vr} is not of type Integer!" ) def set_int64(self, vrs: List[int], values: List[int]): - for vr, value in zip(vrs, values): + offset = 0 + for vr in vrs: var = self.vars[vr] if isinstance(var, (Enumeration, Int64)): - var.setter(value) + size = var.size(self.vars) + if size > 1: + var.setter(values[offset:offset+size]) + else: + var.setter(values[offset]) + offset += size else: raise TypeError( f"Variable with valueReference={vr} is not of type Integer!" ) def set_uint64(self, vrs: List[int], values: List[int]): - for vr, value in zip(vrs, values): + offset = 0 + for vr in vrs: var = self.vars[vr] if isinstance(var, UInt64): - var.setter(value) + size = var.size(self.vars) + if size > 1: + var.setter(values[offset:offset+size]) + else: + var.setter(values[offset]) + offset += size else: raise TypeError( f"Variable with valueReference={vr} is not of type UInt64!" @@ -412,10 +430,16 @@ def set_float64(self, vrs: List[int], values: List[float]): ) def set_boolean(self, vrs: List[int], values: List[bool]): - for vr, value in zip(vrs, values): + offset = 0 + for vr in vrs: var = self.vars[vr] if isinstance(var, Boolean): - var.setter(value) + size = var.size(self.vars) + if size > 1: + var.setter(values[offset:offset+size]) + else: + var.setter(values[offset]) + offset += size else: raise TypeError( f"Variable with valueReference={vr} is not of type Boolean!" diff --git a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp index 8f09eb1..59508be 100644 --- a/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +++ b/pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp @@ -289,7 +289,7 @@ void PySlaveInstance::SetInt32(const cppfmu::FMIValueReference* vr, std::size_t { py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); + PyObject* refs = PyList_New(nValues); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); } @@ -313,7 +313,7 @@ void PySlaveInstance::SetInt64(const cppfmu::FMIValueReference* vr, std::size_t { py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); + PyObject* refs = PyList_New(nValues); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); } @@ -336,7 +336,7 @@ void PySlaveInstance::SetUInt64(const cppfmu::FMIValueReference* vr, std::size_t { py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); + PyObject* refs = PyList_New(nValues); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); } @@ -359,7 +359,7 @@ void PySlaveInstance::SetBoolean(const cppfmu::FMIValueReference* vr, std::size_ { py_safe_run([this, &vr, nvr, &values, nValues](PyGILState_STATE gilState) { PyObject* vrs = PyList_New(nvr); - PyObject* refs = PyList_New(nvr); + PyObject* refs = PyList_New(nValues); for (int i = 0; i < nvr; i++) { PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i])); } diff --git a/pythonfmu3/tests/slaves/pythonslave_arraytypes.py b/pythonfmu3/tests/slaves/pythonslave_arraytypes.py new file mode 100644 index 0000000..c1a67d5 --- /dev/null +++ b/pythonfmu3/tests/slaves/pythonslave_arraytypes.py @@ -0,0 +1,80 @@ + +from pythonfmu3 import Fmi3Causality, Fmi3Variability, Dimension, Fmi3Slave, Fmi3Status, Float64, Int32, Int64, UInt64, String, Boolean + +import numpy as np + +SIZE = 10 + +TYPES = [UInt64, Float64, Boolean, Int32, Int64] +CAUSALITY = [Fmi3Causality.output, Fmi3Causality.input] + +TYPE_MAP = { + UInt64: np.uint64, + Float64: np.float64, + Int32: np.int32, + Int64: np.int64, + Boolean: bool +} + +def var_names(var_type, causality): + return f"{var_type.__name__.lower()}_{causality.name.lower()}" + + +def init_var(var_type, causality, array=True): + if array: + return np.zeros(SIZE, dtype=TYPE_MAP[var_type]) + else: + return TYPE_MAP[var_type]() + + +def create_vars(self): + dimensions = [Dimension(start=str(SIZE))] + for var_type in TYPES: + for causality in CAUSALITY: + name = var_names(var_type, causality) + if var_type == Float64: + var = var_type(name, causality=causality, variability=Fmi3Variability.continuous, dimensions=dimensions) + else: + var = var_type(name, causality=causality, variability=Fmi3Variability.discrete, dimensions=dimensions) + setattr(self, name, init_var(var_type, causality)) + self.register_variable(var) + + +def generate_data(self): + for var_type in TYPES: + causality = Fmi3Causality.output + name = var_names(var_type, causality) + var = getattr(self, name) + if var_type == Boolean: + data = [False, True, False, True, True, False, False, True, False, True] + setattr(self, name, np.array(data).astype(bool)) + elif var_type == Int32 or var_type == Int64 or var_type == UInt64: + data = [1,2,3,4,5,6,7,8,9,10] + setattr(self, name, np.array(data).astype(TYPE_MAP[var_type])) + else: + data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + setattr(self, name, np.array(data).astype(TYPE_MAP[var_type])) + +class ArrayTypes(Fmi3Slave): + + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.author = "Tester" + self.description = "FMU with all basic types as arrays" + + self.time = 0.0 + + self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous)) + + create_vars(self) + + generate_data(self) + + + def do_step(self, current_time: float, step_size: float) -> Fmi3Status: + generate_data(self) + return True + + diff --git a/pythonfmu3/tests/test_integration.py b/pythonfmu3/tests/test_integration.py index 10640a3..cc8a1b7 100644 --- a/pythonfmu3/tests/test_integration.py +++ b/pythonfmu3/tests/test_integration.py @@ -280,6 +280,57 @@ def test_integration_get(tmp_path): model.terminate() model.freeInstance() +@pytest.mark.integration +def test_integration_get_array(tmp_path): + script_file = Path(__file__).parent / "slaves/pythonslave_arraytypes.py" + fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") + assert fmu.exists() + + md = fmpy.read_model_description(fmu) + unzip_dir = fmpy.extract(fmu) + + model = fmpy.fmi3.FMU3Slave( + guid=md.guid, + unzipDirectory=unzip_dir, + modelIdentifier=md.coSimulation.modelIdentifier, + instanceName='instance1') + + model.instantiate() + model.enterInitializationMode() + model.exitInitializationMode() + + to_test = { + "int32_output": [1,2,3,4,5,6,7,8,9,10], + "int64_output": [1,2,3,4,5,6,7,8,9,10], + "uint64_output": [1,2,3,4,5,6,7,8,9,10], + "float64_output": [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0], + "boolean_output": [False, True, False, True, True, False, False, True, False, True] + } + + model_value = None + variables = mapped(md) + for key, value in to_test.items(): + var = variables[key] + vrs = [var.valueReference] + if var.type == "Int32": + model_values = model.getInt32(vrs, nValues=10) + elif var.type == "Int64": + model_values = model.getInt64(vrs, nValues=10) + elif var.type == "UInt64": + model_values = model.getUInt64(vrs, nValues=10) + elif var.type == "Float64": + model_values = model.getFloat64(vrs, nValues=10) + elif var.type == "Boolean": + model_values = model.getBoolean(vrs, nValues=10) + else: + pytest.xfail("Unsupported type") + + assert len(model_values) == len(value) + assert model_values == value + + model.terminate() + model.freeInstance() + @pytest.mark.integration def test_integration_read_from_file(tmp_path): @@ -367,6 +418,61 @@ def test_integration_set(tmp_path): model.terminate() model.freeInstance() +@pytest.mark.integration +def test_integration_set_array(tmp_path): + script_file = Path(__file__).parent / "slaves/pythonslave_arraytypes.py" + fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") + assert fmu.exists() + + md = fmpy.read_model_description(fmu) + unzip_dir = fmpy.extract(fmu) + + model = fmpy.fmi3.FMU3Slave( + guid=md.guid, + unzipDirectory=unzip_dir, + modelIdentifier=md.coSimulation.modelIdentifier, + instanceName='instance1') + + model.instantiate() + model.enterInitializationMode() + model.exitInitializationMode() + + to_test = { + "int32_input": [1,2,3,4,5,6,7,8,9,10], + "int64_input": [1,2,3,4,5,6,7,8,9,10], + "uint64_input": [1,2,3,4,5,6,7,8,9,10], + "float64_input": [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0], + "boolean_input": [False, True, False, True, True, False, False, True, False, True] + } + + model_value = None + variables = mapped(md) + for key, value in to_test.items(): + var = variables[key] + vrs = [var.valueReference] + if var.type == "Int32": + model.setInt32(vrs, value) + model_value = model.getInt32(vrs, nValues=10) + elif var.type == "Int64": + model.setInt64(vrs, value) + model_value = model.getInt64(vrs, nValues=10) + elif var.type == "UInt64": + model.setUInt64(vrs, value) + model_value = model.getUInt64(vrs, nValues=10) + elif var.type == "Float64": + model.setFloat64(vrs, value) + model_value = model.getFloat64(vrs,nValues=10) + elif var.type == "Boolean": + model.setBoolean(vrs, value) + model_value = model.getBoolean(vrs, nValues=10) + else: + pytest.xfail("Unsupported type") + + assert list(model_value) == value + + model.terminate() + model.freeInstance() + @pytest.mark.integration def test_simple_integration_fmpy(tmp_path): From 96d4b49e6be55a27062509492ac5da4ab90a66d3 Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Tue, 30 Sep 2025 15:19:05 +0100 Subject: [PATCH 6/7] fix error messages --- pythonfmu3/fmi3slave.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pythonfmu3/fmi3slave.py b/pythonfmu3/fmi3slave.py index 17063ab..2a95dc1 100644 --- a/pythonfmu3/fmi3slave.py +++ b/pythonfmu3/fmi3slave.py @@ -287,7 +287,7 @@ def get_int32(self, vrs: List[int]) -> List[int]: refs.extend(map(int, var.getter())) else: raise TypeError( - f"Variable with valueReference={vr} is not of type Integer!" + f"Variable with valueReference={vr} is not of type Int32!" ) return refs @@ -318,7 +318,7 @@ def get_uint64(self, vrs: List[int]) -> List[ctypes.c_uint64]: refs.extend(map(ctypes.c_uint64, var.getter())) else: raise TypeError( - f"Variable with valueReference={vr} is not of type Real!" + f"Variable with valueReference={vr} is not of type Uint64!" ) return refs @@ -333,7 +333,7 @@ def get_float64(self, vrs: List[int]) -> List[float]: refs.extend(var.getter()) else: raise TypeError( - f"Variable with valueReference={vr} is not of type Real!" + f"Variable with valueReference={vr} is not of type Float64!" ) return refs @@ -378,7 +378,7 @@ def set_int32(self, vrs: List[int], values: List[int]): offset += size else: raise TypeError( - f"Variable with valueReference={vr} is not of type Integer!" + f"Variable with valueReference={vr} is not of type Int32!" ) def set_int64(self, vrs: List[int], values: List[int]): @@ -394,7 +394,7 @@ def set_int64(self, vrs: List[int], values: List[int]): offset += size else: raise TypeError( - f"Variable with valueReference={vr} is not of type Integer!" + f"Variable with valueReference={vr} is not of type Int64!" ) def set_uint64(self, vrs: List[int], values: List[int]): @@ -426,7 +426,7 @@ def set_float64(self, vrs: List[int], values: List[float]): offset += size else: raise TypeError( - f"Variable with valueReference={vr} is not of type Real!" + f"Variable with valueReference={vr} is not of type Float64!" ) def set_boolean(self, vrs: List[int], values: List[bool]): From 5ce50a3d833ff4d40b105bfec96483dc986784f9 Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Tue, 30 Sep 2025 15:33:58 +0100 Subject: [PATCH 7/7] add feedthrogh example --- examples/alltypes.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 examples/alltypes.py diff --git a/examples/alltypes.py b/examples/alltypes.py new file mode 100644 index 0000000..5bad6b9 --- /dev/null +++ b/examples/alltypes.py @@ -0,0 +1,52 @@ +from pythonfmu3 import Fmi3Causality, Fmi3Variability, Dimension, Fmi3Slave, Fmi3Status, Float64, Int32, Int64, UInt64, String, Boolean + + +TYPES = [UInt64, Float64, Boolean, Int32, Int64] +CAUSALITY = [Fmi3Causality.output, Fmi3Causality.input] +type_map = { + UInt64: int, + Float64: float, + Int32: int, + Int64: int, + Boolean: bool +} + +def var_names(var_type, causality): + return f"{var_type.__name__.lower()}_{causality.name.lower()}" + +def init_var(var_type, causality): + return type_map[var_type]() + +def create_vars(self): + for var_type in TYPES: + for causality in CAUSALITY: + name = var_names(var_type, causality) + if var_type == Float64: + var = var_type(name, causality=causality, variability=Fmi3Variability.continuous) + else: + var = var_type(name, causality=causality, variability=Fmi3Variability.discrete) + setattr(self, name, init_var(var_type, causality)) + self.register_variable(var) + +class AllTypes(Fmi3Slave): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.author = "Stephen Smith" + self.description = "All types example" + + self.time = 0.0 + + self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous)) + + create_vars(self) + + + def do_step(self, current_time: float, step_size: float) -> Fmi3Status: + # feedthrough + for var_type in TYPES: + input_var = getattr(self, var_names(var_type, Fmi3Causality.input)) + setattr(self, var_names(var_type, Fmi3Causality.output), input_var) + + return True