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 } : 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
122187class 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