Skip to content
Merged
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
2 changes: 1 addition & 1 deletion modelitool/corrai_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def function(self, x_dict):
param[Parameter.NAME]: x_dict[param[Parameter.NAME]]
for param in self.param_list
}
self.om_model._set_param_dict(temp_dict)
self.om_model.set_param_dict(temp_dict)
res = self.om_model.simulate()

function_results = {}
Expand Down
59 changes: 35 additions & 24 deletions modelitool/simulate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import tempfile
import warnings
from pathlib import Path

import pandas as pd
Expand All @@ -15,7 +16,6 @@ def __init__(
self,
model_path: Path | str,
simulation_options: dict[str, float | str | int] = None,
x: pd.DataFrame = None,
output_list: list[str] = None,
simulation_path: Path = None,
x_combitimetable_name: str = None,
Expand All @@ -33,9 +33,6 @@ def __init__(
- simulation_options (dict[str, float | str | int], optional):
Options for the simulation. May include values for "startTime",
"stopTime", "stepSize", "tolerance", "solver", "outputFormat".
- x (pd.DataFrame, optional): Input data for the simulation. Index shall
be a DatetimeIndex or integers. Columns must match the combi time table
used to specify boundary conditions in the Modelica System.
- output_list (list[str], optional): List of output variables. Default
will output all available variables.
- simulation_path (Path, optional): Path to run the simulation and
Expand All @@ -57,7 +54,7 @@ def __init__(
if not os.path.exists(self._simulation_path):
os.mkdir(simulation_path)

self._x = x if x is not None else pd.DataFrame()
self._x = pd.DataFrame()
self.output_list = output_list
self.omc = OMCSessionZMQ()
self.omc.sendExpression(f'cd("{self._simulation_path.as_posix()}")')
Expand All @@ -84,14 +81,16 @@ def simulate(
) -> pd.DataFrame:
"""
Runs the simulation with the provided parameters, simulation options and
boundariy conditions.
boundary conditions.
- parameter_dict (dict, optional): Dictionary of parameters.
- simulation_options (dict, optional): Will update simulation options if it
had been given at the init phase. May include values for "startTime",
"stopTime", "stepSize", "tolerance", "solver", "outputFormat".
- simulation_options (dict, optional): May include values for "startTime",
"stopTime", "stepSize", "tolerance", "solver", "outputFormat". Can
also include 'x' with a DataFrame for boundary conditions.
- x (pd.DataFrame, optional): Input data for the simulation. Index shall
be a DatetimeIndex or integers. Columns must match the combi time table
used to specify boundary conditions in the Modelica System.
be a DatetimeIndex or integers. Columns must match the combitimetable
used to specify boundary conditions in the Modelica System. If 'x' is
provided both in simulation_options and as a direct parameter, the one
provided as direct parameter will be used.
- verbose (bool, optional): If True, prints simulation progress. Defaults to
True.
- simflags (str, optional): Additional simulation flags.
Expand All @@ -102,9 +101,17 @@ def simulate(
"""

if parameter_dict is not None:
self._set_param_dict(parameter_dict)
self.set_param_dict(parameter_dict)

if simulation_options is not None:
if x is not None and "x" in simulation_options:
warnings.warn(
"Boundary file 'x' specified both in simulation_options and as a "
"direct parameter. The 'x' provided in simulate() will be used.",
UserWarning,
stacklevel=2,
)

self._set_simulation_options(simulation_options)

if x is not None:
Expand Down Expand Up @@ -172,28 +179,32 @@ def get_parameters(self):
return self.model.getParameters()

def _set_simulation_options(self, simulation_options):
self.model.setSimulationOptions(
[
f'startTime={simulation_options["startTime"]}',
f'stopTime={simulation_options["stopTime"]}',
f'stepSize={simulation_options["stepSize"]}',
f'tolerance={simulation_options["tolerance"]}',
f'solver={simulation_options["solver"]}',
f'outputFormat={simulation_options["outputFormat"]}',
]
)
standard_options = {
"startTime": simulation_options.get("startTime"),
"stopTime": simulation_options.get("stopTime"),
"stepSize": simulation_options.get("stepSize"),
"tolerance": simulation_options.get("tolerance"),
"solver": simulation_options.get("solver"),
"outputFormat": simulation_options.get("outputFormat"),
}

options = [f"{k}={v}" for k, v in standard_options.items() if v is not None]
self.model.setSimulationOptions(options)
self.simulation_options = simulation_options

if "x" in simulation_options:
self._set_x(simulation_options["x"])

def _set_x(self, df: pd.DataFrame):
"""Sets the input data for the simulation and updates the corresponding file."""
if not self._x.equals(df):
new_bounds_path = self._simulation_path / "boundaries.txt"
df_to_combitimetable(df, new_bounds_path)
full_path = (self._simulation_path / "boundaries.txt").resolve().as_posix()
self._set_param_dict({f"{self.x_combitimetable_name}.fileName": full_path})
self.set_param_dict({f"{self.x_combitimetable_name}.fileName": full_path})
self._x = df

def _set_param_dict(self, param_dict):
def set_param_dict(self, param_dict):
self.model.setParameters([f"{item}={val}" for item, val in param_dict.items()])


Expand Down
63 changes: 56 additions & 7 deletions tests/test_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_set_param_dict(self, simul):
"y.k": 2.0,
}

simul._set_param_dict(test_dict)
simul.set_param_dict(test_dict)

for key in test_dict.keys():
assert float(test_dict[key]) == float(simul.model.getParameters()[key])
Expand Down Expand Up @@ -100,19 +100,68 @@ def test_set_boundaries_df(self):
"outputFormat": "mat",
}

x = pd.DataFrame(
x_options = pd.DataFrame(
{"Boundaries.y[1]": [10, 20, 30], "Boundaries.y[2]": [3, 4, 5]},
index=pd.date_range("2009-07-13 00:00:00", periods=3, freq="h"),
)
x_direct = pd.DataFrame(
{"Boundaries.y[1]": [100, 200, 300], "Boundaries.y[2]": [30, 40, 50]},
index=pd.date_range("2009-07-13 00:00:00", periods=3, freq="h"),
)

simu = OMModel(
model_path="TestLib.boundary_test",
package_path=PACKAGE_DIR / "package.mo",
lmodel=["Modelica"],
)

simulation_options_with_x = simulation_options.copy()
simulation_options_with_x["x"] = x_options
res1 = simu.simulate(simulation_options=simulation_options_with_x)
res1 = res1.loc[:, ["Boundaries.y[1]", "Boundaries.y[2]"]]
np.testing.assert_allclose(x_options.to_numpy(), res1.to_numpy())
assert np.all(
[x_options.index[i] == res1.index[i] for i in range(len(x_options.index))]
)
assert np.all(
[
x_options.columns[i] == res1.columns[i]
for i in range(len(x_options.columns))
]
)

simu = OMModel(
model_path="TestLib.boundary_test",
package_path=PACKAGE_DIR / "package.mo",
lmodel=["Modelica"],
)
res2 = simu.simulate(simulation_options=simulation_options, x=x_direct)
res2 = res2.loc[:, ["Boundaries.y[1]", "Boundaries.y[2]"]]
np.testing.assert_allclose(x_direct.to_numpy(), res2.to_numpy())
assert np.all(
[x_direct.index[i] == res2.index[i] for i in range(len(x_direct.index))]
)
assert np.all(
[
x_direct.columns[i] == res2.columns[i]
for i in range(len(x_direct.columns))
]
)

res = simu.simulate(simulation_options=simulation_options, x=x)
res = res.loc[:, ["Boundaries.y[1]", "Boundaries.y[2]"]]
assert np.all([x.index[i] == res.index[i] for i in range(len(x.index))])
np.testing.assert_allclose(x.to_numpy(), res.to_numpy())
assert np.all([x.columns[i] == res.columns[i] for i in range(len(x.columns))])
simu = OMModel(
model_path="TestLib.boundary_test",
package_path=PACKAGE_DIR / "package.mo",
lmodel=["Modelica"],
)
with pytest.warns(
UserWarning,
match="Boundary file 'x' specified both in simulation_options and as a "
"direct parameter",
):
res3 = simu.simulate(
simulation_options=simulation_options_with_x, x=x_direct
)
res3 = res3.loc[:, ["Boundaries.y[1]", "Boundaries.y[2]"]]
np.testing.assert_allclose(x_direct.to_numpy(), res3.to_numpy())
with pytest.raises(AssertionError):
np.testing.assert_allclose(x_options.to_numpy(), res3.to_numpy())
Loading
Loading