Skip to content

Commit ca86982

Browse files
committed
Merge branch 'ModelicaSystem_rewrite_set_functions' into remove_depreciated_functionality
2 parents b2d67ac + fda379b commit ca86982

4 files changed

Lines changed: 221 additions & 135 deletions

File tree

OMPython/ModelicaSystem.py

Lines changed: 198 additions & 112 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
@@ -937,164 +938,249 @@ def getSolutions(self, varList=None, resultfile=None): # 12
937938
raise ModelicaSystemError("Unhandled input for getSolutions()")
938939

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

944-
if isinstance(name, list):
945-
return [x.replace(" ", "") for x in name]
978+
return input_data
946979

947-
raise ModelicaSystemError("Unhandled input for strip_space()")
980+
if isinstance(raw_input, dict):
981+
for key, val in raw_input.items():
982+
# convert all values to strings to align it on one type: dict[str, str]
983+
# spaces have to be removed as setInput() could take list of tuples as input and spaces would
984+
str_val = str(val).replace(' ', '')
985+
if ' ' in key or ' ' in str_val:
986+
raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!")
987+
input_data[key] = str_val
948988

949-
def setMethodHelper(self, args1, args2, args3, args4=None):
989+
return input_data
990+
991+
raise ModelicaSystemError(f"Invalid type of input: {type(raw_input)}")
992+
993+
def _set_method_helper(
994+
self,
995+
inputdata: dict[str, str],
996+
classdata: dict[str, Any],
997+
datatype: str,
998+
overwritedata: Optional[dict[str, str]] = None,
999+
) -> bool:
9501000
"""
951-
Helper function for setParameter(),setContinuous(),setSimulationOptions(),setLinearizationOption(),setOptimizationOption()
952-
args1 - string or list of string given by user
953-
args2 - dict() containing the values of different variables(eg:, parameter,continuous,simulation parameters)
954-
args3 - function name (eg; continuous, parameter, simulation, linearization,optimization)
955-
args4 - dict() which stores the new override variables list,
1001+
Helper function for:
1002+
* setParameter()
1003+
* setContinuous()
1004+
* setSimulationOptions()
1005+
* setLinearizationOption()
1006+
* setOptimizationOption()
1007+
* setInputs()
1008+
1009+
Parameters
1010+
----------
1011+
inputdata
1012+
string or list of string given by user
1013+
classdata
1014+
dict() containing the values of different variables (eg: parameter, continuous, simulation parameters)
1015+
datatype
1016+
type identifier (eg; continuous, parameter, simulation, linearization, optimization)
1017+
overwritedata
1018+
dict() which stores the new override variables list,
9561019
"""
957-
def apply_single(args1):
958-
args1 = self._strip_space(args1)
959-
value = args1.split("=")
960-
if value[0] in args2:
961-
if args3 == "parameter" and self.isParameterChangeable(value[0], value[1]):
962-
args2[value[0]] = value[1]
963-
if args4 is not None:
964-
args4[value[0]] = value[1]
965-
elif args3 != "parameter":
966-
args2[value[0]] = value[1]
967-
if args4 is not None:
968-
args4[value[0]] = value[1]
969-
970-
return True
9711020

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

976-
result = []
977-
if isinstance(args1, str):
978-
result = [apply_single(args1)]
1041+
inputdata_status[key] = status
9791042

980-
elif isinstance(args1, list):
981-
result = []
982-
args1 = self._strip_space(args1)
983-
for var in args1:
984-
result.append(apply_single(var))
1043+
return all(inputdata_status.values())
9851044

986-
return all(result)
1045+
def isParameterChangeable(
1046+
self,
1047+
name: str,
1048+
) -> bool:
1049+
q = self.getQuantities(name)
1050+
if q[0]["changeable"] == "false":
1051+
return False
1052+
return True
9871053

988-
def setContinuous(self, cvals): # 13
1054+
def setContinuous(self, cvals: str | list[str] | dict[str, Any]) -> bool:
9891055
"""
9901056
This method is used to set continuous values. It can be called:
9911057
with a sequence of continuous name and assigning corresponding values as arguments as show in the example below:
9921058
usage
993-
>>> setContinuous("Name=value")
994-
>>> setContinuous(["Name1=value1","Name2=value2"])
1059+
>>> setContinuous("Name=value") # depreciated
1060+
>>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated
1061+
>>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"})
9951062
"""
996-
return self.setMethodHelper(cvals, self.continuouslist, "continuous", self.overridevariables)
1063+
inputdata = self._prepare_input_data(raw_input=cvals)
1064+
1065+
return self._set_method_helper(
1066+
inputdata=inputdata,
1067+
classdata=self.continuouslist,
1068+
datatype="continuous",
1069+
overwritedata=self.overridevariables)
9971070

998-
def setParameters(self, pvals): # 14
1071+
def setParameters(self, pvals: str | list[str] | dict[str, Any]) -> bool:
9991072
"""
10001073
This method is used to set parameter values. It can be called:
10011074
with a sequence of parameter name and assigning corresponding value as arguments as show in the example below:
10021075
usage
1003-
>>> setParameters("Name=value")
1004-
>>> setParameters(["Name1=value1","Name2=value2"])
1076+
>>> setParameters("Name=value") # depreciated
1077+
>>> setParameters(["Name1=value1","Name2=value2"]) # depreciated
1078+
>>> setParameters(pvals={"Name1": "value1", "Name2": "value2"})
10051079
"""
1006-
return self.setMethodHelper(pvals, self.paramlist, "parameter", self.overridevariables)
1080+
inputdata = self._prepare_input_data(raw_input=pvals)
10071081

1008-
def isParameterChangeable(self, name, value):
1009-
q = self.getQuantities(name)
1010-
if q[0]["changeable"] == "false":
1011-
logger.debug(f"setParameters() failed : It is not possible to set the following signal {repr(name)}. "
1012-
"It seems to be structural, final, protected or evaluated or has a non-constant binding, "
1013-
f"use sendExpression(\"setParameterValue({self.modelName}, {name}, {value})\") "
1014-
"and rebuild the model using buildModel() API")
1015-
return False
1016-
return True
1082+
return self._set_method_helper(
1083+
inputdata=inputdata,
1084+
classdata=self.paramlist,
1085+
datatype="parameter",
1086+
overwritedata=self.overridevariables)
10171087

1018-
def setSimulationOptions(self, simOptions): # 16
1088+
def setSimulationOptions(self, simOptions: str | list[str] | dict[str, Any]) -> bool:
10191089
"""
10201090
This method is used to set simulation options. It can be called:
10211091
with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below:
10221092
usage
1023-
>>> setSimulationOptions("Name=value")
1024-
>>> setSimulationOptions(["Name1=value1","Name2=value2"])
1093+
>>> setSimulationOptions("Name=value") # depreciated
1094+
>>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated
1095+
>>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"})
10251096
"""
1026-
return self.setMethodHelper(simOptions, self.simulateOptions, "simulation-option", self.simoptionsoverride)
1097+
inputdata = self._prepare_input_data(raw_input=simOptions)
10271098

1028-
def setLinearizationOptions(self, linearizationOptions): # 18
1099+
return self._set_method_helper(
1100+
inputdata=inputdata,
1101+
classdata=self.simulateOptions,
1102+
datatype="simulation-option",
1103+
overwritedata=self.simoptionsoverride)
1104+
1105+
def setLinearizationOptions(self, linearizationOptions: str | list[str] | dict[str, Any]) -> bool:
10291106
"""
10301107
This method is used to set linearization options. It can be called:
10311108
with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below
10321109
usage
1033-
>>> setLinearizationOptions("Name=value")
1034-
>>> setLinearizationOptions(["Name1=value1","Name2=value2"])
1110+
>>> setLinearizationOptions("Name=value") # depreciated
1111+
>>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1112+
>>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"})
10351113
"""
1036-
return self.setMethodHelper(linearizationOptions, self.linearOptions, "Linearization-option", None)
1114+
inputdata = self._prepare_input_data(raw_input=linearizationOptions)
1115+
1116+
return self._set_method_helper(
1117+
inputdata=inputdata,
1118+
classdata=self.linearOptions,
1119+
datatype="Linearization-option",
1120+
overwritedata=None)
10371121

1038-
def setOptimizationOptions(self, optimizationOptions): # 17
1122+
def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, Any]) -> bool:
10391123
"""
10401124
This method is used to set optimization options. It can be called:
10411125
with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below:
10421126
usage
1043-
>>> setOptimizationOptions("Name=value")
1044-
>>> setOptimizationOptions(["Name1=value1","Name2=value2"])
1127+
>>> setOptimizationOptions("Name=value") # depreciated
1128+
>>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1129+
>>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"})
10451130
"""
1046-
return self.setMethodHelper(optimizationOptions, self.optimizeOptions, "optimization-option", None)
1131+
inputdata = self._prepare_input_data(raw_input=optimizationOptions)
10471132

1048-
def setInputs(self, name): # 15
1133+
return self._set_method_helper(
1134+
inputdata=inputdata,
1135+
classdata=self.optimizeOptions,
1136+
datatype="optimization-option",
1137+
overwritedata=None)
1138+
1139+
def setInputs(self, name: str | list[str] | dict[str, Any]) -> bool:
10491140
"""
1050-
This method is used to set input values. It can be called:
1051-
with a sequence of input name and assigning corresponding values as arguments as show in the example below:
1052-
usage
1053-
>>> setInputs("Name=value")
1054-
>>> setInputs(["Name1=value1","Name2=value2"])
1141+
This method is used to set input values. It can be called with a sequence of input name and assigning
1142+
corresponding values as arguments as show in the example below. Compared to other set*() methods this is a
1143+
special case as value could be a list of tuples - these are converted to a string in _prepare_input_data()
1144+
and restored here via ast.literal_eval().
1145+
1146+
>>> setInputs("Name=value") # depreciated
1147+
>>> setInputs(["Name1=value1","Name2=value2"]) # depreciated
1148+
>>> setInputs(name={"Name1": "value1", "Name2": "value2"})
10551149
"""
1056-
if isinstance(name, str):
1057-
name = self._strip_space(name)
1058-
value = name.split("=")
1059-
if value[0] in self.inputlist:
1060-
tmpvalue = eval(value[1])
1061-
if isinstance(tmpvalue, (int, float)):
1062-
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
1063-
(float(self.simulateOptions["stopTime"]), float(value[1]))]
1064-
elif isinstance(tmpvalue, list):
1065-
self.checkValidInputs(tmpvalue)
1066-
self.inputlist[value[0]] = tmpvalue
1150+
inputdata = self._prepare_input_data(raw_input=name)
1151+
1152+
for key, val in inputdata.items():
1153+
if key in self.inputlist:
1154+
if not isinstance(val, str):
1155+
raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}")
1156+
1157+
val_evaluated = ast.literal_eval(val)
1158+
1159+
if isinstance(val_evaluated, (int, float)):
1160+
self.inputlist[key] = [(float(self.simulateOptions["startTime"]), float(val)),
1161+
(float(self.simulateOptions["stopTime"]), float(val))]
1162+
elif isinstance(val_evaluated, list):
1163+
if not all([isinstance(item, tuple) for item in val_evaluated]):
1164+
raise ModelicaSystemError("Value for setInput() must be in tuple format; "
1165+
f"got {repr(val_evaluated)}")
1166+
if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]):
1167+
raise ModelicaSystemError("Time value should be in increasing order; "
1168+
f"got {repr(val_evaluated)}")
1169+
1170+
for item in val_evaluated:
1171+
if item[0] < float(self.simulateOptions["startTime"]):
1172+
raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less "
1173+
"than the simulation start time")
1174+
if len(item) != 2:
1175+
raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} "
1176+
"is in incorrect format!")
1177+
1178+
self.inputlist[key] = val_evaluated
10671179
self.inputFlag = True
10681180
else:
1069-
raise ModelicaSystemError(f"{value[0]} is not an input")
1070-
elif isinstance(name, list):
1071-
name = self._strip_space(name)
1072-
for var in name:
1073-
value = var.split("=")
1074-
if value[0] in self.inputlist:
1075-
tmpvalue = eval(value[1])
1076-
if isinstance(tmpvalue, (int, float)):
1077-
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
1078-
(float(self.simulateOptions["stopTime"]), float(value[1]))]
1079-
elif isinstance(tmpvalue, list):
1080-
self.checkValidInputs(tmpvalue)
1081-
self.inputlist[value[0]] = tmpvalue
1082-
self.inputFlag = True
1083-
else:
1084-
raise ModelicaSystemError(f"{value[0]} is not an input!")
1085-
1086-
def checkValidInputs(self, name):
1087-
if name != sorted(name, key=lambda x: x[0]):
1088-
raise ModelicaSystemError('Time value should be in increasing order')
1089-
for l in name:
1090-
if isinstance(l, tuple):
1091-
# if l[0] < float(self.simValuesList[0]):
1092-
if l[0] < float(self.simulateOptions["startTime"]):
1093-
raise ModelicaSystemError('Input time value is less than simulation startTime')
1094-
if len(l) != 2:
1095-
raise ModelicaSystemError(f'Value for {l} is in incorrect format!')
1096-
else:
1097-
raise ModelicaSystemError('Error!!! Value must be in tuple format')
1181+
raise ModelicaSystemError(f"{key} is not an input")
1182+
1183+
return True
10981184

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

0 commit comments

Comments
 (0)