|
32 | 32 | CONDITIONS OF OSMC-PL. |
33 | 33 | """ |
34 | 34 |
|
| 35 | +import ast |
35 | 36 | import csv |
36 | 37 | from dataclasses import dataclass |
37 | 38 | import importlib |
@@ -928,164 +929,249 @@ def getSolutions(self, varList=None, resultfile=None): # 12 |
928 | 929 | raise ModelicaSystemError("Unhandled input for getSolutions()") |
929 | 930 |
|
930 | 931 | @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) |
934 | 968 |
|
935 | | - if isinstance(name, list): |
936 | | - return [x.replace(" ", "") for x in name] |
| 969 | + return input_data |
937 | 970 |
|
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 |
939 | 979 |
|
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: |
941 | 991 | """ |
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, |
947 | 1010 | """ |
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 |
962 | 1011 |
|
| 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 |
963 | 1028 | else: |
964 | 1029 | 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") |
966 | 1031 |
|
967 | | - result = [] |
968 | | - if isinstance(args1, str): |
969 | | - result = [apply_single(args1)] |
| 1032 | + inputdata_status[key] = status |
970 | 1033 |
|
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()) |
976 | 1035 |
|
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 |
978 | 1044 |
|
979 | | - def setContinuous(self, cvals): # 13 |
| 1045 | + def setContinuous(self, cvals: str | list[str] | dict[str, Any]) -> bool: |
980 | 1046 | """ |
981 | 1047 | This method is used to set continuous values. It can be called: |
982 | 1048 | with a sequence of continuous name and assigning corresponding values as arguments as show in the example below: |
983 | 1049 | 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"}) |
986 | 1053 | """ |
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) |
988 | 1061 |
|
989 | | - def setParameters(self, pvals): # 14 |
| 1062 | + def setParameters(self, pvals: str | list[str] | dict[str, Any]) -> bool: |
990 | 1063 | """ |
991 | 1064 | This method is used to set parameter values. It can be called: |
992 | 1065 | with a sequence of parameter name and assigning corresponding value as arguments as show in the example below: |
993 | 1066 | 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"}) |
996 | 1070 | """ |
997 | | - return self.setMethodHelper(pvals, self.paramlist, "parameter", self.overridevariables) |
| 1071 | + inputdata = self._prepare_input_data(raw_input=pvals) |
998 | 1072 |
|
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) |
1008 | 1078 |
|
1009 | | - def setSimulationOptions(self, simOptions): # 16 |
| 1079 | + def setSimulationOptions(self, simOptions: str | list[str] | dict[str, Any]) -> bool: |
1010 | 1080 | """ |
1011 | 1081 | This method is used to set simulation options. It can be called: |
1012 | 1082 | with a sequence of simulation options name and assigning corresponding values as arguments as show in the example below: |
1013 | 1083 | 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"}) |
1016 | 1087 | """ |
1017 | | - return self.setMethodHelper(simOptions, self.simulateOptions, "simulation-option", self.simoptionsoverride) |
| 1088 | + inputdata = self._prepare_input_data(raw_input=simOptions) |
1018 | 1089 |
|
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: |
1020 | 1097 | """ |
1021 | 1098 | This method is used to set linearization options. It can be called: |
1022 | 1099 | with a sequence of linearization options name and assigning corresponding value as arguments as show in the example below |
1023 | 1100 | 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"}) |
1026 | 1104 | """ |
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) |
1028 | 1112 |
|
1029 | | - def setOptimizationOptions(self, optimizationOptions): # 17 |
| 1113 | + def setOptimizationOptions(self, optimizationOptions: str | list[str] | dict[str, Any]) -> bool: |
1030 | 1114 | """ |
1031 | 1115 | This method is used to set optimization options. It can be called: |
1032 | 1116 | with a sequence of optimization options name and assigning corresponding values as arguments as show in the example below: |
1033 | 1117 | 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"}) |
1036 | 1121 | """ |
1037 | | - return self.setMethodHelper(optimizationOptions, self.optimizeOptions, "optimization-option", None) |
| 1122 | + inputdata = self._prepare_input_data(raw_input=optimizationOptions) |
1038 | 1123 |
|
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: |
1040 | 1131 | """ |
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"}) |
1046 | 1140 | """ |
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 |
1058 | 1170 | self.inputFlag = True |
1059 | 1171 | 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 |
1089 | 1175 |
|
1090 | 1176 | def createCSVData(self) -> pathlib.Path: |
1091 | 1177 | start_time: float = float(self.simulateOptions["startTime"]) |
|
0 commit comments