Skip to content
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
274 changes: 194 additions & 80 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,8 @@ def __init__(
self._quantities: list[dict[str, Any]] = []
self._params: dict[str, str] = {} # even numerical values are stored as str
self._inputs: dict[str, list[tuple[float, float]]] = {}
# _outputs values are str before simulate(), but they can be
# np.float64 after simulate().
self._outputs: dict[str, Any] = {}
# same for _continuous
self._continuous: dict[str, Any] = {}
self._outputs: dict[str, np.float64] = {} # numpy.float64 as it allows to define None values
self._continuous: dict[str, np.float64] = {} # numpy.float64 as it allows to define None values
self._simulate_options: dict[str, str] = {}
self._override_variables: dict[str, str] = {}
self._simulate_options_override: dict[str, str] = {}
Expand Down Expand Up @@ -630,11 +627,11 @@ def _xmlparse(self, xml_file: OMCPath):
else:
self._params[scalar["name"]] = scalar["start"]
if scalar["variability"] == "continuous":
self._continuous[scalar["name"]] = scalar["start"]
self._continuous[scalar["name"]] = np.float64(scalar["start"])
if scalar["causality"] == "input":
self._inputs[scalar["name"]] = scalar["start"]
if scalar["causality"] == "output":
self._outputs[scalar["name"]] = scalar["start"]
self._outputs[scalar["name"]] = np.float64(scalar["start"])

self._quantities.append(scalar)

Expand Down Expand Up @@ -695,15 +692,104 @@ def getQuantities(self, names: Optional[str | list[str]] = None) -> list[dict]:

raise ModelicaSystemError("Unhandled input for getQuantities()")

def getContinuousInitial(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, np.float64] | list[np.float64]:
"""
Get (initial) values of continuous signals.

Args:
names: Either None (default), a string with the continuous signal
name, or a list of signal name strings.
Returns:
If `names` is None, a dict in the format
{signal_name: signal_value} is returned.
If `names` is a string, a single element list [signal_value] is
returned.
If `names` is a list, a list with one value for each signal name
in names is returned: [signal1_value, signal2_value, ...].

Examples:
>>> mod.getContinuousInitial()
{'x': '1.0', 'der(x)': None, 'y': '-0.4'}
>>> mod.getContinuousInitial("y")
['-0.4']
>>> mod.getContinuousInitial(["y","x"])
['-0.4', '1.0']
"""
if names is None:
return self._continuous
if isinstance(names, str):
return [self._continuous[names]]
if isinstance(names, list):
return [self._continuous[x] for x in names]

raise ModelicaSystemError("Unhandled input for getContinousInitial()")

def getContinuousFinal(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, np.float64] | list[np.float64]:
"""
Get (final) values of continuous signals (at stopTime).

Args:
names: Either None (default), a string with the continuous signal
name, or a list of signal name strings.
Returns:
If `names` is None, a dict in the format
{signal_name: signal_value} is returned.
If `names` is a string, a single element list [signal_value] is
returned.
If `names` is a list, a list with one value for each signal name
in names is returned: [signal1_value, signal2_value, ...].

Examples:
>>> mod.getContinuousFinal()
{'x': np.float64(0.68), 'der(x)': np.float64(-0.24), 'y': np.float64(-0.24)}
>>> mod.getContinuousFinal("x")
[np.float64(0.68)]
>>> mod.getContinuousFinal(["y","x"])
[np.float64(-0.24), np.float64(0.68)]
"""
if not self._simulated:
raise ModelicaSystemError("Please use getContinuousInitial() before the simulation was started!")

def get_continuous_solution(name_list: list[str]) -> None:
for name in name_list:
if name in self._continuous:
value = self.getSolutions(name)
self._continuous[name] = np.float64(value[0][-1])
else:
raise KeyError(f"{names} is not continuous")

if names is None:
get_continuous_solution(name_list=list(self._continuous.keys()))
return self._continuous

if isinstance(names, str):
get_continuous_solution(name_list=[names])
return [self._continuous[names]]

if isinstance(names, list):
get_continuous_solution(name_list=names)
values = []
for name in names:
values.append(self._continuous[name])
return values

raise ModelicaSystemError("Unhandled input for getContinousFinal()")

def getContinuous(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, str | numbers.Real] | list[str | numbers.Real]:
) -> dict[str, np.float64] | list[np.float64]:
"""Get values of continuous signals.

If called before simulate(), the initial values are returned as
strings (or None). If called after simulate(), the final values (at
stopTime) are returned as numpy.float64.
If called before simulate(), the initial values are returned.
If called after simulate(), the final values (at stopTime) are returned.
The return format is always numpy.float64.

Args:
names: Either None (default), a string with the continuous signal
Expand All @@ -730,45 +816,13 @@ def getContinuous(
{'x': np.float64(0.68), 'der(x)': np.float64(-0.24), 'y': np.float64(-0.24)}
>>> mod.getContinuous("x")
[np.float64(0.68)]
>>> mod.getOutputs(["y","x"])
>>> mod.getContinuous(["y","x"])
[np.float64(-0.24), np.float64(0.68)]
"""
if not self._simulated:
if names is None:
return self._continuous
if isinstance(names, str):
return [self._continuous[names]]
if isinstance(names, list):
return [self._continuous[x] for x in names]

if names is None:
for name in self._continuous:
try:
value = self.getSolutions(name)
self._continuous[name] = value[0][-1]
except (OMCSessionException, ModelicaSystemError) as ex:
raise ModelicaSystemError(f"{name} could not be computed") from ex
return self._continuous

if isinstance(names, str):
if names in self._continuous:
value = self.getSolutions(names)
self._continuous[names] = value[0][-1]
return [self._continuous[names]]
raise ModelicaSystemError(f"{names} is not continuous")

if isinstance(names, list):
valuelist = []
for name in names:
if name in self._continuous:
value = self.getSolutions(name)
self._continuous[name] = value[0][-1]
valuelist.append(value[0][-1])
else:
raise ModelicaSystemError(f"{name} is not continuous")
return valuelist
return self.getContinuousInitial(names=names)

raise ModelicaSystemError("Unhandled input for getContinous()")
return self.getContinuousFinal(names=names)

def getParameters(
self,
Expand Down Expand Up @@ -841,15 +895,103 @@ def getInputs(

raise ModelicaSystemError("Unhandled input for getInputs()")

def getOutputsInitial(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, np.float64] | list[np.float64]:
"""
Get (initial) values of output signals.

Args:
names: Either None (default), a string with the output name,
or a list of output name strings.
Returns:
If `names` is None, a dict in the format
{output_name: output_value} is returned.
If `names` is a string, a single element list [output_value] is
returned.
If `names` is a list, a list with one value for each output name
in names is returned: [output1_value, output2_value, ...].

Examples:
>>> mod.getOutputsInitial()
{'out1': '-0.4', 'out2': '1.2'}
>>> mod.getOutputsInitial("out1")
['-0.4']
>>> mod.getOutputsInitial(["out1","out2"])
['-0.4', '1.2']
"""
if names is None:
return self._outputs
if isinstance(names, str):
return [self._outputs[names]]
if isinstance(names, list):
return [self._outputs[x] for x in names]

raise ModelicaSystemError("Unhandled input for getOutputsInitial()")

def getOutputsFinal(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, np.float64] | list[np.float64]:
"""Get (final) values of output signals (at stopTime).

Args:
names: Either None (default), a string with the output name,
or a list of output name strings.
Returns:
If `names` is None, a dict in the format
{output_name: output_value} is returned.
If `names` is a string, a single element list [output_value] is
returned.
If `names` is a list, a list with one value for each output name
in names is returned: [output1_value, output2_value, ...].

Examples:
>>> mod.getOutputsFinal()
{'out1': np.float64(-0.1234), 'out2': np.float64(2.1)}
>>> mod.getOutputsFinal("out1")
[np.float64(-0.1234)]
>>> mod.getOutputsFinal(["out1","out2"])
[np.float64(-0.1234), np.float64(2.1)]
"""
if not self._simulated:
raise ModelicaSystemError("Please use getOuputsInitial() before the simulation was started!")

def get_outputs_solution(name_list: list[str]) -> None:
for name in name_list:
if name in self._outputs:
value = self.getSolutions(name)
self._outputs[name] = np.float64(value[0][-1])
else:
raise KeyError(f"{names} is not a valid output")

if names is None:
get_outputs_solution(name_list=list(self._outputs.keys()))
return self._outputs

if isinstance(names, str):
get_outputs_solution(name_list=[names])
return [self._outputs[names]]

if isinstance(names, list):
get_outputs_solution(name_list=names)
values = []
for name in names:
values.append(self._outputs[name])
return values

raise ModelicaSystemError("Unhandled input for getOutputs()")

def getOutputs(
self,
names: Optional[str | list[str]] = None,
) -> dict[str, str | numbers.Real] | list[str | numbers.Real]:
) -> dict[str, np.float64] | list[np.float64]:
"""Get values of output signals.

If called before simulate(), the initial values are returned as
strings. If called after simulate(), the final values (at stopTime)
are returned as numpy.float64.
If called before simulate(), the initial values are returned.
If called after simulate(), the final values (at stopTime) are returned.
The return format is always numpy.float64.

Args:
names: Either None (default), a string with the output name,
Expand Down Expand Up @@ -880,37 +1022,9 @@ def getOutputs(
[np.float64(-0.1234), np.float64(2.1)]
"""
if not self._simulated:
if names is None:
return self._outputs
if isinstance(names, str):
return [self._outputs[names]]
return [self._outputs[x] for x in names]

if names is None:
for name in self._outputs:
value = self.getSolutions(name)
self._outputs[name] = value[0][-1]
return self._outputs
return self.getOutputsInitial(names=names)

if isinstance(names, str):
if names in self._outputs:
value = self.getSolutions(names)
self._outputs[names] = value[0][-1]
return [self._outputs[names]]
raise KeyError(names)

if isinstance(names, list):
valuelist = []
for name in names:
if name in self._outputs:
value = self.getSolutions(name)
self._outputs[name] = value[0][-1]
valuelist.append(value[0][-1])
else:
raise KeyError(name)
return valuelist

raise ModelicaSystemError("Unhandled input for getOutputs()")
return self.getOutputsFinal(names=names)

def getSimulationOptions(
self,
Expand Down
39 changes: 26 additions & 13 deletions tests/test_ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,20 +345,33 @@ def test_getters(tmp_path):
with pytest.raises(KeyError):
mod.getInputs("thisInputDoesNotExist")
# getOutputs before simulate()
assert mod.getOutputs() == {'y': '-0.4'}
assert mod.getOutputs("y") == ["-0.4"]
assert mod.getOutputs(["y", "y"]) == ["-0.4", "-0.4"]
output = mod.getOutputs()
assert len(output) == 1
assert 'y' in output.keys()
assert np.isclose(output['y'], -0.4)
assert np.isclose(mod.getOutputs("y"), -0.4)
output = mod.getOutputs(["y", "y"])
assert len(output) == 2
assert np.isclose(output[0], -0.4)
assert np.isclose(output[1], -0.4)
with pytest.raises(KeyError):
mod.getOutputs("thisOutputDoesNotExist")

# getContinuous before simulate():
assert mod.getContinuous() == {
'x': '1.0',
'der(x)': None,
'y': '-0.4'
}
assert mod.getContinuous("y") == ['-0.4']
assert mod.getContinuous(["y", "x"]) == ['-0.4', '1.0']
continuous = mod.getContinuous()
assert len(continuous) == 3
assert 'x' in continuous.keys()
assert np.isclose(continuous['x'], 1.0)
assert 'der(x)' in continuous.keys()
assert np.isnan(continuous['der(x)'])
assert 'y' in continuous.keys()
assert np.isclose(continuous['y'], -0.4)
continuous = mod.getContinuous('y')
assert np.isclose(continuous, -0.4)
continuous = mod.getContinuous(['y', 'x'])
assert np.isclose(continuous[0], -0.4)
assert np.isclose(continuous[1], 1.0)

with pytest.raises(KeyError):
mod.getContinuous("a") # a is a parameter

Expand All @@ -381,9 +394,9 @@ def test_getters(tmp_path):
mod.getOutputs("thisOutputDoesNotExist")

# getContinuous after simulate() should return values at end of simulation:
with pytest.raises(OMPython.ModelicaSystemError):
with pytest.raises(KeyError):
mod.getContinuous("a") # a is a parameter
with pytest.raises(OMPython.ModelicaSystemError):
with pytest.raises(KeyError):
mod.getContinuous(["x", "a", "y"]) # a is a parameter
d = mod.getContinuous()
assert d.keys() == {"x", "der(x)", "y"}
Expand All @@ -393,7 +406,7 @@ def test_getters(tmp_path):
assert mod.getContinuous("x") == [d["x"]]
assert mod.getContinuous(["y", "x"]) == [d["y"], d["x"]]

with pytest.raises(OMPython.ModelicaSystemError):
with pytest.raises(KeyError):
mod.getContinuous("a") # a is a parameter

with pytest.raises(OMPython.ModelicaSystemError):
Expand Down