Skip to content

Commit ee20aa2

Browse files
committed
Merge branch 'ModelicaSystem_rewrite_set_functions' into remove_depreciated_functionality
2 parents 0aec589 + 68c21de commit ee20aa2

File tree

4 files changed

+219
-133
lines changed

4 files changed

+219
-133
lines changed

OMPython/ModelicaSystem.py

Lines changed: 197 additions & 111 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
@@ -928,164 +929,249 @@ def getSolutions(self, varList=None, resultfile=None): # 12
928929
raise ModelicaSystemError("Unhandled input for getSolutions()")
929930

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

935-
if isinstance(name, list):
936-
return [x.replace(" ", "") for x in name]
969+
return input_data
937970

938-
raise ModelicaSystemError("Unhandled input for strip_space()")
971+
if isinstance(raw_input, dict):
972+
for key, val in raw_input.items():
973+
# convert all values to strings to align it on one type: dict[str, str]
974+
# spaces have to be removed as setInput() could take list of tuples as input and spaces would
975+
str_val = str(val).replace(' ', '')
976+
if ' ' in key or ' ' in str_val:
977+
raise ModelicaSystemError(f"Spaces not allowed in key/value pairs: {repr(key)} = {repr(val)}!")
978+
input_data[key] = str_val
939979

940-
def setMethodHelper(self, args1, args2, args3, args4=None):
980+
return input_data
981+
982+
raise ModelicaSystemError(f"Invalid type of input: {type(raw_input)}")
983+
984+
def _set_method_helper(
985+
self,
986+
inputdata: dict[str, str],
987+
classdata: dict[str, Any],
988+
datatype: str,
989+
overwritedata: Optional[dict[str, str]] = None,
990+
) -> bool:
941991
"""
942-
Helper function for setParameter(),setContinuous(),setSimulationOptions(),setLinearizationOption(),setOptimizationOption()
943-
args1 - string or list of string given by user
944-
args2 - dict() containing the values of different variables(eg:, parameter,continuous,simulation parameters)
945-
args3 - function name (eg; continuous, parameter, simulation, linearization,optimization)
946-
args4 - dict() which stores the new override variables list,
992+
Helper function for:
993+
* setParameter()
994+
* setContinuous()
995+
* setSimulationOptions()
996+
* setLinearizationOption()
997+
* setOptimizationOption()
998+
* setInputs()
999+
1000+
Parameters
1001+
----------
1002+
inputdata
1003+
string or list of string given by user
1004+
classdata
1005+
dict() containing the values of different variables (eg: parameter, continuous, simulation parameters)
1006+
datatype
1007+
type identifier (eg; continuous, parameter, simulation, linearization, optimization)
1008+
overwritedata
1009+
dict() which stores the new override variables list,
9471010
"""
948-
def apply_single(args1):
949-
args1 = self._strip_space(args1)
950-
value = args1.split("=")
951-
if value[0] in args2:
952-
if args3 == "parameter" and self.isParameterChangeable(value[0], value[1]):
953-
args2[value[0]] = value[1]
954-
if args4 is not None:
955-
args4[value[0]] = value[1]
956-
elif args3 != "parameter":
957-
args2[value[0]] = value[1]
958-
if args4 is not None:
959-
args4[value[0]] = value[1]
960-
961-
return True
9621011

1012+
inputdata_status: dict[str, bool] = {}
1013+
for key, val in inputdata.items():
1014+
status = False
1015+
if key in classdata:
1016+
if datatype == "parameter" and not self.isParameterChangeable(key):
1017+
logger.debug(f"It is not possible to set the parameter {repr(key)}. It seems to be "
1018+
"structural, final, protected, evaluated or has a non-constant binding. "
1019+
"Use sendExpression(...) and rebuild the model using buildModel() API; example: "
1020+
"sendExpression(\"setParameterValue("
1021+
f"{self.modelName}, {key}, {val if val is not None else '<?value?>'}"
1022+
")\") ")
1023+
else:
1024+
classdata[key] = val
1025+
if overwritedata is not None:
1026+
overwritedata[key] = val
1027+
status = True
9631028
else:
9641029
raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - "
965-
f"{repr(value[0])} is not a {repr(args3)} variable")
1030+
f"{repr(key)} is not a {repr(datatype)} variable")
9661031

967-
result = []
968-
if isinstance(args1, str):
969-
result = [apply_single(args1)]
1032+
inputdata_status[key] = status
9701033

971-
elif isinstance(args1, list):
972-
result = []
973-
args1 = self._strip_space(args1)
974-
for var in args1:
975-
result.append(apply_single(var))
1034+
return all(inputdata_status.values())
9761035

977-
return all(result)
1036+
def isParameterChangeable(
1037+
self,
1038+
name: str,
1039+
) -> bool:
1040+
q = self.getQuantities(name)
1041+
if q[0]["changeable"] == "false":
1042+
return False
1043+
return True
9781044

979-
def setContinuous(self, cvals): # 13
1045+
def setContinuous(self, cvals: str | list[str] | dict[str, Any]) -> bool:
9801046
"""
9811047
This method is used to set continuous values. It can be called:
9821048
with a sequence of continuous name and assigning corresponding values as arguments as show in the example below:
9831049
usage
984-
>>> setContinuous("Name=value")
985-
>>> setContinuous(["Name1=value1","Name2=value2"])
1050+
>>> setContinuous("Name=value") # depreciated
1051+
>>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated
1052+
>>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"})
9861053
"""
987-
return self.setMethodHelper(cvals, self.continuouslist, "continuous", self.overridevariables)
1054+
inputdata = self._prepare_input_data(raw_input=cvals)
1055+
1056+
return self._set_method_helper(
1057+
inputdata=inputdata,
1058+
classdata=self.continuouslist,
1059+
datatype="continuous",
1060+
overwritedata=self.overridevariables)
9881061

989-
def setParameters(self, pvals): # 14
1062+
def setParameters(self, pvals: str | list[str] | dict[str, Any]) -> bool:
9901063
"""
9911064
This method is used to set parameter values. It can be called:
9921065
with a sequence of parameter name and assigning corresponding value as arguments as show in the example below:
9931066
usage
994-
>>> setParameters("Name=value")
995-
>>> setParameters(["Name1=value1","Name2=value2"])
1067+
>>> setParameters("Name=value") # depreciated
1068+
>>> setParameters(["Name1=value1","Name2=value2"]) # depreciated
1069+
>>> setParameters(pvals={"Name1": "value1", "Name2": "value2"})
9961070
"""
997-
return self.setMethodHelper(pvals, self.paramlist, "parameter", self.overridevariables)
1071+
inputdata = self._prepare_input_data(raw_input=pvals)
9981072

999-
def isParameterChangeable(self, name, value):
1000-
q = self.getQuantities(name)
1001-
if q[0]["changeable"] == "false":
1002-
logger.verbose(f"setParameters() failed : It is not possible to set the following signal {repr(name)}. "
1003-
"It seems to be structural, final, protected or evaluated or has a non-constant binding, "
1004-
f"use sendExpression(\"setParameterValue({self.modelName}, {name}, {value})\") "
1005-
"and rebuild the model using buildModel() API")
1006-
return False
1007-
return True
1073+
return self._set_method_helper(
1074+
inputdata=inputdata,
1075+
classdata=self.paramlist,
1076+
datatype="parameter",
1077+
overwritedata=self.overridevariables)
10081078

1009-
def setSimulationOptions(self, simOptions): # 16
1079+
def setSimulationOptions(self, simOptions: str | list[str] | dict[str, Any]) -> bool:
10101080
"""
10111081
This method is used to set simulation options. It can be called:
10121082
with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below:
10131083
usage
1014-
>>> setSimulationOptions("Name=value")
1015-
>>> setSimulationOptions(["Name1=value1","Name2=value2"])
1084+
>>> setSimulationOptions("Name=value") # depreciated
1085+
>>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated
1086+
>>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"})
10161087
"""
1017-
return self.setMethodHelper(simOptions, self.simulateOptions, "simulation-option", self.simoptionsoverride)
1088+
inputdata = self._prepare_input_data(raw_input=simOptions)
10181089

1019-
def setLinearizationOptions(self, linearizationOptions): # 18
1090+
return self._set_method_helper(
1091+
inputdata=inputdata,
1092+
classdata=self.simulateOptions,
1093+
datatype="simulation-option",
1094+
overwritedata=self.simoptionsoverride)
1095+
1096+
def setLinearizationOptions(self, linearizationOptions: str | list[str] | dict[str, Any]) -> bool:
10201097
"""
10211098
This method is used to set linearization options. It can be called:
10221099
with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below
10231100
usage
1024-
>>> setLinearizationOptions("Name=value")
1025-
>>> setLinearizationOptions(["Name1=value1","Name2=value2"])
1101+
>>> setLinearizationOptions("Name=value") # depreciated
1102+
>>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1103+
>>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"})
10261104
"""
1027-
return self.setMethodHelper(linearizationOptions, self.linearOptions, "Linearization-option", None)
1105+
inputdata = self._prepare_input_data(raw_input=linearizationOptions)
1106+
1107+
return self._set_method_helper(
1108+
inputdata=inputdata,
1109+
classdata=self.linearOptions,
1110+
datatype="Linearization-option",
1111+
overwritedata=None)
10281112

1029-
def setOptimizationOptions(self, optimizationOptions): # 17
1113+
def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, Any]) -> bool:
10301114
"""
10311115
This method is used to set optimization options. It can be called:
10321116
with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below:
10331117
usage
1034-
>>> setOptimizationOptions("Name=value")
1035-
>>> setOptimizationOptions(["Name1=value1","Name2=value2"])
1118+
>>> setOptimizationOptions("Name=value") # depreciated
1119+
>>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated
1120+
>>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"})
10361121
"""
1037-
return self.setMethodHelper(optimizationOptions, self.optimizeOptions, "optimization-option", None)
1122+
inputdata = self._prepare_input_data(raw_input=optimizationOptions)
10381123

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

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

0 commit comments

Comments
 (0)