Skip to content

Commit f4f8406

Browse files
committed
[ModelicaSystemCmd] update handling of (override) args
* sort args for a defined output * update type hints
1 parent 4875e5b commit f4f8406

File tree

1 file changed

+66
-20
lines changed

1 file changed

+66
-20
lines changed

OMPython/ModelicaSystem.py

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
CONDITIONS OF OSMC-PL.
3333
"""
3434

35+
import ast
3536
import csv
3637
from dataclasses import dataclass
3738
import importlib
@@ -119,10 +120,18 @@ def __init__(self, runpath: pathlib.Path, modelname: str, timeout: Optional[floa
119120
self._runpath = pathlib.Path(runpath).resolve().absolute()
120121
self._model_name = modelname
121122
self._timeout = timeout
123+
124+
# dictionaries of command line arguments for the model executable
122125
self._args: dict[str, str | None] = {}
126+
# 'override' argument needs special handling, as it is a dict on its own saved as dict elements following the
127+
# structure: 'key' => 'key=value'
123128
self._arg_override: dict[str, str] = {}
124129

125-
def arg_set(self, key: str, val: Optional[str | dict] = None) -> None:
130+
def arg_set(
131+
self,
132+
key: str,
133+
val: Optional[str | dict[str, Any] | numbers.Number] = None,
134+
) -> None:
126135
"""
127136
Set one argument for the executable model.
128137
@@ -132,12 +141,24 @@ def arg_set(self, key: str, val: Optional[str | dict] = None) -> None:
132141
indicates variables to override
133142
"""
134143

135-
def override2str(okey: str, oval: Any) -> str:
144+
def override2str(
145+
okey: str,
146+
oval: str | bool | numbers.Number,
147+
) -> str:
136148
"""
137149
Convert a value for 'override' to a string taking into account differences between Modelica and Python.
138150
"""
151+
# check oval for any string representations of numbers (or bool) and convert these to Python representations
152+
if isinstance(oval, str):
153+
try:
154+
oval_evaluated = ast.literal_eval(oval)
155+
if isinstance(oval_evaluated, (numbers.Number, bool)):
156+
oval = oval_evaluated
157+
except (ValueError, SyntaxError):
158+
pass
159+
139160
if isinstance(oval, str):
140-
oval_str = f"\"{oval.strip()}\""
161+
oval_str = oval.strip()
141162
elif isinstance(oval, bool):
142163
oval_str = 'true' if oval else 'false'
143164
elif isinstance(oval, numbers.Number):
@@ -151,14 +172,32 @@ def override2str(okey: str, oval: Any) -> str:
151172
raise ModelicaSystemError(f"Invalid argument key: {repr(key)} (type: {type(key)})")
152173
key = key.strip()
153174

154-
if key == 'override' and isinstance(val, dict):
155-
for okey in val:
156-
if not isinstance(okey, str) or not isinstance(val[okey], (str, bool, numbers.Number)):
157-
raise ModelicaSystemError("Invalid argument for 'override': "
158-
f"{repr(okey)} = {repr(val[okey])}")
159-
self._arg_override[okey] = val[okey]
175+
if isinstance(val, dict):
176+
if key != 'override':
177+
raise ModelicaSystemError("Dictionary input only possible for key 'override'!")
178+
179+
for okey, oval in val.items():
180+
if not isinstance(okey, str):
181+
raise ModelicaSystemError("Invalid key for argument 'override': "
182+
f"{repr(okey)} (type: {type(okey)})")
183+
184+
if not isinstance(oval, (str, bool, numbers.Number, type(None))):
185+
raise ModelicaSystemError(f"Invalid input for 'override'.{repr(okey)}: "
186+
f"{repr(oval)} (type: {type(oval)})")
187+
188+
if okey in self._arg_override:
189+
if oval is None:
190+
logger.info(f"Remove model executable override argument: {repr(self._arg_override[okey])}")
191+
del self._arg_override[okey]
192+
continue
160193

161-
argval = ','.join([override2str(okey=okey, oval=oval) for okey, oval in self._arg_override.items()])
194+
logger.info(f"Update model executable override argument: {repr(okey)} = {repr(oval)} "
195+
f"(was: {repr(self._arg_override[okey])})")
196+
197+
if oval is not None:
198+
self._arg_override[okey] = override2str(okey=okey, oval=oval)
199+
200+
argval = ','.join(sorted(self._arg_override.values()))
162201
elif val is None:
163202
argval = None
164203
elif isinstance(val, str):
@@ -173,7 +212,7 @@ def override2str(okey: str, oval: Any) -> str:
173212
f"(was: {repr(self._args[key])})")
174213
self._args[key] = argval
175214

176-
def arg_get(self, key: str) -> Optional[str | dict]:
215+
def arg_get(self, key: str) -> Optional[str | dict[str, str | bool | numbers.Number]]:
177216
"""
178217
Return the value for the given key
179218
"""
@@ -182,7 +221,10 @@ def arg_get(self, key: str) -> Optional[str | dict]:
182221

183222
return None
184223

185-
def args_set(self, args: dict[str, Optional[str | dict[str, str]]]) -> None:
224+
def args_set(
225+
self,
226+
args: dict[str, Optional[str | dict[str, Any] | numbers.Number]],
227+
) -> None:
186228
"""
187229
Define arguments for the model executable.
188230
"""
@@ -210,7 +252,7 @@ def get_cmd(self) -> list:
210252
path_exe = self.get_exe()
211253

212254
cmdl = [path_exe.as_posix()]
213-
for key in self._args:
255+
for key in sorted(self._args):
214256
if self._args[key] is None:
215257
cmdl.append(f"-{key}")
216258
else:
@@ -268,7 +310,7 @@ def run(self) -> int:
268310
return returncode
269311

270312
@staticmethod
271-
def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, str]]]:
313+
def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]:
272314
"""
273315
Parse a simflag definition; this is deprecated!
274316
@@ -277,7 +319,7 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, str]]]:
277319
warnings.warn("The argument 'simflags' is depreciated and will be removed in future versions; "
278320
"please use 'simargs' instead", DeprecationWarning, stacklevel=2)
279321

280-
simargs: dict[str, Optional[str | dict[str, str]]] = {}
322+
simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {}
281323

282324
args = [s for s in simflags.split(' ') if s]
283325
for arg in args:
@@ -931,7 +973,7 @@ def simulate_cmd(
931973
self,
932974
result_file: pathlib.Path,
933975
simflags: Optional[str] = None,
934-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
976+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
935977
timeout: Optional[float] = None,
936978
) -> ModelicaSystemCmd:
937979
"""
@@ -1006,7 +1048,7 @@ def simulate(
10061048
self,
10071049
resultfile: Optional[str] = None,
10081050
simflags: Optional[str] = None,
1009-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
1051+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
10101052
timeout: Optional[float] = None,
10111053
) -> None:
10121054
"""Simulate the model according to simulation options.
@@ -1427,9 +1469,13 @@ def optimize(self) -> dict[str, Any]:
14271469

14281470
return optimizeResult
14291471

1430-
def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = None,
1431-
simargs: Optional[dict[str, Optional[str | dict[str, str]]]] = None,
1432-
timeout: Optional[float] = None) -> LinearizationResult:
1472+
def linearize(
1473+
self,
1474+
lintime: Optional[float] = None,
1475+
simflags: Optional[str] = None,
1476+
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
1477+
timeout: Optional[float] = None,
1478+
) -> LinearizationResult:
14331479
"""Linearize the model according to linearization options.
14341480
14351481
See setLinearizationOptions.

0 commit comments

Comments
 (0)