Skip to content

ModelicaSystemCMD - use OMCPath #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 74 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
8d19d26
[ModelicaSystem] add type hints for set*() functions and rename argum…
syntron Jun 23, 2025
9ac6a0e
[ModelicaSystem] add _prepare_inputdata()
syntron Jun 23, 2025
980ac33
[ModelicaSystem] update _set_method_helper()
syntron Jun 23, 2025
7c24f10
[ModelicaSystem] improve definition of _prepare_inputdata()
syntron Jun 23, 2025
b864a6c
[ModelicaSystem] rename _prepare_inputdata() => _prepare_input_data()
syntron Jun 23, 2025
3f180a6
[ModelicaSystem] update setInput()
syntron Jun 23, 2025
55051d9
update tests - use new dict based input for set*() methods
syntron Jun 23, 2025
3d8b83a
[ModelicaSystem] add type hint for return value of isParameterChangea…
syntron Jun 23, 2025
1b5917c
[ModelicaSystem] fix type hint for _prepare_input_data() - use dict[s…
syntron Jun 23, 2025
b5766f4
[ModelicaSystem] setInput() - handly input data as list of tuples
syntron Jun 23, 2025
9ef9d69
update tests - use new dict based input for setInput() method
syntron Jun 23, 2025
2fc67b9
[test_linearization] fix setInput() call
syntron Jun 24, 2025
bbd8740
[ModelicaSystem] simplify _set_method_helper()
syntron Jun 26, 2025
675a648
[ModelicaSystem] improve setInputs() - reduce spaces / cleanup
syntron Jun 26, 2025
9fcd39a
[ModelicaSystem] fix rebase fallout
syntron Jul 7, 2025
92651c5
[ModelicaSystem] fix rebase fallout 2
syntron Jul 7, 2025
71aa67b
[OMCPath] add class
syntron Jun 28, 2025
28585c7
[OMCPath] add implementation using OMC via sendExpression()
syntron Jun 27, 2025
3ce71b9
[OMCPath] add pytest (only docker at the moment)
syntron Jun 28, 2025
e452c98
[OMCPath] TODO items
syntron Jun 28, 2025
a262189
[test_OMCPath] mypy fix
syntron Jul 2, 2025
70a9c65
[test_OMCPath] fix end of file
syntron Jul 2, 2025
9240ff6
[test_OMCPath] define test using OMCSessionZMQ() locally
syntron Jul 3, 2025
7af3eb7
add TODO - need to check Python versions
syntron Jul 6, 2025
d5445c6
[test_OMCPath] activate docker based on test_docker
syntron Jul 6, 2025
4c9f0ca
[ModelicaSystem.linearize] do not execute python file but use ast to …
syntron Jul 9, 2025
191cd59
[ModelicaSystem.linearize] remove old check / use of file in current dir
syntron Jul 9, 2025
2802d9f
[ModelicaSystem.linearize] fix mypy
syntron Jul 10, 2025
d695899
[ModelicaSystem] remove _has_inputs - is defined by _inputs empty or not
syntron Jul 7, 2025
4fa05f1
[test_ModelicaSystem] cleanup
syntron Jul 7, 2025
ab0beae
[ModelicaSystem] add spelling fix (fox codespell)
syntron Jul 11, 2025
f10e4e0
[ModelicaSystem] update handling of xml_file
syntron Jul 9, 2025
4ed4bc9
[ModelicaSystem] replace ET.parse() with ET.ElementTree(ET.fromstring())
syntron Jul 9, 2025
111326f
[ModelicaSystem._xmlparse] mypy fixes & cleanup
syntron Jul 10, 2025
f958964
[ModelicaSystem] remove class variable _xml_file
syntron Jul 10, 2025
aa74b36
[OMCPath] add more functionality and docstrings
syntron Jul 11, 2025
89cf543
[OMCPath] remove TODO entries
syntron Jul 11, 2025
8e1745c
[OMCPath] define limited compatibility for Python < 3.12
syntron Jul 11, 2025
e793a93
[OMCSEssionZMQ] use OMCpath
syntron Jul 11, 2025
0ceb2bf
[OMCSessionZMQ] create a tempdir using omcpath_tempdir()
syntron Jul 11, 2025
43288dc
[OMCPath] fix mypy
syntron Jul 11, 2025
3029138
[OMCPath] add warning message for Python < 3.12
syntron Jul 11, 2025
a5f2ad6
[OMCPath] try to make mypy happy ...
syntron Jul 11, 2025
9416597
[test_OMCPath] only for Python >= 3.12
syntron Jul 11, 2025
c45b4f7
[test_OMCPath] update test
syntron Jul 11, 2025
30cb70e
[ModelicaSystem] simplify handling of inputs
syntron Jul 11, 2025
b822bf5
Merge branch 'ModelicaSystem_rewrite_set_functions' into ModelicaSyst…
syntron Jul 12, 2025
3ba84ab
[ModelicaSystem] fix mypy warning - value can have different types in…
syntron Jul 12, 2025
4b58c03
[OMCPath._omc_resolve] use sendExpression() with parsed=False
syntron Jul 12, 2025
c71b137
[test_OMCPath] cleanup; use the same code for local OMC and docker ba…
syntron Jul 12, 2025
a97bdd0
[test_OMCPath] define test for WSL
syntron Jul 12, 2025
2dadf3e
[test_OMCPath] use omcpath_tempdir() instead of hard-coded tempdir de…
syntron Jul 12, 2025
19c08c2
[OMCPath] spelling fix
syntron Jul 15, 2025
9206faf
[OMCPath] implementation version 3
syntron Jul 16, 2025
f5cdf33
[OMCSession*] fix flake8 (PyCharm likes the empty lines)
syntron Jul 16, 2025
668cd8b
[OMCSessionZMQ] more generic definiton for omcpath_tempdir()
syntron Jul 16, 2025
029fe19
[OMCPathCompatibility] mypy on github ...
syntron Jul 16, 2025
761f334
[OMCPathCompatibility] improve log messages
syntron Jul 16, 2025
77b3dd1
[test_OMCPath] update
syntron Jul 16, 2025
720f7d5
Merge branch 'ModelicaSystem_xml' into OMCPath_merge
syntron Jul 22, 2025
47be61b
Merge branch 'ModelicaSystem_linearize' into OMCPath_merge
syntron Jul 22, 2025
a28b908
[ModelicaSystem] use OMCPath for nearly all file system interactions
syntron Jul 11, 2025
c4ac63a
[test_ModelicaSystem] fix test_customBuildDirectory()
syntron Jul 11, 2025
3278b53
[ModelicaSystem] fix blank lines (flake8)
syntron Jul 22, 2025
c283158
[ModelicaSystemCmd] use OMCPath for file system interactions
syntron Jul 11, 2025
51c4700
[OMCProcessDockerHelper] implement omc_run_data_update() - UNTESTED!
syntron Jul 11, 2025
92cd9a1
[OMCProcessWSL] implement omc_run_data_update() - UNTESTED!
syntron Jul 11, 2025
d58ff43
[ModelicaSystemCmd] run session.omc_run_data_update() within run_def()
syntron Jul 12, 2025
4044a40
[OMCSessionRunData] define cmd_cwd and use it in OMCProcessLocal
syntron Jul 12, 2025
ba49be8
[OMCProcessDockerHelper] define work directory in docker
syntron Jul 12, 2025
53eead0
[OMCProcessWSL] define work directory for WSL
syntron Jul 12, 2025
8580b46
[OMCSessionRunData] update docstring and comments
syntron Jul 12, 2025
7885e25
[ModelicaSystem] allow for non local execution, i.e. docker or WSL
syntron Jul 12, 2025
f469523
[test_ModelicaSystem] include test of ModelicaSystem using docker
syntron Jul 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
675 changes: 409 additions & 266 deletions OMPython/ModelicaSystem.py

Large diffs are not rendered by default.

404 changes: 392 additions & 12 deletions OMPython/OMCSession.py

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions tests/test_FMIExport.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import OMPython
import shutil
import os
import pathlib


def test_CauerLowPassAnalog():
mod = OMPython.ModelicaSystem(modelName="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
lmodel=["Modelica"])
tmp = mod.getWorkDirectory()
# TODO [OMCPath]: need to work using OMCPath
tmp = pathlib.Path(mod.getWorkDirectory())
try:
fmu = mod.convertMo2Fmu(fileNamePrefix="CauerLowPassAnalog")
assert os.path.exists(fmu)
Expand All @@ -16,7 +18,8 @@ def test_CauerLowPassAnalog():

def test_DrumBoiler():
mod = OMPython.ModelicaSystem(modelName="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler", lmodel=["Modelica"])
tmp = mod.getWorkDirectory()
# TODO [OMCPath]: need to work using OMCPath
tmp = pathlib.Path(mod.getWorkDirectory())
try:
fmu = mod.convertMo2Fmu(fileNamePrefix="DrumBoiler")
assert os.path.exists(fmu)
Expand Down
91 changes: 67 additions & 24 deletions tests/test_ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@
import os
import pathlib
import pytest
import sys
import tempfile
import numpy as np

skip_on_windows = pytest.mark.skipif(
sys.platform.startswith("win"),
reason="OpenModelica Docker image is Linux-only; skipping on Windows.",
)

skip_python_older_312 = pytest.mark.skipif(
sys.version_info < (3, 12),
reason="OMCPath(non-local) only working for Python >= 3.12.",
)


@pytest.fixture
def model_firstorder(tmp_path):
mod = tmp_path / "M.mo"
mod.write_text("""model M
def model_firstorder_content():
return ("""model M
Real x(start = 1, fixed = true);
parameter Real a = -1;
equation
der(x) = x*a;
end M;
""")


@pytest.fixture
def model_firstorder(tmp_path, model_firstorder_content):
mod = tmp_path / "M.mo"
mod.write_text(model_firstorder_content)
return mod


Expand All @@ -35,8 +51,8 @@ def test_setParameters():
mod = OMPython.ModelicaSystem(model_path + "BouncingBall.mo", "BouncingBall")

# method 1
mod.setParameters("e=1.234")
mod.setParameters("g=321.0")
mod.setParameters(pvals={"e": 1.234})
mod.setParameters(pvals={"g": 321.0})
assert mod.getParameters("e") == ["1.234"]
assert mod.getParameters("g") == ["321.0"]
assert mod.getParameters() == {
Expand All @@ -47,7 +63,7 @@ def test_setParameters():
mod.getParameters("thisParameterDoesNotExist")

# method 2
mod.setParameters(["e=21.3", "g=0.12"])
mod.setParameters(pvals={"e": 21.3, "g": 0.12})
assert mod.getParameters() == {
"e": "21.3",
"g": "0.12",
Expand All @@ -64,8 +80,8 @@ def test_setSimulationOptions():
mod = OMPython.ModelicaSystem(fileName=model_path + "BouncingBall.mo", modelName="BouncingBall")

# method 1
mod.setSimulationOptions("stopTime=1.234")
mod.setSimulationOptions("tolerance=1.1e-08")
mod.setSimulationOptions(simOptions={"stopTime": 1.234})
mod.setSimulationOptions(simOptions={"tolerance": 1.1e-08})
assert mod.getSimulationOptions("stopTime") == ["1.234"]
assert mod.getSimulationOptions("tolerance") == ["1.1e-08"]
assert mod.getSimulationOptions(["tolerance", "stopTime"]) == ["1.1e-08", "1.234"]
Expand All @@ -77,7 +93,7 @@ def test_setSimulationOptions():
mod.getSimulationOptions("thisOptionDoesNotExist")

# method 2
mod.setSimulationOptions(["stopTime=2.1", "tolerance=1.2e-08"])
mod.setSimulationOptions(simOptions={"stopTime": 2.1, "tolerance": "1.2e-08"})
d = mod.getSimulationOptions()
assert d["stopTime"] == "2.1"
assert d["tolerance"] == "1.2e-08"
Expand Down Expand Up @@ -105,21 +121,46 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
tmpdir = tmp_path / "tmpdir1"
tmpdir.mkdir()
m = OMPython.ModelicaSystem(filePath, "M", customBuildDirectory=tmpdir)
assert m.getWorkDirectory().resolve() == tmpdir.resolve()
# TODO [OMCPath]: need to work using OMCPath
assert pathlib.Path(m.getWorkDirectory()).resolve() == tmpdir.resolve()
result_file = tmpdir / "a.mat"
assert not result_file.exists()
m.simulate(resultfile="a.mat")
assert result_file.is_file()


@skip_on_windows
@skip_python_older_312
def test_getSolutions_docker(model_firstorder_content):
omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
omc = OMPython.OMCSessionZMQ(omc_process=omcp)

modelpath = omc.omcpath_tempdir() / 'M.mo'
modelpath.write_text(model_firstorder_content)

file_path = pathlib.Path(modelpath)
mod = OMPython.ModelicaSystem(
fileName=file_path,
modelName="M",
omc_process=omc.omc_process,
)

_run_getSolutions(mod)


def test_getSolutions(model_firstorder):
filePath = model_firstorder.as_posix()
mod = OMPython.ModelicaSystem(filePath, "M")

_run_getSolutions(mod)


def _run_getSolutions(mod):
x0 = 1
a = -1
tau = -1 / a
stopTime = 5*tau
mod.setSimulationOptions([f"stopTime={stopTime}", "stepSize=0.1", "tolerance=1e-8"])
mod.setSimulationOptions(simOptions={"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8})
mod.simulate()

x = mod.getSolutions("x")
Expand Down Expand Up @@ -298,7 +339,7 @@ def test_getters(tmp_path):
x0 = 1.0
x_analytical = -b/a + (x0 + b/a) * np.exp(a * stopTime)
dx_analytical = (x0 + b/a) * a * np.exp(a * stopTime)
mod.setSimulationOptions(f"stopTime={stopTime}")
mod.setSimulationOptions(simOptions={"stopTime": stopTime})
mod.simulate()

# getOutputs after simulate()
Expand Down Expand Up @@ -327,7 +368,7 @@ def test_getters(tmp_path):
mod.getContinuous("a") # a is a parameter

with pytest.raises(OMPython.ModelicaSystemError):
mod.setSimulationOptions("thisOptionDoesNotExist=3")
mod.setSimulationOptions(simOptions={"thisOptionDoesNotExist": 3})


def test_simulate_inputs(tmp_path):
Expand All @@ -345,7 +386,7 @@ def test_simulate_inputs(tmp_path):
""")
mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="M_input")

mod.setSimulationOptions("stopTime=1.0")
mod.setSimulationOptions(simOptions={"stopTime": 1.0})

# integrate zero (no setInputs call) - it should default to None -> 0
assert mod.getInputs() == {
Expand All @@ -357,20 +398,24 @@ def test_simulate_inputs(tmp_path):
assert np.isclose(y[-1], 0.0)

# integrate a constant
mod.setInputs("u1=2.5")
mod.setInputs(name={"u1": 2.5})
assert mod.getInputs() == {
"u1": [
(0.0, 2.5),
(1.0, 2.5),
],
"u2": None,
# u2 is set due to the call to simulate() above
"u2": [
(0.0, 0.0),
(1.0, 0.0),
],
}
mod.simulate()
y = mod.getSolutions("y")[0]
assert np.isclose(y[-1], 2.5)

# now let's integrate the sum of two ramps
mod.setInputs("u1=[(0.0, 0.0), (0.5, 2), (1.0, 0)]")
mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 2), (1.0, 0)]})
assert mod.getInputs("u1") == [[
(0.0, 0.0),
(0.5, 2.0),
Expand All @@ -383,19 +428,17 @@ def test_simulate_inputs(tmp_path):
# let's try some edge cases
# unmatched startTime
with pytest.raises(OMPython.ModelicaSystemError):
mod.setInputs("u1=[(-0.5, 0.0), (1.0, 1)]")
mod.setInputs(name={"u1": [(-0.5, 0.0), (1.0, 1)]})
mod.simulate()
# unmatched stopTime
with pytest.raises(OMPython.ModelicaSystemError):
mod.setInputs("u1=[(0.0, 0.0), (0.5, 1)]")
mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 1)]})
mod.simulate()

# Let's use both inputs, but each one with different number of of
# Let's use both inputs, but each one with different number of
# samples. This has an effect when generating the csv file.
mod.setInputs([
"u1=[(0.0, 0), (1.0, 1)]",
"u2=[(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]",
])
mod.setInputs(name={"u1": [(0.0, 0), (1.0, 1)],
"u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]})
csv_file = mod._createCSVData()
assert pathlib.Path(csv_file).read_text() == """time,u1,u2,end
0.0,0.0,0.0,0
Expand Down
9 changes: 6 additions & 3 deletions tests/test_ModelicaSystemCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ def model_firstorder(tmp_path):
@pytest.fixture
def mscmd_firstorder(model_firstorder):
mod = OMPython.ModelicaSystem(fileName=model_firstorder.as_posix(), modelName="M")
mscmd = OMPython.ModelicaSystemCmd(runpath=mod.getWorkDirectory(), modelname=mod._model_name)
mscmd = OMPython.ModelicaSystemCmd(
session=mod._getconn,
runpath=mod.getWorkDirectory(),
modelname=mod._model_name,
)
return mscmd


Expand All @@ -32,8 +36,7 @@ def test_simflags(mscmd_firstorder):
with pytest.deprecated_call():
mscmd.args_set(args=mscmd.parse_simflags(simflags="-noEventEmit -noRestart -override=a=1,x=3"))

assert mscmd.get_cmd() == [
mscmd.get_exe().as_posix(),
assert mscmd.get_cmd_args() == [
'-noEventEmit',
'-override=b=2,a=1,x=3',
'-noRestart',
Expand Down
78 changes: 78 additions & 0 deletions tests/test_OMCPath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import sys
import OMPython
import pytest

skip_on_windows = pytest.mark.skipif(
sys.platform.startswith("win"),
reason="OpenModelica Docker image is Linux-only; skipping on Windows.",
)

skip_python_older_312 = pytest.mark.skipif(
sys.version_info < (3, 12),
reason="OMCPath(non-local) only working for Python >= 3.12.",
)


def test_OMCPath_OMCSessionZMQ():
om = OMPython.OMCSessionZMQ()

_run_OMCPath_checks(om)

del om


def test_OMCPath_OMCProcessLocal():
omp = OMPython.OMCProcessLocal()
om = OMPython.OMCSessionZMQ(omc_process=omp)

_run_OMCPath_checks(om)

del om


@skip_on_windows
@skip_python_older_312
def test_OMCPath_OMCProcessDocker():
omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
om = OMPython.OMCSessionZMQ(omc_process=omcp)
assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0"

_run_OMCPath_checks(om)

del omcp
del om


@pytest.mark.skip(reason="Not able to run WSL on github")
@skip_python_older_312
def test_OMCPath_OMCProcessWSL():
omcp = OMPython.OMCProcessWSL(
wsl_omc='omc',
wsl_user='omc',
timeout=30.0,
)
om = OMPython.OMCSessionZMQ(omc_process=omcp)

_run_OMCPath_checks(om)

del omcp
del om


def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ):
p1 = om.omcpath_tempdir()
p2 = p1 / 'test'
p2.mkdir()
assert p2.is_dir()
p3 = p2 / '..' / p2.name / 'test.txt'
assert p3.is_file() is False
assert p3.write_text('test')
assert p3.is_file()
assert p3.size() > 0
p3 = p3.resolve().absolute()
assert str(p3) == str((p2 / 'test.txt').resolve().absolute())
assert p3.read_text() == "test"
assert p3.is_file()
assert p3.parent.is_dir()
assert p3.unlink() is None
assert p3.is_file() is False
4 changes: 2 additions & 2 deletions tests/test_linearization.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ def test_getters(tmp_path):
assert "startTime" in d
assert "stopTime" in d
assert mod.getLinearizationOptions(["stopTime", "startTime"]) == [d["stopTime"], d["startTime"]]
mod.setLinearizationOptions("stopTime=0.02")
mod.setLinearizationOptions(linearizationOptions={"stopTime": 0.02})
assert mod.getLinearizationOptions("stopTime") == ["0.02"]

mod.setInputs(["u1=10", "u2=0"])
mod.setInputs(name={"u1": 10, "u2": 0})
[A, B, C, D] = mod.linearize()
g = float(mod.getParameters("g")[0])
l = float(mod.getParameters("l")[0])
Expand Down
8 changes: 5 additions & 3 deletions tests/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ def test_optimization_example(tmp_path):

mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="BangBang2021")

mod.setOptimizationOptions(["numberOfIntervals=16", "stopTime=1",
"stepSize=0.001", "tolerance=1e-8"])
mod.setOptimizationOptions(optimizationOptions={"numberOfIntervals": 16,
"stopTime": 1,
"stepSize": 0.001,
"tolerance": 1e-8})

# test the getter
assert mod.getOptimizationOptions()["stopTime"] == "1"
assert mod.getOptimizationOptions("stopTime") == ["1"]
assert mod.getOptimizationOptions(["tolerance", "stopTime"]) == ["1e-8", "1"]
assert mod.getOptimizationOptions(["tolerance", "stopTime"]) == ["1e-08", "1"]

r = mod.optimize()
# it is necessary to specify resultfile, otherwise it wouldn't find it.
Expand Down
Loading