From 849e3e85f1eda969048673f008500926fc47004a Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 09:51:50 +0200 Subject: [PATCH 01/18] [ModelicaSystem] add type hints for set*() functions and rename arguments * fix some type hint issues in setInput() * prepare for definition via dictionary replacing 'a=b' and '[a=b, c=d]' style --- OMPython/ModelicaSystem.py | 186 +++++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 61 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 2f02b710..fb4769a3 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1081,46 +1081,82 @@ def _strip_space(name): raise ModelicaSystemError("Unhandled input for strip_space()") - def _setMethodHelper(self, args1, args2, args3, args4=None): - """Helper function for setters. + def _setMethodHelper( + self, + inputdata: str | list[str] | dict[str, str | int | float], + classdata: dict[str, Any], + datatype: str, + overwritedata: Optional[dict[str, str]] = None, + ) -> bool: + """ + Helper function for setters. args1 - string or list of string given by user args2 - dict() containing the values of different variables(eg:, parameter,continuous,simulation parameters) args3 - function name (eg; continuous, parameter, simulation, linearization,optimization) args4 - dict() which stores the new override variables list, """ - def apply_single(args1): - args1 = self._strip_space(args1) - value = args1.split("=") - if value[0] in args2: - if args3 == "parameter" and self.isParameterChangeable(value[0], value[1]): - args2[value[0]] = value[1] - if args4 is not None: - args4[value[0]] = value[1] - elif args3 != "parameter": - args2[value[0]] = value[1] - if args4 is not None: - args4[value[0]] = value[1] + + # TODO: cleanup / data handling / ... + # (1) args1: Optional[str | list[str]] -> convert to dict + # (2) work on dict! inputs: dict[str, str | int | float | ?numbers.number?] + # (3) handle function + # (4) use it also for other functions with such an input, i.e. 'key=value' | ['key=value'] + # (5) include setInputs() + + def apply_single(key_val: str): + key_val_list = key_val.split("=") + if len(key_val_list) != 2: + raise ModelicaSystemError(f"Invalid key = value pair: {key_val}") + + name = self._strip_space(key_val_list[0]) + value = self._strip_space(key_val_list[1]) + + if name in classdata: + if datatype == "parameter" and not self.isParameterChangeable(name): + logger.debug(f"It is not possible to set the parameter {repr(name)}. It seems to be " + "structural, final, protected, evaluated or has a non-constant binding. " + "Use sendExpression(...) and rebuild the model using buildModel() API; example: " + "sendExpression(\"setParameterValue(" + f"{self.modelName}, {name}, {value if value is not None else ''}" + ")\") ") + return False + + classdata[name] = value + if overwritedata is not None: + overwritedata[name] = value return True else: - raise ModelicaSystemError("Unhandled case in _setMethodHelper.apply_single() - " - f"{repr(value[0])} is not a {repr(args3)} variable") + raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - " + f"{repr(name)} is not a {repr(datatype)} variable") result = [] - if isinstance(args1, str): - result = [apply_single(args1)] + if isinstance(inputdata, str): + result = [apply_single(inputdata)] - elif isinstance(args1, list): + elif isinstance(inputdata, list): result = [] - args1 = self._strip_space(args1) - for var in args1: + inputdata = self._strip_space(inputdata) + for var in inputdata: result.append(apply_single(var)) return all(result) - def setContinuous(self, cvals): # 13 + def isParameterChangeable( + self, + name: str, + ): + q = self.getQuantities(name) + if q[0]["changeable"] == "false": + return False + return True + + def setContinuous( + self, + cvals: str | list[str] | dict[str, str | int | float], + ) -> bool: """ This method is used to set continuous values. It can be called: with a sequence of continuous name and assigning corresponding values as arguments as show in the example below: @@ -1128,9 +1164,16 @@ def setContinuous(self, cvals): # 13 >>> setContinuous("Name=value") >>> setContinuous(["Name1=value1","Name2=value2"]) """ - return self._setMethodHelper(cvals, self._continuous, "continuous", self._override_variables) + return self._setMethodHelper( + inputdata=cvals, + classdata=self.continuouslist, + datatype="continuous", + overwritedata=self.overridevariables) - def setParameters(self, pvals): # 14 + def setParameters( + self, + pvals: str | list[str] | dict[str, str | int | float], + ) -> bool: """ This method is used to set parameter values. It can be called: with a sequence of parameter name and assigning corresponding value as arguments as show in the example below: @@ -1138,19 +1181,16 @@ def setParameters(self, pvals): # 14 >>> setParameters("Name=value") >>> setParameters(["Name1=value1","Name2=value2"]) """ - return self._setMethodHelper(pvals, self._params, "parameter", self._override_variables) + return self._setMethodHelper( + inputdata=pvals, + classdata=self.paramlist, + datatype="parameter", + overwritedata=self.overridevariables) - def isParameterChangeable(self, name, value): - q = self.getQuantities(name) - if q[0]["changeable"] == "false": - logger.debug(f"setParameters() failed : It is not possible to set the following signal {repr(name)}. " - "It seems to be structural, final, protected or evaluated or has a non-constant binding, " - f"use sendExpression(\"setParameterValue({self._model_name}, {name}, {value})\") " - "and rebuild the model using buildModel() API") - return False - return True - - def setSimulationOptions(self, simOptions): # 16 + def setSimulationOptions( + self, + simOptions: str | list[str] | dict[str, str | int | float], + ) -> bool: """ This method is used to set simulation options. It can be called: with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below: @@ -1158,9 +1198,16 @@ def setSimulationOptions(self, simOptions): # 16 >>> setSimulationOptions("Name=value") >>> setSimulationOptions(["Name1=value1","Name2=value2"]) """ - return self._setMethodHelper(simOptions, self._simulate_options, "simulation-option", self._simulate_options_override) + return _self.setMethodHelper( + inputdata=simOptions, + classdata=self.simulateOptions, + datatype="simulation-option", + overwritedata=self.simoptionsoverride) - def setLinearizationOptions(self, linearizationOptions): # 18 + def setLinearizationOptions( + self, + linearizationOptions: str | list[str] | dict[str, str | int | float], + ) -> bool: """ This method is used to set linearization options. It can be called: with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below @@ -1168,9 +1215,13 @@ def setLinearizationOptions(self, linearizationOptions): # 18 >>> setLinearizationOptions("Name=value") >>> setLinearizationOptions(["Name1=value1","Name2=value2"]) """ - return self._setMethodHelper(linearizationOptions, self._linearization_options, "Linearization-option", None) + return self._setMethodHelper( + inputdata=linearizationOptions, + classdata=self.linearOptions, + datatype="Linearization-option", + overwritedata=None) - def setOptimizationOptions(self, optimizationOptions): # 17 + def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, str | int | float]) -> bool: """ This method is used to set optimization options. It can be called: with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below: @@ -1178,9 +1229,16 @@ def setOptimizationOptions(self, optimizationOptions): # 17 >>> setOptimizationOptions("Name=value") >>> setOptimizationOptions(["Name1=value1","Name2=value2"]) """ - return self._setMethodHelper(optimizationOptions, self._optimization_options, "optimization-option", None) + return self._setMethodHelper( + inputdata=optimizationOptions, + classdata=self.optimizeOptions, + datatype="optimization-option", + overwritedata=None) - def setInputs(self, name): # 15 + def setInputs( + self, + name: str | list[str] | dict[str, str | int | float], + ) -> bool: """ This method is used to set input values. It can be called: with a sequence of input name and assigning corresponding values as arguments as show in the example below: @@ -1189,34 +1247,40 @@ def setInputs(self, name): # 15 >>> setInputs(["Name1=value1","Name2=value2"]) """ if isinstance(name, str): - name = self._strip_space(name) - value = name.split("=") - if value[0] in self._inputs: - tmpvalue = eval(value[1]) + name1: str = name + name1 = name1.replace(" ", "") + value1 = name1.split("=") + if value1[0] in self.inputlist: + tmpvalue = eval(value1[1]) if isinstance(tmpvalue, (int, float)): - self._inputs[value[0]] = [(float(self._simulate_options["startTime"]), float(value[1])), - (float(self._simulate_options["stopTime"]), float(value[1]))] + self.inputlist[value1[0]] = [(float(self._simulateOptions["startTime"]), float(value1[1])), + (float(self._simulateOptions["stopTime"]), float(value1[1]))] elif isinstance(tmpvalue, list): self._checkValidInputs(tmpvalue) - self._inputs[value[0]] = tmpvalue - self._has_inputs = True + self._inputs[value1[0]] = tmpvalue + self._inputFlag = True else: - raise ModelicaSystemError(f"{value[0]} is not an input") + raise ModelicaSystemError(f"{value1[0]} is not an input") elif isinstance(name, list): - name = self._strip_space(name) - for var in name: - value = var.split("=") - if value[0] in self._inputs: - tmpvalue = eval(value[1]) + name_list: list[str] = name + for name2 in name_list: + name2 = name2.replace(" ", "") + value2 = name2.split("=") + if value2[0] in self.inputlist: + tmpvalue = eval(value2[1]) if isinstance(tmpvalue, (int, float)): - self._inputs[value[0]] = [(float(self._simulate_options["startTime"]), float(value[1])), - (float(self._simulate_options["stopTime"]), float(value[1]))] + self.inputlist[value2[0]] = [(float(self._simulateOptions["startTime"]), float(value2[1])), + (float(self.:simulateOptions["stopTime"]), float(value2[1]))] elif isinstance(tmpvalue, list): self._checkValidInputs(tmpvalue) - self._inputs[value[0]] = tmpvalue - self._has_inputs = True + self._inputs[value2[0]] = tmpvalue + self._inputFlag = True else: - raise ModelicaSystemError(f"{value[0]} is not an input!") + raise ModelicaSystemError(f"{value2[0]} is not an input!") + elif isinstance(name, dict): + raise NotImplementedError("Must be defined!") + + return True def _checkValidInputs(self, name): if name != sorted(name, key=lambda x: x[0]): From 03069f5bac5f4d64b2bed6d4c52118d4b0725370 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 10:38:58 +0200 Subject: [PATCH 02/18] [ModelicaSystem] add _prepare_inputdata() --- OMPython/ModelicaSystem.py | 46 +++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index fb4769a3..b804ca89 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1081,7 +1081,51 @@ def _strip_space(name): raise ModelicaSystemError("Unhandled input for strip_space()") - def _setMethodHelper( + def _prepare_inputdata( + self, + rawinput: str | list[str] | dict[str, str | int | float], + ) -> dict[str, str]: + """ + Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}. + """ + + def prepare_str(str_in: str) -> dict[str, str]: + str_in = str_in.replace(" ", "") + key_val_list: list[str] = str_in.split("=") + if len(key_val_list) != 2: + raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}") + + inputdata = {key_val_list[0]: key_val_list[1]} + + return inputdata + + if isinstance(rawinput, str): + warnings.warn(message="The definition of values to set should use a dictionary, " + "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " + "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", + category=DeprecationWarning, + stacklevel=3) + return prepare_str(rawinput) + + if isinstance(rawinput, list): + warnings.warn(message="The definition of values to set should use a dictionary, " + "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " + "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", + category=DeprecationWarning, + stacklevel=3) + + inputdata: dict[str, str] = {} + for item in rawinput: + inputdata |= prepare_str(item) + + return inputdata + + if isinstance(rawinput, dict): + inputdata = {key: str(val) for key, val in rawinput.items()} + + return inputdata + + def setMethodHelper( self, inputdata: str | list[str] | dict[str, str | int | float], classdata: dict[str, Any], From f40957f09e349e0da5f01e75dfcc2d6eb7368287 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 10:39:55 +0200 Subject: [PATCH 03/18] [ModelicaSystem] update _set_method_helper() * rename from setMethodHelper() * use _prepare_inputdata() * cleanup code to align with new input as dict[str, str] * setInput() is a special case --- OMPython/ModelicaSystem.py | 157 +++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index b804ca89..9ed32d83 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1072,17 +1072,7 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op return np_res @staticmethod - def _strip_space(name): - if isinstance(name, str): - return name.replace(" ", "") - - if isinstance(name, list): - return [x.replace(" ", "") for x in name] - - raise ModelicaSystemError("Unhandled input for strip_space()") - def _prepare_inputdata( - self, rawinput: str | list[str] | dict[str, str | int | float], ) -> dict[str, str]: """ @@ -1121,72 +1111,65 @@ def prepare_str(str_in: str) -> dict[str, str]: return inputdata if isinstance(rawinput, dict): - inputdata = {key: str(val) for key, val in rawinput.items()} + for key, val in rawinput.items(): + str_val = str(val) + if ' ' in key or ' ' in str_val: + raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!") + inputdata[key] = str_val return inputdata - def setMethodHelper( + def _set_method_helper( self, - inputdata: str | list[str] | dict[str, str | int | float], + inputdata: dict[str, str], classdata: dict[str, Any], datatype: str, overwritedata: Optional[dict[str, str]] = None, ) -> bool: """ - Helper function for setters. + Helper function for: + * setParameter() + * setContinuous() + * setSimulationOptions() + * setLinearizationOption() + * setOptimizationOption() + * setInputs() - args1 - string or list of string given by user - args2 - dict() containing the values of different variables(eg:, parameter,continuous,simulation parameters) - args3 - function name (eg; continuous, parameter, simulation, linearization,optimization) - args4 - dict() which stores the new override variables list, + Parameters + ---------- + inputdata + string or list of string given by user + classdata + dict() containing the values of different variables (eg: parameter, continuous, simulation parameters) + datatype + type identifier (eg; continuous, parameter, simulation, linearization, optimization) + overwritedata + dict() which stores the new override variables list, """ - # TODO: cleanup / data handling / ... - # (1) args1: Optional[str | list[str]] -> convert to dict - # (2) work on dict! inputs: dict[str, str | int | float | ?numbers.number?] - # (3) handle function - # (4) use it also for other functions with such an input, i.e. 'key=value' | ['key=value'] - # (5) include setInputs() - - def apply_single(key_val: str): - key_val_list = key_val.split("=") - if len(key_val_list) != 2: - raise ModelicaSystemError(f"Invalid key = value pair: {key_val}") - - name = self._strip_space(key_val_list[0]) - value = self._strip_space(key_val_list[1]) - - if name in classdata: - if datatype == "parameter" and not self.isParameterChangeable(name): - logger.debug(f"It is not possible to set the parameter {repr(name)}. It seems to be " + inputdata_status: dict[str, bool] = {} + for key, val in inputdata.items(): + status = False + if key in classdata: + if datatype == "parameter" and not self.isParameterChangeable(key): + logger.debug(f"It is not possible to set the parameter {repr(key)}. It seems to be " "structural, final, protected, evaluated or has a non-constant binding. " "Use sendExpression(...) and rebuild the model using buildModel() API; example: " "sendExpression(\"setParameterValue(" - f"{self.modelName}, {name}, {value if value is not None else ''}" + f"{self.modelName}, {key}, {val if val is not None else ''}" ")\") ") - return False - - classdata[name] = value - if overwritedata is not None: - overwritedata[name] = value - - return True - + else: + classdata[key] = val + if overwritedata is not None: + overwritedata[key] = val + status = True else: raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - " - f"{repr(name)} is not a {repr(datatype)} variable") - - result = [] - if isinstance(inputdata, str): - result = [apply_single(inputdata)] + f"{repr(key)} is not a {repr(datatype)} variable") - elif isinstance(inputdata, list): - result = [] - inputdata = self._strip_space(inputdata) - for var in inputdata: - result.append(apply_single(var)) + inputdata_status[key] = status - return all(result) + return all(inputdata_status.values()) def isParameterChangeable( self, @@ -1205,11 +1188,14 @@ def setContinuous( This method is used to set continuous values. It can be called: with a sequence of continuous name and assigning corresponding values as arguments as show in the example below: usage - >>> setContinuous("Name=value") - >>> setContinuous(["Name1=value1","Name2=value2"]) + >>> setContinuous("Name=value") # depreciated + >>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated + >>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"}) """ - return self._setMethodHelper( - inputdata=cvals, + inputdata = self._prepare_inputdata(rawinput=cvals) + + return self._set_method_helper( + inputdata=inputdata, classdata=self.continuouslist, datatype="continuous", overwritedata=self.overridevariables) @@ -1222,11 +1208,14 @@ def setParameters( This method is used to set parameter values. It can be called: with a sequence of parameter name and assigning corresponding value as arguments as show in the example below: usage - >>> setParameters("Name=value") - >>> setParameters(["Name1=value1","Name2=value2"]) + >>> setParameters("Name=value") # depreciated + >>> setParameters(["Name1=value1","Name2=value2"]) # depreciated + >>> setParameters(pvals={"Name1": "value1", "Name2": "value2"}) """ - return self._setMethodHelper( - inputdata=pvals, + inputdata = self._prepare_inputdata(rawinput=pvals) + + return self._set_method_helper( + inputdata=inputdata, classdata=self.paramlist, datatype="parameter", overwritedata=self.overridevariables) @@ -1239,11 +1228,14 @@ def setSimulationOptions( This method is used to set simulation options. It can be called: with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below: usage - >>> setSimulationOptions("Name=value") - >>> setSimulationOptions(["Name1=value1","Name2=value2"]) + >>> setSimulationOptions("Name=value") # depreciated + >>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated + >>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"}) """ - return _self.setMethodHelper( - inputdata=simOptions, + inputdata = self._prepare_inputdata(rawinput=simOptions) + + return self._set_method_helper( + inputdata=inputdata, classdata=self.simulateOptions, datatype="simulation-option", overwritedata=self.simoptionsoverride) @@ -1256,11 +1248,14 @@ def setLinearizationOptions( This method is used to set linearization options. It can be called: with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below usage - >>> setLinearizationOptions("Name=value") - >>> setLinearizationOptions(["Name1=value1","Name2=value2"]) + >>> setLinearizationOptions("Name=value") # depreciated + >>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated + >>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"}) """ - return self._setMethodHelper( - inputdata=linearizationOptions, + inputdata = self._prepare_inputdata(rawinput=linearizationOptions) + + return self._set_method_helper( + inputdata=inputdata, classdata=self.linearOptions, datatype="Linearization-option", overwritedata=None) @@ -1270,11 +1265,14 @@ def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str This method is used to set optimization options. It can be called: with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below: usage - >>> setOptimizationOptions("Name=value") - >>> setOptimizationOptions(["Name1=value1","Name2=value2"]) + >>> setOptimizationOptions("Name=value") # depreciated + >>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated + >>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"}) """ - return self._setMethodHelper( - inputdata=optimizationOptions, + inputdata = self._prepare_inputdata(rawinput=optimizationOptions) + + return self._set_method_helper( + inputdata=inputdata, classdata=self.optimizeOptions, datatype="optimization-option", overwritedata=None) @@ -1287,9 +1285,12 @@ def setInputs( This method is used to set input values. It can be called: with a sequence of input name and assigning corresponding values as arguments as show in the example below: usage - >>> setInputs("Name=value") - >>> setInputs(["Name1=value1","Name2=value2"]) + >>> setInputs("Name=value") # depreciated + >>> setInputs(["Name1=value1","Name2=value2"]) # depreciated + >>> setInputs(name={"Name1": "value1", "Name2": "value2"}) """ + # inputdata = self._prepare_inputdata(rawinput=name) + if isinstance(name, str): name1: str = name name1 = name1.replace(" ", "") From ee2ee109ad30aa4e660c1bdd3b01bb12ba31cd95 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 16:15:29 +0200 Subject: [PATCH 04/18] [ModelicaSystem] improve definition of _prepare_inputdata() --- OMPython/ModelicaSystem.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 9ed32d83..98bfa4dd 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1085,9 +1085,11 @@ def prepare_str(str_in: str) -> dict[str, str]: if len(key_val_list) != 2: raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}") - inputdata = {key_val_list[0]: key_val_list[1]} + input_data_from_str: dict[str, str] = {key_val_list[0]: key_val_list[1]} - return inputdata + return input_data_from_str + + input_data: dict[str, str] = {} if isinstance(rawinput, str): warnings.warn(message="The definition of values to set should use a dictionary, " @@ -1104,20 +1106,21 @@ def prepare_str(str_in: str) -> dict[str, str]: category=DeprecationWarning, stacklevel=3) - inputdata: dict[str, str] = {} for item in rawinput: - inputdata |= prepare_str(item) + input_data |= prepare_str(item) - return inputdata + return input_data if isinstance(rawinput, dict): for key, val in rawinput.items(): str_val = str(val) if ' ' in key or ' ' in str_val: raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!") - inputdata[key] = str_val + input_data[key] = str_val + + return input_data - return inputdata + raise ModelicaSystemError(f"Invalid type of input: {type(rawinput)}") def _set_method_helper( self, From 7cd874810145a2e8d0cf884715de66128b5fd46e Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 16:16:02 +0200 Subject: [PATCH 05/18] [ModelicaSystem] rename _prepare_inputdata() => _prepare_input_data() --- OMPython/ModelicaSystem.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 98bfa4dd..425a5bca 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1072,8 +1072,8 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op return np_res @staticmethod - def _prepare_inputdata( - rawinput: str | list[str] | dict[str, str | int | float], + def _prepare_input_data( + raw_input: str | list[str] | dict[str, str | int | float], ) -> dict[str, str]: """ Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}. @@ -1091,28 +1091,28 @@ def prepare_str(str_in: str) -> dict[str, str]: input_data: dict[str, str] = {} - if isinstance(rawinput, str): + if isinstance(raw_input, str): warnings.warn(message="The definition of values to set should use a dictionary, " "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", category=DeprecationWarning, stacklevel=3) - return prepare_str(rawinput) + return prepare_str(raw_input) - if isinstance(rawinput, list): + if isinstance(raw_input, list): warnings.warn(message="The definition of values to set should use a dictionary, " "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", category=DeprecationWarning, stacklevel=3) - for item in rawinput: + for item in raw_input: input_data |= prepare_str(item) return input_data - if isinstance(rawinput, dict): - for key, val in rawinput.items(): + if isinstance(raw_input, dict): + for key, val in raw_input.items(): str_val = str(val) if ' ' in key or ' ' in str_val: raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!") @@ -1120,7 +1120,7 @@ def prepare_str(str_in: str) -> dict[str, str]: return input_data - raise ModelicaSystemError(f"Invalid type of input: {type(rawinput)}") + raise ModelicaSystemError(f"Invalid type of input: {type(raw_input)}") def _set_method_helper( self, @@ -1195,7 +1195,7 @@ def setContinuous( >>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated >>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"}) """ - inputdata = self._prepare_inputdata(rawinput=cvals) + inputdata = self._prepare_input_data(raw_input=cvals) return self._set_method_helper( inputdata=inputdata, @@ -1215,7 +1215,7 @@ def setParameters( >>> setParameters(["Name1=value1","Name2=value2"]) # depreciated >>> setParameters(pvals={"Name1": "value1", "Name2": "value2"}) """ - inputdata = self._prepare_inputdata(rawinput=pvals) + inputdata = self._prepare_input_data(raw_input=pvals) return self._set_method_helper( inputdata=inputdata, @@ -1235,7 +1235,7 @@ def setSimulationOptions( >>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated >>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"}) """ - inputdata = self._prepare_inputdata(rawinput=simOptions) + inputdata = self._prepare_input_data(raw_input=simOptions) return self._set_method_helper( inputdata=inputdata, @@ -1255,7 +1255,7 @@ def setLinearizationOptions( >>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated >>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"}) """ - inputdata = self._prepare_inputdata(rawinput=linearizationOptions) + inputdata = self._prepare_input_data(raw_input=linearizationOptions) return self._set_method_helper( inputdata=inputdata, @@ -1272,7 +1272,7 @@ def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str >>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated >>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"}) """ - inputdata = self._prepare_inputdata(rawinput=optimizationOptions) + inputdata = self._prepare_input_data(raw_input=optimizationOptions) return self._set_method_helper( inputdata=inputdata, @@ -1292,7 +1292,7 @@ def setInputs( >>> setInputs(["Name1=value1","Name2=value2"]) # depreciated >>> setInputs(name={"Name1": "value1", "Name2": "value2"}) """ - # inputdata = self._prepare_inputdata(rawinput=name) + # inputdata = self._prepare_input_data(raw_input=name) if isinstance(name, str): name1: str = name From a3e32e4c88344779ddfe704e39f2bda254144bd4 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 16:33:58 +0200 Subject: [PATCH 06/18] [ModelicaSystem] update setInput() * replace eval() with ast.literal_eval() as a saver version * use _prepare_input_data() * simplify code --- OMPython/ModelicaSystem.py | 73 ++++++++++++++------------------------ 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 425a5bca..8ed5bb83 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -32,6 +32,7 @@ CONDITIONS OF OSMC-PL. """ +import ast import csv from dataclasses import dataclass import importlib @@ -1292,57 +1293,37 @@ def setInputs( >>> setInputs(["Name1=value1","Name2=value2"]) # depreciated >>> setInputs(name={"Name1": "value1", "Name2": "value2"}) """ - # inputdata = self._prepare_input_data(raw_input=name) - - if isinstance(name, str): - name1: str = name - name1 = name1.replace(" ", "") - value1 = name1.split("=") - if value1[0] in self.inputlist: - tmpvalue = eval(value1[1]) - if isinstance(tmpvalue, (int, float)): - self.inputlist[value1[0]] = [(float(self._simulateOptions["startTime"]), float(value1[1])), - (float(self._simulateOptions["stopTime"]), float(value1[1]))] - elif isinstance(tmpvalue, list): - self._checkValidInputs(tmpvalue) - self._inputs[value1[0]] = tmpvalue + inputdata = self._prepare_input_data(raw_input=name) + + for key, val in inputdata.items(): + if key in self._inputs: + val_evaluated = ast.literal_eval(val) + if isinstance(val_evaluated, (int, float)): + self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), + (float(self._simulate_options["stopTime"]), float(val))] + elif isinstance(val_evaluated, list): + if not all([isinstance(item, tuple) for item in val_evaluated]): + raise ModelicaSystemError("Value for setInput() must be in tuple format; " + f"got {repr(val_evaluated)}") + if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): + raise ModelicaSystemError("Time value should be in increasing order; " + f"got {repr(val_evaluated)}") + + for item in val_evaluated: + if item[0] < float(self._simulate_options["startTime"]): + raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less " + "than the simulation start time") + if len(item) != 2: + raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} " + "is in incorrect format!") + + self._inputs[key] = val_evaluated self._inputFlag = True else: - raise ModelicaSystemError(f"{value1[0]} is not an input") - elif isinstance(name, list): - name_list: list[str] = name - for name2 in name_list: - name2 = name2.replace(" ", "") - value2 = name2.split("=") - if value2[0] in self.inputlist: - tmpvalue = eval(value2[1]) - if isinstance(tmpvalue, (int, float)): - self.inputlist[value2[0]] = [(float(self._simulateOptions["startTime"]), float(value2[1])), - (float(self.:simulateOptions["stopTime"]), float(value2[1]))] - elif isinstance(tmpvalue, list): - self._checkValidInputs(tmpvalue) - self._inputs[value2[0]] = tmpvalue - self._inputFlag = True - else: - raise ModelicaSystemError(f"{value2[0]} is not an input!") - elif isinstance(name, dict): - raise NotImplementedError("Must be defined!") + raise ModelicaSystemError(f"{key} is not an input") return True - def _checkValidInputs(self, name): - if name != sorted(name, key=lambda x: x[0]): - raise ModelicaSystemError('Time value should be in increasing order') - for l in name: - if isinstance(l, tuple): - # if l[0] < float(self.simValuesList[0]): - if l[0] < float(self._simulate_options["startTime"]): - raise ModelicaSystemError('Input time value is less than simulation startTime') - if len(l) != 2: - raise ModelicaSystemError(f'Value for {l} is in incorrect format!') - else: - raise ModelicaSystemError('Error!!! Value must be in tuple format') - def _createCSVData(self) -> pathlib.Path: start_time: float = float(self._simulate_options["startTime"]) stop_time: float = float(self._simulate_options["stopTime"]) From e91ef299e6f175351d9de54254c099b69901b052 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 16:49:52 +0200 Subject: [PATCH 07/18] update tests - use new dict based input for set*() methods --- tests/test_ModelicaSystem.py | 20 ++++++++++---------- tests/test_linearization.py | 2 +- tests/test_optimization.py | 8 +++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index b4d328e9..9e900c1c 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -35,8 +35,8 @@ def test_setParameters(): mod = OMPython.ModelicaSystem(model_path + "BouncingBall.mo", "BouncingBall") # method 1 - mod.setParameters("e=1.234") - mod.setParameters("g=321.0") + mod.setParameters(pvals={"e": 1.234}) + mod.setParameters(pvals={"g": 321.0}) assert mod.getParameters("e") == ["1.234"] assert mod.getParameters("g") == ["321.0"] assert mod.getParameters() == { @@ -47,7 +47,7 @@ def test_setParameters(): mod.getParameters("thisParameterDoesNotExist") # method 2 - mod.setParameters(["e=21.3", "g=0.12"]) + mod.setParameters(pvals={"e": 21.3, "g": 0.12}) assert mod.getParameters() == { "e": "21.3", "g": "0.12", @@ -64,8 +64,8 @@ def test_setSimulationOptions(): mod = OMPython.ModelicaSystem(fileName=model_path + "BouncingBall.mo", modelName="BouncingBall") # method 1 - mod.setSimulationOptions("stopTime=1.234") - mod.setSimulationOptions("tolerance=1.1e-08") + mod.setSimulationOptions(simOptions={"stopTime": 1.234}) + mod.setSimulationOptions(simOptions={"tolerance": 1.1e-08}) assert mod.getSimulationOptions("stopTime") == ["1.234"] assert mod.getSimulationOptions("tolerance") == ["1.1e-08"] assert mod.getSimulationOptions(["tolerance", "stopTime"]) == ["1.1e-08", "1.234"] @@ -77,7 +77,7 @@ def test_setSimulationOptions(): mod.getSimulationOptions("thisOptionDoesNotExist") # method 2 - mod.setSimulationOptions(["stopTime=2.1", "tolerance=1.2e-08"]) + mod.setSimulationOptions(simOptions={"stopTime": 2.1, "tolerance": "1.2e-08"}) d = mod.getSimulationOptions() assert d["stopTime"] == "2.1" assert d["tolerance"] == "1.2e-08" @@ -119,7 +119,7 @@ def test_getSolutions(model_firstorder): a = -1 tau = -1 / a stopTime = 5*tau - mod.setSimulationOptions([f"stopTime={stopTime}", "stepSize=0.1", "tolerance=1e-8"]) + mod.setSimulationOptions(simOptions={"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8}) mod.simulate() x = mod.getSolutions("x") @@ -298,7 +298,7 @@ def test_getters(tmp_path): x0 = 1.0 x_analytical = -b/a + (x0 + b/a) * np.exp(a * stopTime) dx_analytical = (x0 + b/a) * a * np.exp(a * stopTime) - mod.setSimulationOptions(f"stopTime={stopTime}") + mod.setSimulationOptions(simOptions={"stopTime": stopTime}) mod.simulate() # getOutputs after simulate() @@ -327,7 +327,7 @@ def test_getters(tmp_path): mod.getContinuous("a") # a is a parameter with pytest.raises(OMPython.ModelicaSystemError): - mod.setSimulationOptions("thisOptionDoesNotExist=3") + mod.setSimulationOptions(simOptions={"thisOptionDoesNotExist": 3}) def test_simulate_inputs(tmp_path): @@ -345,7 +345,7 @@ def test_simulate_inputs(tmp_path): """) mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="M_input") - mod.setSimulationOptions("stopTime=1.0") + mod.setSimulationOptions(simOptions={"stopTime": 1.0}) # integrate zero (no setInputs call) - it should default to None -> 0 assert mod.getInputs() == { diff --git a/tests/test_linearization.py b/tests/test_linearization.py index 2c79190c..baec6202 100644 --- a/tests/test_linearization.py +++ b/tests/test_linearization.py @@ -62,7 +62,7 @@ def test_getters(tmp_path): assert "startTime" in d assert "stopTime" in d assert mod.getLinearizationOptions(["stopTime", "startTime"]) == [d["stopTime"], d["startTime"]] - mod.setLinearizationOptions("stopTime=0.02") + mod.setLinearizationOptions(linearizationOptions={"stopTime": 0.02}) assert mod.getLinearizationOptions("stopTime") == ["0.02"] mod.setInputs(["u1=10", "u2=0"]) diff --git a/tests/test_optimization.py b/tests/test_optimization.py index aa74df79..b4164397 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -35,13 +35,15 @@ def test_optimization_example(tmp_path): mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="BangBang2021") - mod.setOptimizationOptions(["numberOfIntervals=16", "stopTime=1", - "stepSize=0.001", "tolerance=1e-8"]) + mod.setOptimizationOptions(optimizationOptions={"numberOfIntervals": 16, + "stopTime": 1, + "stepSize": 0.001, + "tolerance": 1e-8}) # test the getter assert mod.getOptimizationOptions()["stopTime"] == "1" assert mod.getOptimizationOptions("stopTime") == ["1"] - assert mod.getOptimizationOptions(["tolerance", "stopTime"]) == ["1e-8", "1"] + assert mod.getOptimizationOptions(["tolerance", "stopTime"]) == ["1e-08", "1"] r = mod.optimize() # it is necessary to specify resultfile, otherwise it wouldn't find it. From 067b9be4ed89a73fe64d24ee94d3bb287745397e Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 17:02:05 +0200 Subject: [PATCH 08/18] [ModelicaSystem] add type hint for return value of isParameterChangeable() --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 8ed5bb83..08f87dea 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1178,7 +1178,7 @@ def _set_method_helper( def isParameterChangeable( self, name: str, - ): + ) -> bool: q = self.getQuantities(name) if q[0]["changeable"] == "false": return False From c0498b1e7b75f23605191acd2024be294e2c1cd7 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 17:09:46 +0200 Subject: [PATCH 09/18] [ModelicaSystem] fix type hint for _prepare_input_data() - use dict[str, Any] --- OMPython/ModelicaSystem.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 08f87dea..b4c22921 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1074,7 +1074,7 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op @staticmethod def _prepare_input_data( - raw_input: str | list[str] | dict[str, str | int | float], + raw_input: str | list[str] | dict[str, Any], ) -> dict[str, str]: """ Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}. @@ -1186,7 +1186,7 @@ def isParameterChangeable( def setContinuous( self, - cvals: str | list[str] | dict[str, str | int | float], + cvals: str | list[str] | dict[str, Any], ) -> bool: """ This method is used to set continuous values. It can be called: @@ -1206,7 +1206,7 @@ def setContinuous( def setParameters( self, - pvals: str | list[str] | dict[str, str | int | float], + pvals: str | list[str] | dict[str, Any], ) -> bool: """ This method is used to set parameter values. It can be called: @@ -1226,7 +1226,7 @@ def setParameters( def setSimulationOptions( self, - simOptions: str | list[str] | dict[str, str | int | float], + simOptions: str | list[str] | dict[str, Any], ) -> bool: """ This method is used to set simulation options. It can be called: @@ -1246,7 +1246,7 @@ def setSimulationOptions( def setLinearizationOptions( self, - linearizationOptions: str | list[str] | dict[str, str | int | float], + linearizationOptions: str | list[str] | dict[str, Any], ) -> bool: """ This method is used to set linearization options. It can be called: @@ -1264,7 +1264,10 @@ def setLinearizationOptions( datatype="Linearization-option", overwritedata=None) - def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, str | int | float]) -> bool: + def setOptimizationOptions( + self, + optimizationOptions: str | list[str] | dict[str, Any], + ) -> bool: """ This method is used to set optimization options. It can be called: with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below: @@ -1283,7 +1286,7 @@ def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str def setInputs( self, - name: str | list[str] | dict[str, str | int | float], + name: str | list[str] | dict[str, Any], ) -> bool: """ This method is used to set input values. It can be called: From 010cabfd5f4deab5d3a4b0c12887796c56eee652 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 17:14:54 +0200 Subject: [PATCH 10/18] [ModelicaSystem] setInput() - handly input data as list of tuples This method is used to set input values. It can be called with a sequence of input name and assigning corresponding values as arguments as show in the example below. Compared to other set*() methods this is a special case as value could be a list of tuples - these are converted to a string in _prepare_input_data() and restored here via ast.literal_eval(). --- OMPython/ModelicaSystem.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index b4c22921..9db4a838 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1114,7 +1114,9 @@ def prepare_str(str_in: str) -> dict[str, str]: if isinstance(raw_input, dict): for key, val in raw_input.items(): - str_val = str(val) + # convert all values to strings to align it on one type: dict[str, str] + # spaces have to be removed as setInput() could take list of tuples as input and spaces would + str_val = str(val).replace(' ', '') if ' ' in key or ' ' in str_val: raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!") input_data[key] = str_val @@ -1289,9 +1291,11 @@ def setInputs( name: str | list[str] | dict[str, Any], ) -> bool: """ - This method is used to set input values. It can be called: - with a sequence of input name and assigning corresponding values as arguments as show in the example below: - usage + This method is used to set input values. It can be called with a sequence of input name and assigning + corresponding values as arguments as show in the example below. Compared to other set*() methods this is a + special case as value could be a list of tuples - these are converted to a string in _prepare_input_data() + and restored here via ast.literal_eval(). + >>> setInputs("Name=value") # depreciated >>> setInputs(["Name1=value1","Name2=value2"]) # depreciated >>> setInputs(name={"Name1": "value1", "Name2": "value2"}) @@ -1300,7 +1304,11 @@ def setInputs( for key, val in inputdata.items(): if key in self._inputs: + if not isinstance(val, str): + raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}") + val_evaluated = ast.literal_eval(val) + if isinstance(val_evaluated, (int, float)): self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), (float(self._simulate_options["stopTime"]), float(val))] From 100114b96d45e29b84b5740f78f466b78231f3a4 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 23 Jun 2025 17:15:43 +0200 Subject: [PATCH 11/18] update tests - use new dict based input for setInput() method --- tests/test_ModelicaSystem.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index 9e900c1c..d916a78a 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -357,7 +357,7 @@ def test_simulate_inputs(tmp_path): assert np.isclose(y[-1], 0.0) # integrate a constant - mod.setInputs("u1=2.5") + mod.setInputs(name={"u1": 2.5}) assert mod.getInputs() == { "u1": [ (0.0, 2.5), @@ -370,7 +370,7 @@ def test_simulate_inputs(tmp_path): assert np.isclose(y[-1], 2.5) # now let's integrate the sum of two ramps - mod.setInputs("u1=[(0.0, 0.0), (0.5, 2), (1.0, 0)]") + mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 2), (1.0, 0)]}) assert mod.getInputs("u1") == [[ (0.0, 0.0), (0.5, 2.0), @@ -383,19 +383,17 @@ def test_simulate_inputs(tmp_path): # let's try some edge cases # unmatched startTime with pytest.raises(OMPython.ModelicaSystemError): - mod.setInputs("u1=[(-0.5, 0.0), (1.0, 1)]") + mod.setInputs(name={"u1": [(-0.5, 0.0), (1.0, 1)]}) mod.simulate() # unmatched stopTime with pytest.raises(OMPython.ModelicaSystemError): - mod.setInputs("u1=[(0.0, 0.0), (0.5, 1)]") + mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 1)]}) mod.simulate() # Let's use both inputs, but each one with different number of of # samples. This has an effect when generating the csv file. - mod.setInputs([ - "u1=[(0.0, 0), (1.0, 1)]", - "u2=[(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]", - ]) + mod.setInputs(name={"u1": [(0.0, 0), (1.0, 1)], + "u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]}) mod.simulate() assert pathlib.Path(mod._csvFile).read_text() == """time,u1,u2,end 0.0,0.0,0.0,0 From df7b0374c6b1d3e6aecdcdaab99c4caa757e9485 Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 24 Jun 2025 21:31:29 +0200 Subject: [PATCH 12/18] [test_linearization] fix setInput() call --- tests/test_linearization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_linearization.py b/tests/test_linearization.py index baec6202..6af565c6 100644 --- a/tests/test_linearization.py +++ b/tests/test_linearization.py @@ -65,7 +65,7 @@ def test_getters(tmp_path): mod.setLinearizationOptions(linearizationOptions={"stopTime": 0.02}) assert mod.getLinearizationOptions("stopTime") == ["0.02"] - mod.setInputs(["u1=10", "u2=0"]) + mod.setInputs(name={"u1": 10, "u2": 0}) [A, B, C, D] = mod.linearize() g = float(mod.getParameters("g")[0]) l = float(mod.getParameters("l")[0]) From 971add69eafb7a96a42cb3a2323b744fe793b98f Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 26 Jun 2025 20:34:19 +0200 Subject: [PATCH 13/18] [ModelicaSystem] simplify _set_method_helper() --- OMPython/ModelicaSystem.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 9db4a838..1c5e4f39 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1155,24 +1155,24 @@ def _set_method_helper( inputdata_status: dict[str, bool] = {} for key, val in inputdata.items(): - status = False - if key in classdata: - if datatype == "parameter" and not self.isParameterChangeable(key): - logger.debug(f"It is not possible to set the parameter {repr(key)}. It seems to be " - "structural, final, protected, evaluated or has a non-constant binding. " - "Use sendExpression(...) and rebuild the model using buildModel() API; example: " - "sendExpression(\"setParameterValue(" - f"{self.modelName}, {key}, {val if val is not None else ''}" - ")\") ") - else: - classdata[key] = val - if overwritedata is not None: - overwritedata[key] = val - status = True - else: + if key not in classdata: raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - " f"{repr(key)} is not a {repr(datatype)} variable") + status = False + if datatype == "parameter" and not self.isParameterChangeable(key): + logger.debug(f"It is not possible to set the parameter {repr(key)}. It seems to be " + "structural, final, protected, evaluated or has a non-constant binding. " + "Use sendExpression(...) and rebuild the model using buildModel() API; example: " + "sendExpression(\"setParameterValue(" + f"{self.modelName}, {key}, {val if val is not None else ''}" + ")\") ") + else: + classdata[key] = val + if overwritedata is not None: + overwritedata[key] = val + status = True + inputdata_status[key] = status return all(inputdata_status.values()) From 09c7624a0499dded0009747e0cd301df2716a55b Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 26 Jun 2025 21:14:55 +0200 Subject: [PATCH 14/18] [ModelicaSystem] improve setInputs() - reduce spaces / cleanup --- OMPython/ModelicaSystem.py | 57 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 1c5e4f39..f09b5e69 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1303,36 +1303,37 @@ def setInputs( inputdata = self._prepare_input_data(raw_input=name) for key, val in inputdata.items(): - if key in self._inputs: - if not isinstance(val, str): - raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}") - - val_evaluated = ast.literal_eval(val) - - if isinstance(val_evaluated, (int, float)): - self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), - (float(self._simulate_options["stopTime"]), float(val))] - elif isinstance(val_evaluated, list): - if not all([isinstance(item, tuple) for item in val_evaluated]): - raise ModelicaSystemError("Value for setInput() must be in tuple format; " - f"got {repr(val_evaluated)}") - if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): - raise ModelicaSystemError("Time value should be in increasing order; " - f"got {repr(val_evaluated)}") - - for item in val_evaluated: - if item[0] < float(self._simulate_options["startTime"]): - raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less " - "than the simulation start time") - if len(item) != 2: - raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} " - "is in incorrect format!") - - self._inputs[key] = val_evaluated - self._inputFlag = True - else: + if key not in self._inputs: raise ModelicaSystemError(f"{key} is not an input") + if not isinstance(val, str): + raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}") + + val_evaluated = ast.literal_eval(val) + + if isinstance(val_evaluated, (int, float)): + self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), + (float(self._simulate_options["stopTime"]), float(val))] + elif isinstance(val_evaluated, list): + if not all([isinstance(item, tuple) for item in val_evaluated]): + raise ModelicaSystemError("Value for setInput() must be in tuple format; " + f"got {repr(val_evaluated)}") + if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): + raise ModelicaSystemError("Time value should be in increasing order; " + f"got {repr(val_evaluated)}") + + for item in val_evaluated: + if item[0] < float(self._simulate_options["startTime"]): + raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less " + "than the simulation start time") + if len(item) != 2: + raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} " + "is in incorrect format!") + + self._inputs[key] = val_evaluated + else: + raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}") + return True def _createCSVData(self) -> pathlib.Path: From a711001c241b3f246195d27e54361d5397dbe65c Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 7 Jul 2025 20:20:02 +0200 Subject: [PATCH 15/18] [ModelicaSystem] fix rebase fallout --- OMPython/ModelicaSystem.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index f09b5e69..e821dc35 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1165,7 +1165,7 @@ def _set_method_helper( "structural, final, protected, evaluated or has a non-constant binding. " "Use sendExpression(...) and rebuild the model using buildModel() API; example: " "sendExpression(\"setParameterValue(" - f"{self.modelName}, {key}, {val if val is not None else ''}" + f"{self._model_name}, {key}, {val if val is not None else ''}" ")\") ") else: classdata[key] = val @@ -1202,9 +1202,9 @@ def setContinuous( return self._set_method_helper( inputdata=inputdata, - classdata=self.continuouslist, + classdata=self._continuous, datatype="continuous", - overwritedata=self.overridevariables) + overwritedata=self._override_variables) def setParameters( self, @@ -1222,9 +1222,9 @@ def setParameters( return self._set_method_helper( inputdata=inputdata, - classdata=self.paramlist, + classdata=self._params, datatype="parameter", - overwritedata=self.overridevariables) + overwritedata=self._override_variables) def setSimulationOptions( self, @@ -1242,9 +1242,9 @@ def setSimulationOptions( return self._set_method_helper( inputdata=inputdata, - classdata=self.simulateOptions, + classdata=self._simulate_options, datatype="simulation-option", - overwritedata=self.simoptionsoverride) + overwritedata=self._simulate_options_override) def setLinearizationOptions( self, @@ -1262,7 +1262,7 @@ def setLinearizationOptions( return self._set_method_helper( inputdata=inputdata, - classdata=self.linearOptions, + classdata=self._linearization_options, datatype="Linearization-option", overwritedata=None) @@ -1282,7 +1282,7 @@ def setOptimizationOptions( return self._set_method_helper( inputdata=inputdata, - classdata=self.optimizeOptions, + classdata=self._optimization_options, datatype="optimization-option", overwritedata=None) From c0a7f73589cf43daabbe31d9a46080215bb0d3e2 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 7 Jul 2025 20:43:02 +0200 Subject: [PATCH 16/18] [ModelicaSystem] fix rebase fallout 2 --- OMPython/ModelicaSystem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index e821dc35..91cd856f 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1334,6 +1334,8 @@ def setInputs( else: raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}") + self._has_inputs = True + return True def _createCSVData(self) -> pathlib.Path: From a34a8ea0fc2372ee76654a3731555b02a344a344 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 7 Jul 2025 20:48:50 +0200 Subject: [PATCH 17/18] [ModelicaSystem] remove _has_inputs - is defined by _inputs empty or not --- OMPython/ModelicaSystem.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 91cd856f..20cf8022 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -388,7 +388,6 @@ def __init__( self._lmodel = lmodel # may be needed if model is derived from other model self._model_name = modelName # Model class name self._file_name = pathlib.Path(fileName).resolve() if fileName is not None else None # Model file/package name - self._has_inputs = False # for model with input quantity self._simulated = False # True if the model has already been simulated self._csvFile: Optional[pathlib.Path] = None # for storing inputs condition self._result_file: Optional[pathlib.Path] = None # for storing result file @@ -969,18 +968,17 @@ def simulate(self, om_cmd.arg_set(key="overrideFile", val=overrideFile.as_posix()) - if self._has_inputs: # if model has input quantities - for i in self._inputs: - val = self._inputs[i] + if self._inputs: # if model has input quantities + for key in self._inputs: + val = self._inputs[key] if val is None: val = [(float(self._simulate_options["startTime"]), 0.0), (float(self._simulate_options["stopTime"]), 0.0)] - self._inputs[i] = [(float(self._simulate_options["startTime"]), 0.0), - (float(self._simulate_options["stopTime"]), 0.0)] + self._inputs[key] = val if float(self._simulate_options["startTime"]) != val[0][0]: - raise ModelicaSystemError(f"startTime not matched for Input {i}!") + raise ModelicaSystemError(f"startTime not matched for Input {key}!") if float(self._simulate_options["stopTime"]) != val[-1][0]: - raise ModelicaSystemError(f"stopTime not matched for Input {i}!") + raise ModelicaSystemError(f"stopTime not matched for Input {key}!") self._csvFile = self._createCSVData() # create csv file om_cmd.arg_set(key="csvInput", val=self._csvFile.as_posix()) @@ -1334,8 +1332,6 @@ def setInputs( else: raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}") - self._has_inputs = True - return True def _createCSVData(self) -> pathlib.Path: @@ -1521,13 +1517,13 @@ def load_module_from_path(module_name, file_path): om_cmd.arg_set(key="overrideFile", val=overrideLinearFile.as_posix()) - if self._has_inputs: - nameVal = self.getInputs() - for n in nameVal: - tupleList = nameVal.get(n) - if tupleList is not None: - for l in tupleList: - if l[0] < float(self._simulate_options["startTime"]): + inputs = self.getInputs() + if inputs: + for key in inputs: + data = inputs[key] + if data is not None: + for value in data: + if value[0] < float(self._simulate_options["startTime"]): raise ModelicaSystemError('Input time value is less than simulation startTime') self._csvFile = self._createCSVData() om_cmd.arg_set(key="csvInput", val=self._csvFile.as_posix()) From d9f992a5a2dcc6be9c556286eaa3d36fc2894550 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 7 Jul 2025 21:09:36 +0200 Subject: [PATCH 18/18] [test_ModelicaSystem] cleanup --- tests/test_ModelicaSystem.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index d916a78a..b4ccea7e 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -363,7 +363,11 @@ def test_simulate_inputs(tmp_path): (0.0, 2.5), (1.0, 2.5), ], - "u2": None, + # u2 is set due to the call to simulate() above + "u2": [ + (0.0, 0.0), + (1.0, 0.0), + ], } mod.simulate() y = mod.getSolutions("y")[0] @@ -390,7 +394,7 @@ def test_simulate_inputs(tmp_path): mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 1)]}) mod.simulate() - # Let's use both inputs, but each one with different number of of + # Let's use both inputs, but each one with different number of # samples. This has an effect when generating the csv file. mod.setInputs(name={"u1": [(0.0, 0), (1.0, 1)], "u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]})