3333"""
3434
3535import ast
36- import csv
3736from dataclasses import dataclass
3837import logging
3938import numbers
@@ -207,7 +206,7 @@ def override2str(
207206 raise ModelicaSystemError (f"Invalid argument value for { repr (key )} : { repr (val )} (type: { type (val )} )" )
208207
209208 if key in self ._args :
210- logger .warning (f"Overwrite model executable argument: { repr (key )} = { repr (argval )} "
209+ logger .warning (f"Override model executable argument: { repr (key )} = { repr (argval )} "
211210 f"(was: { repr (self ._args [key ])} )" )
212211 self ._args [key ] = argval
213212
@@ -355,7 +354,7 @@ def __init__(
355354 lmodel : Optional [list [str | tuple [str , str ]]] = None ,
356355 commandLineOptions : Optional [str ] = None ,
357356 variableFilter : Optional [str ] = None ,
358- customBuildDirectory : Optional [str | os .PathLike | pathlib . Path ] = None ,
357+ customBuildDirectory : Optional [str | os .PathLike ] = None ,
359358 omhome : Optional [str ] = None ,
360359 omc_process : Optional [OMCProcessLocal ] = None ,
361360 build : bool = True ,
@@ -415,8 +414,7 @@ def __init__(
415414 self ._override_variables : dict [str , str ] = {}
416415 self ._simulate_options_override : dict [str , str ] = {}
417416 self ._linearization_options = {'startTime' : 0.0 , 'stopTime' : 1.0 , 'stepSize' : 0.002 , 'tolerance' : 1e-8 }
418- self ._optimization_options = {'startTime' : 0.0 , 'stopTime' : 1.0 , 'numberOfIntervals' : 500 , 'stepSize' : 0.002 ,
419- 'tolerance' : 1e-8 }
417+ self ._optimization_options = self ._linearization_options | {'numberOfIntervals' : 500 }
420418 self ._linearized_inputs : list [str ] = [] # linearization input list
421419 self ._linearized_outputs : list [str ] = [] # linearization output list
422420 self ._linearized_states : list [str ] = [] # linearization states list
@@ -437,7 +435,6 @@ def __init__(
437435 if not isinstance (lmodel , list ):
438436 raise ModelicaSystemError (f"Invalid input type for lmodel: { type (lmodel )} - list expected!" )
439437
440- self ._xml_file = None
441438 self ._lmodel = lmodel # may be needed if model is derived from other model
442439 self ._model_name = modelName # Model class name
443440 self ._file_name = pathlib .Path (fileName ).resolve () if fileName is not None else None # Model file/package name
@@ -454,7 +451,7 @@ def __init__(
454451 self .setCommandLineOptions ("--linearizationDumpLanguage=python" )
455452 self .setCommandLineOptions ("--generateSymbolicLinearization" )
456453
457- self ._tempdir = self .setTempDirectory (customBuildDirectory )
454+ self ._work_dir : pathlib . Path = self .setWorkDirectory (customBuildDirectory )
458455
459456 if self ._file_name is not None :
460457 self ._loadLibrary (lmodel = self ._lmodel )
@@ -488,7 +485,7 @@ def _loadLibrary(self, lmodel: list):
488485 apiCall = "loadFile"
489486 else :
490487 apiCall = "loadModel"
491- self ._requestApi (apiCall , element )
488+ self ._requestApi (apiName = apiCall , entity = element )
492489 elif isinstance (element , tuple ):
493490 if not element [1 ]:
494491 expr_load_lib = f"loadModel({ element [0 ]} )"
@@ -502,25 +499,34 @@ def _loadLibrary(self, lmodel: list):
502499 '1)["Modelica"]\n '
503500 '2)[("Modelica","3.2.3"), "PowerSystems"]\n ' )
504501
505- def setTempDirectory (self , customBuildDirectory : Optional [str | os .PathLike | pathlib .Path ] = None ) -> pathlib .Path :
506- # create a unique temp directory for each session and build the model in that directory
502+ def setWorkDirectory (self , customBuildDirectory : Optional [str | os .PathLike ] = None ) -> pathlib .Path :
503+ """
504+ Define the work directory for the ModelicaSystem / OpenModelica session. The model is build within this
505+ directory. If no directory is defined a unique temporary directory is created.
506+ """
507507 if customBuildDirectory is not None :
508- if not os . path . exists (customBuildDirectory ):
509- raise IOError ( f" { customBuildDirectory } does not exist" )
510- tempdir = pathlib . Path ( customBuildDirectory ). absolute ( )
508+ workdir = pathlib . Path (customBuildDirectory ). absolute ()
509+ if not workdir . is_dir ():
510+ raise IOError ( f"Provided work directory does not exists: { customBuildDirectory } !" )
511511 else :
512- tempdir = pathlib .Path (tempfile .mkdtemp ()).absolute ()
513- if not tempdir .is_dir ():
514- raise IOError (f"{ tempdir } could not be created" )
512+ workdir = pathlib .Path (tempfile .mkdtemp ()).absolute ()
513+ if not workdir .is_dir ():
514+ raise IOError (f"{ workdir } could not be created" )
515515
516- logger .info ("Define tempdir as %s" , tempdir )
517- exp = f'cd("{ tempdir .as_posix ()} ")'
516+ logger .info ("Define work dir as %s" , workdir )
517+ exp = f'cd("{ workdir .as_posix ()} ")'
518518 self .sendExpression (exp )
519519
520- return tempdir
520+ # set the class variable _work_dir ...
521+ self ._work_dir = workdir
522+ # ... and also return the defined path
523+ return workdir
521524
522525 def getWorkDirectory (self ) -> pathlib .Path :
523- return self ._tempdir
526+ """
527+ Return the defined working directory for this ModelicaSystem / OpenModelica session.
528+ """
529+ return self ._work_dir
524530
525531 def buildModel (self , variableFilter : Optional [str ] = None ):
526532 if variableFilter is not None :
@@ -531,13 +537,13 @@ def buildModel(self, variableFilter: Optional[str] = None):
531537 else :
532538 varFilter = 'variableFilter=".*"'
533539
534- buildModelResult = self ._requestApi ("buildModel" , self ._model_name , properties = varFilter )
540+ buildModelResult = self ._requestApi (apiName = "buildModel" , entity = self ._model_name , properties = varFilter )
535541 logger .debug ("OM model build result: %s" , buildModelResult )
536542
537- self . _xml_file = pathlib .Path (buildModelResult [0 ]).parent / buildModelResult [1 ]
538- self ._xmlparse ()
543+ xml_file = pathlib .Path (buildModelResult [0 ]).parent / buildModelResult [1 ]
544+ self ._xmlparse (xml_file = xml_file )
539545
540- def sendExpression (self , expr : str , parsed : bool = True ):
546+ def sendExpression (self , expr : str , parsed : bool = True ) -> Any :
541547 try :
542548 retval = self ._getconn .sendExpression (expr , parsed )
543549 except OMCSessionException as ex :
@@ -548,7 +554,12 @@ def sendExpression(self, expr: str, parsed: bool = True):
548554 return retval
549555
550556 # request to OMC
551- def _requestApi (self , apiName , entity = None , properties = None ): # 2
557+ def _requestApi (
558+ self ,
559+ apiName : str ,
560+ entity : Optional [str ] = None ,
561+ properties : Optional [str ] = None ,
562+ ) -> Any :
552563 if entity is not None and properties is not None :
553564 exp = f'{ apiName } ({ entity } , { properties } )'
554565 elif entity is not None and properties is None :
@@ -561,30 +572,42 @@ def _requestApi(self, apiName, entity=None, properties=None): # 2
561572
562573 return self .sendExpression (exp )
563574
564- def _xmlparse (self ):
565- if not self . _xml_file .is_file ():
566- raise ModelicaSystemError (f"XML file not generated: { self . _xml_file } " )
575+ def _xmlparse (self , xml_file : pathlib . Path ):
576+ if not xml_file .is_file ():
577+ raise ModelicaSystemError (f"XML file not generated: { xml_file } " )
567578
568- tree = ET .parse (self ._xml_file )
579+ xml_content = xml_file .read_text ()
580+ tree = ET .ElementTree (ET .fromstring (xml_content ))
569581 rootCQ = tree .getroot ()
570582 for attr in rootCQ .iter ('DefaultExperiment' ):
571583 for key in ("startTime" , "stopTime" , "stepSize" , "tolerance" ,
572584 "solver" , "outputFormat" ):
573- self ._simulate_options [key ] = attr .get (key )
585+ self ._simulate_options [key ] = str ( attr .get (key ) )
574586
575587 for sv in rootCQ .iter ('ScalarVariable' ):
576- scalar = {}
577- for key in ("name" , "description" , "variability" , "causality" , "alias" ):
578- scalar [key ] = sv .get (key )
579- scalar ["changeable" ] = sv .get ('isValueChangeable' )
580- scalar ["aliasvariable" ] = sv .get ('aliasVariable' )
588+ translations = {
589+ "alias" : "alias" ,
590+ "aliasvariable" : "aliasVariable" ,
591+ "causality" : "causality" ,
592+ "changeable" : "isValueChangeable" ,
593+ "description" : "description" ,
594+ "name" : "name" ,
595+ "variability" : "variability" ,
596+ }
597+
598+ scalar : dict [str , Any ] = {}
599+ for key_dst , key_src in translations .items ():
600+ val = sv .get (key_src )
601+ scalar [key_dst ] = None if val is None else str (val )
602+
581603 ch = list (sv )
582604 for att in ch :
583605 scalar ["start" ] = att .get ('start' )
584606 scalar ["min" ] = att .get ('min' )
585607 scalar ["max" ] = att .get ('max' )
586608 scalar ["unit" ] = att .get ('unit' )
587609
610+ # save parameters in the corresponding class variables
588611 if scalar ["variability" ] == "parameter" :
589612 if scalar ["name" ] in self ._override_variables :
590613 self ._params [scalar ["name" ]] = self ._override_variables [scalar ["name" ]]
@@ -996,7 +1019,11 @@ def simulate_cmd(
9961019 An instance if ModelicaSystemCmd to run the requested simulation.
9971020 """
9981021
999- om_cmd = ModelicaSystemCmd (runpath = self ._tempdir , modelname = self ._model_name , timeout = timeout )
1022+ om_cmd = ModelicaSystemCmd (
1023+ runpath = self .getWorkDirectory (),
1024+ modelname = self ._model_name ,
1025+ timeout = timeout ,
1026+ )
10001027
10011028 # always define the result file to use
10021029 om_cmd .arg_set (key = "r" , val = result_file .as_posix ())
@@ -1008,16 +1035,17 @@ def simulate_cmd(
10081035 if simargs :
10091036 om_cmd .args_set (args = simargs )
10101037
1011- overrideFile = self ._tempdir / f"{ self ._model_name } _override.txt"
10121038 if self ._override_variables or self ._simulate_options_override :
1013- tmpdict = self ._override_variables .copy ()
1014- tmpdict .update (self ._simulate_options_override )
1015- # write to override file
1016- with open (file = overrideFile , mode = "w" , encoding = "utf-8" ) as fh :
1017- for key , value in tmpdict .items ():
1018- fh .write (f"{ key } ={ value } \n " )
1039+ override_file = result_file .parent / f"{ result_file .stem } _override.txt"
1040+
1041+ override_content = (
1042+ "\n " .join ([f"{ key } ={ value } " for key , value in self ._override_variables .items ()])
1043+ + "\n " .join ([f"{ key } ={ value } " for key , value in self ._simulate_options_override .items ()])
1044+ + "\n "
1045+ )
10191046
1020- om_cmd .arg_set (key = "overrideFile" , val = overrideFile .as_posix ())
1047+ override_file .write_text (override_content )
1048+ om_cmd .arg_set (key = "overrideFile" , val = override_file .as_posix ())
10211049
10221050 if self ._inputs : # if model has input quantities
10231051 for key in self ._inputs :
@@ -1067,11 +1095,11 @@ def simulate(
10671095
10681096 if resultfile is None :
10691097 # default result file generated by OM
1070- self ._result_file = self ._tempdir / f"{ self ._model_name } _res.mat"
1098+ self ._result_file = self .getWorkDirectory () / f"{ self ._model_name } _res.mat"
10711099 elif os .path .exists (resultfile ):
10721100 self ._result_file = pathlib .Path (resultfile )
10731101 else :
1074- self ._result_file = self ._tempdir / resultfile
1102+ self ._result_file = self .getWorkDirectory () / resultfile
10751103
10761104 om_cmd = self .simulate_cmd (
10771105 result_file = self ._result_file ,
@@ -1137,9 +1165,9 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op
11371165 else :
11381166 result_file = pathlib .Path (resultfile )
11391167
1140- # check for result file exits
1168+ # check if the result file exits
11411169 if not result_file .is_file ():
1142- raise ModelicaSystemError (f"Result file does not exist { result_file } " )
1170+ raise ModelicaSystemError (f"Result file does not exist { result_file . as_posix () } " )
11431171
11441172 # get absolute path
11451173 result_file = result_file .absolute ()
@@ -1456,7 +1484,7 @@ def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path
14561484 interpolated_inputs [signal_name ] = np .interp (
14571485 all_times ,
14581486 signal [:, 0 ], # times
1459- signal [:, 1 ] # values
1487+ signal [:, 1 ], # values
14601488 )
14611489
14621490 # Write CSV file
@@ -1468,16 +1496,17 @@ def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path
14681496 row = [
14691497 t , # time
14701498 * (interpolated_inputs [name ][i ] for name in input_names ), # input values
1471- 0 # trailing 'end' column
1499+ 0 , # trailing 'end' column
14721500 ]
14731501 csv_rows .append (row )
14741502
14751503 if csvfile is None :
1476- csvfile = self ._tempdir / f'{ self ._model_name } .csv'
1504+ csvfile = self .getWorkDirectory () / f'{ self ._model_name } .csv'
14771505
1478- with open (file = csvfile , mode = "w" , encoding = "utf-8" , newline = "" ) as fh :
1479- writer = csv .writer (fh )
1480- writer .writerows (csv_rows )
1506+ # basic definition of a CSV file using csv_rows as input
1507+ csv_content = "\n " .join (["," .join (map (str , row )) for row in csv_rows ]) + "\n "
1508+
1509+ csvfile .write_text (csv_content )
14811510
14821511 return csvfile
14831512
@@ -1505,8 +1534,9 @@ def convertMo2Fmu(self, version: str = "2.0", fmuType: str = "me_cs",
15051534 includeResourcesStr = "true"
15061535 else :
15071536 includeResourcesStr = "false"
1508- properties = f'version="{ version } ", fmuType="{ fmuType } ", fileNamePrefix="{ fileNamePrefix } ", includeResources={ includeResourcesStr } '
1509- fmu = self ._requestApi ('buildModelFMU' , self ._model_name , properties )
1537+ properties = (f'version="{ version } ", fmuType="{ fmuType } ", '
1538+ f'fileNamePrefix="{ fileNamePrefix } ", includeResources={ includeResourcesStr } ' )
1539+ fmu = self ._requestApi (apiName = 'buildModelFMU' , entity = self ._model_name , properties = properties )
15101540
15111541 # report proper error message
15121542 if not os .path .exists (fmu ):
@@ -1523,7 +1553,7 @@ def convertFmu2Mo(self, fmuName): # 20
15231553 >>> convertFmu2Mo("c:/BouncingBall.Fmu")
15241554 """
15251555
1526- fileName = self ._requestApi ('importFMU' , fmuName )
1556+ fileName = self ._requestApi (apiName = 'importFMU' , entity = fmuName )
15271557
15281558 # report proper error message
15291559 if not os .path .exists (fileName ):
@@ -1561,7 +1591,7 @@ def optimize(self) -> dict[str, Any]:
15611591 cName = self ._model_name
15621592 properties = ',' .join (f"{ key } ={ val } " for key , val in self ._optimization_options .items ())
15631593 self .setCommandLineOptions ("-g=Optimica" )
1564- optimizeResult = self ._requestApi ('optimize' , cName , properties )
1594+ optimizeResult = self ._requestApi (apiName = 'optimize' , entity = cName , properties = properties )
15651595
15661596 return optimizeResult
15671597
@@ -1593,21 +1623,26 @@ def linearize(
15931623 compatibility, because linearize() used to return `[A, B, C, D]`.
15941624 """
15951625
1596- if self ._xml_file is None :
1626+ if len (self ._quantities ) == 0 :
1627+ # if self._quantities has no content, the xml file was not parsed; see self._xmlparse()
15971628 raise ModelicaSystemError (
15981629 "Linearization cannot be performed as the model is not build, "
15991630 "use ModelicaSystem() to build the model first"
16001631 )
16011632
1602- om_cmd = ModelicaSystemCmd (runpath = self ._tempdir , modelname = self ._model_name , timeout = timeout )
1633+ om_cmd = ModelicaSystemCmd (
1634+ runpath = self .getWorkDirectory (),
1635+ modelname = self ._model_name ,
1636+ timeout = timeout ,
1637+ )
16031638
1604- overrideLinearFile = self ._tempdir / f'{ self ._model_name } _override_linear.txt'
1639+ overrideLinearFile = self .getWorkDirectory () / f'{ self ._model_name } _override_linear.txt'
16051640
16061641 with open (file = overrideLinearFile , mode = "w" , encoding = "utf-8" ) as fh :
1607- for key , value in self ._override_variables .items ():
1608- fh .write (f"{ key } ={ value } \n " )
1609- for key , value in self ._linearization_options .items ():
1610- fh .write (f"{ key } ={ value } \n " )
1642+ for key1 , value1 in self ._override_variables .items ():
1643+ fh .write (f"{ key1 } ={ value1 } \n " )
1644+ for key2 , value2 in self ._linearization_options .items ():
1645+ fh .write (f"{ key2 } ={ value2 } \n " )
16111646
16121647 om_cmd .arg_set (key = "overrideFile" , val = overrideLinearFile .as_posix ())
16131648
@@ -1631,19 +1666,17 @@ def linearize(
16311666 om_cmd .args_set (args = simargs )
16321667
16331668 # the file create by the model executable which contains the matrix and linear inputs, outputs and states
1634- linear_file = self ._tempdir / "linearized_model.py"
1635-
1669+ linear_file = self .getWorkDirectory () / "linearized_model.py"
16361670 linear_file .unlink (missing_ok = True )
16371671
16381672 returncode = om_cmd .run ()
16391673 if returncode != 0 :
16401674 raise ModelicaSystemError (f"Linearize failed with return code: { returncode } " )
1641-
1642- self ._simulated = True
1643-
16441675 if not linear_file .exists ():
16451676 raise ModelicaSystemError (f"Linearization failed: { linear_file } not found!" )
16461677
1678+ self ._simulated = True
1679+
16471680 # extract data from the python file with the linearized model using the ast module - this allows to get the
16481681 # needed information without executing the created code
16491682 linear_data = {}
0 commit comments