Getting started¶
To start using sofirpy, import the package:
import sofirpy
The following 3 examples demonstrate how to export a Modelica model as a FMU, simulate a FMU and a controller and how to initialize a project.
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_direcotry = dir_path
export_open_modelica_model(model_path, model_name, dir_path)
Exporting a Dymola model and importing parameters
from pathlib import Path
from sofirpy import export_dymola_model
dir_path = Path(__file__).parent
model_path = dir_path.parent / "DC_Motor.mo"
output_direcotry = dir_path
dymola_exe_path = r"C:\Program Files\Dymola 2018 FD01\bin64\Dymola.exe"
model_name = "DC_Motor"
# To import paramater define the parameters and their values in a dictionary as
# follows:
parameters = {"damper.d": 0.1, "damper.useHeatPort": False}
# Note: The values can also be strings, but then they must correspond to the
# Modelica syntax:
# >>> parameters = {"damper.d": "0.1", "damper.useHeatPort": "false"}
export_dymola_model(
dymola_exe_path, model_path, model_name, output_direcotry, parameters=parameters, keep_mos=False
)
Simulating a FMU and a Controller¶
from pathlib import Path
from sofirpy import simulate
from discrete_pid import PID # custom implemented pid controller
fmu_path = Path(__file__).parent / "DC_Motor.fmu"
fmu_info = [
{
"name": "DC_Motor",
"path": str(fmu_path),
"connections": [
{
"parameter name": "u",
"connect to system": "pid",
"connect to external parameter": "u",
}
],
}
]
control_infos = [
{
"name": "pid",
"connections": [
{
"parameter name": "speed",
"connect to system": "DC_Motor",
"connect to external parameter": "y",
}
],
}
]
pid = PID(1e-3, 3, 20, 0.1, set_point=100, u_max=100, u_min=0)
control_class = {"pid": pid}
parameters_to_log = {"DC_Motor": ["y", "MotorTorque.tau"], "pid": ["u"]}
results, units = simulate(
stop_time=10,
step_size=1e-3,
fmu_infos=fmu_info,
model_infos=control_infos,
model_classes=control_class,
parameters_to_log=parameters_to_log,
get_units=True,
)
The custom implemented pid controller is shown below.
from sofirpy import SimulationEntity
class PID(SimulationEntity):
"""Simple implementation of a discrete pid controller"""
def __init__(
self, step_size, K_p=1, K_i=0, K_d=0, set_point=0, u_max=1000, u_min=-1000
):
self.T_a = step_size
self.K_p = K_p
self.K_i = K_i
self.K_d = K_d
self.set_point = set_point
self.inputs = {"speed": 0}
self.outputs = {"u": 0}
self.d_0 = K_p * (
1 + (self.T_a * self.K_i / self.K_p) + self.K_d / (self.K_p * self.T_a)
)
self.d_1 = K_p * (-1 - 2 * self.K_d / (self.K_p * self.T_a))
self.d_2 = K_p * self.K_d / (self.K_p * self.T_a)
self.error = [0, 0, 0]
self.u_max = u_max
self.u_min = u_min
def compute_error(self):
self.error[2] = self.error[1]
self.error[1] = self.error[0]
self.error[0] = self.set_point - self.inputs["speed"]
def set_input(self, input_name, input_value): # mandatory methode
self.inputs[input_name] = input_value
def do_step(self, time): # mandatory methode
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 get_parameter_value(self, output_name): # mandatory methode
return self.outputs[output_name]
Initializing a project and storing data¶
from pathlib import Path
from sofirpy import Project
# Initialize a project by giving it the path to a project directory and the
# path to a hdf5. If the given pathts do not exist, they will be created.
project_dir = Path(__file__).parent
hdf5_name = "example_project"
hdf5_path = Path(project_dir) / f"{hdf5_name}.hdf5"
project = Project(hdf5_path, project_dir)
# actions can be taken only on the hdf5, only on the project directory and
# simultaneously on both the hdf5 and the project directory.
# storing data in the hdf5
data = [
[15, 8, 1, 24, 17],
[16, 14, 7, 5, 23],
[22, 20, 13, 6, 4],
[3, 21, 19, 12, 10],
[9, 2, 25, 18, 11],
]
data_name = "magic"
data_attributes = {"created on": "01.01.01"}
# specify in which folder the matrix should be stored, if the data should be
# stored at the top level, folder_name must be set to None.
folder_name = "magic_matrices"
project.hdf5.store_data(
data_name, data, group_path=folder_name, attributes=data_attributes
)
# reading data
data, attr = project.hdf5.read_data(data_name, folder_name, get_attributes=True)
# create a folder in the project directory
folder_name = "magic_matrices"
project.project_dir.create_folder(folder_name)
# create a folder in the project directory and the hdf5 simultaneously
folder_name = "magic_matrices/examples"
project.create_folder(folder_name)
# delete a folder in the project directory and the hdf5 simultaneously
project.delete_folder(folder_name)
Additional examples¶
Additional examples can be found here.