Skip to content
128 changes: 79 additions & 49 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,8 @@ def getSolutions(

@staticmethod
def _prepare_input_data(
raw_input: str | list[str] | dict[str, Any],
input_args: Any,
input_kwargs: dict[str, Any],
) -> dict[str, str]:
"""
Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}.
Expand All @@ -1238,38 +1239,44 @@ def prepare_str(str_in: str) -> dict[str, 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(' ', '')
for input_arg in input_args:
if isinstance(input_arg, 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)
input_data = input_data | prepare_str(input_arg)
elif isinstance(input_arg, 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 input_arg:
if not isinstance(item, str):
raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(item)}!")
input_data = input_data | prepare_str(item)
elif isinstance(input_arg, dict):
input_data = input_data | input_arg
else:
raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(input_arg)}!")

if len(input_kwargs):
for key, val in input_kwargs.items():
# ensure all values are strings to align it on one type: dict[str, str]
if not isinstance(val, str):
# spaces have to be removed as setInput() could take list of tuples as input and spaces would
# result in an error on recreating the input data
str_val = str(val).replace(' ', '')
else:
str_val = val
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)}")
return input_data

def _set_method_helper(
self,
Expand Down Expand Up @@ -1301,8 +1308,7 @@ def _set_method_helper(

for key, val in inputdata.items():
if key not in classdata:
raise ModelicaSystemError("Unhandled case in setMethodHelper.apply_single() - "
f"{repr(key)} is not a {repr(datatype)} variable")
raise ModelicaSystemError(f"Invalid variable for type {repr(datatype)}: {repr(key)}")

if datatype == "parameter" and not self.isParameterChangeable(key):
raise ModelicaSystemError(f"It is not possible to set the parameter {repr(key)}. It seems to be "
Expand Down Expand Up @@ -1330,17 +1336,21 @@ def isParameterChangeable(

def setContinuous(
self,
cvals: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: 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") # depreciated
>>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated
>>> setContinuous(cvals={"Name1": "value1", "Name2": "value2"})

>>> setContinuous(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setContinuous(**param)
"""
inputdata = self._prepare_input_data(raw_input=cvals)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

return self._set_method_helper(
inputdata=inputdata,
Expand All @@ -1350,17 +1360,21 @@ def setContinuous(

def setParameters(
self,
pvals: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: 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") # depreciated
>>> setParameters(["Name1=value1","Name2=value2"]) # depreciated
>>> setParameters(pvals={"Name1": "value1", "Name2": "value2"})

>>> setParameters(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setParameters(**param)
"""
inputdata = self._prepare_input_data(raw_input=pvals)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

return self._set_method_helper(
inputdata=inputdata,
Expand All @@ -1370,17 +1384,21 @@ def setParameters(

def setSimulationOptions(
self,
simOptions: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: 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") # depreciated
>>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated
>>> setSimulationOptions(simOptions={"Name1": "value1", "Name2": "value2"})

>>> setSimulationOptions(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setSimulationOptions(**param)
"""
inputdata = self._prepare_input_data(raw_input=simOptions)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

return self._set_method_helper(
inputdata=inputdata,
Expand All @@ -1390,17 +1408,21 @@ def setSimulationOptions(

def setLinearizationOptions(
self,
linearizationOptions: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: 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") # depreciated
>>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated
>>> setLinearizationOptions(linearizationOtions={"Name1": "value1", "Name2": "value2"})

>>> setLinearizationOptions(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setLinearizationOptions(**param)
"""
inputdata = self._prepare_input_data(raw_input=linearizationOptions)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

return self._set_method_helper(
inputdata=inputdata,
Expand All @@ -1410,17 +1432,21 @@ def setLinearizationOptions(

def setOptimizationOptions(
self,
optimizationOptions: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: 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") # depreciated
>>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated
>>> setOptimizationOptions(optimizationOptions={"Name1": "value1", "Name2": "value2"})

>>> setOptimizationOptions(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setOptimizationOptions(**param)
"""
inputdata = self._prepare_input_data(raw_input=optimizationOptions)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

return self._set_method_helper(
inputdata=inputdata,
Expand All @@ -1430,7 +1456,8 @@ def setOptimizationOptions(

def setInputs(
self,
name: str | list[str] | dict[str, Any],
*args: Any,
**kwargs: dict[str, Any],
) -> bool:
"""
This method is used to set input values. It can be called with a sequence of input name and assigning
Expand All @@ -1440,9 +1467,12 @@ def setInputs(

>>> setInputs("Name=value") # depreciated
>>> setInputs(["Name1=value1","Name2=value2"]) # depreciated
>>> setInputs(name={"Name1": "value1", "Name2": "value2"})

>>> setInputs(Name1="value1", Name2="value2")
>>> param = {"Name1": "value1", "Name2": "value2"}
>>> setInputs(**param)
"""
inputdata = self._prepare_input_data(raw_input=name)
inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs)

for key, val in inputdata.items():
if key not in self._inputs:
Expand Down
44 changes: 26 additions & 18 deletions tests/test_ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ def test_setParameters():
model_path = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels/"
mod = OMPython.ModelicaSystem(model_path + "BouncingBall.mo", "BouncingBall")

# method 1
mod.setParameters(pvals={"e": 1.234})
mod.setParameters(pvals={"g": 321.0})
# method 1 (test depreciated variants)
mod.setParameters("e=1.234")
mod.setParameters(["g=321.0"])
assert mod.getParameters("e") == ["1.234"]
assert mod.getParameters("g") == ["321.0"]
assert mod.getParameters() == {
Expand All @@ -46,8 +46,9 @@ def test_setParameters():
with pytest.raises(KeyError):
mod.getParameters("thisParameterDoesNotExist")

# method 2
mod.setParameters(pvals={"e": 21.3, "g": 0.12})
# method 2 (new style)
pvals = {"e": 21.3, "g": 0.12}
mod.setParameters(**pvals)
assert mod.getParameters() == {
"e": "21.3",
"g": "0.12",
Expand All @@ -64,8 +65,8 @@ def test_setSimulationOptions():
mod = OMPython.ModelicaSystem(fileName=model_path + "BouncingBall.mo", modelName="BouncingBall")

# method 1
mod.setSimulationOptions(simOptions={"stopTime": 1.234})
mod.setSimulationOptions(simOptions={"tolerance": 1.1e-08})
mod.setSimulationOptions(stopTime=1.234)
mod.setSimulationOptions(tolerance=1.1e-08)
assert mod.getSimulationOptions("stopTime") == ["1.234"]
assert mod.getSimulationOptions("tolerance") == ["1.1e-08"]
assert mod.getSimulationOptions(["tolerance", "stopTime"]) == ["1.1e-08", "1.234"]
Expand All @@ -77,7 +78,7 @@ def test_setSimulationOptions():
mod.getSimulationOptions("thisOptionDoesNotExist")

# method 2
mod.setSimulationOptions(simOptions={"stopTime": 2.1, "tolerance": "1.2e-08"})
mod.setSimulationOptions(stopTime=2.1, tolerance=1.2e-08)
d = mod.getSimulationOptions()
assert d["stopTime"] == "2.1"
assert d["tolerance"] == "1.2e-08"
Expand Down Expand Up @@ -119,7 +120,9 @@ def test_getSolutions(model_firstorder):
a = -1
tau = -1 / a
stopTime = 5*tau
mod.setSimulationOptions(simOptions={"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8})

simOptions = {"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8}
mod.setSimulationOptions(**simOptions)
mod.simulate()

x = mod.getSolutions("x")
Expand Down Expand Up @@ -298,7 +301,7 @@ def test_getters(tmp_path):
x0 = 1.0
x_analytical = -b/a + (x0 + b/a) * np.exp(a * stopTime)
dx_analytical = (x0 + b/a) * a * np.exp(a * stopTime)
mod.setSimulationOptions(simOptions={"stopTime": stopTime})
mod.setSimulationOptions(stopTime=stopTime)
mod.simulate()

# getOutputs after simulate()
Expand Down Expand Up @@ -327,7 +330,7 @@ def test_getters(tmp_path):
mod.getContinuous("a") # a is a parameter

with pytest.raises(OMPython.ModelicaSystemError):
mod.setSimulationOptions(simOptions={"thisOptionDoesNotExist": 3})
mod.setSimulationOptions(thisOptionDoesNotExist=3)


def test_simulate_inputs(tmp_path):
Expand All @@ -345,7 +348,8 @@ def test_simulate_inputs(tmp_path):
""")
mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="M_input")

mod.setSimulationOptions(simOptions={"stopTime": 1.0})
simOptions = {"stopTime": 1.0}
mod.setSimulationOptions(**simOptions)

# integrate zero (no setInputs call) - it should default to None -> 0
assert mod.getInputs() == {
Expand All @@ -357,7 +361,7 @@ def test_simulate_inputs(tmp_path):
assert np.isclose(y[-1], 0.0)

# integrate a constant
mod.setInputs(name={"u1": 2.5})
mod.setInputs(u1=2.5)
assert mod.getInputs() == {
"u1": [
(0.0, 2.5),
Expand All @@ -374,7 +378,8 @@ def test_simulate_inputs(tmp_path):
assert np.isclose(y[-1], 2.5)

# now let's integrate the sum of two ramps
mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 2), (1.0, 0)]})
inputs = {"u1": [(0.0, 0.0), (0.5, 2), (1.0, 0)]}
mod.setInputs(**inputs)
assert mod.getInputs("u1") == [[
(0.0, 0.0),
(0.5, 2.0),
Expand All @@ -387,17 +392,20 @@ def test_simulate_inputs(tmp_path):
# let's try some edge cases
# unmatched startTime
with pytest.raises(OMPython.ModelicaSystemError):
mod.setInputs(name={"u1": [(-0.5, 0.0), (1.0, 1)]})
mod.setInputs(u1=[(-0.5, 0.0), (1.0, 1)])
mod.simulate()
# unmatched stopTime
with pytest.raises(OMPython.ModelicaSystemError):
mod.setInputs(name={"u1": [(0.0, 0.0), (0.5, 1)]})
mod.setInputs(u1=[(0.0, 0.0), (0.5, 1)])
mod.simulate()

# Let's use both inputs, but each one with different number of
# samples. This has an effect when generating the csv file.
mod.setInputs(name={"u1": [(0.0, 0), (1.0, 1)],
"u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)]})
inputs = {
"u1": [(0.0, 0), (1.0, 1)],
"u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)],
}
mod.setInputs(**inputs)
csv_file = mod._createCSVData()
assert pathlib.Path(csv_file).read_text() == """time,u1,u2,end
0.0,0.0,0.0,0
Expand Down
4 changes: 2 additions & 2 deletions tests/test_linearization.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ def test_getters(tmp_path):
assert "startTime" in d
assert "stopTime" in d
assert mod.getLinearizationOptions(["stopTime", "startTime"]) == [d["stopTime"], d["startTime"]]
mod.setLinearizationOptions(linearizationOptions={"stopTime": 0.02})
mod.setLinearizationOptions(stopTime=0.02)
assert mod.getLinearizationOptions("stopTime") == ["0.02"]

mod.setInputs(name={"u1": 10, "u2": 0})
mod.setInputs(u1=10, u2=0)
[A, B, C, D] = mod.linearize()
g = float(mod.getParameters("g")[0])
l = float(mod.getParameters("l")[0])
Expand Down
Loading