Skip to content

Commit 6c5d62f

Browse files
committed
Merge branch 'ModelicaSystem_rewrite_set_functions' into remove_depreciated_functionality
2 parents f7c04b5 + 817f20d commit 6c5d62f

File tree

4 files changed

+225
-136
lines changed

4 files changed

+225
-136
lines changed

OMPython/ModelicaSystem.py

Lines changed: 202 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
CONDITIONS OF OSMC-PL.
3333
"""
3434

35+
import ast
3536
import csv
3637
from dataclasses import dataclass
3738
import importlib
@@ -944,164 +945,252 @@ def getSolutions(self, varList=None, resultfile=None): # 12
944945
return np_res
945946

946947
@staticmethod
947-
def _strip_space(name):
948-
if isinstance(name, str):
949-
return name.replace(" ", "")
948+
def _prepare_input_data(
949+
raw_input: str | list[str] | dict[str, Any],
950+
) -> dict[str, str]:
951+
"""
952+
Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}.
953+
"""
954+
955+
def prepare_str(str_in: str) -> dict[str, str]:
956+
str_in = str_in.replace(" ", "")
957+
key_val_list: list[str] = str_in.split("=")
958+
if len(key_val_list) != 2:
959+
raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}")
960+
961+
input_data_from_str: dict[str, str] = {key_val_list[0]: key_val_list[1]}
962+
963+
return input_data_from_str
964+
965+
input_data: dict[str, str] = {}
966+
967+
if isinstance(raw_input, str):
968+
warnings.warn(message="The definition of values to set should use a dictionary, "
969+
"i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which "
970+
"use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]",
971+
category=DeprecationWarning,
972+
stacklevel=3)
973+
return prepare_str(raw_input)
974+
975+
if isinstance(raw_input, list):
976+
warnings.warn(message="The definition of values to set should use a dictionary, "
977+
"i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which "
978+
"use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]",
979+
category=DeprecationWarning,
980+
stacklevel=3)
981+
982+
for item in raw_input:
983+
input_data |= prepare_str(item)
950984

951-
if isinstance(name, list):
952-
return [x.replace(" ", "") for x in name]
985+
return input_data
953986

954-
raise ModelicaSystemError("Unhandled input for strip_space()")
987+
if isinstance(raw_input, dict):
988+
for key, val in raw_input.items():
989+
# convert all values to strings to align it on one type: dict[str, str]
990+
# spaces have to be removed as setInput() could take list of tuples as input and spaces would
991+
str_val = str(val).replace(' ', '')
992+
if ' ' in key or ' ' in str_val:
993+
raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!")
994+
input_data[key] = str_val
955995

956-
def setMethodHelper(self, args1, args2, args3, args4=None):
996+
return input_data
997+
998+
raise ModelicaSystemError(f"Invalid type of input: {type(raw_input)}")
999+
1000+
def _set_method_helper(
1001+
self,
1002+
inputdata: dict[str, str],
1003+
classdata: dict[str, Any],
1004+
datatype: str,
1005+
overwritedata: Optional[dict[str, str]] = None,
1006+
) -> bool:
9571007
"""
958-
Helper function for setParameter(),setContinuous(),setSimulationOptions(),setLinearizationOption(),setOptimizationOption()
959-
args1 - string or list of string given by user
960-
args2 - dict() containing the values of different variables(eg:, parameter,continuous,simulation parameters)
961-
args3 - function name (eg; continuous, parameter, simulation, linearization,optimization)
962-
args4 - dict() which stores the new override variables list,
1008+
Helper function for:
1009+
* setParameter()
1010+
* setContinuous()
1011+
* setSimulationOptions()
1012+
* setLinearizationOption()
1013+
* setOptimizationOption()
1014+
* setInputs()
1015+
1016+
Parameters
1017+
----------
1018+
inputdata
1019+
string or list of string given by user
1020+
classdata
1021+
dict() containing the values of different variables (eg: parameter, continuous, simulation parameters)
1022+
datatype
1023+
type identifier (eg; continuous, parameter, simulation, linearization, optimization)
1024+
overwritedata
1025+
dict() which stores the new override variables list,
9631026
"""
964-
def apply_single(args1):
965-
args1 = self._strip_space(args1)
966-
value = args1.split("=")
967-
if value[0] in args2:
968-
if args3 == "parameter" and self.isParameterChangeable(value[0], value[1]):
969-
args2[value[0]] = value[1]
970-
if args4 is not None:
971-
args4[value[0]] = value[1]
972-
elif args3 != "parameter":
973-
args2[value[0]] = value[1]
974-
if args4 is not None:
975-
args4[value[0]] = value[1]
976-
977-
return True
9781027

979-
else:
1028+
inputdata_status: dict[str, bool] = {}
1029+
for key, val in inputdata.items():
1030+
if key not in classdata:
9801031
raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - "
981-
f"{repr(value[0])} is not a {repr(args3)} variable")
1032+
f"{repr(key)} is not a {repr(datatype)} variable")
1033+
1034+
status = False
1035+
if datatype == "parameter" and not self.isParameterChangeable(key):
1036+
logger.debug(f"It is not possible to set the parameter {repr(key)}. It seems to be "
1037+
"structural, final, protected, evaluated or has a non-constant binding. "
1038+
"Use sendExpression(...) and rebuild the model using buildModel() API; example: "
1039+
"sendExpression(\"setParameterValue("
1040+
f"{self.modelName}, {key}, {val if val is not None else '<?value?>'}"
1041+
")\") ")
1042+
else:
1043+
classdata[key] = val
1044+
if overwritedata is not None:
1045+
overwritedata[key] = val
1046+
status = True
9821047

983-
result = []
984-
if isinstance(args1, str):
985-
result = [apply_single(args1)]
1048+
inputdata_status[key] = status
9861049

987-
elif isinstance(args1, list):
988-
result = []
989-
args1 = self._strip_space(args1)
990-
for var in args1:
991-
result.append(apply_single(var))
1050+
return all(inputdata_status.values())
9921051

993-
return all(result)
1052+
def isParameterChangeable(
1053+
self,
1054+
name: str,
1055+
) -> bool:
1056+
q = self.getQuantities(name)
1057+
if q[0]["changeable"] == "false":
1058+
return False
1059+
return True
9941060

995-
def setContinuous(self, cvals): # 13
1061+
def setContinuous(self, cvals: str | list[str] | dict[str, Any]) -> bool:
9961062
"""
9971063
This method is used to set continuous values. It can be called:
9981064
with a sequence of continuous name and assigning corresponding values as arguments as show in the example below:
9991065
usage
1000-
>>> setContinuous("Name=value")
1001-
>>> setContinuous(["Name1=value1","Name2=value2"])
1066+
>>> setContinuous("Name=value") # depreciated
1067+
>>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated
1068+
>>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"})
10021069
"""
1003-
return self.setMethodHelper(cvals, self.continuouslist, "continuous", self.overridevariables)
1070+
inputdata = self._prepare_input_data(raw_input=cvals)
1071+
1072+
return self._set_method_helper(
1073+
inputdata=inputdata,
1074+
classdata=self.continuouslist,
1075+
datatype="continuous",
1076+
overwritedata=self.overridevariables)
10041077

1005-
def setParameters(self, pvals): # 14
1078+
def setParameters(self, pvals: str | list[str] | dict[str, Any]) -> bool:
10061079
"""
10071080
This method is used to set parameter values. It can be called:
10081081
with a sequence of parameter name and assigning corresponding value as arguments as show in the example below:
10091082
usage
1010-
>>> setParameters("Name=value")
1011-
>>> setParameters(["Name1=value1","Name2=value2"])
1083+
>>> setParameters("Name=value") # depreciated
1084+
>>> setParameters(["Name1=value1","Name2=value2"]) # depreciated
1085+
>>> setParameters(pvals={"Name1": "value1", "Name2": "value2"})
10121086
"""
1013-
return self.setMethodHelper(pvals, self.paramlist, "parameter", self.overridevariables)
1087+
inputdata = self._prepare_input_data(raw_input=pvals)
10141088

1015-
def isParameterChangeable(self, name, value):
1016-
q = self.getQuantities(name)
1017-
if q[0]["changeable"] == "false":
1018-
logger.debug(f"setParameters() failed : It is not possible to set the following signal {repr(name)}. "
1019-
"It seems to be structural, final, protected or evaluated or has a non-constant binding, "
1020-
f"use sendExpression(\"setParameterValue({self.modelName}, {name}, {value})\") "
1021-
"and rebuild the model using buildModel() API")
1022-
return False
1023-
return True
1089+
return self._set_method_helper(
1090+
inputdata=inputdata,
1091+
classdata=self.paramlist,
1092+
datatype="parameter",
1093+
overwritedata=self.overridevariables)
10241094

1025-
def setSimulationOptions(self, simOptions): # 16
1095+
def setSimulationOptions(self, simOptions: str | list[str] | dict[str, Any]) -> bool:
10261096
"""
10271097
This method is used to set simulation options. It can be called:
10281098
with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below:
10291099
usage
1030-
>>> setSimulationOptions("Name=value")
1031-
>>> setSimulationOptions(["Name1=value1","Name2=value2"])
1100+
>>> setSimulationOptions("Name=value") # depreciated
1101+
>>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated
1102+
>>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"})
10321103
"""
1033-
return self.setMethodHelper(simOptions, self.simulateOptions, "simulation-option", self.simoptionsoverride)
1104+
inputdata = self._prepare_input_data(raw_input=simOptions)
10341105

1035-
def setLinearizationOptions(self, linearizationOptions): # 18
1106+
return self._set_method_helper(
1107+
inputdata=inputdata,
1108+
classdata=self.simulateOptions,
1109+
datatype="simulation-option",
1110+
overwritedata=self.simoptionsoverride)
1111+
1112+
def setLinearizationOptions(self, linearizationOptions: str | list[str] | dict[str, Any]) -> bool:
10361113
"""
10371114
This method is used to set linearization options. It can be called:
10381115
with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below
10391116
usage
1040-
>>> setLinearizationOptions("Name=value")
1041-
>>> setLinearizationOptions(["Name1=value1","Name2=value2"])
1117+
>>> setLinearizationOptions("Name=value") # depreciated
1118+
>>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1119+
>>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"})
10421120
"""
1043-
return self.setMethodHelper(linearizationOptions, self.linearOptions, "Linearization-option", None)
1121+
inputdata = self._prepare_input_data(raw_input=linearizationOptions)
1122+
1123+
return self._set_method_helper(
1124+
inputdata=inputdata,
1125+
classdata=self.linearOptions,
1126+
datatype="Linearization-option",
1127+
overwritedata=None)
10441128

1045-
def setOptimizationOptions(self, optimizationOptions): # 17
1129+
def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, Any]) -> bool:
10461130
"""
10471131
This method is used to set optimization options. It can be called:
10481132
with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below:
10491133
usage
1050-
>>> setOptimizationOptions("Name=value")
1051-
>>> setOptimizationOptions(["Name1=value1","Name2=value2"])
1134+
>>> setOptimizationOptions("Name=value") # depreciated
1135+
>>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1136+
>>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"})
10521137
"""
1053-
return self.setMethodHelper(optimizationOptions, self.optimizeOptions, "optimization-option", None)
1138+
inputdata = self._prepare_input_data(raw_input=optimizationOptions)
1139+
1140+
return self._set_method_helper(
1141+
inputdata=inputdata,
1142+
classdata=self.optimizeOptions,
1143+
datatype="optimization-option",
1144+
overwritedata=None)
10541145

1055-
def setInputs(self, name): # 15
1146+
def setInputs(self, name: str | list[str] | dict[str, Any]) -> bool:
10561147
"""
1057-
This method is used to set input values. It can be called:
1058-
with a sequence of input name and assigning corresponding values as arguments as show in the example below:
1059-
usage
1060-
>>> setInputs("Name=value")
1061-
>>> setInputs(["Name1=value1","Name2=value2"])
1148+
This method is used to set input values. It can be called with a sequence of input name and assigning
1149+
corresponding values as arguments as show in the example below. Compared to other set*() methods this is a
1150+
special case as value could be a list of tuples - these are converted to a string in _prepare_input_data()
1151+
and restored here via ast.literal_eval().
1152+
1153+
>>> setInputs("Name=value") # depreciated
1154+
>>> setInputs(["Name1=value1","Name2=value2"]) # depreciated
1155+
>>> setInputs(name={"Name1": "value1", "Name2": "value2"})
10621156
"""
1063-
if isinstance(name, str):
1064-
name = self._strip_space(name)
1065-
value = name.split("=")
1066-
if value[0] in self.inputlist:
1067-
tmpvalue = eval(value[1])
1068-
if isinstance(tmpvalue, (int, float)):
1069-
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
1070-
(float(self.simulateOptions["stopTime"]), float(value[1]))]
1071-
elif isinstance(tmpvalue, list):
1072-
self.checkValidInputs(tmpvalue)
1073-
self.inputlist[value[0]] = tmpvalue
1074-
self.inputFlag = True
1157+
inputdata = self._prepare_input_data(raw_input=name)
1158+
1159+
for key, val in inputdata.items():
1160+
if key not in self.inputlist:
1161+
raise ModelicaSystemError(f"{key} is not an input")
1162+
1163+
if not isinstance(val, str):
1164+
raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}")
1165+
1166+
val_evaluated = ast.literal_eval(val)
1167+
1168+
if isinstance(val_evaluated, (int, float)):
1169+
self.inputlist[key] = [(float(self.simulateOptions["startTime"]), float(val)),
1170+
(float(self.simulateOptions["stopTime"]), float(val))]
1171+
elif isinstance(val_evaluated, list):
1172+
if not all([isinstance(item, tuple) for item in val_evaluated]):
1173+
raise ModelicaSystemError("Value for setInput() must be in tuple format; "
1174+
f"got {repr(val_evaluated)}")
1175+
if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]):
1176+
raise ModelicaSystemError("Time value should be in increasing order; "
1177+
f"got {repr(val_evaluated)}")
1178+
1179+
for item in val_evaluated:
1180+
if item[0] < float(self.simulateOptions["startTime"]):
1181+
raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less "
1182+
"than the simulation start time")
1183+
if len(item) != 2:
1184+
raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} "
1185+
"is in incorrect format!")
1186+
1187+
self.inputlist[key] = val_evaluated
10751188
else:
1076-
raise ModelicaSystemError(f"{value[0]} is not an input")
1077-
elif isinstance(name, list):
1078-
name = self._strip_space(name)
1079-
for var in name:
1080-
value = var.split("=")
1081-
if value[0] in self.inputlist:
1082-
tmpvalue = eval(value[1])
1083-
if isinstance(tmpvalue, (int, float)):
1084-
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
1085-
(float(self.simulateOptions["stopTime"]), float(value[1]))]
1086-
elif isinstance(tmpvalue, list):
1087-
self.checkValidInputs(tmpvalue)
1088-
self.inputlist[value[0]] = tmpvalue
1089-
self.inputFlag = True
1090-
else:
1091-
raise ModelicaSystemError(f"{value[0]} is not an input!")
1092-
1093-
def checkValidInputs(self, name):
1094-
if name != sorted(name, key=lambda x: x[0]):
1095-
raise ModelicaSystemError('Time value should be in increasing order')
1096-
for l in name:
1097-
if isinstance(l, tuple):
1098-
# if l[0] < float(self.simValuesList[0]):
1099-
if l[0] < float(self.simulateOptions["startTime"]):
1100-
raise ModelicaSystemError('Input time value is less than simulation startTime')
1101-
if len(l) != 2:
1102-
raise ModelicaSystemError(f'Value for {l} is in incorrect format!')
1103-
else:
1104-
raise ModelicaSystemError('Error!!! Value must be in tuple format')
1189+
raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}")
1190+
1191+
self.inputFlag = True
1192+
1193+
return True
11051194

11061195
def createCSVData(self) -> pathlib.Path:
11071196
start_time: float = float(self.simulateOptions["startTime"])

0 commit comments

Comments
 (0)