3333"""
3434
3535import ast
36- import csv
3736from dataclasses import dataclass
3837import logging
3938import numbers
@@ -152,7 +151,7 @@ def arg_set(self, key: str, val: Optional[str | dict] = None) -> None:
152151 raise ModelicaSystemError (f"Invalid argument value for { repr (key )} : { repr (val )} (type: { type (val )} )" )
153152
154153 if key in self ._args :
155- logger .warning (f"Overwrite model executable argument: { repr (key )} = { repr (argval )} "
154+ logger .warning (f"Override model executable argument: { repr (key )} = { repr (argval )} "
156155 f"(was: { repr (self ._args [key ])} )" )
157156 self ._args [key ] = argval
158157
@@ -301,7 +300,7 @@ def __init__(
301300 lmodel : Optional [list [str | tuple [str , str ]]] = None ,
302301 commandLineOptions : Optional [str ] = None ,
303302 variableFilter : Optional [str ] = None ,
304- customBuildDirectory : Optional [str | os .PathLike | pathlib . Path ] = None ,
303+ customBuildDirectory : Optional [str | os .PathLike ] = None ,
305304 omhome : Optional [str ] = None ,
306305 omc_process : Optional [OMCProcessLocal ] = None ,
307306 build : bool = True ,
@@ -361,8 +360,7 @@ def __init__(
361360 self ._override_variables : dict [str , str ] = {}
362361 self ._simulate_options_override : dict [str , str ] = {}
363362 self ._linearization_options = {'startTime' : 0.0 , 'stopTime' : 1.0 , 'stepSize' : 0.002 , 'tolerance' : 1e-8 }
364- self ._optimization_options = {'startTime' : 0.0 , 'stopTime' : 1.0 , 'numberOfIntervals' : 500 , 'stepSize' : 0.002 ,
365- 'tolerance' : 1e-8 }
363+ self ._optimization_options = self ._linearization_options | {'numberOfIntervals' : 500 }
366364 self ._linearized_inputs : list [str ] = [] # linearization input list
367365 self ._linearized_outputs : list [str ] = [] # linearization output list
368366 self ._linearized_states : list [str ] = [] # linearization states list
@@ -383,7 +381,6 @@ def __init__(
383381 if not isinstance (lmodel , list ):
384382 raise ModelicaSystemError (f"Invalid input type for lmodel: { type (lmodel )} - list expected!" )
385383
386- self ._xml_file = None
387384 self ._lmodel = lmodel # may be needed if model is derived from other model
388385 self ._model_name = modelName # Model class name
389386 self ._file_name = pathlib .Path (fileName ).resolve () if fileName is not None else None # Model file/package name
@@ -400,7 +397,7 @@ def __init__(
400397 self .setCommandLineOptions ("--linearizationDumpLanguage=python" )
401398 self .setCommandLineOptions ("--generateSymbolicLinearization" )
402399
403- self ._tempdir = self .setTempDirectory (customBuildDirectory )
400+ self ._work_dir : pathlib . Path = self .setWorkDirectory (customBuildDirectory )
404401
405402 if self ._file_name is not None :
406403 self ._loadLibrary (lmodel = self ._lmodel )
@@ -448,25 +445,34 @@ def _loadLibrary(self, lmodel: list):
448445 '1)["Modelica"]\n '
449446 '2)[("Modelica","3.2.3"), "PowerSystems"]\n ' )
450447
451- def setTempDirectory (self , customBuildDirectory : Optional [str | os .PathLike | pathlib .Path ] = None ) -> pathlib .Path :
452- # create a unique temp directory for each session and build the model in that directory
448+ def setWorkDirectory (self , customBuildDirectory : Optional [str | os .PathLike ] = None ) -> pathlib .Path :
449+ """
450+ Define the work directory for the ModelicaSystem / OpenModelica session. The model is build within this
451+ directory. If no directory is defined a unique temporary directory is created.
452+ """
453453 if customBuildDirectory is not None :
454- if not os . path . exists (customBuildDirectory ):
455- raise IOError ( f" { customBuildDirectory } does not exist" )
456- tempdir = pathlib . Path ( customBuildDirectory ). absolute ( )
454+ workdir = pathlib . Path (customBuildDirectory ). absolute ()
455+ if not workdir . is_dir ():
456+ raise IOError ( f"Provided work directory does not exists: { customBuildDirectory } !" )
457457 else :
458- tempdir = pathlib .Path (tempfile .mkdtemp ()).absolute ()
459- if not tempdir .is_dir ():
460- raise IOError (f"{ tempdir } could not be created" )
458+ workdir = pathlib .Path (tempfile .mkdtemp ()).absolute ()
459+ if not workdir .is_dir ():
460+ raise IOError (f"{ workdir } could not be created" )
461461
462- logger .info ("Define tempdir as %s" , tempdir )
463- exp = f'cd("{ tempdir .as_posix ()} ")'
462+ logger .info ("Define work dir as %s" , workdir )
463+ exp = f'cd("{ workdir .as_posix ()} ")'
464464 self .sendExpression (exp )
465465
466- return tempdir
466+ # set the class variable _work_dir ...
467+ self ._work_dir = workdir
468+ # ... and also return the defined path
469+ return workdir
467470
468471 def getWorkDirectory (self ) -> pathlib .Path :
469- return self ._tempdir
472+ """
473+ Return the defined working directory for this ModelicaSystem / OpenModelica session.
474+ """
475+ return self ._work_dir
470476
471477 def buildModel (self , variableFilter : Optional [str ] = None ):
472478 if variableFilter is not None :
@@ -480,8 +486,8 @@ def buildModel(self, variableFilter: Optional[str] = None):
480486 buildModelResult = self ._requestApi (apiName = "buildModel" , entity = self ._model_name , properties = varFilter )
481487 logger .debug ("OM model build result: %s" , buildModelResult )
482488
483- self . _xml_file = pathlib .Path (buildModelResult [0 ]).parent / buildModelResult [1 ]
484- self ._xmlparse ()
489+ xml_file = pathlib .Path (buildModelResult [0 ]).parent / buildModelResult [1 ]
490+ self ._xmlparse (xml_file = xml_file )
485491
486492 def sendExpression (self , expr : str , parsed : bool = True ) -> Any :
487493 try :
@@ -512,30 +518,42 @@ def _requestApi(
512518
513519 return self .sendExpression (exp )
514520
515- def _xmlparse (self ):
516- if not self . _xml_file .is_file ():
517- raise ModelicaSystemError (f"XML file not generated: { self . _xml_file } " )
521+ def _xmlparse (self , xml_file : pathlib . Path ):
522+ if not xml_file .is_file ():
523+ raise ModelicaSystemError (f"XML file not generated: { xml_file } " )
518524
519- tree = ET .parse (self ._xml_file )
525+ xml_content = xml_file .read_text ()
526+ tree = ET .ElementTree (ET .fromstring (xml_content ))
520527 rootCQ = tree .getroot ()
521528 for attr in rootCQ .iter ('DefaultExperiment' ):
522529 for key in ("startTime" , "stopTime" , "stepSize" , "tolerance" ,
523530 "solver" , "outputFormat" ):
524- self ._simulate_options [key ] = attr .get (key )
531+ self ._simulate_options [key ] = str ( attr .get (key ) )
525532
526533 for sv in rootCQ .iter ('ScalarVariable' ):
527- scalar = {}
528- for key in ("name" , "description" , "variability" , "causality" , "alias" ):
529- scalar [key ] = sv .get (key )
530- scalar ["changeable" ] = sv .get ('isValueChangeable' )
531- scalar ["aliasvariable" ] = sv .get ('aliasVariable' )
534+ translations = {
535+ "alias" : "alias" ,
536+ "aliasvariable" : "aliasVariable" ,
537+ "causality" : "causality" ,
538+ "changeable" : "isValueChangeable" ,
539+ "description" : "description" ,
540+ "name" : "name" ,
541+ "variability" : "variability" ,
542+ }
543+
544+ scalar : dict [str , Any ] = {}
545+ for key_dst , key_src in translations .items ():
546+ val = sv .get (key_src )
547+ scalar [key_dst ] = None if val is None else str (val )
548+
532549 ch = list (sv )
533550 for att in ch :
534551 scalar ["start" ] = att .get ('start' )
535552 scalar ["min" ] = att .get ('min' )
536553 scalar ["max" ] = att .get ('max' )
537554 scalar ["unit" ] = att .get ('unit' )
538555
556+ # save parameters in the corresponding class variables
539557 if scalar ["variability" ] == "parameter" :
540558 if scalar ["name" ] in self ._override_variables :
541559 self ._params [scalar ["name" ]] = self ._override_variables [scalar ["name" ]]
@@ -947,7 +965,11 @@ def simulate_cmd(
947965 An instance if ModelicaSystemCmd to run the requested simulation.
948966 """
949967
950- om_cmd = ModelicaSystemCmd (runpath = self ._tempdir , modelname = self ._model_name , timeout = timeout )
968+ om_cmd = ModelicaSystemCmd (
969+ runpath = self .getWorkDirectory (),
970+ modelname = self ._model_name ,
971+ timeout = timeout ,
972+ )
951973
952974 # always define the result file to use
953975 om_cmd .arg_set (key = "r" , val = result_file .as_posix ())
@@ -959,16 +981,17 @@ def simulate_cmd(
959981 if simargs :
960982 om_cmd .args_set (args = simargs )
961983
962- overrideFile = self ._tempdir / f"{ self ._model_name } _override.txt"
963984 if self ._override_variables or self ._simulate_options_override :
964- tmpdict = self ._override_variables .copy ()
965- tmpdict .update (self ._simulate_options_override )
966- # write to override file
967- with open (file = overrideFile , mode = "w" , encoding = "utf-8" ) as fh :
968- for key , value in tmpdict .items ():
969- fh .write (f"{ key } ={ value } \n " )
985+ override_file = result_file .parent / f"{ result_file .stem } _override.txt"
986+
987+ override_content = (
988+ "\n " .join ([f"{ key } ={ value } " for key , value in self ._override_variables .items ()])
989+ + "\n " .join ([f"{ key } ={ value } " for key , value in self ._simulate_options_override .items ()])
990+ + "\n "
991+ )
970992
971- om_cmd .arg_set (key = "overrideFile" , val = overrideFile .as_posix ())
993+ override_file .write_text (override_content )
994+ om_cmd .arg_set (key = "overrideFile" , val = override_file .as_posix ())
972995
973996 if self ._inputs : # if model has input quantities
974997 for key in self ._inputs :
@@ -1018,11 +1041,11 @@ def simulate(
10181041
10191042 if resultfile is None :
10201043 # default result file generated by OM
1021- self ._result_file = self ._tempdir / f"{ self ._model_name } _res.mat"
1044+ self ._result_file = self .getWorkDirectory () / f"{ self ._model_name } _res.mat"
10221045 elif os .path .exists (resultfile ):
10231046 self ._result_file = pathlib .Path (resultfile )
10241047 else :
1025- self ._result_file = self ._tempdir / resultfile
1048+ self ._result_file = self .getWorkDirectory () / resultfile
10261049
10271050 om_cmd = self .simulate_cmd (
10281051 result_file = self ._result_file ,
@@ -1088,9 +1111,9 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op
10881111 else :
10891112 result_file = pathlib .Path (resultfile )
10901113
1091- # check for result file exits
1114+ # check if the result file exits
10921115 if not result_file .is_file ():
1093- raise ModelicaSystemError (f"Result file does not exist { result_file } " )
1116+ raise ModelicaSystemError (f"Result file does not exist { result_file . as_posix () } " )
10941117
10951118 # get absolute path
10961119 result_file = result_file .absolute ()
@@ -1407,7 +1430,7 @@ def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path
14071430 interpolated_inputs [signal_name ] = np .interp (
14081431 all_times ,
14091432 signal [:, 0 ], # times
1410- signal [:, 1 ] # values
1433+ signal [:, 1 ], # values
14111434 )
14121435
14131436 # Write CSV file
@@ -1419,16 +1442,17 @@ def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path
14191442 row = [
14201443 t , # time
14211444 * (interpolated_inputs [name ][i ] for name in input_names ), # input values
1422- 0 # trailing 'end' column
1445+ 0 , # trailing 'end' column
14231446 ]
14241447 csv_rows .append (row )
14251448
14261449 if csvfile is None :
1427- csvfile = self ._tempdir / f'{ self ._model_name } .csv'
1450+ csvfile = self .getWorkDirectory () / f'{ self ._model_name } .csv'
14281451
1429- with open (file = csvfile , mode = "w" , encoding = "utf-8" , newline = "" ) as fh :
1430- writer = csv .writer (fh )
1431- writer .writerows (csv_rows )
1452+ # basic definition of a CSV file using csv_rows as input
1453+ csv_content = "\n " .join (["," .join (map (str , row )) for row in csv_rows ]) + "\n "
1454+
1455+ csvfile .write_text (csv_content )
14321456
14331457 return csvfile
14341458
@@ -1541,21 +1565,26 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
15411565 compatibility, because linearize() used to return `[A, B, C, D]`.
15421566 """
15431567
1544- if self ._xml_file is None :
1568+ if len (self ._quantities ) == 0 :
1569+ # if self._quantities has no content, the xml file was not parsed; see self._xmlparse()
15451570 raise ModelicaSystemError (
15461571 "Linearization cannot be performed as the model is not build, "
15471572 "use ModelicaSystem() to build the model first"
15481573 )
15491574
1550- om_cmd = ModelicaSystemCmd (runpath = self ._tempdir , modelname = self ._model_name , timeout = timeout )
1575+ om_cmd = ModelicaSystemCmd (
1576+ runpath = self .getWorkDirectory (),
1577+ modelname = self ._model_name ,
1578+ timeout = timeout ,
1579+ )
15511580
1552- overrideLinearFile = self ._tempdir / f'{ self ._model_name } _override_linear.txt'
1581+ overrideLinearFile = self .getWorkDirectory () / f'{ self ._model_name } _override_linear.txt'
15531582
15541583 with open (file = overrideLinearFile , mode = "w" , encoding = "utf-8" ) as fh :
1555- for key , value in self ._override_variables .items ():
1556- fh .write (f"{ key } ={ value } \n " )
1557- for key , value in self ._linearization_options .items ():
1558- fh .write (f"{ key } ={ value } \n " )
1584+ for key1 , value1 in self ._override_variables .items ():
1585+ fh .write (f"{ key1 } ={ value1 } \n " )
1586+ for key2 , value2 in self ._linearization_options .items ():
1587+ fh .write (f"{ key2 } ={ value2 } \n " )
15591588
15601589 om_cmd .arg_set (key = "overrideFile" , val = overrideLinearFile .as_posix ())
15611590
@@ -1579,19 +1608,17 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
15791608 om_cmd .args_set (args = simargs )
15801609
15811610 # the file create by the model executable which contains the matrix and linear inputs, outputs and states
1582- linear_file = self ._tempdir / "linearized_model.py"
1583-
1611+ linear_file = self .getWorkDirectory () / "linearized_model.py"
15841612 linear_file .unlink (missing_ok = True )
15851613
15861614 returncode = om_cmd .run ()
15871615 if returncode != 0 :
15881616 raise ModelicaSystemError (f"Linearize failed with return code: { returncode } " )
1589-
1590- self ._simulated = True
1591-
15921617 if not linear_file .exists ():
15931618 raise ModelicaSystemError (f"Linearization failed: { linear_file } not found!" )
15941619
1620+ self ._simulated = True
1621+
15951622 # extract data from the python file with the linearized model using the ast module - this allows to get the
15961623 # needed information without executing the created code
15971624 linear_data = {}
0 commit comments