@@ -108,17 +108,54 @@ def __getitem__(self, index: int):
108108
109109
110110class ModelicaSystem :
111- def __init__ (self , fileName = None , modelName = None , lmodel = None , commandLineOptions = None ,
112- variableFilter = None , customBuildDirectory = None , verbose = True , raiseerrors = False ,
113- omhome : str = None , session : OMCSessionBase = None ): # 1
114- """
115- "constructor"
116- It initializes to load file and build a model, generating object, exe, xml, mat, and json files. etc. It can be called :
117- •without any arguments: In this case it neither loads a file nor build a model. This is useful when a FMU needed to convert to Modelica model
118- •with two arguments as file name with ".mo" extension and the model name respectively
119- •with three arguments, the first and second are file name and model name respectively and the third arguments is Modelica standard library to load a model, which is common in such models where the model is based on the standard library. For example, here is a model named "dcmotor.mo" below table 4-2, which is located in the directory of OpenModelica at "C:\\ OpenModelica1.9.4-dev.beta2\\ share\\ doc\\ omc\\ testmodels".
120- Note: If the model file is not in the current working directory, then the path where file is located must be included together with file name. Besides, if the Modelica model contains several different models within the same package, then in order to build the specific model, in second argument, user must put the package name with dot(.) followed by specific model name.
121- ex: myModel = ModelicaSystem("ModelicaModel.mo", "modelName")
111+ def __init__ (
112+ self ,
113+ fileName : Optional [str | os .PathLike ] = None ,
114+ modelName : Optional [str ] = None ,
115+ lmodel : Optional [list [str | tuple [str , str ]]] = None ,
116+ commandLineOptions : Optional [str ] = None ,
117+ variableFilter : Optional [str ] = None ,
118+ customBuildDirectory : Optional [str | os .PathLike ] = None ,
119+ verbose : bool = True ,
120+ raiseerrors : bool = False ,
121+ omhome : Optional [str ] = None ,
122+ session : Optional [OMCSessionBase ] = None
123+ ):
124+ """Initialize, load and build a model.
125+
126+ The constructor loads the model file and builds it, generating exe and
127+ xml files, etc.
128+
129+ Args:
130+ fileName: Path to the model file. Either absolute or relative to
131+ the current working directory.
132+ modelName: The name of the model class. If it is contained within
133+ a package, "PackageName.ModelName" should be used.
134+ lmodel: List of libraries to be loaded before the model itself is
135+ loaded. Two formats are supported for the list elements:
136+ lmodel=["Modelica"] for just the library name
137+ and lmodel=[("Modelica","3.2.3")] for specifying both the name
138+ and the version.
139+ commandLineOptions: String with extra command line options to be
140+ provided to omc via setCommandLineOptions().
141+ variableFilter: A regular expression. Only variables fully
142+ matching the regexp will be stored in the result file.
143+ Leaving it unspecified is equivalent to ".*".
144+ customBuildDirectory: Path to a directory to be used for temporary
145+ files like the model executable. If left unspecified, a tmp
146+ directory will be created.
147+ verbose: If True, enable verbose logging.
148+ raiseerrors: If True, raise exceptions instead of just logging
149+ OpenModelica errors.
150+ omhome: OPENMODELICAHOME value to be used when creating the OMC
151+ session.
152+ session: OMC session to be used. If unspecified, a new session
153+ will be created.
154+
155+ Examples:
156+ mod = ModelicaSystem("ModelicaModel.mo", "modelName")
157+ mod = ModelicaSystem("ModelicaModel.mo", "modelName", ["Modelica"])
158+ mod = ModelicaSystem("ModelicaModel.mo", "modelName", [("Modelica","3.2.3"), "PowerSystems"])
122159 """
123160 if fileName is None and modelName is None and not lmodel : # all None
124161 raise Exception ("Cannot create ModelicaSystem object without any arguments" )
@@ -445,14 +482,27 @@ def getContinuous(self, names=None): # 4
445482 raise ModelicaSystemError (f"OM error: { i } is not continuous" )
446483 return valuelist
447484
448- def getParameters (self , names = None ): # 5
449- """
450- This method returns dict. The key is parameter names and value is corresponding parameter value.
451- If name is None then the function will return dict which contain all parameter names as key and value as corresponding values.
452- usage:
453- >>> getParameters()
454- >>> getParameters("Name1")
455- >>> getParameters(["Name1","Name2"])
485+ def getParameters (self , names : Optional [str | list [str ]] = None ) -> dict [str , str ] | list [str ]: # 5
486+ """Get parameter values.
487+
488+ Args:
489+ names: Either None (default), a string with the parameter name,
490+ or a list of parameter name strings.
491+ Returns:
492+ If `names` is None, a dict in the format
493+ {parameter_name: parameter_value} is returned.
494+ If `names` is a string, a single element list is returned.
495+ If `names` is a list, a list with one value for each parameter name
496+ in names is returned.
497+ In all cases, parameter values are returned as strings.
498+
499+ Examples:
500+ >>> mod.getParameters()
501+ {'Name1': '1.23', 'Name2': '4.56'}
502+ >>> mod.getParameters("Name1")
503+ ['1.23']
504+ >>> mod.getParameters(["Name1","Name2"])
505+ ['1.23', '4.56']
456506 """
457507 if names is None :
458508 return self .paramlist
@@ -461,24 +511,32 @@ def getParameters(self, names=None): # 5
461511 elif isinstance (names , list ):
462512 return ([self .paramlist .get (x , "NotExist" ) for x in names ])
463513
464- def getlinearParameters (self , names = None ): # 5
465- """
466- This method returns dict. The key is parameter names and value is corresponding parameter value.
467- If *name is None then the function will return dict which contain all parameter names as key and value as corresponding values. eg., getParameters()
468- Otherwise variable number of arguments can be passed as parameter name in string format separated by commas. eg., getParameters('paraName1', 'paraName2')
469- """
470- if names is None :
471- return self .linearparameters
472- elif isinstance (names , str ):
473- return [self .linearparameters .get (names , "NotExist" )]
474- else :
475- return [self .linearparameters .get (x , "NotExist" ) for x in names ]
514+ def getInputs (self , names : Optional [str | list [str ]] = None ) -> dict | list : # 6
515+ """Get input values.
476516
477- def getInputs (self , names = None ): # 6
478- """
479- This method returns dict. The key is input names and value is corresponding input value.
480- If *name is None then the function will return dict which contain all input names as key and value as corresponding values. eg., getInputs()
481- Otherwise variable number of arguments can be passed as input name in string format separated by commas. eg., getInputs('iName1', 'iName2')
517+ Args:
518+ names: Either None (default), a string with the input name,
519+ or a list of input name strings.
520+ Returns:
521+ If `names` is None, a dict in the format
522+ {input_name: input_value} is returned.
523+ If `names` is a string, a single element list [input_value] is
524+ returned.
525+ If `names` is a list, a list with one value for each input name
526+ in names is returned: [input1_values, input2_values, ...].
527+ In all cases, input values are returned as a list of tuples,
528+ where the first element in the tuple is the time and the second
529+ element is the input value.
530+
531+ Examples:
532+ >>> mod.getInputs()
533+ {'Name1': [(0.0, 0.0), (1.0, 1.0)], 'Name2': None}
534+ >>> mod.getInputs("Name1")
535+ [[(0.0, 0.0), (1.0, 1.0)]]
536+ >>> mod.getInputs(["Name1","Name2"])
537+ [[(0.0, 0.0), (1.0, 1.0)], None]
538+ >>> mod.getInputs("ThisInputDoesNotExist")
539+ ['NotExist']
482540 """
483541 if names is None :
484542 return self .inputlist
@@ -487,14 +545,42 @@ def getInputs(self, names=None): # 6
487545 elif isinstance (names , list ):
488546 return ([self .inputlist .get (x , "NotExist" ) for x in names ])
489547
490- def getOutputs (self , names = None ): # 7
491- """
492- This method returns dict. The key is output names and value is corresponding output value.
493- If name is None then the function will return dict which contain all output names as key and value as corresponding values. eg., getOutputs()
494- usage:
495- >>> getOutputs()
496- >>> getOutputs("Name1")
497- >>> getOutputs(["Name1","Name2"])
548+ def getOutputs (self , names : Optional [str | list [str ]] = None ): # 7
549+ """Get output values.
550+
551+ If called before simulate(), the initial values are returned as
552+ strings. If called after simulate(), the final values (at stopTime)
553+ are returned as numpy.float64.
554+
555+ Args:
556+ names: Either None (default), a string with the output name,
557+ or a list of output name strings.
558+ Returns:
559+ If `names` is None, a dict in the format
560+ {output_name: output_value} is returned.
561+ If `names` is a string, a single element list [output_value] is
562+ returned.
563+ If `names` is a list, a list with one value for each output name
564+ in names is returned: [output1_value, output2_value, ...].
565+
566+ Examples:
567+ Before simulate():
568+ >>> mod.getOutputs()
569+ {'out1': '-0.4', 'out2': '1.2'}
570+ >>> mod.getOutputs("out1")
571+ ['-0.4']
572+ >>> mod.getOutputs(["out1","out2"])
573+ ['-0.4', '1.2']
574+ >>> mod.getOutputs("ThisOutputDoesNotExist")
575+ ['NotExist']
576+
577+ After simulate():
578+ >>> mod.getOutputs()
579+ {'out1': np.float64(-0.1234), 'out2': np.float64(2.1)}
580+ >>> mod.getOutputs("out1")
581+ [np.float64(-0.1234)]
582+ >>> mod.getOutputs(["out1","out2"])
583+ [np.float64(-0.1234), np.float64(2.1)]
498584 """
499585 if not self .simulationFlag :
500586 if names is None :
@@ -854,112 +940,52 @@ def checkValidInputs(self, name):
854940 else :
855941 ModelicaSystemError ('Error!!! Value must be in tuple format' )
856942
857- # To create csv file for inputs
858- def createCSVData (self ):
859- sl = [] # Actual timestamps
860- skip = False
861-
862- # check for NONE in input list and replace with proper data (e.g) [(startTime, 0.0), (stopTime, 0.0)]
863- tmpinputlist = {}
864- for key , value in self .inputlist .items ():
865- if value is None :
866- tmpinputlist [key ] = [(float (self .simulateOptions ["startTime" ]), 0.0 ),
867- (float (self .simulateOptions ["stopTime" ]), 0.0 )]
943+ def createCSVData (self ) -> None :
944+ start_time : float = float (self .simulateOptions ["startTime" ])
945+ stop_time : float = float (self .simulateOptions ["stopTime" ])
946+
947+ # Replace None inputs with a default constant zero signal
948+ inputs : dict [str , list [tuple [float , float ]]] = {}
949+ for input_name , input_signal in self .inputlist .items ():
950+ if input_signal is None :
951+ inputs [input_name ] = [(start_time , 0.0 ), (stop_time , 0.0 )]
868952 else :
869- tmpinputlist [key ] = value
870-
871- inp = list (tmpinputlist .values ())
872-
873- for i in inp :
874- cl = list ()
875- el = list ()
876- for t , x in i :
877- cl .append (t )
878- for i in cl :
879- if skip is True :
880- skip = False
881- continue
882- if i not in sl :
883- el .append (i )
884- else :
885- elem_no = cl .count (i )
886- sl_no = sl .count (i )
887- if elem_no == 2 and sl_no == 1 :
888- el .append (i )
889- skip = True
890- sl = sl + el
891-
892- sl .sort ()
893- for t in sl :
894- for i in inp :
895- for ttt in [tt [0 ] for tt in i ]:
896- if t not in [tt [0 ] for tt in i ]:
897- i .append ((t , '?' ))
898- inpSortedList = list ()
899- sortedList = list ()
900- for i in inp :
901- sortedList = sorted (i , key = lambda x : x [0 ])
902- inpSortedList .append (sortedList )
903- for i in inpSortedList :
904- ind = 0
905- for t , x in i :
906- if x == '?' :
907- t1 = i [ind - 1 ][0 ]
908- u1 = i [ind - 1 ][1 ]
909- t2 = i [ind + 1 ][0 ]
910- u2 = i [ind + 1 ][1 ]
911- nex = 2
912- while (u2 == '?' ):
913- u2 = i [ind + nex ][1 ]
914- t2 = i [ind + nex ][0 ]
915- nex += 1
916- x = float (u1 + (u2 - u1 ) * (t - t1 ) / (t2 - t1 ))
917- i [ind ] = (t , x )
918- ind += 1
919- slSet = list ()
920- slSet = set (sl )
921- for i in inpSortedList :
922- tempTime = list ()
923- for (t , x ) in i :
924- tempTime .append (t )
925- inSl = None
926- inI = None
927- for s in slSet :
928- inSl = sl .count (s )
929- inI = tempTime .count (s )
930- if inSl != inI :
931- test = list ()
932- test = [(x , y ) for x , y in i if x == s ]
933- i .append (test [0 ])
934- newInpList = list ()
935- tempSorting = list ()
936- for i in inpSortedList :
937- # i.sort() => just sorting might not work so need to sort according to 1st element of a tuple
938- tempSorting = sorted (i , key = lambda x : x [0 ])
939- newInpList .append (tempSorting )
940-
941- interpolated_inputs_all = list ()
942- for i in newInpList :
943- templist = list ()
944- for (t , x ) in i :
945- templist .append (x )
946- interpolated_inputs_all .append (templist )
947-
948- name = ',' .join (list (self .inputlist .keys ()))
949- name = f'time,{ name } ,end'
950-
951- a = ''
952- l = []
953- l .append (name )
954- for i in range (0 , len (sl )):
955- a = f'{ float (sl [i ])} ,{ "," .join (str (float (inppp [i ])) for inppp in interpolated_inputs_all )} ,0'
956- l .append (a )
957-
958- self .csvFile = (pathlib .Path (self .tempdir ) / f'{ self .modelName } .csv' ).as_posix ()
953+ inputs [input_name ] = input_signal
954+
955+ # Collect all unique timestamps across all input signals
956+ all_times = np .array (
957+ sorted ({t for signal in inputs .values () for t , _ in signal }),
958+ dtype = float
959+ )
960+
961+ # Interpolate missing values
962+ interpolated_inputs : dict [str , np .ndarray ] = {}
963+ for signal_name , signal_values in inputs .items ():
964+ signal = np .array (signal_values )
965+ interpolated_inputs [signal_name ] = np .interp (
966+ all_times ,
967+ signal [:, 0 ], # times
968+ signal [:, 1 ] # values
969+ )
970+
971+ # Write CSV file
972+ input_names = list (interpolated_inputs .keys ())
973+ header = ['time' ] + input_names + ['end' ]
974+
975+ csv_rows = [header ]
976+ for i , t in enumerate (all_times ):
977+ row = [
978+ t , # time
979+ * (interpolated_inputs [name ][i ] for name in input_names ), # input values
980+ 0 # trailing 'end' column
981+ ]
982+ csv_rows .append (row )
983+
984+ self .csvFile : str = (pathlib .Path (self .tempdir ) / f'{ self .modelName } .csv' ).as_posix ()
985+
959986 with open (self .csvFile , "w" , newline = "" ) as f :
960- writer = csv .writer (f , delimiter = '\n ' )
961- writer .writerow (l )
962- f .close ()
987+ writer = csv .writer (f )
988+ writer .writerows (csv_rows )
963989
964990 # to convert Modelica model to FMU
965991 def convertMo2Fmu (self , version = "2.0" , fmuType = "me_cs" , fileNamePrefix = "<default>" , includeResources = True ): # 19
0 commit comments