From 667212b45d988b58de97187434224fad14c3ea24 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Mon, 21 Sep 2026 15:06:35 +0200 Subject: [PATCH 1/5] fix loadFile for package.mo: don't copy it out of its package directory --- OMPython/modelica_system_omc.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index c4a441c6..97044d53 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -140,15 +140,24 @@ def model( if not file_path.is_file(): raise IOError(f"Model file {file_path} does not exist!") - self._file_name = self.getWorkDirectory() / file_path.name - if (isinstance(self._session, OMCSessionLocal) - and file_path.as_posix() == self._file_name.as_posix()): - pass - elif self._file_name.is_file(): - raise IOError(f"Simulation model file {self._file_name} exist - not overwriting!") + if isinstance(self._session, OMCSessionLocal) and file_path.name == "package.mo": + # Structured/directory-based package: OMC requires the enclosing + # directory's name to match the package name declared inside + # package.mo, and the package may span sibling .mo files in that + # same directory tree (e.g. Tests/StableBouncingBall.mo). Copying + # just this one file into a randomly-named work directory breaks + # both of those, so load it in place instead. + self._file_name = file_path else: - content = file_path.read_text(encoding='utf-8') - self._file_name.write_text(content) + self._file_name = self.getWorkDirectory() / file_path.name + if (isinstance(self._session, OMCSessionLocal) + and file_path.as_posix() == self._file_name.as_posix()): + pass + elif self._file_name.is_file(): + raise IOError(f"Simulation model file {self._file_name} exist - not overwriting!") + else: + content = file_path.read_text(encoding='utf-8') + self._file_name.write_text(content) if self._file_name is not None: self._loadFile(fileName=self._file_name) From a1e9454e188d26bfd7a1716f274e9f37a657d170 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Mon, 21 Sep 2026 15:15:36 +0200 Subject: [PATCH 2/5] use omcpath --- OMPython/modelica_system_omc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index 97044d53..a99389ac 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -147,7 +147,7 @@ def model( # same directory tree (e.g. Tests/StableBouncingBall.mo). Copying # just this one file into a randomly-named work directory breaks # both of those, so load it in place instead. - self._file_name = file_path + self._file_name = self._session.omcpath(file_path) else: self._file_name = self.getWorkDirectory() / file_path.name if (isinstance(self._session, OMCSessionLocal) From 0b8f58bc269a2601f1e7699f364adbea71a269e7 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Tue, 29 Sep 2026 15:42:04 +0200 Subject: [PATCH 3/5] add test for loading structured package --- .../BouncingBalls/Tests/BouncingBall.mo | 22 +++++++++++++++ .../BouncingBalls/Tests/StableBouncingBall.mo | 27 +++++++++++++++++++ .../resources/BouncingBalls/Tests/package.mo | 8 ++++++ .../BouncingBalls/Tests/package.order | 2 ++ tests/resources/BouncingBalls/package.mo | 18 +++++++++++++ tests/resources/BouncingBalls/package.order | 1 + tests/test_ModelicaSystemOMC.py | 17 ++++++++++++ 7 files changed, 95 insertions(+) create mode 100644 tests/resources/BouncingBalls/Tests/BouncingBall.mo create mode 100644 tests/resources/BouncingBalls/Tests/StableBouncingBall.mo create mode 100644 tests/resources/BouncingBalls/Tests/package.mo create mode 100644 tests/resources/BouncingBalls/Tests/package.order create mode 100644 tests/resources/BouncingBalls/package.mo create mode 100644 tests/resources/BouncingBalls/package.order diff --git a/tests/resources/BouncingBalls/Tests/BouncingBall.mo b/tests/resources/BouncingBalls/Tests/BouncingBall.mo new file mode 100644 index 00000000..9441e4ea --- /dev/null +++ b/tests/resources/BouncingBalls/Tests/BouncingBall.mo @@ -0,0 +1,22 @@ +within BouncingBalls.Tests; + +model BouncingBall "The 'classic' bouncing ball model" + type Height=Real(unit="m"); + type Velocity=Real(unit="m/s"); + parameter Real e=0.8 "Coefficient of restitution"; + parameter Height h0=1.0 "Initial height"; + Height h "Height"; + Velocity v(start=0.0, fixed=true) "Velocity"; +initial equation + h = h0; +equation + v = der(h); + der(v) = -9.81; + when h<0 then + reinit(v, -e*pre(v)); + end when; +annotation( + experiment(StartTime = 0, StopTime = 10, Tolerance = 1e-06, Interval = 0.01), + __OpenModelica_commandLineOptions = "--matchingAlgorithm=PFPlusExt --indexReductionMethod=dynamicStateSelection -d=initialization,NLSanalyticJacobian", + __OpenModelica_simulationFlags(lv = "LOG_STDOUT,LOG_ASSERT,LOG_STATS", s = "dassl", variableFilter = ".*")); +end BouncingBall; diff --git a/tests/resources/BouncingBalls/Tests/StableBouncingBall.mo b/tests/resources/BouncingBalls/Tests/StableBouncingBall.mo new file mode 100644 index 00000000..d2cace57 --- /dev/null +++ b/tests/resources/BouncingBalls/Tests/StableBouncingBall.mo @@ -0,0 +1,27 @@ +within BouncingBalls.Tests; + +model StableBouncingBall + "The 'classic' bouncing ball model with numerical tolerances" + type Height=Real(unit="m"); + type Velocity=Real(unit="m/s"); + parameter Real e=0.8 "Coefficient of restitution"; + parameter Height h0=1.0 "Initial height"; + constant Height eps=1e-3 "Small height"; + Boolean done "Flag when to turn off gravity"; + Height h "Height"; + Velocity v(start=0.0, fixed=true) "Velocity"; +initial equation + h = h0; + done = false; +equation + v = der(h); + der(v) = if done then 0 else -9.81; + when {h<0,h<-eps} then + done = h<-eps; + reinit(v, -e*(if h<-eps then 0 else pre(v))); + end when; +annotation( + experiment(StartTime = 0, StopTime = 10, Tolerance = 1e-06, Interval = 0.01), + __OpenModelica_commandLineOptions = "--matchingAlgorithm=PFPlusExt --indexReductionMethod=dynamicStateSelection -d=initialization,NLSanalyticJacobian", + __OpenModelica_simulationFlags(lv = "LOG_STDOUT,LOG_ASSERT,LOG_STATS", s = "dassl", variableFilter = ".*")); +end StableBouncingBall; diff --git a/tests/resources/BouncingBalls/Tests/package.mo b/tests/resources/BouncingBalls/Tests/package.mo new file mode 100644 index 00000000..807f4a86 --- /dev/null +++ b/tests/resources/BouncingBalls/Tests/package.mo @@ -0,0 +1,8 @@ +within BouncingBalls; +package Tests "Tests" + extends Modelica.Icons.Package; + +annotation (Documentation(info=" +

This package contains testing models

+")); +end Tests; diff --git a/tests/resources/BouncingBalls/Tests/package.order b/tests/resources/BouncingBalls/Tests/package.order new file mode 100644 index 00000000..b3c895c2 --- /dev/null +++ b/tests/resources/BouncingBalls/Tests/package.order @@ -0,0 +1,2 @@ +BouncingBall +StableBouncingBall \ No newline at end of file diff --git a/tests/resources/BouncingBalls/package.mo b/tests/resources/BouncingBalls/package.mo new file mode 100644 index 00000000..3eace593 --- /dev/null +++ b/tests/resources/BouncingBalls/package.mo @@ -0,0 +1,18 @@ +within ; +package BouncingBalls "Test" + extends Modelica.Icons.Package; + + annotation(version="0.0.1", + versionDate = "2026-09-18", + uses(Modelica(version = "4.1.0")), + preferredView = "info", Documentation(info = " +

BouncingBalls

+ +

BouncingBall test package

+ +

Short description

+ +

BlaBla

+")); + +end BouncingBalls; diff --git a/tests/resources/BouncingBalls/package.order b/tests/resources/BouncingBalls/package.order new file mode 100644 index 00000000..c1543f72 --- /dev/null +++ b/tests/resources/BouncingBalls/package.order @@ -0,0 +1 @@ +Tests diff --git a/tests/test_ModelicaSystemOMC.py b/tests/test_ModelicaSystemOMC.py index a5e996c2..550a6c70 100644 --- a/tests/test_ModelicaSystemOMC.py +++ b/tests/test_ModelicaSystemOMC.py @@ -140,6 +140,23 @@ def test_relative_path(model_firstorder): model_file.unlink() # clean up the temporary file +def test_load_structured_package(): + # Regression test for loading a directory-based package via its package.mo: + # OMC requires the enclosing directory's name to match the package name, and + # the package spans a sibling file in a nested subdirectory + # (Tests/StableBouncingBall.mo). Both break if package.mo gets copied out of + # its directory instead of being loaded in place. + package_dir = pathlib.Path(__file__).parent / "resources" / "BouncingBalls" + + mod = OMPython.ModelicaSystemOMC() + mod.model( + model_file=package_dir / "package.mo", + model_name="BouncingBalls.Tests.StableBouncingBall", + libraries=["Modelica"], + ) + mod.simulate() + + def test_customBuildDirectory(tmp_path, model_firstorder): tmpdir = tmp_path / "tmpdir1" tmpdir.mkdir() From bb2696511199107eec64f41aaa0db9be3e5904fa Mon Sep 17 00:00:00 2001 From: arun3688 Date: Tue, 29 Sep 2026 15:48:41 +0200 Subject: [PATCH 4/5] fix package.order --- tests/resources/BouncingBalls/Tests/package.order | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/BouncingBalls/Tests/package.order b/tests/resources/BouncingBalls/Tests/package.order index b3c895c2..3d94c3ef 100644 --- a/tests/resources/BouncingBalls/Tests/package.order +++ b/tests/resources/BouncingBalls/Tests/package.order @@ -1,2 +1,2 @@ BouncingBall -StableBouncingBall \ No newline at end of file +StableBouncingBall From df9edfb77350a0e9b42768b08bca9256e5d1b543 Mon Sep 17 00:00:00 2001 From: arun3688 Date: Tue, 29 Sep 2026 15:59:28 +0200 Subject: [PATCH 5/5] fix CI test --- tests/test_linearization.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_linearization.py b/tests/test_linearization.py index 7070a45b..fdc8d97f 100644 --- a/tests/test_linearization.py +++ b/tests/test_linearization.py @@ -82,11 +82,10 @@ def test_getters(tmp_path): assert mod.getLinearInputs() == ["u1", "u2"] assert mod.getLinearStates() == ["omega", "phi"] assert mod.getLinearOutputs() == ["y1", "y2"] - assert np.isclose(A, [[0, param_g / param_l], [1, 0]]).all() - assert np.isclose(B, [[0, 0], [0, 1]]).all() - assert np.isclose(C, [[0.5, 1], [0, 1]]).all() - assert np.isclose(D, [[1, 0], [1, 0]]).all() - + assert np.isclose(A, [[0, param_g / param_l], [1, 0]], atol=1e-10, rtol=1e-9).all() + assert np.isclose(B, [[0, 0], [0, 1]], atol=1e-10, rtol=1e-9).all() + assert np.isclose(C, [[0.5, 1], [0, 1]], atol=1e-10, rtol=1e-9).all() + assert np.isclose(D, [[1, 0], [1, 0]], atol=1e-10, rtol=1e-9).all() # test LinearizationResult result = mod.linearize() assert result[0] == A