2121import numpy as np
2222
2323from OMPython .OMCSession import (
24+ ModelExecutionData ,
25+ ModelExecutionException ,
26+
2427 OMCSessionException ,
25- OMCSessionRunData ,
2628 OMCSession ,
2729 OMCSessionLocal ,
2830 OMCPath ,
3436
3537class ModelicaSystemError (Exception ):
3638 """
37- Exception used in ModelicaSystem and ModelicaSystemCmd classes.
39+ Exception used in ModelicaSystem classes.
3840 """
3941
4042
@@ -89,7 +91,7 @@ def __getitem__(self, index: int):
8991 return {0 : self .A , 1 : self .B , 2 : self .C , 3 : self .D }[index ]
9092
9193
92- class ModelicaSystemCmd :
94+ class ModelExecutionCmd :
9395 """
9496 All information about a compiled model executable. This should include data about all structured parameters, i.e.
9597 parameters which need a recompilation of the model. All non-structured parameters can be easily changed without
@@ -98,16 +100,22 @@ class ModelicaSystemCmd:
98100
99101 def __init__ (
100102 self ,
101- session : OMCSession ,
102- runpath : OMCPath ,
103- modelname : Optional [str ] = None ,
103+ runpath : os .PathLike ,
104+ cmd_prefix : list [str ],
105+ cmd_local : bool = False ,
106+ cmd_windows : bool = False ,
107+ timeout : float = 10.0 ,
108+ model_name : Optional [str ] = None ,
104109 ) -> None :
105- if modelname is None :
106- raise ModelicaSystemError ("Missing model name!" )
110+ if model_name is None :
111+ raise ModelExecutionException ("Missing model name!" )
107112
108- self ._session = session
109- self ._runpath = runpath
110- self ._model_name = modelname
113+ self ._cmd_local = cmd_local
114+ self ._cmd_windows = cmd_windows
115+ self ._cmd_prefix = cmd_prefix
116+ self ._runpath = pathlib .PurePosixPath (runpath )
117+ self ._model_name = model_name
118+ self ._timeout = timeout
111119
112120 # dictionaries of command line arguments for the model executable
113121 self ._args : dict [str , str | None ] = {}
@@ -152,26 +160,26 @@ def override2str(
152160 elif isinstance (oval , numbers .Number ):
153161 oval_str = str (oval )
154162 else :
155- raise ModelicaSystemError (f"Invalid value for override key { okey } : { type (oval )} " )
163+ raise ModelExecutionException (f"Invalid value for override key { okey } : { type (oval )} " )
156164
157165 return f"{ okey } ={ oval_str } "
158166
159167 if not isinstance (key , str ):
160- raise ModelicaSystemError (f"Invalid argument key: { repr (key )} (type: { type (key )} )" )
168+ raise ModelExecutionException (f"Invalid argument key: { repr (key )} (type: { type (key )} )" )
161169 key = key .strip ()
162170
163171 if isinstance (val , dict ):
164172 if key != 'override' :
165- raise ModelicaSystemError ("Dictionary input only possible for key 'override'!" )
173+ raise ModelExecutionException ("Dictionary input only possible for key 'override'!" )
166174
167175 for okey , oval in val .items ():
168176 if not isinstance (okey , str ):
169- raise ModelicaSystemError ("Invalid key for argument 'override': "
170- f"{ repr (okey )} (type: { type (okey )} )" )
177+ raise ModelExecutionException ("Invalid key for argument 'override': "
178+ f"{ repr (okey )} (type: { type (okey )} )" )
171179
172180 if not isinstance (oval , (str , bool , numbers .Number , type (None ))):
173- raise ModelicaSystemError (f"Invalid input for 'override'.{ repr (okey )} : "
174- f"{ repr (oval )} (type: { type (oval )} )" )
181+ raise ModelExecutionException (f"Invalid input for 'override'.{ repr (okey )} : "
182+ f"{ repr (oval )} (type: { type (oval )} )" )
175183
176184 if okey in self ._arg_override :
177185 if oval is None :
@@ -193,7 +201,7 @@ def override2str(
193201 elif isinstance (val , numbers .Number ):
194202 argval = str (val )
195203 else :
196- raise ModelicaSystemError (f"Invalid argument value for { repr (key )} : { repr (val )} (type: { type (val )} )" )
204+ raise ModelExecutionException (f"Invalid argument value for { repr (key )} : { repr (val )} (type: { type (val )} )" )
197205
198206 if key in self ._args :
199207 logger .warning (f"Override model executable argument: { repr (key )} = { repr (argval )} "
@@ -233,7 +241,7 @@ def get_cmd_args(self) -> list[str]:
233241
234242 return cmdl
235243
236- def definition (self ) -> OMCSessionRunData :
244+ def definition (self ) -> ModelExecutionData :
237245 """
238246 Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object.
239247 """
@@ -242,18 +250,50 @@ def definition(self) -> OMCSessionRunData:
242250 if not isinstance (result_file , str ):
243251 result_file = (self ._runpath / f"{ self ._model_name } .mat" ).as_posix ()
244252
245- omc_run_data = OMCSessionRunData (
246- cmd_path = self ._runpath .as_posix (),
253+ # as this is the local implementation, pathlib.Path can be used
254+ cmd_path = self ._runpath
255+
256+ cmd_library_path = None
257+ if self ._cmd_local and self ._cmd_windows :
258+ cmd_library_path = ""
259+
260+ # set the process environment from the generated .bat file in windows which should have all the dependencies
261+ # for this pathlib.PurePosixPath() must be converted to a pathlib.Path() object, i.e. WindowsPath
262+ path_bat = pathlib .Path (cmd_path ) / f"{ self ._model_name } .bat"
263+ if not path_bat .is_file ():
264+ raise ModelExecutionException ("Batch file (*.bat) does not exist " + str (path_bat ))
265+
266+ content = path_bat .read_text (encoding = 'utf-8' )
267+ for line in content .splitlines ():
268+ match = re .match (pattern = r"^SET PATH=([^%]*)" , string = line , flags = re .IGNORECASE )
269+ if match :
270+ cmd_library_path = match .group (1 ).strip (';' ) # Remove any trailing semicolons
271+ my_env = os .environ .copy ()
272+ my_env ["PATH" ] = cmd_library_path + os .pathsep + my_env ["PATH" ]
273+
274+ cmd_model_executable = cmd_path / f"{ self ._model_name } .exe"
275+ else :
276+ # for Linux the paths to the needed libraries should be included in the executable (using rpath)
277+ cmd_model_executable = cmd_path / self ._model_name
278+
279+ # define local(!) working directory
280+ cmd_cwd_local = None
281+ if self ._cmd_local :
282+ cmd_cwd_local = cmd_path .as_posix ()
283+
284+ omc_run_data = ModelExecutionData (
285+ cmd_path = cmd_path .as_posix (),
247286 cmd_model_name = self ._model_name ,
248287 cmd_args = self .get_cmd_args (),
249- cmd_result_path = result_file ,
288+ cmd_result_file = result_file ,
289+ cmd_prefix = self ._cmd_prefix ,
290+ cmd_library_path = cmd_library_path ,
291+ cmd_model_executable = cmd_model_executable .as_posix (),
292+ cmd_cwd_local = cmd_cwd_local ,
293+ cmd_timeout = self ._timeout ,
250294 )
251295
252- omc_run_data_updated = self ._session .omc_run_data_update (
253- omc_run_data = omc_run_data ,
254- )
255-
256- return omc_run_data_updated
296+ return omc_run_data
257297
258298 @staticmethod
259299 def parse_simflags (simflags : str ) -> dict [str , Optional [str | dict [str , Any ] | numbers .Number ]]:
@@ -262,17 +302,19 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
262302
263303 The return data can be used as input for self.args_set().
264304 """
265- warnings .warn (message = "The argument 'simflags' is depreciated and will be removed in future versions; "
266- "please use 'simargs' instead" ,
267- category = DeprecationWarning ,
268- stacklevel = 2 )
305+ warnings .warn (
306+ message = "The argument 'simflags' is depreciated and will be removed in future versions; "
307+ "please use 'simargs' instead" ,
308+ category = DeprecationWarning ,
309+ stacklevel = 2 ,
310+ )
269311
270312 simargs : dict [str , Optional [str | dict [str , Any ] | numbers .Number ]] = {}
271313
272314 args = [s for s in simflags .split (' ' ) if s ]
273315 for arg in args :
274316 if arg [0 ] != '-' :
275- raise ModelicaSystemError (f"Invalid simulation flag: { arg } " )
317+ raise ModelExecutionException (f"Invalid simulation flag: { arg } " )
276318 arg = arg [1 :]
277319 parts = arg .split ('=' )
278320 if len (parts ) == 1 :
@@ -284,12 +326,12 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
284326 for item in override .split (',' ):
285327 kv = item .split ('=' )
286328 if not 0 < len (kv ) < 3 :
287- raise ModelicaSystemError (f"Invalid value for '-override': { override } " )
329+ raise ModelExecutionException (f"Invalid value for '-override': { override } " )
288330 if kv [0 ]:
289331 try :
290332 override_dict [kv [0 ]] = kv [1 ]
291333 except (KeyError , IndexError ) as ex :
292- raise ModelicaSystemError (f"Invalid value for '-override': { override } " ) from ex
334+ raise ModelExecutionException (f"Invalid value for '-override': { override } " ) from ex
293335
294336 simargs [parts [0 ]] = override_dict
295337
@@ -544,15 +586,17 @@ def buildModel(self, variableFilter: Optional[str] = None):
544586 logger .debug ("OM model build result: %s" , build_model_result )
545587
546588 # check if the executable exists ...
547- om_cmd = ModelicaSystemCmd (
548- session = self ._session ,
589+ om_cmd = ModelExecutionCmd (
549590 runpath = self .getWorkDirectory (),
550- modelname = self ._model_name ,
591+ cmd_local = self ._session .model_execution_local ,
592+ cmd_windows = self ._session .model_execution_windows ,
593+ cmd_prefix = self ._session .model_execution_prefix (cwd = self .getWorkDirectory ()),
594+ model_name = self ._model_name ,
551595 )
552596 # ... by running it - output help for command help
553597 om_cmd .arg_set (key = "help" , val = "help" )
554598 cmd_definition = om_cmd .definition ()
555- returncode = self . _session . run_model_executable ( cmd_run_data = cmd_definition )
599+ returncode = cmd_definition . run ( )
556600 if returncode != 0 :
557601 raise ModelicaSystemError ("Model executable not working!" )
558602
@@ -1069,7 +1113,7 @@ def simulate_cmd(
10691113 result_file : OMCPath ,
10701114 simflags : Optional [str ] = None ,
10711115 simargs : Optional [dict [str , Optional [str | dict [str , Any ] | numbers .Number ]]] = None ,
1072- ) -> ModelicaSystemCmd :
1116+ ) -> ModelExecutionCmd :
10731117 """
10741118 This method prepares the simulates model according to the simulation options. It returns an instance of
10751119 ModelicaSystemCmd which can be used to run the simulation.
@@ -1091,10 +1135,12 @@ def simulate_cmd(
10911135 An instance if ModelicaSystemCmd to run the requested simulation.
10921136 """
10931137
1094- om_cmd = ModelicaSystemCmd (
1095- session = self ._session ,
1138+ om_cmd = ModelExecutionCmd (
10961139 runpath = self .getWorkDirectory (),
1097- modelname = self ._model_name ,
1140+ cmd_local = self ._session .model_execution_local ,
1141+ cmd_windows = self ._session .model_execution_windows ,
1142+ cmd_prefix = self ._session .model_execution_prefix (cwd = self .getWorkDirectory ()),
1143+ model_name = self ._model_name ,
10981144 )
10991145
11001146 # always define the result file to use
@@ -1183,7 +1229,7 @@ def simulate(
11831229 self ._result_file .unlink ()
11841230 # ... run simulation ...
11851231 cmd_definition = om_cmd .definition ()
1186- returncode = self . _session . run_model_executable ( cmd_run_data = cmd_definition )
1232+ returncode = cmd_definition . run ( )
11871233 # and check returncode *AND* resultfile
11881234 if returncode != 0 and self ._result_file .is_file ():
11891235 # check for an empty (=> 0B) result file which indicates a crash of the model executable
@@ -1786,10 +1832,12 @@ def linearize(
17861832 "use ModelicaSystem() to build the model first"
17871833 )
17881834
1789- om_cmd = ModelicaSystemCmd (
1790- session = self ._session ,
1835+ om_cmd = ModelExecutionCmd (
17911836 runpath = self .getWorkDirectory (),
1792- modelname = self ._model_name ,
1837+ cmd_local = self ._session .model_execution_local ,
1838+ cmd_windows = self ._session .model_execution_windows ,
1839+ cmd_prefix = self ._session .model_execution_prefix (cwd = self .getWorkDirectory ()),
1840+ model_name = self ._model_name ,
17931841 )
17941842
17951843 self ._process_override_data (
@@ -1829,7 +1877,7 @@ def linearize(
18291877 linear_file .unlink (missing_ok = True )
18301878
18311879 cmd_definition = om_cmd .definition ()
1832- returncode = self . _session . run_model_executable ( cmd_run_data = cmd_definition )
1880+ returncode = cmd_definition . run ( )
18331881 if returncode != 0 :
18341882 raise ModelicaSystemError (f"Linearize failed with return code: { returncode } " )
18351883 if not linear_file .is_file ():
@@ -2015,7 +2063,7 @@ def __init__(
20152063 self ._parameters = {}
20162064
20172065 self ._doe_def : Optional [dict [str , dict [str , Any ]]] = None
2018- self ._doe_cmd : Optional [dict [str , OMCSessionRunData ]] = None
2066+ self ._doe_cmd : Optional [dict [str , ModelExecutionData ]] = None
20192067
20202068 def get_session (self ) -> OMCSession :
20212069 """
@@ -2134,7 +2182,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]:
21342182 """
21352183 return self ._doe_def
21362184
2137- def get_doe_command (self ) -> Optional [dict [str , OMCSessionRunData ]]:
2185+ def get_doe_command (self ) -> Optional [dict [str , ModelExecutionData ]]:
21382186 """
21392187 Get the definitions of simulations commands to run for this DoE.
21402188 """
@@ -2180,13 +2228,13 @@ def worker(worker_id, task_queue):
21802228 if cmd_definition is None :
21812229 raise ModelicaSystemError ("Missing simulation definition!" )
21822230
2183- resultfile = cmd_definition .cmd_result_path
2231+ resultfile = cmd_definition .cmd_result_file
21842232 resultpath = self .get_session ().omcpath (resultfile )
21852233
21862234 logger .info (f"[Worker { worker_id } ] Performing task: { resultpath .name } " )
21872235
21882236 try :
2189- returncode = self . get_session (). run_model_executable ( cmd_run_data = cmd_definition )
2237+ returncode = cmd_definition . run ( )
21902238 logger .info (f"[Worker { worker_id } ] Simulation { resultpath .name } "
21912239 f"finished with return code: { returncode } " )
21922240 except ModelicaSystemError as ex :
0 commit comments