3838import numbers
3939import numpy as np
4040import os
41- import platform
42- import re
43- import subprocess
4441import textwrap
4542from typing import Optional , Any
4643import warnings
4744import xml .etree .ElementTree as ET
4845
49- from OMPython .OMCSession import OMCSessionException , OMCSessionZMQ , OMCProcessLocal , OMCPath
46+ from OMPython .OMCSession import OMCSessionException , OMCSessionRunData , OMCSessionZMQ , OMCProcessLocal , OMCPath
5047
5148# define logger using the current module name as ID
5249logger = logging .getLogger (__name__ )
@@ -112,7 +109,14 @@ def __getitem__(self, index: int):
112109class ModelicaSystemCmd :
113110 """A compiled model executable."""
114111
115- def __init__ (self , runpath : OMCPath , modelname : str , timeout : Optional [float ] = None ) -> None :
112+ def __init__ (
113+ self ,
114+ session : OMCSessionZMQ ,
115+ runpath : OMCPath ,
116+ modelname : str ,
117+ timeout : Optional [float ] = None ,
118+ ) -> None :
119+ self ._session = session
116120 self ._runpath = runpath
117121 self ._model_name = modelname
118122 self ._timeout = timeout
@@ -227,27 +231,12 @@ def args_set(
227231 for arg in args :
228232 self .arg_set (key = arg , val = args [arg ])
229233
230- def get_exe (self ) -> OMCPath :
231- """Get the path to the compiled model executable."""
232- if platform .system () == "Windows" :
233- path_exe = self ._runpath / f"{ self ._model_name } .exe"
234- else :
235- path_exe = self ._runpath / self ._model_name
236-
237- if not path_exe .exists ():
238- raise ModelicaSystemError (f"Application file path not found: { path_exe } " )
239-
240- return path_exe
241-
242- def get_cmd (self ) -> list :
243- """Get a list with the path to the executable and all command line args.
244-
245- This can later be used as an argument for subprocess.run().
234+ def get_cmd_args (self ) -> list [str ]:
235+ """
236+ Get a list with the command arguments for the model executable.
246237 """
247238
248- path_exe = self .get_exe ()
249-
250- cmdl = [path_exe .as_posix ()]
239+ cmdl = []
251240 for key in sorted (self ._args ):
252241 if self ._args [key ] is None :
253242 cmdl .append (f"-{ key } " )
@@ -256,54 +245,26 @@ def get_cmd(self) -> list:
256245
257246 return cmdl
258247
259- def run (self ) -> int :
260- """Run the requested simulation.
261-
262- Returns
263- -------
264- Subprocess return code (0 on success).
248+ def definition (self ) -> OMCSessionRunData :
265249 """
250+ Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object.
251+ """
252+ # ensure that a result filename is provided
253+ result_file = self .arg_get ('r' )
254+ if not isinstance (result_file , str ):
255+ result_file = (self ._runpath / f"{ self ._model_name } .mat" ).as_posix ()
256+
257+ omc_run_data = OMCSessionRunData (
258+ cmd_path = self ._runpath .as_posix (),
259+ cmd_model_name = self ._model_name ,
260+ cmd_args = self .get_cmd_args (),
261+ cmd_result_path = result_file ,
262+ cmd_timeout = self ._timeout ,
263+ )
266264
267- cmdl : list = self .get_cmd ()
268-
269- logger .debug ("Run OM command %s in %s" , repr (cmdl ), self ._runpath .as_posix ())
270-
271- if platform .system () == "Windows" :
272- path_dll = ""
273-
274- # set the process environment from the generated .bat file in windows which should have all the dependencies
275- path_bat = self ._runpath / f"{ self ._model_name } .bat"
276- if not path_bat .exists ():
277- raise ModelicaSystemError ("Batch file (*.bat) does not exist " + str (path_bat ))
278-
279- with open (file = path_bat , mode = 'r' , encoding = 'utf-8' ) as fh :
280- for line in fh :
281- match = re .match (r"^SET PATH=([^%]*)" , line , re .IGNORECASE )
282- if match :
283- path_dll = match .group (1 ).strip (';' ) # Remove any trailing semicolons
284- my_env = os .environ .copy ()
285- my_env ["PATH" ] = path_dll + os .pathsep + my_env ["PATH" ]
286- else :
287- # TODO: how to handle path to resources of external libraries for any system not Windows?
288- my_env = None
289-
290- try :
291- cmdres = subprocess .run (cmdl , capture_output = True , text = True , env = my_env , cwd = self ._runpath ,
292- timeout = self ._timeout , check = True )
293- stdout = cmdres .stdout .strip ()
294- stderr = cmdres .stderr .strip ()
295- returncode = cmdres .returncode
296-
297- logger .debug ("OM output for command %s:\n %s" , repr (cmdl ), stdout )
298-
299- if stderr :
300- raise ModelicaSystemError (f"Error running command { repr (cmdl )} : { stderr } " )
301- except subprocess .TimeoutExpired as ex :
302- raise ModelicaSystemError (f"Timeout running command { repr (cmdl )} " ) from ex
303- except subprocess .CalledProcessError as ex :
304- raise ModelicaSystemError (f"Error running command { repr (cmdl )} " ) from ex
265+ omc_run_data_updated = self ._session .omc_run_data_update (omc_run_data = omc_run_data )
305266
306- return returncode
267+ return omc_run_data_updated
307268
308269 @staticmethod
309270 def parse_simflags (simflags : str ) -> dict [str , Optional [str | dict [str , Any ] | numbers .Number ]]:
@@ -1032,6 +993,7 @@ def simulate_cmd(
1032993 """
1033994
1034995 om_cmd = ModelicaSystemCmd (
996+ session = self ._getconn ,
1035997 runpath = self .getWorkDirectory (),
1036998 modelname = self ._model_name ,
1037999 timeout = timeout ,
@@ -1130,7 +1092,8 @@ def simulate(
11301092 if self ._result_file .is_file ():
11311093 self ._result_file .unlink ()
11321094 # ... run simulation ...
1133- returncode = om_cmd .run ()
1095+ cmd_definition = om_cmd .definition ()
1096+ returncode = self ._getconn .run_model_executable (cmd_run_data = cmd_definition )
11341097 # and check returncode *AND* resultfile
11351098 if returncode != 0 and self ._result_file .is_file ():
11361099 # check for an empty (=> 0B) result file which indicates a crash of the model executable
@@ -1144,6 +1107,34 @@ def simulate(
11441107
11451108 self ._simulated = True
11461109
1110+ def plot (
1111+ self ,
1112+ plotdata : str ,
1113+ resultfile : Optional [str | os .PathLike ] = None ,
1114+ ) -> None :
1115+ """
1116+ Plot a variable using OMC; this will work for local OMC usage only (OMCProcessLocal). The reason is that the
1117+ plot is created by OMC which needs access to the local display. This is not the case for docker and WSL.
1118+ """
1119+
1120+ if not isinstance (self ._getconn .omc_process , OMCProcessLocal ):
1121+ raise ModelicaSystemError ("Plot is using the OMC plot functionality; "
1122+ "thus, it is only working if OMC is running locally!" )
1123+
1124+ if resultfile is not None :
1125+ plot_result_file = self ._getconn .omcpath (resultfile )
1126+ elif self ._result_file is not None :
1127+ plot_result_file = self ._result_file
1128+ else :
1129+ raise ModelicaSystemError ("No resultfile available - either run simulate() before plotting "
1130+ "or provide a result file!" )
1131+
1132+ if not plot_result_file .is_file ():
1133+ raise ModelicaSystemError (f"Provided resultfile { repr (plot_result_file .as_posix ())} does not exists!" )
1134+
1135+ expr = f'plot({ plotdata } , fileName="{ plot_result_file .as_posix ()} ")'
1136+ self .sendExpression (expr = expr )
1137+
11471138 def getSolutions (
11481139 self ,
11491140 varList : Optional [str | list [str ]] = None ,
@@ -1687,6 +1678,7 @@ def linearize(
16871678 )
16881679
16891680 om_cmd = ModelicaSystemCmd (
1681+ session = self ._getconn ,
16901682 runpath = self .getWorkDirectory (),
16911683 modelname = self ._model_name ,
16921684 timeout = timeout ,
@@ -1724,7 +1716,8 @@ def linearize(
17241716 linear_file = self .getWorkDirectory () / "linearized_model.py"
17251717 linear_file .unlink (missing_ok = True )
17261718
1727- returncode = om_cmd .run ()
1719+ cmd_definition = om_cmd .definition ()
1720+ returncode = self ._getconn .run_model_executable (cmd_run_data = cmd_definition )
17281721 if returncode != 0 :
17291722 raise ModelicaSystemError (f"Linearize failed with return code: { returncode } " )
17301723 if not linear_file .is_file ():
0 commit comments