Skip to content

Commit f986470

Browse files
authored
ModelicaSystemCMD - use OMCPath (#324)
* [ModelicaSystemCmd] use OMCPath for file system interactions * [OMCProcessDockerHelper] implement omc_run_data_update() * [OMCProcessWSL] implement omc_run_data_update() * [OMCProcessDockerHelper] define work directory in docker * [OMCProcessWSL] define work directory for WSL * [OMCSessionRunData] update docstring and comments * [test_ModelicaSystem] include test of ModelicaSystem using docker * [OMCSessionZMQ] no session for omc_run_data_update() * [OMCProcess] remove session argument for OMCProcess.omc_run_data_update() * no dependency loop OMCsessionZMQ => OMCProcess* => OMCSessionZMQ * check if model executable exists will be handled via ModelicaSystemCmd * [ModelicaSystem.buildModel] check if executable exists via ModelicaSystemCmd * [ModelicaSystem*] rebase cleanup * [ModelicaSystem] add missing import
1 parent 70cb446 commit f986470

File tree

3 files changed

+111
-22
lines changed

3 files changed

+111
-22
lines changed

OMPython/ModelicaSystem.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
import warnings
4444
import xml.etree.ElementTree as ET
4545

46-
from OMPython.OMCSession import OMCSessionException, OMCSessionRunData, OMCSessionZMQ, OMCProcessLocal, OMCPath
46+
from OMPython.OMCSession import (OMCSessionException, OMCSessionRunData, OMCSessionZMQ,
47+
OMCProcess, OMCProcessLocal, OMCPath)
4748

4849
# define logger using the current module name as ID
4950
logger = logging.getLogger(__name__)
@@ -262,7 +263,9 @@ def definition(self) -> OMCSessionRunData:
262263
cmd_timeout=self._timeout,
263264
)
264265

265-
omc_run_data_updated = self._session.omc_run_data_update(omc_run_data=omc_run_data)
266+
omc_run_data_updated = self._session.omc_run_data_update(
267+
omc_run_data=omc_run_data,
268+
)
266269

267270
return omc_run_data_updated
268271

@@ -315,7 +318,7 @@ def __init__(
315318
variableFilter: Optional[str] = None,
316319
customBuildDirectory: Optional[str | os.PathLike] = None,
317320
omhome: Optional[str] = None,
318-
omc_process: Optional[OMCProcessLocal] = None,
321+
omc_process: Optional[OMCProcess] = None,
319322
build: bool = True,
320323
) -> None:
321324
"""Initialize, load and build a model.
@@ -380,8 +383,6 @@ def __init__(
380383
self._linearized_states: list[str] = [] # linearization states list
381384

382385
if omc_process is not None:
383-
if not isinstance(omc_process, OMCProcessLocal):
384-
raise ModelicaSystemError("Invalid (local) omc process definition provided!")
385386
self._getconn = OMCSessionZMQ(omc_process=omc_process)
386387
else:
387388
self._getconn = OMCSessionZMQ(omhome=omhome)
@@ -515,6 +516,20 @@ def buildModel(self, variableFilter: Optional[str] = None):
515516
buildModelResult = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter)
516517
logger.debug("OM model build result: %s", buildModelResult)
517518

519+
# check if the executable exists ...
520+
om_cmd = ModelicaSystemCmd(
521+
session=self._getconn,
522+
runpath=self.getWorkDirectory(),
523+
modelname=self._model_name,
524+
timeout=5.0,
525+
)
526+
# ... by running it - output help for command help
527+
om_cmd.arg_set(key="help", val="help")
528+
cmd_definition = om_cmd.definition()
529+
returncode = self._getconn.run_model_executable(cmd_run_data=cmd_definition)
530+
if returncode != 0:
531+
raise ModelicaSystemError("Model executable not working!")
532+
518533
xml_file = self._getconn.omcpath(buildModelResult[0]).parent / buildModelResult[1]
519534
self._xmlparse(xml_file=xml_file)
520535

OMPython/OMCSession.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,6 @@ class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility):
461461

462462
@dataclasses.dataclass
463463
class OMCSessionRunData:
464-
# TODO: rename OMCExcecutableModelData
465464
"""
466465
Data class to store the command line data for running a model executable in the OMC environment.
467466
@@ -655,8 +654,11 @@ def execute(self, command: str):
655654
return self.sendExpression(command, parsed=False)
656655

657656
def sendExpression(self, command: str, parsed: bool = True) -> Any:
657+
"""
658+
Send an expression to the OMC server and return the result.
659+
"""
658660
if self.omc_zmq is None:
659-
raise OMCSessionException("No OMC running. Create a new instance of OMCSessionZMQ!")
661+
raise OMCSessionException("No OMC running. Create a new instance of OMCProcess!")
660662

661663
logger.debug("sendExpression(%r, parsed=%r)", command, parsed)
662664

@@ -1113,7 +1115,23 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
11131115
"""
11141116
Update the OMCSessionRunData object based on the selected OMCProcess implementation.
11151117
"""
1116-
raise OMCSessionException("OMCProcessDocker* does not support omc_run_data_update()!")
1118+
omc_run_data_copy = dataclasses.replace(omc_run_data)
1119+
1120+
omc_run_data_copy.cmd_prefix = (
1121+
[
1122+
"docker", "exec",
1123+
"--user", str(self._getuid()),
1124+
"--workdir", omc_run_data_copy.cmd_path,
1125+
]
1126+
+ self._dockerExtraArgs
1127+
+ [self._dockerCid]
1128+
)
1129+
1130+
cmd_path = pathlib.PurePosixPath(omc_run_data_copy.cmd_path)
1131+
cmd_model_executable = cmd_path / omc_run_data_copy.cmd_model_name
1132+
omc_run_data_copy.cmd_model_executable = cmd_model_executable.as_posix()
1133+
1134+
return omc_run_data_copy
11171135

11181136

11191137
class OMCProcessDocker(OMCProcessDockerHelper):
@@ -1367,25 +1385,33 @@ def __init__(
13671385

13681386
super().__init__(timeout=timeout)
13691387

1370-
# get wsl base command
1371-
self._wsl_cmd = ['wsl']
1372-
if isinstance(wsl_distribution, str):
1373-
self._wsl_cmd += ['--distribution', wsl_distribution]
1374-
if isinstance(wsl_user, str):
1375-
self._wsl_cmd += ['--user', wsl_user]
1376-
self._wsl_cmd += ['--']
1377-
13781388
# where to find OpenModelica
13791389
self._wsl_omc = wsl_omc
1390+
# store WSL distribution and user
1391+
self._wsl_distribution = wsl_distribution
1392+
self._wsl_user = wsl_user
13801393
# start up omc executable, which is waiting for the ZMQ connection
13811394
self._omc_process = self._omc_process_get()
13821395
# connect to the running omc instance using ZMQ
13831396
self._omc_port = self._omc_port_get()
13841397

1398+
def _wsl_cmd(self, wsl_cwd: Optional[str] = None) -> list[str]:
1399+
# get wsl base command
1400+
wsl_cmd = ['wsl']
1401+
if isinstance(self._wsl_distribution, str):
1402+
wsl_cmd += ['--distribution', self._wsl_distribution]
1403+
if isinstance(self._wsl_user, str):
1404+
wsl_cmd += ['--user', self._wsl_user]
1405+
if isinstance(wsl_cwd, str):
1406+
wsl_cmd += ['--cd', wsl_cwd]
1407+
wsl_cmd += ['--']
1408+
1409+
return wsl_cmd
1410+
13851411
def _omc_process_get(self) -> subprocess.Popen:
13861412
my_env = os.environ.copy()
13871413

1388-
omc_command = self._wsl_cmd + [
1414+
omc_command = self._wsl_cmd() + [
13891415
self._wsl_omc,
13901416
"--locale=C",
13911417
"--interactive=zmq",
@@ -1408,7 +1434,7 @@ def _omc_port_get(self) -> str:
14081434
omc_portfile_path = self._get_portfile_path()
14091435
if omc_portfile_path is not None:
14101436
output = subprocess.check_output(
1411-
args=self._wsl_cmd + ["cat", omc_portfile_path.as_posix()],
1437+
args=self._wsl_cmd() + ["cat", omc_portfile_path.as_posix()],
14121438
stderr=subprocess.DEVNULL,
14131439
)
14141440
port = output.decode().strip()
@@ -1434,4 +1460,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
14341460
"""
14351461
Update the OMCSessionRunData object based on the selected OMCProcess implementation.
14361462
"""
1437-
raise OMCSessionException("OMCProcessWSL does not support omc_run_data_update()!")
1463+
omc_run_data_copy = dataclasses.replace(omc_run_data)
1464+
1465+
omc_run_data_copy.cmd_prefix = self._wsl_cmd(wsl_cwd=omc_run_data.cmd_path)
1466+
1467+
cmd_path = pathlib.PurePosixPath(omc_run_data_copy.cmd_path)
1468+
cmd_model_executable = cmd_path / omc_run_data_copy.cmd_model_name
1469+
omc_run_data_copy.cmd_model_executable = cmd_model_executable.as_posix()
1470+
1471+
return omc_run_data_copy

tests/test_ModelicaSystem.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,36 @@
22
import os
33
import pathlib
44
import pytest
5+
import sys
56
import tempfile
67
import numpy as np
78

9+
skip_on_windows = pytest.mark.skipif(
10+
sys.platform.startswith("win"),
11+
reason="OpenModelica Docker image is Linux-only; skipping on Windows.",
12+
)
13+
14+
skip_python_older_312 = pytest.mark.skipif(
15+
sys.version_info < (3, 12),
16+
reason="OMCPath(non-local) only working for Python >= 3.12.",
17+
)
18+
819

920
@pytest.fixture
10-
def model_firstorder(tmp_path):
11-
mod = tmp_path / "M.mo"
12-
mod.write_text("""model M
21+
def model_firstorder_content():
22+
return ("""model M
1323
Real x(start = 1, fixed = true);
1424
parameter Real a = -1;
1525
equation
1626
der(x) = x*a;
1727
end M;
1828
""")
29+
30+
31+
@pytest.fixture
32+
def model_firstorder(tmp_path, model_firstorder_content):
33+
mod = tmp_path / "M.mo"
34+
mod.write_text(model_firstorder_content)
1935
return mod
2036

2137

@@ -113,9 +129,33 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
113129
assert result_file.is_file()
114130

115131

132+
@skip_on_windows
133+
@skip_python_older_312
134+
def test_getSolutions_docker(model_firstorder_content):
135+
omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
136+
omc = OMPython.OMCSessionZMQ(omc_process=omcp)
137+
138+
modelpath = omc.omcpath_tempdir() / 'M.mo'
139+
modelpath.write_text(model_firstorder_content)
140+
141+
file_path = pathlib.Path(modelpath)
142+
mod = OMPython.ModelicaSystem(
143+
fileName=file_path,
144+
modelName="M",
145+
omc_process=omc.omc_process,
146+
)
147+
148+
_run_getSolutions(mod)
149+
150+
116151
def test_getSolutions(model_firstorder):
117152
filePath = model_firstorder.as_posix()
118153
mod = OMPython.ModelicaSystem(filePath, "M")
154+
155+
_run_getSolutions(mod)
156+
157+
158+
def _run_getSolutions(mod):
119159
x0 = 1
120160
a = -1
121161
tau = -1 / a

0 commit comments

Comments
 (0)