Skip to content

Commit b9c04b2

Browse files
committed
[ModelicaSystemCmd] define and use it - needs cleanup!
1 parent 6547f14 commit b9c04b2

1 file changed

Lines changed: 124 additions & 88 deletions

File tree

OMPython/ModelicaSystem.py

Lines changed: 124 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import pathlib
4646
from dataclasses import dataclass
4747
from typing import Optional
48+
import warnings
4849

4950
from OMPython.OMCSession import OMCSessionZMQ, OMCSessionException
5051

@@ -109,14 +110,78 @@ def __getitem__(self, index: int):
109110

110111
class ModelicaSystemCmd:
111112

112-
def __init__(self, cmdpath: pathlib.Path, modelname: str):
113-
pass
113+
def __init__(self, cmdpath: pathlib.Path, modelname: str, timeout: Optional[int] = None):
114+
self.tempdir = cmdpath
115+
self.modelName = modelname
116+
self._exe_file = self.get_exe_file(tempdir=cmdpath, modelName=modelname)
117+
if not self._exe_file.exists():
118+
raise ModelicaSystemError(f"Application file path not found: {self._exe_file}")
119+
120+
self._timeout = timeout
121+
self._args = {}
114122

115123
def arg_set(self, key, val=None):
116-
pass
124+
key = key.strip()
125+
if val is not None:
126+
val = val.strip()
127+
self._args[key] = val
128+
129+
def args_set(self, args: dict):
130+
for arg in args:
131+
self.arg_set(key=arg, val=args[arg])
117132

118133
def run(self):
119-
pass
134+
135+
cmd = [self._exe_file.as_posix()] + [f"{key}={self._args[key]}" for key in self._args]
136+
self._run_cmd(cmd=cmd, timeout=self._timeout)
137+
138+
return True
139+
140+
def _run_cmd(self, cmd: list, timeout: Optional[int] = None):
141+
logger.debug("Run OM command %s in %s", cmd, self.tempdir)
142+
143+
if platform.system() == "Windows":
144+
dllPath = ""
145+
146+
# set the process environment from the generated .bat file in windows which should have all the dependencies
147+
batFilePath = pathlib.Path(self.tempdir) / f"{self.modelName}.bat"
148+
if not batFilePath.exists():
149+
ModelicaSystemError("Batch file (*.bat) does not exist " + str(batFilePath))
150+
151+
with open(batFilePath, 'r') as file:
152+
for line in file:
153+
match = re.match(r"^SET PATH=([^%]*)", line, re.IGNORECASE)
154+
if match:
155+
dllPath = match.group(1).strip(';') # Remove any trailing semicolons
156+
my_env = os.environ.copy()
157+
my_env["PATH"] = dllPath + os.pathsep + my_env["PATH"]
158+
else:
159+
# TODO: how to handle path to resources of external libraries for any system not Windows?
160+
my_env = None
161+
162+
try:
163+
cmdres = subprocess.run(cmd, capture_output=True, text=True, env=my_env, cwd=self.tempdir,
164+
timeout=timeout)
165+
stdout = cmdres.stdout.strip()
166+
stderr = cmdres.stderr.strip()
167+
168+
logger.debug("OM output for command %s:\n%s", cmd, stdout)
169+
170+
if cmdres.returncode != 0:
171+
raise ModelicaSystemError(f"Error running command {cmd}: nonzero return code")
172+
if stderr:
173+
raise ModelicaSystemError(f"Error running command {cmd}: {stderr}")
174+
except subprocess.TimeoutExpired:
175+
raise ModelicaSystemError(f"Timeout running command {repr(cmd)}")
176+
except Exception as ex:
177+
raise ModelicaSystemError(f"Error running command {cmd}") from ex
178+
179+
def get_exe_file(self, tempdir, modelName) -> pathlib.Path:
180+
"""Get path to model executable."""
181+
if platform.system() == "Windows":
182+
return pathlib.Path(tempdir) / f"{modelName}.exe"
183+
else:
184+
return pathlib.Path(tempdir) / modelName
120185

121186

122187
class ModelicaSystem:
@@ -284,45 +349,6 @@ def setTempDirectory(self, customBuildDirectory):
284349
def getWorkDirectory(self):
285350
return self.tempdir
286351

287-
def _run_cmd(self, cmd: list, timeout: Optional[int] = None):
288-
logger.debug("Run OM command %s in %s", cmd, self.tempdir)
289-
290-
if platform.system() == "Windows":
291-
dllPath = ""
292-
293-
# set the process environment from the generated .bat file in windows which should have all the dependencies
294-
batFilePath = pathlib.Path(self.tempdir) / f"{self.modelName}.bat"
295-
if not batFilePath.exists():
296-
ModelicaSystemError("Batch file (*.bat) does not exist " + str(batFilePath))
297-
298-
with open(batFilePath, 'r') as file:
299-
for line in file:
300-
match = re.match(r"^SET PATH=([^%]*)", line, re.IGNORECASE)
301-
if match:
302-
dllPath = match.group(1).strip(';') # Remove any trailing semicolons
303-
my_env = os.environ.copy()
304-
my_env["PATH"] = dllPath + os.pathsep + my_env["PATH"]
305-
else:
306-
# TODO: how to handle path to resources of external libraries for any system not Windows?
307-
my_env = None
308-
309-
try:
310-
cmdres = subprocess.run(cmd, capture_output=True, text=True, env=my_env, cwd=self.tempdir,
311-
timeout=timeout)
312-
stdout = cmdres.stdout.strip()
313-
stderr = cmdres.stderr.strip()
314-
315-
logger.debug("OM output for command %s:\n%s", cmd, stdout)
316-
317-
if cmdres.returncode != 0:
318-
raise ModelicaSystemError(f"Error running command {cmd}: nonzero return code")
319-
if stderr:
320-
raise ModelicaSystemError(f"Error running command {cmd}: {stderr}")
321-
except subprocess.TimeoutExpired:
322-
raise ModelicaSystemError(f"Timeout running command {repr(cmd)}")
323-
except Exception as ex:
324-
raise ModelicaSystemError(f"Error running command {cmd}") from ex
325-
326352
def buildModel(self, variableFilter=None):
327353
if variableFilter is not None:
328354
self.variableFilter = variableFilter
@@ -652,21 +678,20 @@ def getOptimizationOptions(self, names=None): # 10
652678

653679
raise ModelicaSystemError("Unhandled input for getOptimizationOptions()")
654680

655-
def get_exe_file(self) -> pathlib.Path:
656-
"""Get path to model executable."""
657-
if platform.system() == "Windows":
658-
return pathlib.Path(self.tempdir) / f"{self.modelName}.exe"
659-
else:
660-
return pathlib.Path(self.tempdir) / self.modelName
661-
662-
def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None): # 11
681+
def simulate(self, resultfile: Optional[str] = None, simflags: Optional[str] = None,
682+
simargs: Optional[dict[str, str | None]] = None,
683+
timeout: Optional[int] = None): # 11
663684
"""
664685
This method simulates model according to the simulation options.
665686
usage
666687
>>> simulate()
667688
>>> simulate(resultfile="a.mat")
668689
>>> simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=10") # set runtime simulation flags
690+
>>> simulate(simargs={"-noEventEmit": None, "-noRestart": None, "-override": "e=0.3,g=10"}) # using simargs
669691
"""
692+
693+
om_cmd = ModelicaSystemCmd(cmdpath=pathlib.Path(self.tempdir), modelname=self.modelName, timeout=timeout)
694+
670695
if resultfile is None:
671696
# default result file generated by OM
672697
self.resultfile = (pathlib.Path(self.tempdir) / f"{self.modelName}_res.mat").as_posix()
@@ -675,13 +700,26 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
675700
else:
676701
self.resultfile = (pathlib.Path(self.tempdir) / resultfile).as_posix()
677702
# always define the resultfile to use
678-
resultfileflag = " -r=" + self.resultfile
703+
om_cmd.arg_set(key="-r", val=self.resultfile)
679704

680705
# allow runtime simulation flags from user input
681-
if simflags is None:
682-
simflags = ""
683-
else:
684-
simflags = " " + simflags
706+
# TODO: merge into ModelicaSystemCmd?
707+
if simflags is not None:
708+
# add old style simulation arguments
709+
warnings.warn("The argument simflags is depreciated and will be removed in future versions; "
710+
"please use simargs instead", DeprecationWarning, stacklevel=1)
711+
712+
args = [s for s in simflags.split(' ') if s]
713+
for arg in args:
714+
parts = arg.split('=')
715+
if len(parts) == 1:
716+
val = None
717+
else:
718+
val = '='.join(parts[1:])
719+
om_cmd.arg_set(key=parts[0], val=val)
720+
721+
if simargs:
722+
om_cmd.args_set(args=simargs)
685723

686724
overrideFile = pathlib.Path(self.tempdir) / f"{self.modelName}_override.txt"
687725
if self.overridevariables or self.simoptionsoverride:
@@ -691,9 +729,8 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
691729
with open(overrideFile, "w") as file:
692730
for key, value in tmpdict.items():
693731
file.write(f"{key}={value}\n")
694-
override = " -overrideFile=" + overrideFile.as_posix()
695-
else:
696-
override = ""
732+
733+
om_cmd.arg_set(key="-overrideFile", val=overrideFile.as_posix())
697734

698735
if self.inputFlag: # if model has input quantities
699736
for i in self.inputlist:
@@ -708,18 +745,10 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
708745
if float(self.simulateOptions["stopTime"]) != val[-1][0]:
709746
raise ModelicaSystemError(f"stopTime not matched for Input {i}!")
710747
self.csvFile = self.createCSVData() # create csv file
711-
csvinput = " -csvInput=" + self.csvFile.as_posix()
712-
else:
713-
csvinput = ""
714748

715-
exe_file = self.get_exe_file()
716-
if not exe_file.exists():
717-
raise ModelicaSystemError(f"Application file path not found: {exe_file}")
749+
om_cmd.arg_set(key="-csvInput", val=self.csvFile.as_posix())
718750

719-
cmd = exe_file.as_posix() + override + csvinput + resultfileflag + simflags
720-
cmd = [s for s in cmd.split(' ') if s]
721-
self._run_cmd(cmd=cmd, timeout=timeout)
722-
self.simulationFlag = True
751+
self.simulationFlag = om_cmd.run()
723752

724753
# to extract simulation results
725754
def getSolutions(self, varList=None, resultfile=None): # 12
@@ -1034,13 +1063,15 @@ def optimize(self): # 21
10341063
return optimizeResult
10351064

10361065
def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = None,
1066+
simargs: Optional[dict[str, str | None]] = None,
10371067
timeout: Optional[int] = None) -> LinearizationResult:
10381068
"""Linearize the model according to linearOptions.
10391069
10401070
Args:
10411071
lintime: Override linearOptions["stopTime"] value.
10421072
simflags: A string of extra command line flags for the model
1043-
binary.
1073+
binary. - depreciated in favor of simargs
1074+
simargs: A dict with command line flags and possible options
10441075
timeout: Possible timeout for the execution of OM.
10451076
10461077
Returns:
@@ -1057,6 +1088,8 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
10571088
raise IOError("Linearization cannot be performed as the model is not build, "
10581089
"use ModelicaSystem() to build the model first")
10591090

1091+
om_cmd = ModelicaSystemCmd(cmdpath=pathlib.Path(self.tempdir), modelname=self.modelName, timeout=timeout)
1092+
10601093
overrideLinearFile = pathlib.Path(self.tempdir) / f'{self.modelName}_override_linear.txt'
10611094

10621095
with open(overrideLinearFile, "w") as file:
@@ -1065,8 +1098,7 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
10651098
for key, value in self.linearOptions.items():
10661099
file.write(f"{key}={value}\n")
10671100

1068-
override = " -overrideFile=" + overrideLinearFile.as_posix()
1069-
logger.debug(f"overwrite = {override}")
1101+
om_cmd.arg_set(key="-overrideFile", val=overrideLinearFile.as_posix())
10701102

10711103
if self.inputFlag:
10721104
nameVal = self.getInputs()
@@ -1077,26 +1109,30 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
10771109
if l[0] < float(self.simulateOptions["startTime"]):
10781110
raise ModelicaSystemError('Input time value is less than simulation startTime')
10791111
self.csvFile = self.createCSVData()
1080-
csvinput = " -csvInput=" + self.csvFile.as_posix()
1081-
else:
1082-
csvinput = ""
1112+
om_cmd.arg_set(key="-csvInput", val=self.csvFile.as_posix())
10831113

1084-
# prepare the linearization runtime command
1085-
exe_file = self.get_exe_file()
1114+
om_cmd.arg_set(key="-l", val=f"{lintime or self.linearOptions["stopTime"]}")
10861115

1087-
linruntime = f' -l={lintime or self.linearOptions["stopTime"]}'
1116+
# allow runtime simulation flags from user input
1117+
# TODO: merge into ModelicaSystemCmd?
1118+
if simflags is not None:
1119+
# add old style simulation arguments
1120+
warnings.warn("The argument simflags is depreciated and will be removed in future versions; "
1121+
"please use simargs instead", DeprecationWarning, stacklevel=1)
1122+
1123+
args = [s for s in simflags.split(' ') if s]
1124+
for arg in args:
1125+
parts = arg.split('=')
1126+
if len(parts) == 1:
1127+
val = None
1128+
else:
1129+
val = '='.join(parts[1:])
1130+
om_cmd.arg_set(key=parts[0], val=val)
10881131

1089-
if simflags is None:
1090-
simflags = ""
1091-
else:
1092-
simflags = " " + simflags
1132+
if simargs:
1133+
om_cmd.args_set(args=simargs)
10931134

1094-
if not exe_file.exists():
1095-
raise ModelicaSystemError(f"Application file path not found: {exe_file}")
1096-
else:
1097-
cmd = exe_file.as_posix() + linruntime + override + csvinput + simflags
1098-
cmd = [s for s in cmd.split(' ') if s]
1099-
self._run_cmd(cmd=cmd, timeout=timeout)
1135+
self.simulationFlag = om_cmd.run()
11001136

11011137
# code to get the matrix and linear inputs, outputs and states
11021138
linearFile = pathlib.Path(self.tempdir) / "linearized_model.py"

0 commit comments

Comments
 (0)