diff --git a/modelitool/corrai_connector.py b/modelitool/corrai_connector.py
index fdf77fa..59d2d41 100644
--- a/modelitool/corrai_connector.py
+++ b/modelitool/corrai_connector.py
@@ -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 = {}
diff --git a/modelitool/simulate.py b/modelitool/simulate.py
index 63caf14..47be737 100644
--- a/modelitool/simulate.py
+++ b/modelitool/simulate.py
@@ -1,5 +1,6 @@
import os
import tempfile
+import warnings
from pathlib import Path
import pandas as pd
@@ -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,
@@ -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
@@ -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()}")')
@@ -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.
@@ -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:
@@ -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()])
diff --git a/tests/test_simulate.py b/tests/test_simulate.py
index 117d295..0bd5a96 100644
--- a/tests/test_simulate.py
+++ b/tests/test_simulate.py
@@ -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])
@@ -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())
diff --git a/tutorials/Modelica models Handling.ipynb b/tutorials/Modelica models Handling.ipynb
index de73e50..9435763 100644
--- a/tutorials/Modelica models Handling.ipynb
+++ b/tutorials/Modelica models Handling.ipynb
@@ -77,7 +77,7 @@
"source": [
"# 2. Set boundary file\n",
"## Option A: load csv file\n",
- "Let's load measurement data on python. We can use this dataframe directly in our simulator class to define boundary conditions."
+ "Let's load measurement data on python. We can use this dataframe to define boundary conditions of our model."
]
},
{
@@ -151,133 +151,139 @@
},
{
"cell_type": "markdown",
- "id": "f2dedef9-dc04-430f-8fe8-754c39ab7105",
- "metadata": {},
- "source": [
- "#### Set up simulation options \n",
- "\n",
- "Before loading the modelica file, we need to specify the simulation running options. In Modelica, startTime and stopTime correspond to the number\n",
- "of seconds since the beginning of the year. \n",
- "\n",
- "The values can be found in the file created earlier using df_to_combitimetable . Another way is to use the index of the DataFrame we just created.\n",
- "The modelitool function modelitool.combitabconvert.datetime_to_seconds\n",
- "helps you convert datetime index in seconds.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "b26a8f6e-2f1a-41ed-a74e-dc9a41435110",
+ "id": "0ea8c4b4-2eab-429c-a67d-743aaa47a5bd",
"metadata": {},
- "outputs": [],
"source": [
- "from modelitool.combitabconvert import datetime_to_seconds"
+ "To avoid loading all ouptut from modelica model, let's first define a list of output that will be included in the dataframe output for any simulation."
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "a7472557-a5af-49bf-8ffc-08f30741e4c9",
+ "id": "64149508-369a-4a8c-8928-6c71090b4428",
"metadata": {},
"outputs": [],
"source": [
- "simulation_df = reference_df.loc[\"2018-03-22\":\"2018-03-23\"]\n",
- "second_index = datetime_to_seconds(simulation_df.index)"
+ "output_list = [\n",
+ " \"T_coat_ins.T\",\n",
+ " \"T_ins_ins.T\",\n",
+ " \"Tw_out.T\"\n",
+ "]"
]
},
{
"cell_type": "markdown",
- "id": "d4d76fff-d5c5-45b3-805b-255ee67e6ddc",
+ "id": "b092bb4236cc85f3",
"metadata": {},
"source": [
- "- stepSize is the simulation timestep size. In this case it's 5 min or\n",
- "300 sec.\n",
- "- tolerance and solver are related to solver configuration\n",
- "do not change if you don't need to.\n",
- "- outputFormat can be either csv or mat. csv will enable faster data handling during sensitivity analyses and optimizations."
+ "Now, we can load the *om file.\n",
+ "\n",
+ "The `OMModel` class is used to load and simulate Modelica models. It requires the following parameters:\n",
+ "\n",
+ "- `model_path`: Path to the Modelica model file (*.mo) or model name if already loaded in OpenModelica\n",
+ "- `package_path` (optional): Path to additional Modelica packages required by the model\n",
+ "- `simulation_options` (optional): Dictionary containing simulation settings like:\n",
+ " - `startTime`: Start time in seconds\n",
+ " - `stopTime`: Stop time in seconds\n",
+ " - `stepSize`: Time step for the simulation\n",
+ " - `tolerance`: Numerical tolerance for the solver\n",
+ " - `solver`: Solver to use (e.g. \"dassl\")\n",
+ " - `outputFormat`: \"mat\" or \"csv\" for results format\n",
+ " - `x`: Boundary conditions as a DataFrame (optional)\n",
+ "- `output_list` (optional): List of variables to include in simulation results\n",
+ "- `lmodel` (optional): List of required Modelica libraries (e.g. [\"Modelica\"])"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "4cbdd9d0-c377-484f-a4bd-d73fe3e741e2",
+ "id": "3264057e-66ef-41c6-b75a-6efd28748f8c",
"metadata": {},
"outputs": [],
"source": [
- "simulation_df"
+ "from modelitool.simulate import OMModel"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "604aa9ed-b37b-4e61-b96e-a6dfdad42ca7",
+ "id": "8d9bfb90-3f07-49e9-9d7f-314ec3a07fc1",
"metadata": {},
"outputs": [],
"source": [
- "simulation_opt = {\n",
- " \"startTime\": second_index[0],\n",
- " \"stopTime\": second_index[-1],\n",
- " \"stepSize\": 300,\n",
- " \"tolerance\": 1e-06,\n",
- " \"solver\": \"dassl\",\n",
- " \"outputFormat\": \"csv\"\n",
- "}"
+ "simu_OM = OMModel(\n",
+ " model_path=Path(TUTORIAL_DIR) / \"resources/etics_v0.mo\",\n",
+ " output_list=output_list,\n",
+ " lmodel=[\"Modelica\"],\n",
+ ")"
]
},
{
"cell_type": "markdown",
- "id": "0ea8c4b4-2eab-429c-a67d-743aaa47a5bd",
+ "id": "766241a0-95b8-4916-9206-1ca240b2f361",
"metadata": {},
"source": [
- "Finally, we can define a list of output that will be included in the dataframe output for any simulation."
+ "#### Set up simulation options \n",
+ "\n",
+ "As they were not specified when instantiating OMModel, simulation running options (if different from the one provided by the modelica model) should be defined.\n",
+ "\n",
+ "In Modelica, startTime and stopTime correspond to the number\n",
+ "of seconds since the beginning of the year. \n",
+ "\n",
+ "The values can be found in the file created earlier using df_to_combitimetable . Another way is to use the index of the DataFrame we just created.\n",
+ "The modelitool function modelitool.combitabconvert.datetime_to_seconds\n",
+ "helps you convert datetime index in seconds.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "64149508-369a-4a8c-8928-6c71090b4428",
+ "id": "b26a8f6e-2f1a-41ed-a74e-dc9a41435110",
"metadata": {},
"outputs": [],
"source": [
- "output_list = [\n",
- " \"T_coat_ins.T\",\n",
- " \"T_ins_ins.T\",\n",
- " \"Tw_out.T\"\n",
- "]"
+ "from modelitool.combitabconvert import datetime_to_seconds"
]
},
{
- "cell_type": "markdown",
- "id": "b092bb4236cc85f3",
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a7472557-a5af-49bf-8ffc-08f30741e4c9",
"metadata": {},
+ "outputs": [],
"source": [
- "Now let's load the *om file: \n"
+ "simulation_df = reference_df.loc[\"2018-03-22\":\"2018-03-23\"]\n",
+ "second_index = datetime_to_seconds(simulation_df.index)"
]
},
{
- "cell_type": "code",
- "execution_count": null,
- "id": "3264057e-66ef-41c6-b75a-6efd28748f8c",
+ "cell_type": "markdown",
+ "id": "9d771fd7-bdde-4b90-9d3e-699d3f488099",
"metadata": {},
- "outputs": [],
"source": [
- "from modelitool.simulate import OMModel"
+ "- stepSize is the simulation timestep size. In this case it's 5 min or\n",
+ "300 sec.\n",
+ "- tolerance and solver are related to solver configuration\n",
+ "do not change if you don't need to.\n",
+ "- outputFormat can be either csv or mat. csv will enable faster data handling during sensitivity analyses and optimizations.\n",
+ "- x: as the boundary conditions. If not given here, it can still be provided in method `simulate`."
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "8d9bfb90-3f07-49e9-9d7f-314ec3a07fc1",
+ "id": "604aa9ed-b37b-4e61-b96e-a6dfdad42ca7",
"metadata": {},
"outputs": [],
"source": [
- "simu_OM = OMModel(\n",
- " model_path=Path(TUTORIAL_DIR) / \"resources/etics_v0.mo\",\n",
- " simulation_options=simulation_opt,\n",
- " x=simulation_df,\n",
- " output_list=output_list,\n",
- " lmodel=[\"Modelica\"],\n",
- ")"
+ "simulation_opt = {\n",
+ " \"startTime\": second_index[0],\n",
+ " \"stopTime\": second_index[-1],\n",
+ " \"stepSize\": 300,\n",
+ " \"tolerance\": 1e-06,\n",
+ " \"solver\": \"dassl\",\n",
+ " \"outputFormat\": \"csv\"\n",
+ "}"
]
},
{
@@ -293,7 +299,7 @@
"id": "02e59be3-e4f4-44f0-adcd-66a43d200146",
"metadata": {},
"source": [
- "Set the initial and parameter values in a dictionary."
+ "Set the initial and parameter values in a dictionary. They can either be set before simluation (with `set_param_dict()` method, or when using method `simulate()`. Each change of paramter value overwrite the previous one. "
]
},
{
@@ -317,12 +323,14 @@
"id": "65fd55a9-959f-4bef-9ee2-14d0c617b75b",
"metadata": {},
"source": [
- "Simulation flags can be specified in simulate() method. Overview of possible simulation flags can be found here: https://openmodelica.org/doc/OpenModelicaUsersGuide/latest/simulationflags.html. Note that the simulation flag override cannot be used, as it was already used in class OMModel with simulation_options.\n",
+ "Simulation flags can also be specified in simulate() method. Overview of possible simulation flags can be found here: https://openmodelica.org/doc/OpenModelicaUsersGuide/latest/simulationflags.html. Note that the simulation flag override cannot be used, as it was already used in class OMModel with simulation_options.\n",
"\n",
- "If x boundary conditions is not specified or do not\n",
+ "If x boundary conditions do not\n",
" have a DateTime index (seconds int), a year can be specified to convert\n",
" int seconds index to a datetime index. If simulation spans overs several\n",
- " years, it shall be the year when it begins."
+ " years, it shall be the year when it begins.\n",
+ "\n",
+ "The output of the `simulate()` method is a dataframe, containing the outputs listed in output_list."
]
},
{
@@ -335,26 +343,10 @@
"init_res_OM = simu_OM.simulate(\n",
" simflags = \"-initialStepSize=60 -maxStepSize=3600 -w -lv=LOG_STATS\",\n",
" parameter_dict=parameter_dict_OM,\n",
+ " x=reference_df,\n",
" year=2024,\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "70cfed9f-467b-44b2-b80d-459dc02288ae",
- "metadata": {},
- "source": [
- "Results are displayed in a dataframe:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "4820b4f3-c446-4f65-869b-aee86ac96806",
- "metadata": {},
- "outputs": [],
- "source": [
- "init_res_OM"
+ ")\n",
+ "init_res_OM.head()"
]
},
{