Skip to content

Commit 2b1eb42

Browse files
committed
[ModelicaSystemCmd] define and use it - needs cleanup!
1 parent 51ac4f8 commit 2b1eb42

File tree

1 file changed

+124
-88
lines changed

1 file changed

+124
-88
lines changed

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}: return code = {cmdres.returncode}")
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:
@@ -289,45 +354,6 @@ def setTempDirectory(self, customBuildDirectory):
289354
def getWorkDirectory(self):
290355
return self.tempdir
291356

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

658684
raise ModelicaSystemError("Unhandled input for getOptimizationOptions()")
659685

660-
def get_exe_file(self) -> pathlib.Path:
661-
"""Get path to model executable."""
662-
if platform.system() == "Windows":
663-
return pathlib.Path(self.tempdir) / f"{self.modelName}.exe"
664-
else:
665-
return pathlib.Path(self.tempdir) / self.modelName
666-
667-
def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None): # 11
686+
def simulate(self, resultfile: Optional[str] = None, simflags: Optional[str] = None,
687+
simargs: Optional[dict[str, str | None]] = None,
688+
timeout: Optional[int] = None): # 11
668689
"""
669690
This method simulates model according to the simulation options.
670691
usage
671692
>>> simulate()
672693
>>> simulate(resultfile="a.mat")
673694
>>> simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=10") # set runtime simulation flags
695+
>>> simulate(simargs={"-noEventEmit": None, "-noRestart": None, "-override": "e=0.3,g=10"}) # using simargs
674696
"""
697+
698+
om_cmd = ModelicaSystemCmd(cmdpath=pathlib.Path(self.tempdir), modelname=self.modelName, timeout=timeout)
699+
675700
if resultfile is None:
676701
# default result file generated by OM
677702
self.resultfile = (pathlib.Path(self.tempdir) / f"{self.modelName}_res.mat").as_posix()
@@ -680,13 +705,26 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
680705
else:
681706
self.resultfile = (pathlib.Path(self.tempdir) / resultfile).as_posix()
682707
# always define the resultfile to use
683-
resultfileflag = " -r=" + self.resultfile
708+
om_cmd.arg_set(key="-r", val=self.resultfile)
684709

685710
# allow runtime simulation flags from user input
686-
if simflags is None:
687-
simflags = ""
688-
else:
689-
simflags = " " + simflags
711+
# TODO: merge into ModelicaSystemCmd?
712+
if simflags is not None:
713+
# add old style simulation arguments
714+
warnings.warn("The argument simflags is depreciated and will be removed in future versions; "
715+
"please use simargs instead", DeprecationWarning, stacklevel=1)
716+
717+
args = [s for s in simflags.split(' ') if s]
718+
for arg in args:
719+
parts = arg.split('=')
720+
if len(parts) == 1:
721+
val = None
722+
else:
723+
val = '='.join(parts[1:])
724+
om_cmd.arg_set(key=parts[0], val=val)
725+
726+
if simargs:
727+
om_cmd.args_set(args=simargs)
690728

691729
overrideFile = pathlib.Path(self.tempdir) / f"{self.modelName}_override.txt"
692730
if self.overridevariables or self.simoptionsoverride:
@@ -696,9 +734,8 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
696734
with open(overrideFile, "w") as file:
697735
for key, value in tmpdict.items():
698736
file.write(f"{key}={value}\n")
699-
override = " -overrideFile=" + overrideFile.as_posix()
700-
else:
701-
override = ""
737+
738+
om_cmd.arg_set(key="-overrideFile", val=overrideFile.as_posix())
702739

703740
if self.inputFlag: # if model has input quantities
704741
for i in self.inputlist:
@@ -713,18 +750,10 @@ def simulate(self, resultfile=None, simflags=None, timeout: Optional[int] = None
713750
if float(self.simulateOptions["stopTime"]) != val[-1][0]:
714751
raise ModelicaSystemError(f"stopTime not matched for Input {i}!")
715752
self.csvFile = self.createCSVData() # create csv file
716-
csvinput = " -csvInput=" + self.csvFile.as_posix()
717-
else:
718-
csvinput = ""
719753

720-
exe_file = self.get_exe_file()
721-
if not exe_file.exists():
722-
raise ModelicaSystemError(f"Application file path not found: {exe_file}")
754+
om_cmd.arg_set(key="-csvInput", val=self.csvFile.as_posix())
723755

724-
cmd = exe_file.as_posix() + override + csvinput + resultfileflag + simflags
725-
cmd = [s for s in cmd.split(' ') if s]
726-
self._run_cmd(cmd=cmd, timeout=timeout)
727-
self.simulationFlag = True
756+
self.simulationFlag = om_cmd.run()
728757

729758
# to extract simulation results
730759
def getSolutions(self, varList=None, resultfile=None): # 12
@@ -1039,13 +1068,15 @@ def optimize(self): # 21
10391068
return optimizeResult
10401069

10411070
def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = None,
1071+
simargs: Optional[dict[str, str | None]] = None,
10421072
timeout: Optional[int] = None) -> LinearizationResult:
10431073
"""Linearize the model according to linearOptions.
10441074
10451075
Args:
10461076
lintime: Override linearOptions["stopTime"] value.
10471077
simflags: A string of extra command line flags for the model
1048-
binary.
1078+
binary. - depreciated in favor of simargs
1079+
simargs: A dict with command line flags and possible options
10491080
timeout: Possible timeout for the execution of OM.
10501081
10511082
Returns:
@@ -1062,6 +1093,8 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
10621093
raise IOError("Linearization cannot be performed as the model is not build, "
10631094
"use ModelicaSystem() to build the model first")
10641095

1096+
om_cmd = ModelicaSystemCmd(cmdpath=pathlib.Path(self.tempdir), modelname=self.modelName, timeout=timeout)
1097+
10651098
overrideLinearFile = pathlib.Path(self.tempdir) / f'{self.modelName}_override_linear.txt'
10661099

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

1073-
override = " -overrideFile=" + overrideLinearFile.as_posix()
1074-
logger.debug(f"overwrite = {override}")
1106+
om_cmd.arg_set(key="-overrideFile", val=overrideLinearFile.as_posix())
10751107

10761108
if self.inputFlag:
10771109
nameVal = self.getInputs()
@@ -1082,26 +1114,30 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
10821114
if l[0] < float(self.simulateOptions["startTime"]):
10831115
raise ModelicaSystemError('Input time value is less than simulation startTime')
10841116
self.csvFile = self.createCSVData()
1085-
csvinput = " -csvInput=" + self.csvFile.as_posix()
1086-
else:
1087-
csvinput = ""
1117+
om_cmd.arg_set(key="-csvInput", val=self.csvFile.as_posix())
10881118

1089-
# prepare the linearization runtime command
1090-
exe_file = self.get_exe_file()
1119+
om_cmd.arg_set(key="-l", val=f"{lintime or self.linearOptions["stopTime"]}")
10911120

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

1094-
if simflags is None:
1095-
simflags = ""
1096-
else:
1097-
simflags = " " + simflags
1137+
if simargs:
1138+
om_cmd.args_set(args=simargs)
10981139

1099-
if not exe_file.exists():
1100-
raise ModelicaSystemError(f"Application file path not found: {exe_file}")
1101-
else:
1102-
cmd = exe_file.as_posix() + linruntime + override + csvinput + simflags
1103-
cmd = [s for s in cmd.split(' ') if s]
1104-
self._run_cmd(cmd=cmd, timeout=timeout)
1140+
self.simulationFlag = om_cmd.run()
11051141

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

0 commit comments

Comments
 (0)