"""Module containing the Fmu class."""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Callable
from fmpy import extract, read_model_description
from fmpy.fmi2 import FMU2Slave
from fmpy.simulation import (
apply_start_values,
settable_in_initialization_mode,
settable_in_instantiated,
)
import sofirpy.common as co
from sofirpy.simulation.simulation_entity import SimulationEntity
SetterFunction = Callable[[list[int], list[co.ParameterValue]], None]
GetterFunction = Callable[[list[int]], list[co.ParameterValue]]
[docs]
class Fmu(SimulationEntity):
"""Class representing a fmu.
Args:
fmu_path (Path): path to the fmu
step_size (float): step size of the simulation
"""
def __init__(self, fmu_path: Path, name: str, step_size: float) -> None:
self.fmu_path = fmu_path
self.name = name
self.step_size = step_size
@property
def fmu_path(self) -> Path:
"""Path to the fmu.
Returns:
Path: Path to the fmu.
"""
return self._fmu_path
@fmu_path.setter
def fmu_path(self, fmu_path: Path) -> None:
"""Set the fmu path.
Args:
fmu_path (str): The path to the fmu.
Raises:
FileNotFoundError: fmu_path doesn't exist
"""
if not fmu_path.exists():
raise FileNotFoundError(f"The path '{fmu_path}' does not exist")
self._fmu_path = fmu_path
[docs]
def initialize(self, start_values: dict[str, co.StartValue]) -> None:
"""Initialize the fmu.
Args:
start_time (float, optional): start time. Defaults to 0.
"""
self.model_description = read_model_description(self.fmu_path)
self.model_description_dict = {
variable.name: variable
for variable in self.model_description.modelVariables
}
unzip_dir = extract(self.fmu_path)
self.fmu = FMU2Slave(
guid=self.model_description.guid,
unzipDirectory=unzip_dir,
modelIdentifier=self.model_description.coSimulation.modelIdentifier,
instanceName=f"fmu_{self.name}",
)
self.setter_functions: dict[str, SetterFunction] = {
"Boolean": self.fmu.setBoolean,
"Integer": self.fmu.setInteger,
"Real": self.fmu.setReal,
"Enumeration": self.fmu.setInteger,
}
self.getter_functions: dict[str, GetterFunction] = {
"Boolean": self.fmu.getBoolean,
"Integer": self.fmu.getInteger,
"Real": self.fmu.getReal,
}
self.fmu.instantiate()
self.fmu.setupExperiment()
not_set_start_values = apply_start_values(
self.fmu,
self.model_description,
start_values,
settable_in_instantiated,
)
self.fmu.enterInitializationMode()
not_set_start_values = apply_start_values(
self.fmu,
self.model_description,
not_set_start_values,
settable_in_initialization_mode,
)
if not_set_start_values:
logging.warning(
f"The following start values for the FMU '{self.name}' "
f"can not be set:\n{not_set_start_values}",
)
self.fmu.exitInitializationMode()
[docs]
def set_parameter(
self,
parameter_name: str,
parameter_value: co.ParameterValue,
) -> None:
var_type = self.model_description_dict[parameter_name].type
self.setter_functions[var_type](
[self.model_description_dict[parameter_name].valueReference],
[parameter_value],
)
[docs]
def get_parameter_value(self, parameter_name: str) -> co.ParameterValue:
"""Return the value of a parameter.
Args:
parameter_name (str): name of parameter whose value is to be
obtained
Returns:
ParameterValue: value of the parameter
"""
var_type = self.model_description_dict[parameter_name].type
value: co.ParameterValue = self.getter_functions[var_type](
[self.model_description_dict[parameter_name].valueReference],
)[0]
return value
[docs]
def do_step(self, time: float) -> None:
"""Perform a simulation step.
Args:
time (float): current time
"""
self.fmu.doStep(
currentCommunicationPoint=time,
communicationStepSize=self.step_size,
)
[docs]
def conclude_simulation(self) -> None:
"""Conclude the simulation process of the FMU."""
self.fmu.terminate()
self.fmu.freeInstance()
[docs]
def get_unit(self, parameter_name: str) -> str | None:
"""Return the unit of a variable.
Args:
parameter_name (str): Name of the variable.
Returns:
str: The unit of the variable.
"""
unit: str | None = self.model_description_dict[parameter_name].unit
return unit
[docs]
def get_dtype_of_parameter(self, parameter_name: str) -> type:
dtype: type = self.model_description_dict[parameter_name]._python_type
return dtype