Skip to content

Commit ca6039d

Browse files
syntronadeas31
andauthored
[ModelicaSystemCmd] update arg_set() (#341)
* [ModelicaSystemCmd] improve arg_set() * fix override values (string/bool/numbers) * [ModelicaSystemCmd] improve arg_set() - improve log message * [ModelicaSystemCmd] update handling of (override) args * sort args for a defined output * update type hints * [test_ModelicaSystemCmd] update test due to sort / test remove of override entry * [test_ModelicaSystemCmd] fix rebase fallout --------- Co-authored-by: Adeel Asghar <adeel.asghar@liu.se>
1 parent c44b088 commit ca6039d

File tree

2 files changed

+98
-29
lines changed

2 files changed

+98
-29
lines changed

OMPython/ModelicaSystem.py

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -118,35 +118,90 @@ def __init__(self, runpath: pathlib.Path, modelname: str, timeout: Optional[floa
118118
self._runpath = pathlib.Path(runpath).resolve().absolute()
119119
self._model_name = modelname
120120
self._timeout = timeout
121+
122+
# dictionaries of command line arguments for the model executable
121123
self._args: dict[str, str | None] = {}
124+
# 'override' argument needs special handling, as it is a dict on its own saved as dict elements following the
125+
# structure: 'key' => 'key=value'
122126
self._arg_override: dict[str, str] = {}
123127

124-
def arg_set(self, key: str, val: Optional[str | dict] = None) -> None:
128+
def arg_set(
129+
self,
130+
key: str,
131+
val: Optional[str | dict[str, Any] | numbers.Number] = None,
132+
) -> None:
125133
"""
126134
Set one argument for the executable model.
127135
128-
Parameters
129-
----------
130-
key : str
131-
val : str, None
136+
Args:
137+
key: identifier / argument name to be used for the call of the model executable.
138+
val: value for the given key; None for no value and for key == 'override' a dictionary can be used which
139+
indicates variables to override
132140
"""
141+
142+
def override2str(
143+
okey: str,
144+
oval: str | bool | numbers.Number,
145+
) -> str:
146+
"""
147+
Convert a value for 'override' to a string taking into account differences between Modelica and Python.
148+
"""
149+
# check oval for any string representations of numbers (or bool) and convert these to Python representations
150+
if isinstance(oval, str):
151+
try:
152+
oval_evaluated = ast.literal_eval(oval)
153+
if isinstance(oval_evaluated, (numbers.Number, bool)):
154+
oval = oval_evaluated
155+
except (ValueError, SyntaxError):
156+
pass
157+
158+
if isinstance(oval, str):
159+
oval_str = oval.strip()
160+
elif isinstance(oval, bool):
161+
oval_str = 'true' if oval else 'false'
162+
elif isinstance(oval, numbers.Number):
163+
oval_str = str(oval)
164+
else:
165+
raise ModelicaSystemError(f"Invalid value for override key {okey}: {type(oval)}")
166+
167+
return f"{okey}={oval_str}"
168+
133169
if not isinstance(key, str):
134170
raise ModelicaSystemError(f"Invalid argument key: {repr(key)} (type: {type(key)})")
135171
key = key.strip()
136-
if val is None:
172+
173+
if isinstance(val, dict):
174+
if key != 'override':
175+
raise ModelicaSystemError("Dictionary input only possible for key 'override'!")
176+
177+
for okey, oval in val.items():
178+
if not isinstance(okey, str):
179+
raise ModelicaSystemError("Invalid key for argument 'override': "
180+
f"{repr(okey)} (type: {type(okey)})")
181+
182+
if not isinstance(oval, (str, bool, numbers.Number, type(None))):
183+
raise ModelicaSystemError(f"Invalid input for 'override'.{repr(okey)}: "
184+
f"{repr(oval)} (type: {type(oval)})")
185+
186+
if okey in self._arg_override:
187+
if oval is None:
188+
logger.info(f"Remove model executable override argument: {repr(self._arg_override[okey])}")
189+
del self._arg_override[okey]
190+
continue
191+
192+
logger.info(f"Update model executable override argument: {repr(okey)} = {repr(oval)} "
193+
f"(was: {repr(self._arg_override[okey])})")
194+
195+
if oval is not None:
196+
self._arg_override[okey] = override2str(okey=okey, oval=oval)
197+
198+
argval = ','.join(sorted(self._arg_override.values()))
199+
elif val is None:
137200
argval = None
138201
elif isinstance(val, str):
139202
argval = val.strip()
140203
elif isinstance(val, numbers.Number):
141204
argval = str(val)
142-
elif key == 'override' and isinstance(val, dict):
143-
for okey in val:
144-
if not isinstance(okey, str) or not isinstance(val[okey], (str, numbers.Number)):
145-
raise ModelicaSystemError("Invalid argument for 'override': "
146-
f"{repr(okey)} = {repr(val[okey])}")
147-
self._arg_override[okey] = val[okey]
148-
149-
argval = ','.join([f"{okey}={str(self._arg_override[okey])}" for okey in self._arg_override])
150205
else:
151206
raise ModelicaSystemError(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})")
152207

@@ -155,7 +210,7 @@ def arg_set(self, key: str, val: Optional[str | dict] = None) -> None:
155210
f"(was: {repr(self._args[key])})")
156211
self._args[key] = argval
157212

158-
def arg_get(self, key: str) -> Optional[str | dict]:
213+
def arg_get(self, key: str) -> Optional[str | dict[str, str | bool | numbers.Number]]:
159214
"""
160215
Return the value for the given key
161216
"""
@@ -164,13 +219,12 @@ def arg_get(self, key: str) -> Optional[str | dict]:
164219

165220
return None
166221

167-
def args_set(self, args: dict[str, Optional[str | dict[str, str]]]) -> None:
222+
def args_set(
223+
self,
224+
args: dict[str, Optional[str | dict[str, Any] | numbers.Number]],
225+
) -> None:
168226
"""
169227
Define arguments for the model executable.
170-
171-
Parameters
172-
----------
173-
args : dict[str, Optional[str | dict[str, str]]]
174228
"""
175229
for arg in args:
176230
self.arg_set(key=arg, val=args[arg])
@@ -196,7 +250,7 @@ def get_cmd(self) -> list:
196250
path_exe = self.get_exe()
197251

198252
cmdl = [path_exe.as_posix()]
199-
for key in self._args:
253+
for key in sorted(self._args):
200254
if self._args[key] is None:
201255
cmdl.append(f"-{key}")
202256
else:
@@ -254,7 +308,7 @@ def run(self) -> int:
254308
return returncode
255309

256310
@staticmethod
257-
def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, str]]]:
311+
def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]:
258312
"""
259313
Parse a simflag definition; this is deprecated!
260314
@@ -263,7 +317,7 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, str]]]:
263317
warnings.warn("The argument 'simflags' is depreciated and will be removed in future versions; "
264318
"please use 'simargs' instead", DeprecationWarning, stacklevel=2)
265319

266-
simargs: dict[str, Optional[str | dict[str, str]]] = {}
320+
simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {}
267321

268322
args = [s for s in simflags.split(' ') if s]
269323
for arg in args:
@@ -940,7 +994,7 @@ def simulate_cmd(
940994
self,
941995
result_file: pathlib.Path,
942996
simflags: Optional[str] = None,
943-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
997+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
944998
timeout: Optional[float] = None,
945999
) -> ModelicaSystemCmd:
9461000
"""
@@ -1018,7 +1072,7 @@ def simulate(
10181072
self,
10191073
resultfile: Optional[str] = None,
10201074
simflags: Optional[str] = None,
1021-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
1075+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
10221076
timeout: Optional[float] = None,
10231077
) -> None:
10241078
"""Simulate the model according to simulation options.
@@ -1541,9 +1595,13 @@ def optimize(self) -> dict[str, Any]:
15411595

15421596
return optimizeResult
15431597

1544-
def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = None,
1545-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
1546-
timeout: Optional[float] = None) -> LinearizationResult:
1598+
def linearize(
1599+
self,
1600+
lintime: Optional[float] = None,
1601+
simflags: Optional[str] = None,
1602+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
1603+
timeout: Optional[float] = None,
1604+
) -> LinearizationResult:
15471605
"""Linearize the model according to linearization options.
15481606
15491607
See setLinearizationOptions.

tests/test_ModelicaSystemCmd.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ def test_simflags(mscmd_firstorder):
3535
assert mscmd.get_cmd() == [
3636
mscmd.get_exe().as_posix(),
3737
'-noEventEmit',
38-
'-override=b=2,a=1,x=3',
3938
'-noRestart',
39+
'-override=a=1,b=2,x=3',
40+
]
41+
42+
mscmd.args_set({
43+
"override": {'b': None},
44+
})
45+
46+
assert mscmd.get_cmd() == [
47+
mscmd.get_exe().as_posix(),
48+
'-noEventEmit',
49+
'-noRestart',
50+
'-override=a=1,x=3',
4051
]

0 commit comments

Comments
 (0)