Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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