Skip to content

Commit 12f19e0

Browse files
authored
Merge branch 'master' into OMCSession_README
2 parents 319cf9d + f42fd6c commit 12f19e0

11 files changed

Lines changed: 181 additions & 182 deletions

OMPython/ModelicaSystem.py

Lines changed: 73 additions & 70 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,
@@ -263,8 +262,10 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
263262
264263
The return data can be used as input for self.args_set().
265264
"""
266-
warnings.warn("The argument 'simflags' is depreciated and will be removed in future versions; "
267-
"please use 'simargs' instead", DeprecationWarning, stacklevel=2)
265+
warnings.warn(message="The argument 'simflags' is depreciated and will be removed in future versions; "
266+
"please use 'simargs' instead",
267+
category=DeprecationWarning,
268+
stacklevel=2)
268269

269270
simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {}
270271

@@ -332,14 +333,14 @@ def __init__(
332333
self._simulate_options: dict[str, str] = {}
333334
self._override_variables: dict[str, str] = {}
334335
self._simulate_options_override: dict[str, str] = {}
335-
self._linearization_options: dict[str, str | float] = {
336-
'startTime': 0.0,
337-
'stopTime': 1.0,
338-
'stepSize': 0.002,
339-
'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),
340341
}
341342
self._optimization_options = self._linearization_options | {
342-
'numberOfIntervals': 500,
343+
'numberOfIntervals': str(500),
343344
}
344345
self._linearized_inputs: list[str] = [] # linearization input list
345346
self._linearized_outputs: list[str] = [] # linearization output list
@@ -351,7 +352,8 @@ def __init__(
351352
self._session = OMCSessionLocal(omhome=omhome)
352353

353354
# get OpenModelica version
354-
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)
355357
# set commandLineOptions using default values or the user defined list
356358
if command_line_options is None:
357359
# set default command line options to improve the performance of linearization and to avoid recompilation if
@@ -559,7 +561,7 @@ def buildModel(self, variableFilter: Optional[str] = None):
559561

560562
def sendExpression(self, expr: str, parsed: bool = True) -> Any:
561563
try:
562-
retval = self._session.sendExpression(expr, parsed)
564+
retval = self._session.sendExpression(command=expr, parsed=parsed)
563565
except OMCSessionException as ex:
564566
raise ModelicaSystemError(f"Error executing {repr(expr)}: {ex}") from ex
565567

@@ -950,7 +952,7 @@ def getSimulationOptions(
950952
def getLinearizationOptions(
951953
self,
952954
names: Optional[str | list[str]] = None,
953-
) -> dict[str, str | float] | list[str | float]:
955+
) -> dict[str, str] | list[str]:
954956
"""Get simulation options used for linearization.
955957
956958
Args:
@@ -964,17 +966,16 @@ def getLinearizationOptions(
964966
returned.
965967
If `names` is a list, a list with one value for each option name
966968
in names is returned: [option1_value, option2_value, ...].
967-
Some option values are returned as float when first initialized,
968-
but always as strings after setLinearizationOptions is used to
969-
change them.
969+
970+
The option values are always returned as strings.
970971
971972
Examples:
972973
>>> mod.getLinearizationOptions()
973-
{'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'}
974975
>>> mod.getLinearizationOptions("stopTime")
975-
[1.0]
976+
['1.0']
976977
>>> mod.getLinearizationOptions(["tolerance", "stopTime"])
977-
[1e-08, 1.0]
978+
['1e-08', '1.0']
978979
"""
979980
if names is None:
980981
return self._linearization_options
@@ -988,7 +989,7 @@ def getLinearizationOptions(
988989
def getOptimizationOptions(
989990
self,
990991
names: Optional[str | list[str]] = None,
991-
) -> dict[str, str | float] | list[str | float]:
992+
) -> dict[str, str] | list[str]:
992993
"""Get simulation options used for optimization.
993994
994995
Args:
@@ -1002,9 +1003,8 @@ def getOptimizationOptions(
10021003
returned.
10031004
If `names` is a list, a list with one value for each option name
10041005
in names is returned: [option1_value, option2_value, ...].
1005-
Some option values are returned as float when first initialized,
1006-
but always as strings after setOptimizationOptions is used to
1007-
change them.
1006+
1007+
The option values are always returned as string.
10081008
10091009
Examples:
10101010
>>> mod.getOptimizationOptions()
@@ -1023,13 +1023,47 @@ def getOptimizationOptions(
10231023

10241024
raise ModelicaSystemError("Unhandled input for getOptimizationOptions()")
10251025

1026-
def parse_om_version(self, version: str) -> tuple[int, int, int]:
1026+
def _parse_om_version(self, version: str) -> tuple[int, int, int]:
10271027
match = re.search(r"v?(\d+)\.(\d+)\.(\d+)", version)
10281028
if not match:
10291029
raise ValueError(f"Version not found in: {version}")
10301030
major, minor, patch = map(int, match.groups())
1031+
10311032
return major, minor, patch
10321033

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+
10331067
def simulate_cmd(
10341068
self,
10351069
result_file: OMCPath,
@@ -1073,29 +1107,12 @@ def simulate_cmd(
10731107
if simargs:
10741108
om_cmd.args_set(args=simargs)
10751109

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

11001117
if self._inputs: # if model has input quantities
11011118
for key, val in self._inputs.items():
@@ -1605,9 +1622,9 @@ def _createCSVData(self, csvfile: Optional[OMCPath] = None) -> OMCPath:
16051622
for signal_name, signal_values in inputs.items():
16061623
signal = np.array(signal_values)
16071624
interpolated_inputs[signal_name] = np.interp(
1608-
all_times,
1609-
signal[:, 0], # times
1610-
signal[:, 1], # values
1625+
x=all_times,
1626+
xp=signal[:, 0], # times
1627+
fp=signal[:, 1], # values
16111628
)
16121629

16131630
# Write CSV file
@@ -1775,26 +1792,12 @@ def linearize(
17751792
modelname=self._model_name,
17761793
)
17771794

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

17991802
if self._inputs:
18001803
for key, data in self._inputs.items():

OMPython/OMCSession.py

Lines changed: 24 additions & 6 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()
@@ -818,8 +818,10 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
818818
return returncode
819819

820820
def execute(self, command: str):
821-
warnings.warn("This function is depreciated and will be removed in future versions; "
822-
"please use sendExpression() instead", DeprecationWarning, stacklevel=2)
821+
warnings.warn(message="This function is depreciated and will be removed in future versions; "
822+
"please use sendExpression() instead",
823+
category=DeprecationWarning,
824+
stacklevel=2)
823825

824826
return self.sendExpression(command, parsed=False)
825827

@@ -1022,11 +1024,27 @@ def __init__(
10221024
super().__init__()
10231025
self._omc_port = omc_port
10241026

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+
10251043
def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunData:
10261044
"""
10271045
Update the OMCSessionRunData object based on the selected OMCSession implementation.
10281046
"""
1029-
raise OMCSessionException("OMCSessionPort does not support omc_run_data_update()!")
1047+
raise OMCSessionException(f"({self.__class__.__name__}) does not support omc_run_data_update()!")
10301048

10311049

10321050
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

0 commit comments

Comments
 (0)