Skip to content

Commit 4e5ef23

Browse files
authored
Merge branch 'master' into linter_fix
2 parents 5cc2554 + 646728e commit 4e5ef23

11 files changed

Lines changed: 169 additions & 174 deletions

OMPython/ModelicaSystem.py

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import pathlib
1313
import queue
14+
import re
1415
import textwrap
1516
import threading
1617
from typing import Any, cast, Optional
@@ -19,8 +20,6 @@
1920

2021
import numpy as np
2122

22-
import re
23-
2423
from OMPython.OMCSession import (
2524
OMCSessionException,
2625
OMCSessionRunData,
@@ -334,14 +333,14 @@ def __init__(
334333
self._simulate_options: dict[str, str] = {}
335334
self._override_variables: dict[str, str] = {}
336335
self._simulate_options_override: dict[str, str] = {}
337-
self._linearization_options: dict[str, str | float] = {
338-
'startTime': 0.0,
339-
'stopTime': 1.0,
340-
'stepSize': 0.002,
341-
'tolerance': 1e-8,
336+
self._linearization_options: dict[str, str] = {
337+
'startTime': str(0.0),
338+
'stopTime': str(1.0),
339+
'stepSize': str(0.002),
340+
'tolerance': str(1e-8),
342341
}
343342
self._optimization_options = self._linearization_options | {
344-
'numberOfIntervals': 500,
343+
'numberOfIntervals': str(500),
345344
}
346345
self._linearized_inputs: list[str] = [] # linearization input list
347346
self._linearized_outputs: list[str] = [] # linearization output list
@@ -353,7 +352,8 @@ def __init__(
353352
self._session = OMCSessionLocal(omhome=omhome)
354353

355354
# get OpenModelica version
356-
self._version = self._session.sendExpression("getVersion()", parsed=True)
355+
version_str = self.sendExpression(expr="getVersion()")
356+
self._version = self._parse_om_version(version=version_str)
357357
# set commandLineOptions using default values or the user defined list
358358
if command_line_options is None:
359359
# set default command line options to improve the performance of linearization and to avoid recompilation if
@@ -952,7 +952,7 @@ def getSimulationOptions(
952952
def getLinearizationOptions(
953953
self,
954954
names: Optional[str | list[str]] = None,
955-
) -> dict[str, str | float] | list[str | float]:
955+
) -> dict[str, str] | list[str]:
956956
"""Get simulation options used for linearization.
957957
958958
Args:
@@ -966,17 +966,16 @@ def getLinearizationOptions(
966966
returned.
967967
If `names` is a list, a list with one value for each option name
968968
in names is returned: [option1_value, option2_value, ...].
969-
Some option values are returned as float when first initialized,
970-
but always as strings after setLinearizationOptions is used to
971-
change them.
969+
970+
The option values are always returned as strings.
972971
973972
Examples:
974973
>>> mod.getLinearizationOptions()
975-
{'startTime': 0.0, 'stopTime': 1.0, 'stepSize': 0.002, 'tolerance': 1e-08}
974+
{'startTime': '0.0', 'stopTime': '1.0', 'stepSize': '0.002', 'tolerance': '1e-08'}
976975
>>> mod.getLinearizationOptions("stopTime")
977-
[1.0]
976+
['1.0']
978977
>>> mod.getLinearizationOptions(["tolerance", "stopTime"])
979-
[1e-08, 1.0]
978+
['1e-08', '1.0']
980979
"""
981980
if names is None:
982981
return self._linearization_options
@@ -990,7 +989,7 @@ def getLinearizationOptions(
990989
def getOptimizationOptions(
991990
self,
992991
names: Optional[str | list[str]] = None,
993-
) -> dict[str, str | float] | list[str | float]:
992+
) -> dict[str, str] | list[str]:
994993
"""Get simulation options used for optimization.
995994
996995
Args:
@@ -1004,9 +1003,8 @@ def getOptimizationOptions(
10041003
returned.
10051004
If `names` is a list, a list with one value for each option name
10061005
in names is returned: [option1_value, option2_value, ...].
1007-
Some option values are returned as float when first initialized,
1008-
but always as strings after setOptimizationOptions is used to
1009-
change them.
1006+
1007+
The option values are always returned as string.
10101008
10111009
Examples:
10121010
>>> mod.getOptimizationOptions()
@@ -1025,13 +1023,47 @@ def getOptimizationOptions(
10251023

10261024
raise ModelicaSystemError("Unhandled input for getOptimizationOptions()")
10271025

1028-
def parse_om_version(self, version: str) -> tuple[int, int, int]:
1026+
def _parse_om_version(self, version: str) -> tuple[int, int, int]:
10291027
match = re.search(r"v?(\d+)\.(\d+)\.(\d+)", version)
10301028
if not match:
10311029
raise ValueError(f"Version not found in: {version}")
10321030
major, minor, patch = map(int, match.groups())
1031+
10331032
return major, minor, patch
10341033

1034+
def _process_override_data(
1035+
self,
1036+
om_cmd: ModelicaSystemCmd,
1037+
override_file: OMCPath,
1038+
override_var: dict[str, str],
1039+
override_sim: dict[str, str],
1040+
) -> None:
1041+
"""
1042+
Define the override parameters. As the definition of simulation specific override parameter changes with OM
1043+
1.26.0, version specific code is needed. Please keep in mind, that this will fail if OMC is not used to run the
1044+
model executable.
1045+
"""
1046+
if len(override_var) == 0 and len(override_sim) == 0:
1047+
return
1048+
1049+
override_content = ""
1050+
if override_var:
1051+
override_content += "\n".join([f"{key}={value}" for key, value in override_var.items()]) + "\n"
1052+
1053+
# simulation options are not read from override file from version >= 1.26.0,
1054+
# pass them to simulation executable directly as individual arguments
1055+
# see https://github.com/OpenModelica/OpenModelica/pull/14813
1056+
if override_sim:
1057+
if self._version >= (1, 26, 0):
1058+
for key, opt_value in override_sim.items():
1059+
om_cmd.arg_set(key=key, val=str(opt_value))
1060+
else:
1061+
override_content += "\n".join([f"{key}={value}" for key, value in override_sim.items()]) + "\n"
1062+
1063+
if override_content:
1064+
override_file.write_text(override_content)
1065+
om_cmd.arg_set(key="overrideFile", val=override_file.as_posix())
1066+
10351067
def simulate_cmd(
10361068
self,
10371069
result_file: OMCPath,
@@ -1075,29 +1107,12 @@ def simulate_cmd(
10751107
if simargs:
10761108
om_cmd.args_set(args=simargs)
10771109

1078-
if self._override_variables or self._simulate_options_override:
1079-
override_file = result_file.parent / f"{result_file.stem}_override.txt"
1080-
1081-
# simulation options are not read from override file from version >= 1.26.0,
1082-
# pass them to simulation executable directly as individual arguments
1083-
# see https://github.com/OpenModelica/OpenModelica/pull/14813
1084-
major, minor, patch = self.parse_om_version(self._version)
1085-
if (major, minor, patch) >= (1, 26, 0):
1086-
for key, opt_value in self._simulate_options_override.items():
1087-
om_cmd.arg_set(key=key, val=str(opt_value))
1088-
override_content = (
1089-
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
1090-
+ "\n"
1091-
)
1092-
else:
1093-
override_content = (
1094-
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
1095-
+ "\n".join([f"{key}={value}" for key, value in self._simulate_options_override.items()])
1096-
+ "\n"
1097-
)
1098-
1099-
override_file.write_text(override_content)
1100-
om_cmd.arg_set(key="overrideFile", val=override_file.as_posix())
1110+
self._process_override_data(
1111+
om_cmd=om_cmd,
1112+
override_file=result_file.parent / f"{result_file.stem}_override.txt",
1113+
override_var=self._override_variables,
1114+
override_sim=self._simulate_options_override,
1115+
)
11011116

11021117
if self._inputs: # if model has input quantities
11031118
for key, val in self._inputs.items():
@@ -1777,26 +1792,12 @@ def linearize(
17771792
modelname=self._model_name,
17781793
)
17791794

1780-
# See comment in simulate_cmd regarding override file and OM version
1781-
major, minor, patch = self.parse_om_version(self._version)
1782-
if (major, minor, patch) >= (1, 26, 0):
1783-
for key, opt_value in self._linearization_options.items():
1784-
om_cmd.arg_set(key=key, val=str(opt_value))
1785-
override_content = (
1786-
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
1787-
+ "\n"
1788-
)
1789-
else:
1790-
override_content = (
1791-
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
1792-
+ "\n".join([f"{key}={value}" for key, value in self._linearization_options.items()])
1793-
+ "\n"
1794-
)
1795-
1796-
override_file = self.getWorkDirectory() / f'{self._model_name}_override_linear.txt'
1797-
override_file.write_text(override_content)
1798-
1799-
om_cmd.arg_set(key="overrideFile", val=override_file.as_posix())
1795+
self._process_override_data(
1796+
om_cmd=om_cmd,
1797+
override_file=self.getWorkDirectory() / f'{self._model_name}_override_linear.txt',
1798+
override_var=self._override_variables,
1799+
override_sim=self._linearization_options,
1800+
)
18001801

18011802
if self._inputs:
18021803
for key, data in self._inputs.items():

OMPython/OMCSession.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -702,8 +702,8 @@ def __del__(self):
702702
if isinstance(self._omc_zmq, zmq.Socket):
703703
try:
704704
self.sendExpression("quit()")
705-
except OMCSessionException:
706-
pass
705+
except OMCSessionException as exc:
706+
logger.warning(f"Exception on sending 'quit()' to OMC: {exc}! Continue nevertheless ...")
707707
finally:
708708
self._omc_zmq = None
709709

@@ -720,7 +720,7 @@ def __del__(self):
720720
self._omc_process.wait(timeout=2.0)
721721
except subprocess.TimeoutExpired:
722722
if self._omc_process:
723-
logger.warning("OMC did not exit after being sent the quit() command; "
723+
logger.warning("OMC did not exit after being sent the 'quit()' command; "
724724
"killing the process with pid=%s", self._omc_process.pid)
725725
self._omc_process.kill()
726726
self._omc_process.wait()
@@ -1024,11 +1024,27 @@ def __init__(
10241024
super().__init__()
10251025
self._omc_port = omc_port
10261026

1027+
@staticmethod
1028+
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
1029+
"""
1030+
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
1031+
keep instances of over classes around.
1032+
"""
1033+
raise OMCSessionException("OMCSessionPort does not support run_model_executable()!")
1034+
1035+
def get_log(self) -> str:
1036+
"""
1037+
Get the log file content of the OMC session.
1038+
"""
1039+
log = f"No log available if OMC session is defined by port ({self.__class__.__name__})"
1040+
1041+
return log
1042+
10271043
def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData:
10281044
"""
10291045
Update the OMCSessionRunData object based on the selected OMCSession implementation.
10301046
"""
1031-
raise OMCSessionException("OMCSessionPort does not support omc_run_data_update()!")
1047+
raise OMCSessionException(f"({self.__class__.__name__}) does not support omc_run_data_update()!")
10321048

10331049

10341050
class OMCSessionLocal(OMCSession):

OMPython/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ModelicaSystemError,
1616
)
1717
from OMPython.OMCSession import (
18+
OMCSession,
1819
OMCSessionCmd,
1920
OMCSessionException,
2021
OMCSessionRunData,
@@ -34,6 +35,7 @@
3435
'ModelicaSystemDoE',
3536
'ModelicaSystemError',
3637

38+
'OMCSession',
3739
'OMCSessionCmd',
3840
'OMCSessionException',
3941
'OMCSessionRunData',

tests/test_ArrayDimension.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33

44
def test_ArrayDimension(tmp_path):
5-
omc = OMPython.OMCSessionZMQ()
5+
omcs = OMPython.OMCSessionLocal()
66

7-
omc.sendExpression(f'cd("{tmp_path.as_posix()}")')
7+
omcs.sendExpression(f'cd("{tmp_path.as_posix()}")')
88

9-
omc.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")')
10-
omc.sendExpression("getErrorString()")
9+
omcs.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")')
10+
omcs.sendExpression("getErrorString()")
1111

12-
result = omc.sendExpression("getComponents(A)")
12+
result = omcs.sendExpression("getComponents(A)")
1313
assert result[0][-1] == (6, 7), "array dimension does not match"
1414

15-
omc.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")')
16-
omc.sendExpression("getErrorString()")
15+
omcs.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")')
16+
omcs.sendExpression("getErrorString()")
1717

18-
result = omc.sendExpression("getComponents(A)")
18+
result = omcs.sendExpression("getComponents(A)")
1919
assert result[-1][-1] == ('y+1', 10), "array dimension does not match"

tests/test_FMIRegression.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77

88

99
def buildModelFMU(modelName):
10-
omc = OMPython.OMCSessionZMQ()
10+
omcs = OMPython.OMCSessionLocal()
1111

1212
tempdir = pathlib.Path(tempfile.mkdtemp())
1313
try:
14-
omc.sendExpression(f'cd("{tempdir.as_posix()}")')
14+
omcs.sendExpression(f'cd("{tempdir.as_posix()}")')
1515

16-
omc.sendExpression("loadModel(Modelica)")
17-
omc.sendExpression("getErrorString()")
16+
omcs.sendExpression("loadModel(Modelica)")
17+
omcs.sendExpression("getErrorString()")
1818

1919
fileNamePrefix = modelName.split(".")[-1]
2020
exp = f'buildModelFMU({modelName}, fileNamePrefix="{fileNamePrefix}")'
21-
fmu = omc.sendExpression(exp)
21+
fmu = omcs.sendExpression(exp)
2222
assert os.path.exists(fmu)
2323
finally:
24-
del omc
24+
del omcs
2525
shutil.rmtree(tempdir, ignore_errors=True)
2626

2727

tests/test_ModelicaSystem.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ def worker():
4747
)
4848
mod.simulate()
4949
mod.convertMo2Fmu(fmuType="me")
50+
5051
for _ in range(10):
5152
worker()
5253

5354

5455
def test_setParameters():
55-
omc = OMPython.OMCSessionZMQ()
56-
model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
57-
model_path = omc.omcpath(model_path_str)
56+
omcs = OMPython.OMCSessionLocal()
57+
model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
58+
model_path = omcs.omcpath(model_path_str)
5859
mod = OMPython.ModelicaSystem()
5960
mod.model(
6061
model_file=model_path / "BouncingBall.mo",
@@ -87,9 +88,9 @@ def test_setParameters():
8788

8889

8990
def test_setSimulationOptions():
90-
omc = OMPython.OMCSessionZMQ()
91-
model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
92-
model_path = omc.omcpath(model_path_str)
91+
omcs = OMPython.OMCSessionLocal()
92+
model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
93+
model_path = omcs.omcpath(model_path_str)
9394
mod = OMPython.ModelicaSystem()
9495
mod.model(
9596
model_file=model_path / "BouncingBall.mo",
@@ -155,11 +156,9 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
155156
@skip_on_windows
156157
@skip_python_older_312
157158
def test_getSolutions_docker(model_firstorder):
158-
omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
159-
omc = OMPython.OMCSessionZMQ(omc_process=omcp)
160-
159+
omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
161160
mod = OMPython.ModelicaSystem(
162-
session=omc.omc_process,
161+
session=omcs,
163162
)
164163
mod.model(
165164
model_file=model_firstorder,

0 commit comments

Comments
 (0)