Getting started#
The following 3 examples demonstrate how to export a Modelica model as a FMU, co-simulate a FMU and a controller and how to use the rdm features.
Exporting a Modelica Model#
SOFIRpy allows to export a OpenModelica and Dymola model as a FMU.
Exporting a OpenModelica model
from pathlib import Path
from sofirpy import export_open_modelica_model
dir_path = Path(__file__).parent.parent
model_path = dir_path / "DC_Motor.mo"
model_name = "DC_Motor"
output_directory = dir_path
fmu_path = export_open_modelica_model(model_path, model_name, dir_path)
Exporting a Dymola model
from pathlib import Path
from sofirpy import export_dymola_model
dir_path = Path(__file__).parent
model_path = dir_path.parent / "DC_Motor.mo"
output_directory = dir_path
dymola_exe_path = r"C:\Program Files\Dymola 2018 FD01\bin64\Dymola.exe"
model_name = "DC_Motor"
fmu_path = export_dymola_model(
dymola_exe_path=dymola_exe_path,
model_path=model_path,
model_name=model_name,
fmu_name="DC_Motor",
output_directory=output_directory,
)
Simulating a FMU and a Controller#
The custom implemented pid controller is shown below.
from __future__ import annotations
from typing import Any
import sofirpy.common as co
from sofirpy import SimulationEntity
class PID(SimulationEntity):
"""Simple implementation of a discrete pid controller"""
def __init__(self, init_config: co.InitConfig) -> None:
self.parameters = {
"sampling_rate": 1e-3,
"K_p": 1,
"K_i": 0,
"K_d": 0,
"set_point": 0,
"u_max": 1000,
"u_min": -1000,
}
self.inputs = {"speed": 0}
self.outputs = {"u": 0}
self.units = {"u": "V"}
self.dtypes = {"u": float}
self.initialize(init_config)
def _compute_error(self) -> None:
self.error[2] = self.error[1]
self.error[1] = self.error[0]
self.error[0] = self.parameters["set_point"] - self.inputs["speed"]
def do_step(self, time: float, time_step: float): # mandatory method
self._compute_error()
u = (
self.outputs["u"]
+ self.d_0 * self.error[0]
+ self.d_1 * self.error[1]
+ self.d_2 * self.error[2]
)
if u > self.u_max or u < self.u_min:
if u > self.u_max:
u = self.u_max
if u < self.u_min:
u = self.u_min
self.outputs["u"] = u
def set_parameter(
self, parameter_name, parameter_value
) -> None: # mandatory method
self.inputs[parameter_name] = parameter_value
def get_parameter_value(self, output_name): # mandatory method
return self.outputs[output_name]
def initialize(self, init_config: dict[str, Any]) -> None:
self._apply_start_values(init_config["start_values"])
K_p = self.parameters["K_p"]
K_i = self.parameters["K_i"]
K_d = self.parameters["K_d"]
T_a = self.parameters["sampling_rate"]
self.d_0 = K_p * (1 + (T_a * K_i / K_p) + K_d / (K_p * T_a))
self.d_1 = K_p * (-1 - 2 * K_d / (K_p * T_a))
self.d_2 = K_p * K_d / (K_p * T_a)
self.error = [0, 0, 0]
self.u_max = self.parameters["u_max"]
self.u_min = self.parameters["u_min"]
def _apply_start_values(self, start_values: dict[str, Any]) -> None:
for name, value in start_values.items():
self.parameters[name] = value
def get_unit(self, parameter_name: str) -> str | None:
return self.units.get(parameter_name)
def get_dtype_of_parameter(self, parameter_name: str) -> type:
return self.dtypes.get(parameter_name, float)
RDM Features#
This example demonstrates how to create a simulation run from a config file, store the run inside a HDF5 file and read the run again from the HDF5 file.
import os
import sys
from pathlib import Path
from sofirpy import Run
sys.path.append(os.path.join(os.path.dirname(__file__), "../"))
from discrete_pid import PID
# Simulating and storing a run to a hdf5
run_name = "Run_1"
config_path = Path(__file__).parent / "example_config.json"
model_classes = {"pid": PID}
if sys.platform == "win32":
fmu_path = Path(__file__).parent.parent / "DC_Motor.fmu"
elif sys.platform == "darwin":
fmu_path = Path(__file__).parent.parent / "DC_Motor_mac.fmu"
elif sys.platform == "linux":
fmu_path = Path(__file__).parent.parent / "DC_Motor_linux.fmu"
fmu_paths = {"DC_Motor": fmu_path}
run = Run.from_config_file(run_name, config_path, fmu_paths, model_classes)
run.simulate()
hdf5_path = Path(__file__).parent / "run_example.hdf5"
run.to_hdf5(hdf5_path)
# Loading the run from the hdf5
run_loaded = Run.from_hdf5(run_name, hdf5_path)