4545import pathlib
4646from dataclasses import dataclass
4747from typing import Optional
48+ import warnings
4849
4950from OMPython .OMCSession import OMCSessionZMQ , OMCSessionException
5051
@@ -109,14 +110,78 @@ def __getitem__(self, index: int):
109110
110111class 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
122187class 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