Skip to content

ModelicaSystem - rewrite set*() functions #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 208 additions & 119 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
CONDITIONS OF OSMC-PL.
"""

import ast
import csv
from dataclasses import dataclass
import importlib
Expand Down Expand Up @@ -992,164 +993,252 @@ def getSolutions(self, varList=None, resultfile=None): # 12
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 setMethodHelper(self, args1, args2, args3, args4=None):
"""
Helper function for setParameter(),setContinuous(),setSimulationOptions(),setLinearizationOption(),setOptimizationOption()
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]

return True
def _prepare_input_data(
raw_input: str | list[str] | dict[str, Any],
) -> dict[str, str]:
"""
Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}.
"""

else:
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}")

input_data_from_str: dict[str, str] = {key_val_list[0]: key_val_list[1]}

return input_data_from_str

input_data: dict[str, 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(raw_input)

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 raw_input:
input_data |= prepare_str(item)

return input_data

if isinstance(raw_input, dict):
for key, val in raw_input.items():
# 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

return input_data

raise ModelicaSystemError(f"Invalid type of input: {type(raw_input)}")

def _set_method_helper(
self,
inputdata: dict[str, str],
classdata: dict[str, Any],
datatype: str,
overwritedata: Optional[dict[str, str]] = None,
) -> bool:
"""
Helper function for:
* setParameter()
* setContinuous()
* setSimulationOptions()
* setLinearizationOption()
* setOptimizationOption()
* setInputs()

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,
"""

inputdata_status: dict[str, bool] = {}
for key, val in inputdata.items():
if key not in classdata:
raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - "
f"{repr(value[0])} is not a {repr(args3)} variable")
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 '<?value?>'}"
")\") ")
else:
classdata[key] = val
if overwritedata is not None:
overwritedata[key] = val
status = True

result = []
if isinstance(args1, str):
result = [apply_single(args1)]
inputdata_status[key] = status

elif isinstance(args1, list):
result = []
args1 = self._strip_space(args1)
for var in args1:
result.append(apply_single(var))
return all(inputdata_status.values())

return all(result)
def isParameterChangeable(
self,
name: str,
) -> bool:
q = self.getQuantities(name)
if q[0]["changeable"] == "false":
return False
return True

def setContinuous(self, cvals): # 13
def setContinuous(self, cvals: str | list[str] | dict[str, Any]) -> 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:
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(cvals, self.continuouslist, "continuous", self.overridevariables)
inputdata = self._prepare_input_data(raw_input=cvals)

def setParameters(self, pvals): # 14
return self._set_method_helper(
inputdata=inputdata,
classdata=self.continuouslist,
datatype="continuous",
overwritedata=self.overridevariables)

def setParameters(self, pvals: str | list[str] | dict[str, Any]) -> 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:
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(pvals, self.paramlist, "parameter", self.overridevariables)
inputdata = self._prepare_input_data(raw_input=pvals)

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.modelName}, {name}, {value})\") "
"and rebuild the model using buildModel() API")
return False
return True
return self._set_method_helper(
inputdata=inputdata,
classdata=self.paramlist,
datatype="parameter",
overwritedata=self.overridevariables)

def setSimulationOptions(self, simOptions): # 16
def setSimulationOptions(self, simOptions: str | list[str] | dict[str, Any]) -> 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:
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(simOptions, self.simulateOptions, "simulation-option", self.simoptionsoverride)
inputdata = self._prepare_input_data(raw_input=simOptions)

def setLinearizationOptions(self, linearizationOptions): # 18
return self._set_method_helper(
inputdata=inputdata,
classdata=self.simulateOptions,
datatype="simulation-option",
overwritedata=self.simoptionsoverride)

def setLinearizationOptions(self, linearizationOptions: str | list[str] | dict[str, Any]) -> 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
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(linearizationOptions, self.linearOptions, "Linearization-option", None)
inputdata = self._prepare_input_data(raw_input=linearizationOptions)

return self._set_method_helper(
inputdata=inputdata,
classdata=self.linearOptions,
datatype="Linearization-option",
overwritedata=None)

def setOptimizationOptions(self, optimizationOptions): # 17
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:
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(optimizationOptions, self.optimizeOptions, "optimization-option", None)
inputdata = self._prepare_input_data(raw_input=optimizationOptions)

return self._set_method_helper(
inputdata=inputdata,
classdata=self.optimizeOptions,
datatype="optimization-option",
overwritedata=None)

def setInputs(self, name): # 15
def setInputs(self, 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
>>> setInputs("Name=value")
>>> setInputs(["Name1=value1","Name2=value2"])
"""
if isinstance(name, str):
name = self._strip_space(name)
value = name.split("=")
if value[0] in self.inputlist:
tmpvalue = eval(value[1])
if isinstance(tmpvalue, (int, float)):
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
(float(self.simulateOptions["stopTime"]), float(value[1]))]
elif isinstance(tmpvalue, list):
self.checkValidInputs(tmpvalue)
self.inputlist[value[0]] = tmpvalue
self.inputFlag = True
else:
raise ModelicaSystemError(f"{value[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.inputlist:
tmpvalue = eval(value[1])
if isinstance(tmpvalue, (int, float)):
self.inputlist[value[0]] = [(float(self.simulateOptions["startTime"]), float(value[1])),
(float(self.simulateOptions["stopTime"]), float(value[1]))]
elif isinstance(tmpvalue, list):
self.checkValidInputs(tmpvalue)
self.inputlist[value[0]] = tmpvalue
self.inputFlag = True
else:
raise ModelicaSystemError(f"{value[0]} is not an input!")

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.simulateOptions["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!')
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"})
"""
inputdata = self._prepare_input_data(raw_input=name)

for key, val in inputdata.items():
if key not in self.inputlist:
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.inputlist[key] = [(float(self.simulateOptions["startTime"]), float(val)),
(float(self.simulateOptions["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.simulateOptions["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.inputlist[key] = val_evaluated
else:
raise ModelicaSystemError('Error!!! Value must be in tuple format')
raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}")

self.inputFlag = True

return True

def createCSVData(self) -> pathlib.Path:
start_time: float = float(self.simulateOptions["startTime"])
Expand Down
Loading
Loading