Skip to content
25 changes: 20 additions & 5 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
import warnings
import xml.etree.ElementTree as ET

from OMPython.OMCSession import OMCSessionException, OMCSessionRunData, OMCSessionZMQ, OMCProcessLocal, OMCPath
from OMPython.OMCSession import (OMCSessionException, OMCSessionRunData, OMCSessionZMQ,
OMCProcess, OMCProcessLocal, OMCPath)

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

omc_run_data_updated = self._session.omc_run_data_update(omc_run_data=omc_run_data)
omc_run_data_updated = self._session.omc_run_data_update(
omc_run_data=omc_run_data,
)

return omc_run_data_updated

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

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

# check if the executable exists ...
om_cmd = ModelicaSystemCmd(
session=self._getconn,
runpath=self.getWorkDirectory(),
modelname=self._model_name,
timeout=5.0,
)
# ... by running it - output help for command help
om_cmd.arg_set(key="help", val="help")
cmd_definition = om_cmd.definition()
returncode = self._getconn.run_model_executable(cmd_run_data=cmd_definition)
if returncode != 0:
raise ModelicaSystemError("Model executable not working!")

xml_file = self._getconn.omcpath(buildModelResult[0]).parent / buildModelResult[1]
self._xmlparse(xml_file=xml_file)

Expand Down
62 changes: 48 additions & 14 deletions OMPython/OMCSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ class OMCPathCompatibilityWindows(pathlib.WindowsPath, OMCPathCompatibility):

@dataclasses.dataclass
class OMCSessionRunData:
# TODO: rename OMCExcecutableModelData
"""
Data class to store the command line data for running a model executable in the OMC environment.

Expand Down Expand Up @@ -655,8 +654,11 @@ def execute(self, command: str):
return self.sendExpression(command, parsed=False)

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

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

Expand Down Expand Up @@ -1113,7 +1115,23 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
"""
Update the OMCSessionRunData object based on the selected OMCProcess implementation.
"""
raise OMCSessionException("OMCProcessDocker* does not support omc_run_data_update()!")
omc_run_data_copy = dataclasses.replace(omc_run_data)

omc_run_data_copy.cmd_prefix = (
[
"docker", "exec",
"--user", str(self._getuid()),
"--workdir", omc_run_data_copy.cmd_path,
]
+ self._dockerExtraArgs
+ [self._dockerCid]
)

cmd_path = pathlib.PurePosixPath(omc_run_data_copy.cmd_path)
cmd_model_executable = cmd_path / omc_run_data_copy.cmd_model_name
omc_run_data_copy.cmd_model_executable = cmd_model_executable.as_posix()

return omc_run_data_copy


class OMCProcessDocker(OMCProcessDockerHelper):
Expand Down Expand Up @@ -1367,25 +1385,33 @@ def __init__(

super().__init__(timeout=timeout)

# get wsl base command
self._wsl_cmd = ['wsl']
if isinstance(wsl_distribution, str):
self._wsl_cmd += ['--distribution', wsl_distribution]
if isinstance(wsl_user, str):
self._wsl_cmd += ['--user', wsl_user]
self._wsl_cmd += ['--']

# where to find OpenModelica
self._wsl_omc = wsl_omc
# store WSL distribution and user
self._wsl_distribution = wsl_distribution
self._wsl_user = wsl_user
# start up omc executable, which is waiting for the ZMQ connection
self._omc_process = self._omc_process_get()
# connect to the running omc instance using ZMQ
self._omc_port = self._omc_port_get()

def _wsl_cmd(self, wsl_cwd: Optional[str] = None) -> list[str]:
# get wsl base command
wsl_cmd = ['wsl']
if isinstance(self._wsl_distribution, str):
wsl_cmd += ['--distribution', self._wsl_distribution]
if isinstance(self._wsl_user, str):
wsl_cmd += ['--user', self._wsl_user]
if isinstance(wsl_cwd, str):
wsl_cmd += ['--cd', wsl_cwd]
wsl_cmd += ['--']

return wsl_cmd

def _omc_process_get(self) -> subprocess.Popen:
my_env = os.environ.copy()

omc_command = self._wsl_cmd + [
omc_command = self._wsl_cmd() + [
self._wsl_omc,
"--locale=C",
"--interactive=zmq",
Expand All @@ -1408,7 +1434,7 @@ def _omc_port_get(self) -> str:
omc_portfile_path = self._get_portfile_path()
if omc_portfile_path is not None:
output = subprocess.check_output(
args=self._wsl_cmd + ["cat", omc_portfile_path.as_posix()],
args=self._wsl_cmd() + ["cat", omc_portfile_path.as_posix()],
stderr=subprocess.DEVNULL,
)
port = output.decode().strip()
Expand All @@ -1434,4 +1460,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
"""
Update the OMCSessionRunData object based on the selected OMCProcess implementation.
"""
raise OMCSessionException("OMCProcessWSL does not support omc_run_data_update()!")
omc_run_data_copy = dataclasses.replace(omc_run_data)

omc_run_data_copy.cmd_prefix = self._wsl_cmd(wsl_cwd=omc_run_data.cmd_path)

cmd_path = pathlib.PurePosixPath(omc_run_data_copy.cmd_path)
cmd_model_executable = cmd_path / omc_run_data_copy.cmd_model_name
omc_run_data_copy.cmd_model_executable = cmd_model_executable.as_posix()

return omc_run_data_copy
46 changes: 43 additions & 3 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 Down Expand Up @@ -113,9 +129,33 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
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
Expand Down