From 9e65e095d4632f1bc6f0d79ae7364a6af7675849 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:55:19 +0200 Subject: [PATCH 1/9] add docstrings with help from copilot --- src/mlfmu/api.py | 20 +- src/mlfmu/cli/mlfmu.py | 11 +- src/mlfmu/cli/publish_docs.py | 1 + .../fmu_build/templates/fmu/fmu_template.cpp | 54 ++++- .../fmu_build/templates/onnx_fmu/onnxFmu.cpp | 151 ++++++++++++++ src/mlfmu/types/component_examples.py | 7 + src/mlfmu/types/fmu_component.py | 190 +++++++++++++++++- src/mlfmu/types/onnx_model.py | 46 ++++- src/mlfmu/utils/builder.py | 109 +++++++++- src/mlfmu/utils/fmi_builder.py | 9 +- src/mlfmu/utils/interface.py | 26 +++ src/mlfmu/utils/logger.py | 14 ++ src/mlfmu/utils/signals.py | 7 +- tests/cli/test_mlfmu_cli.py | 77 +++++-- tests/conftest.py | 20 ++ 15 files changed, 680 insertions(+), 62 deletions(-) diff --git a/src/mlfmu/api.py b/src/mlfmu/api.py index d85c798..21050b0 100644 --- a/src/mlfmu/api.py +++ b/src/mlfmu/api.py @@ -136,8 +136,13 @@ def __init__( logger.debug(f"Created temp folder: {self.temp_folder_path}") def __del__(self): + """ + Destructor for the MlFmuBuilder class. + + This method is automatically called when the object is about to be destroyed. + The destructor should automatically delete the temporary directory (goes out of scope). + """ logger.debug("MlFmuBuilder: destructor called, removing temporary build directory.") - # The destructor should automatically delete the temporary directory (goes out of scope). def build(self): """ @@ -191,7 +196,7 @@ def build(self): def generate(self): """ - Generate FMU c++ source code and model description from ml_model_file and interface_file and saves it to source_folder. + Generate FMU C++ source code and model description from ml_model_file and interface_file and saves it to source_folder. If the paths to the necessary files and directories are not given the function will try to find files and directories that match the ones needed. @@ -232,7 +237,7 @@ def generate(self): def compile(self): """ - Compile FMU from FMU c++ source code and model description contained in source_folder and saves it to fmu_output_folder. + Compile FMU from FMU C++ source code and model description contained in source_folder and saves it to fmu_output_folder. If the paths to the necessary files and directories are not given the function will try to find files and directories that match the ones needed. @@ -416,7 +421,8 @@ def __init__( ) def run(self): - """Run the mlfmu process. + """ + Run the mlfmu process. Runs the mlfmu process in a self-terminated loop. """ @@ -440,14 +446,16 @@ def run_number(self) -> int: @property def max_number_of_runs(self) -> int: - """Example for a read/write property implemented through a pair of explicit + """ + Example for a read/write property implemented through a pair of explicit getter and setter methods (see below for the related setter method). """ return self._max_number_of_runs @max_number_of_runs.setter def max_number_of_runs(self, value: int): - """Setter method that belongs to above getter method. + """ + Setter method that belongs to above getter method. Note that implementing specific getter- and setter methods is in most cases not necessary. The same can be achieved by simply making the instance variable a public attribute. diff --git a/src/mlfmu/cli/mlfmu.py b/src/mlfmu/cli/mlfmu.py index e2d4454..a4ff1ad 100644 --- a/src/mlfmu/cli/mlfmu.py +++ b/src/mlfmu/cli/mlfmu.py @@ -13,6 +13,14 @@ def _argparser() -> argparse.ArgumentParser: + """ + Create and return an ArgumentParser object for parsing command line arguments. + + Returns + ------- + argparse.ArgumentParser: The ArgumentParser object. + """ + parser = argparse.ArgumentParser( prog="mlfmu", epilog="_________________mlfmu___________________", @@ -128,7 +136,8 @@ def _argparser() -> argparse.ArgumentParser: def main(): - """Entry point for console script as configured in setup.cfg. + """ + Entry point for console script as configured in setup.cfg. Runs the command line interface and parses arguments and options entered on the console. """ diff --git a/src/mlfmu/cli/publish_docs.py b/src/mlfmu/cli/publish_docs.py index f8abca4..03f71cf 100644 --- a/src/mlfmu/cli/publish_docs.py +++ b/src/mlfmu/cli/publish_docs.py @@ -6,5 +6,6 @@ def main(): + """Start the publishing process for the mlfmu interface docs, by calling the publish_interface_schema function.""" logger.info("Start publish-interface-docs.py") publish_interface_schema() diff --git a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp index a4e5aca..94e6ffb 100644 --- a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp +++ b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp @@ -5,19 +5,55 @@ #include -class {FmuName} : public OnnxFmu {{ +/** + * \class {FmuName} + * \brief A class representing an {FmuName} FMU. + * + * This class is derived from the OnnxFmu class and provides functionality specific to the {FmuName} FMU. + */ +class +{ + FmuName +} : public OnnxFmu {{ public : - {FmuName}(cppfmu::FMIString fmuResourceLocation) : OnnxFmu(fmuResourceLocation) {{}} private : -}}; + /** + * \brief Constructs a new {FmuName} object. + * + * \param fmuResourceLocation The location of the FMU resource. + */ + {FmuName}(cppfmu::FMIString fmuResourceLocation) : OnnxFmu(fmuResourceLocation) {} + private : + // Add private members and functions here +}}; +/** + * \brief Instantiate a `slave` instance of the FMU. + * + * \param instanceName The name of the FMU instance. + * \param fmuGUID The GUID of the FMU. + * \param fmuResourceLocation The location of the FMU resource. + * \param mimeType The MIME type of the FMU. + * \param timeout The timeout value for the instantiation process. + * \param visible Flag indicating whether the FMU should be visible. + * \param interactive Flag indicating whether the FMU should be interactive. + * \param memory The memory to be used for the FMU instance. + * \param logger The logger to be used for logging messages. + * \returns A unique pointer to the instantiated slave instance. + * + * \throws std::runtime_error if the FMU GUID does not match. + */ cppfmu::UniquePtr CppfmuInstantiateSlave( cppfmu::FMIString /*instanceName*/, cppfmu::FMIString fmuGUID, cppfmu::FMIString fmuResourceLocation, cppfmu::FMIString /*mimeType*/, cppfmu::FMIReal /*timeout*/, cppfmu::FMIBoolean /*visible*/, cppfmu::FMIBoolean /*interactive*/, cppfmu::Memory memory, cppfmu::Logger /*logger*/) -{{ - if (std::strcmp(fmuGUID, FMU_UUID) != 0) {{ - throw std::runtime_error("FMU GUID mismatch"); - }} - return cppfmu::AllocateUnique<{FmuName}>(memory, fmuResourceLocation); -}} +{ + { + if (std::strcmp(fmuGUID, FMU_UUID) != 0) { + { + throw std::runtime_error("FMU GUID mismatch"); + } + } + return cppfmu::AllocateUnique<{FmuName}>(memory, fmuResourceLocation); + } +} diff --git a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp index 425d6a2..77b77aa 100644 --- a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp +++ b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp @@ -1,14 +1,33 @@ #include "onnxFmu.hpp" +/** + * \brief Constructs an instance of the OnnxFmu class. + * + * Constructs an instance of the OnnxFmu class. + * + * \param fmuResourceLocation The location of the FMU resource. + */ OnnxFmu::OnnxFmu(cppfmu::FMIString fmuResourceLocation) { + onnxPath_ = formatOnnxPath(fmuResourceLocation); CreateSession(); OnnxFmu::Reset(); } + +/** + * \brief Formats the onnx path. + * + * Formats the onnx path by appending the ONNX_FILENAME to the given fmuResourceLocation. + * If the path starts with "file:///", it removes the "file://" prefix. + * + * \param fmuResourceLocation The location of the FMU resource. + * \returns the formatted onnx path (std::wstring) + */ std::wstring OnnxFmu::formatOnnxPath(cppfmu::FMIString fmuResourceLocation) { + // Creating complete path to onnx file std::wostringstream onnxPathStream; onnxPathStream << fmuResourceLocation; @@ -25,17 +44,37 @@ std::wstring OnnxFmu::formatOnnxPath(cppfmu::FMIString fmuResourceLocation) return path; } +/** + * \brief Creates a session for the ONNX FMU. + * + * This function creates a session for the ONNX FMU using the specified ONNX model file. + * + * \note The ONNX model file path must be set before calling this function. + * \throws std::runtime_error if the session creation fails. + */ void OnnxFmu::CreateSession() { session_ = Ort::Session(env, onnxPath_.c_str(), Ort::SessionOptions {nullptr}); } +/** + * \brief Resets the state of the OnnxFmu. + * + * This function is called to reset the state of the OnnxFmu. + */ void OnnxFmu::Reset() { doStateInit_ = true; return; } +/** + * Sets the real values of the specified FMI value references. + * + * \param vr An array of FMI value references. + * \param nvr The number of FMI value references in the array. + * \param value An array of FMI real values to be set. + */ void OnnxFmu::SetReal(const cppfmu::FMIValueReference vr[], std::size_t nvr, const cppfmu::FMIReal value[]) { for (std::size_t i = 0; i < nvr; ++i) { @@ -43,6 +82,14 @@ void OnnxFmu::SetReal(const cppfmu::FMIValueReference vr[], std::size_t nvr, con } } + +/** + * \brief Retrieves the real values of the specified FMI value references. + * + * \param vr An array of FMI value references. + * \param nvr The number of FMI value references in the array. + * \param value An array to store the retrieved real values. + */ void OnnxFmu::GetReal(const cppfmu::FMIValueReference vr[], std::size_t nvr, cppfmu::FMIReal value[]) const { for (std::size_t i = 0; i < nvr; ++i) { @@ -50,6 +97,17 @@ void OnnxFmu::GetReal(const cppfmu::FMIValueReference vr[], std::size_t nvr, cpp } } + +/** + * \brief Sets the integer values of the specified FMI value references. + * + * This function sets the integer values of the FMI value references specified in the + * `vr` array to the corresponding values in the `value` array. + * + * \param vr An array of FMI value references. + * \param nvr The number of FMI value references in the `vr` array. + * \param value An array of FMI integer values. + */ void OnnxFmu::SetInteger(const cppfmu::FMIValueReference vr[], std::size_t nvr, const cppfmu::FMIInteger value[]) { for (std::size_t i = 0; i < nvr; ++i) { @@ -57,6 +115,17 @@ void OnnxFmu::SetInteger(const cppfmu::FMIValueReference vr[], std::size_t nvr, } } + +/** + * \brief Retrieves the integer values of the specified FMI value references. + * + * This function retrieves the integer values of the FMI value references specified in the + * `vr` array and stores them in the `value` array. + * + * \param vr An array of FMI value references. + * \param nvr The number of FMI value references in the `vr` array. + * \param value An array to store the retrieved integer values. + */ void OnnxFmu::GetInteger(const cppfmu::FMIValueReference vr[], std::size_t nvr, cppfmu::FMIInteger value[]) const { for (std::size_t i = 0; i < nvr; ++i) { @@ -64,6 +133,16 @@ void OnnxFmu::GetInteger(const cppfmu::FMIValueReference vr[], std::size_t nvr, } } +/** + * \brief Sets the boolean values for the specified FMI value references. + * + * This function sets the boolean values for the specified FMI value references in the + * OnnxFmu object. + * + * \param vr An array of FMI value references. + * \param nvr The number of FMI value references in the array. + * \param value An array of FMI boolean values. + */ void OnnxFmu::SetBoolean(const cppfmu::FMIValueReference vr[], std::size_t nvr, const cppfmu::FMIBoolean value[]) { for (std::size_t i = 0; i < nvr; ++i) { @@ -71,6 +150,16 @@ void OnnxFmu::SetBoolean(const cppfmu::FMIValueReference vr[], std::size_t nvr, } } +/** + * \brief Retrieves boolean values for the specified value references. + * + * This function retrieves boolean values for the specified value references from the + * OnnxFmu object. + * + * \param vr An array of FMIValueReference representing the value references. + * \param nvr The number of value references in the vr array. + * \param value An array of FMIBoolean to store the retrieved boolean values. + */ void OnnxFmu::GetBoolean(const cppfmu::FMIValueReference vr[], std::size_t nvr, cppfmu::FMIBoolean value[]) const { for (std::size_t i = 0; i < nvr; ++i) { @@ -78,6 +167,15 @@ void OnnxFmu::GetBoolean(const cppfmu::FMIValueReference vr[], std::size_t nvr, } } +/** + * \brief Sets the ONNX inputs for the ONNX FMU. + * + * This function sets the ONNX inputs for the ONNX FMU by iterating over the ONNX input + * value-reference index pairs and assigning the corresponding FMU variable's real value + * to the ONNX input. + * + * \returns `true` if the ONNX inputs are successfully set, `false` otherwise. + */ bool OnnxFmu::SetOnnxInputs() { for (int index = 0; index < NUM_ONNX_FMU_INPUTS; index++) { @@ -90,6 +188,14 @@ bool OnnxFmu::SetOnnxInputs() return true; } +/** + * \brief Sets the ONNX states. + * + * This function sets the ONNX states by assigning the corresponding ONNX outputs to the + * ONNX states array. + * + * \returns `true` if the ONNX states are successfully set, `false` otherwise. + */ bool OnnxFmu::SetOnnxStates() { for (int index = 0; index < NUM_ONNX_STATES_OUTPUTS; index++) { @@ -98,6 +204,17 @@ bool OnnxFmu::SetOnnxStates() return true; } +/** + * \brief Retrieves the ONNX outputs and updates the corresponding FMU variables. + * + * This function iterates over the ONNX output value-reference index pairs and updates + * the FMU variables with the corresponding ONNX outputs. + * The function assumes that the FMU variables are of type `FMIVariable` and Real valued + * and the ONNX outputs are stored in the `onnxOutputs_` array. + * + * \returns `true` if the ONNX outputs are successfully retrieved and FMU variables are + * updated, `false` otherwise. + */ bool OnnxFmu::GetOnnxOutputs() { for (int index = 0; index < NUM_ONNX_FMU_OUTPUTS; index++) { @@ -112,6 +229,15 @@ bool OnnxFmu::GetOnnxOutputs() return true; } +/** + * \brief Initializes the ONNX states of the ONNX FMU. + * + * This function initializes the ONNX states of the ONNX FMU by assigning the initial values + * of the ONNX states from the corresponding variables in the FMU. + * The function assumes that the FMU variables are of type `FMIVariable` and Real valued. + * + * \returns `true` if the ONNX states are successfully initialized, `false` otherwise. + */ bool OnnxFmu::InitOnnxStates() { for (int index = 0; index < NUM_ONNX_STATE_INIT; index++) { @@ -124,6 +250,18 @@ bool OnnxFmu::InitOnnxStates() return true; } +/** + * \brief Runs the ONNX model for the FMU. + * + * This function runs the ONNX model for the FMU using the provided current communication + * point and time step. + * It takes the input data, states (if any), and time inputs (if enabled) and calls Run + * to produce the output data. + * + * \param currentCommunicationPoint The current communication point of the FMU. + * \param dt The time step of the FMU. + * \returns `true` if the ONNX model was successfully run, `false` otherwise. + */ bool OnnxFmu::RunOnnxModel(cppfmu::FMIReal currentCommunicationPoint, cppfmu::FMIReal dt) { try { @@ -163,6 +301,19 @@ bool OnnxFmu::RunOnnxModel(cppfmu::FMIReal currentCommunicationPoint, cppfmu::FM return true; } +/** + * \brief Performs a step in the OnnxFmu simulation. + * + * This function is called by the FMU framework to perform a step in the simulation. + * It initializes the ONNX states if necessary, sets the ONNX inputs, runs the ONNX model, + * gets the ONNX outputs, and sets the ONNX states. + * + * \param currentCommunicationPoint The current communication point in the simulation. + * \param dt The time step size for the simulation. + * \param newStep A boolean indicating whether a new step has started. + * \param endOfStep The end of the current step in the simulation. + * \returns `true` if the step was successful, `false` otherwise. + */ bool OnnxFmu::DoStep(cppfmu::FMIReal currentCommunicationPoint, cppfmu::FMIReal dt, cppfmu::FMIBoolean /*newStep*/, cppfmu::FMIReal& /*endOfStep*/) { diff --git a/src/mlfmu/types/component_examples.py b/src/mlfmu/types/component_examples.py index 8b39f8e..b96f2fb 100644 --- a/src/mlfmu/types/component_examples.py +++ b/src/mlfmu/types/component_examples.py @@ -1,4 +1,11 @@ def create_fmu_signal_example(): + """ + Create an example FMU signal variable. + + Returns + ------- + Variable: An instance of the Variable class representing the FMU signal variable. + """ from mlfmu.types.fmu_component import FmiVariableType, Variable return Variable( diff --git a/src/mlfmu/types/fmu_component.py b/src/mlfmu/types/fmu_component.py index 2dc802c..3b9cc5f 100644 --- a/src/mlfmu/types/fmu_component.py +++ b/src/mlfmu/types/fmu_component.py @@ -48,6 +48,20 @@ class BaseModelConfig(BaseModel): class Variable(BaseModelConfig): + """ + Represents a variable in an FMU component. + + Attributes + ---------- + name (str): Unique name for the port. + type (Optional[FmiVariableType]): Data type as defined by FMI standard, defaults to Real. + description (Optional[str]): Short FMU variable description. + variability (Optional[FmiVariability]): Signal variability as defined by FMI. + start_value (Optional[Union[float, str, bool, int]]): Initial value of the signal at time step 1. Type should match the variable type. + is_array (Optional[bool]): When dealing with an array signal, it is essential to specify the LENGTH parameter. Arrays are indexed starting from 0, and FMU signals will be structured as SIGNAL_NAME[0], SIGNAL_NAME[1], and so forth. By default, this feature is set to False. + length (Optional[int]): Defines the number of entries in the signal if the signal is array. + """ + name: str = Field( None, description="Unique name for the port.", @@ -76,6 +90,23 @@ class Variable(BaseModelConfig): class InternalState(BaseModelConfig): + """ + Represents an internal state of an FMU component. + + Attributes + ---------- + name (Optional[str]): Unique name for the state. Only needed if start_value is set (!= None). + Initialization FMU parameters will be generated using this name. + description (Optional[str]): Short description of the FMU variable. + start_value (Optional[float]): The default value of the parameter used for initialization. + If this field is set, parameters for initialization will be automatically generated for these states. + initialization_variable (Optional[str]): The name of an input or parameter in the same model interface + that should be used to initialize this state. + agent_output_indexes (List[str]): Index or range of indices of agent outputs that will be stored as + internal states and will be fed as inputs in the next time step. Note: the FMU signal and the + agent outputs need to have the same length. + """ + name: Optional[str] = Field( None, description="Unique name for state. Only needed if start_value is set (!= None). Initialization FMU parameters will be generated using this name", @@ -103,6 +134,21 @@ class InternalState(BaseModelConfig): @model_validator(mode="after") def check_only_one_initialization(self): + """ + Check if only one state initialization method is used at a time. + + Raises a ValueError if multiple state initialization methods are used simultaneously. + + Returns + ------- + self: The FMU component instance. + + Raises + ------ + ValueError: If initialization_variable is set and either start_value or name is also set. + ValueError: If name is set without start_value being set. + ValueError: If start_value is set without name being set. + """ init_var = self.initialization_variable is not None name = self.name is not None start_value = self.start_value is not None @@ -123,6 +169,19 @@ def check_only_one_initialization(self): class InputVariable(Variable): + """ + Represents an input variable for an FMU component. + + Attributes + ---------- + agent_input_indexes (List[str]): Index or range of indices of agent inputs to which this FMU signal shall be linked to. + Note: The FMU signal and the agent inputs need to have the same length. + + Examples + -------- + An example of `agent_input_indexes` can be ["10", "10:20", "30"]. + """ + agent_input_indexes: List[ Annotated[ str, @@ -136,6 +195,19 @@ class InputVariable(Variable): class OutputVariable(Variable): + """ + Represents an output variable in the FMU component. + + Attributes + ---------- + agent_output_indexes (List[str]): Index or range of indices of agent outputs that will be linked to this output signal. + Note: The FMU signal and the agent outputs need to have the same length. + + Examples + -------- + An example of `agent_output_indexes` can be ["10", "10:20", "30"]. + """ + agent_output_indexes: List[ Annotated[ str, @@ -143,13 +215,29 @@ class OutputVariable(Variable): ] ] = Field( [], - description="Index or range of indices of agent outputs that will be linked to this output signal. Note: the FMU signal and the agent outputs need to have the same length.", + description="Index or range of indices of agent outputs that will be linked to this output signal. Note: The FMU signal and the agent outputs need to have the same length.", examples=["10", "10:20", "30"], ) @dataclass class FmiInputVariable(InputVariable): + """ + Represents an input variable in an FMI component. + + Attributes + ---------- + causality (FmiCausality): The causality of the input variable. + variable_references (List[int]): List of variable references. + agent_state_init_indexes (List[List[int]]): List of agent state initialization indexes. + + Args + ---- + **kwargs: Additional keyword arguments to initialize the input variable. + - causality (FmiCausality): The causality of the input variable. + - variable_references (List[int]): List of variable references. + """ + causality: FmiCausality variable_references: List[int] = [] agent_state_init_indexes: List[List[int]] = [] @@ -162,6 +250,21 @@ def __init__(self, **kwargs): # type: ignore @dataclass class FmiOutputVariable(OutputVariable): + """ + Represent an output variable in an FMI component. + + Attributes + ---------- + causality (FmiCausality): The causality of the output variable. + variable_references (List[int]): The list of variable references associated with the output variable. + + Args + ---- + **kwargs: Additional keyword arguments to initialize the output variable. + - causality (FmiCausality): The causality of the output variable. Defaults to FmiCausality.OUTPUT. + - variable_references (List[int]): The list of variable references associated with the output variable. Defaults to an empty list. + """ + causality: FmiCausality variable_references: List[int] = [] @@ -173,6 +276,20 @@ def __init__(self, **kwargs): # type: ignore @dataclass class FmiVariable: + """ + Represents a variable in an FMU component. + + Attributes + ---------- + name (str): The name of the variable. + variable_reference (int): The reference ID of the variable. Default: 0 + type (FmiVariableType): The type of the variable. + start_value (Union[bool, str, int, float]): The initial value of the variable. Default: 0 + causality (FmiCausality): The causality of the variable. + description (str): The description of the variable. + variability (FmiVariability): The variability of the variable. + """ + name: str = "" variable_reference: int = 0 type: FmiVariableType = FmiVariableType.REAL @@ -183,6 +300,25 @@ class FmiVariable: class ModelComponent(BaseModelConfig): + """ + Represents a simulation model component. + + Attributes + ---------- + name (str): The name of the simulation model. + version (str): The version number of the model. + author (Optional[str]): Name or email of the model's author. + description (Optional[str]): Brief description of the model. + copyright (Optional[str]): Copyright line for use in full license text. + license (Optional[str]): License text or file name (relative to source files). + inputs (List[InputVariable]): List of input signals of the simulation model. + outputs (List[OutputVariable]): List of output signals of the simulation model. + parameters (List[InputVariable]): List of parameter signals of the simulation model. + states (List[InternalState]): Internal states that will be stored in the simulation model's memory, these will be passed as inputs to the agent in the next time step. + uses_time (Optional[bool]): Whether the agent consumes time data from co-simulation algorithm. + state_initialization_reuse (bool): Whether variables are allowed to be reused for state initialization when initialization_variable is used for state initialization. If set to true the variable referred to in initialization_variable will be repeated for the state initialization until the entire state is initialized. + """ + name: str = Field(None, description="The name of the simulation model.") version: str = Field("0.0.1", description="The version number of the model.") author: Optional[str] = Field(None, description="Name or email of the model's author.") @@ -219,6 +355,35 @@ class ModelComponent(BaseModelConfig): class FmiModel: + """ + Represents an FMU model with its associated properties and variables. + + Attributes + ---------- + name (str): The name of the FMU model. + guid (Optional[UUID]): The globally unique identifier of the FMU model. + inputs (List[FmiInputVariable]): The list of input variables for the FMU model. + outputs (List[FmiOutputVariable]): The list of output variables for the FMU model. + parameters (List[FmiInputVariable]): The list of parameter variables for the FMU model. + author (Optional[str]): The author of the FMU model. + version (str): The version of the FMU model. + description (Optional[str]): The description of the FMU model. + copyright (Optional[str]): The copyright information of the FMU model. + license (Optional[str]): The license information of the FMU model. + state_initialization_reuse (bool): Indicates whether the FMU model reuses state initialization. + + Methods + ------- + __init__(self, model: ModelComponent): Initializes the FmiModel object with a ModelComponent object. + add_variable_references(self, inputs: List[InputVariable], parameters: List[InputVariable], outputs: List[OutputVariable]): + Assigns variable references to inputs, parameters, and outputs from the user interface to the FMU model class. + add_state_initialization_parameters(self, states: List[InternalState]): + Generates or modifies FmuInputVariables for initialization of states for the InternalState objects. + format_fmi_variable(self, var: Union[FmiInputVariable, FmiOutputVariable]) -> List[FmiVariable]: + Gets an inclusive list of variables from an interface variable definition. + + """ + name: str = "" guid: Optional[UUID] = None inputs: List[FmiInputVariable] = [] @@ -250,9 +415,11 @@ def add_variable_references( parameters: List[InputVariable], outputs: List[OutputVariable], ): - """Assign variable references to inputs, parameters and outputs from user interface to FMU model class. + """ + Assign variable references to inputs, parameters and outputs from user interface to FMU model class. - Args: + Args + ---- inputs (List[InputVariable]): List of input variables from JSON interface parameters (List[InputVariable]): List of model parameters from JSON interface outputs (List[InputVariable]): List of output variables from JSON interface @@ -328,12 +495,14 @@ def add_variable_references( self.parameters = fmu_parameters def add_state_initialization_parameters(self, states: List[InternalState]): - """Generate or modifies FmuInputVariables for initialization of states for the InternalState objects that have set start_value and name or have set initialization_variable. Any generated parameters are appended to self.parameters. + """ + Generate or modifies FmuInputVariables for initialization of states for the InternalState objects that have set start_value and name or have set initialization_variable. Any generated parameters are appended to self.parameters. - Args: + Args + ---- states (List[InternalState]): List of states from JSON interface - """ + init_parameters: List[FmiInputVariable] = [] value_reference_start = ( @@ -394,10 +563,12 @@ def add_state_initialization_parameters(self, states: List[InternalState]): self.parameters = [*self.parameters, *init_parameters] def format_fmi_variable(self, var: Union[FmiInputVariable, FmiOutputVariable]) -> List[FmiVariable]: - """Get an inclusive list of variables from an interface variable definition. + """ + Get an inclusive list of variables from an interface variable definition. Vectors are separated as N number of signals, being N the size of the array. - Args: + Args + ---- var (FmiInputVariable, FmiOutputVariable): Interface variable definition with the variable references. Returns @@ -448,7 +619,8 @@ def get_fmi_model_variables(self) -> List[FmiVariable]: def get_template_mapping( self, ) -> Tuple[List[Tuple[int, int]], List[Tuple[int, int]], List[Tuple[int, int]]]: - """Calculate the index to value reference mapping between onnx inputs/outputs/state to fmu variables. + """ + Calculate the index to value reference mapping between onnx inputs/outputs/state to fmu variables. Returns ------- diff --git a/src/mlfmu/types/onnx_model.py b/src/mlfmu/types/onnx_model.py index e621b3b..05ed845 100644 --- a/src/mlfmu/types/onnx_model.py +++ b/src/mlfmu/types/onnx_model.py @@ -5,14 +5,37 @@ import onnxruntime from onnxruntime import InferenceSession -""" - ONNX Metadata class - - Allows to import the ONNX file and figure out the input/output sizes. -""" - class ONNXModel: + """ + ONNX Metadata class + Represents an ONNX model and provides methods to load inputs and outputs. + Allows to import the ONNX file and figure out the input/output sizes. + + Attributes + ---------- + filename (str): The name of the ONNX file. + states_name (str): The name of the internal states input. + state_size (int): The size of the internal states input. + input_name (str): The name of the main input. + input_size (int): The size of the main input. + output_name (str): The name of the output. + output_size (int): The size of the output. + time_input_name (str): The name of the time input. + time_input (bool): Flag indicating whether the model uses time input. + __onnx_path (Path): The path to the ONNX file. + __onnx_session (InferenceSession): The ONNX runtime inference session. + + Methods + ------- + __init__(onnx_path: Union[str, os.PathLike[str]], time_input: bool = False): + Initializes the ONNXModel object by loading the ONNX file and assigning model parameters. + load_inputs(): + Loads the inputs from the ONNX file and assigns the input name and size. + load_outputs(): + Loads the outputs from the ONNX file and assigns the output name and size. + """ + filename: str = "" states_name: str = "" state_size: int = 0 @@ -26,6 +49,14 @@ class ONNXModel: __onnx_session: InferenceSession def __init__(self, onnx_path: Union[str, os.PathLike[str]], time_input: bool = False): + """ + Initialize the ONNXModel object by loading the ONNX file and assigning model parameters. + + Args: + onnx_path (Union[str, os.PathLike[str]]): The path to the ONNX file. + time_input (bool, optional): Flag indicating whether the model uses time input. Defaults to False. + """ + # Load ONNX file into memory self.__onnx_path = onnx_path if isinstance(onnx_path, Path) else Path(onnx_path) self.__onnx_session = onnxruntime.InferenceSession(onnx_path) @@ -38,6 +69,8 @@ def __init__(self, onnx_path: Union[str, os.PathLike[str]], time_input: bool = F self.load_outputs() def load_inputs(self): + """Load the inputs from the ONNX file and assign the input name and size.""" + # Get inputs from ONNX file inputs: List[Any] = self.__onnx_session.get_inputs() input_names = [inp.name for inp in inputs] # type: ignore No typing support provided by ONNX library @@ -70,6 +103,7 @@ def load_inputs(self): self.state_size = num_states def load_outputs(self): + """Load the outputs from the ONNX file and assign the output name and size.""" # Get outputs from ONNX file outputs: List[Any] = self.__onnx_session.get_outputs() output_names = [out.name for out in outputs] # type: ignore diff --git a/src/mlfmu/utils/builder.py b/src/mlfmu/utils/builder.py index daf7458..955a729 100644 --- a/src/mlfmu/utils/builder.py +++ b/src/mlfmu/utils/builder.py @@ -21,8 +21,17 @@ logger = logging.getLogger(__name__) -# Replacing all the template strings with their corresponding values and saving to new file def format_template_file(template_path: Path, save_path: Path, data: dict[str, str]): + """ + Replace all the template strings with their corresponding values and save to a new file. + + Args + ---- + template_path (Path): The path to the template file. + save_path (Path): The path to save the formatted file. + data (dict[str, str]): The data containing the values to replace in the template. + """ + # TODO: Need to check that these calls are safe from a cybersecurity point of view with open(template_path, "r", encoding="utf-8") as template_file: template_string = template_file.read() @@ -33,23 +42,46 @@ def format_template_file(template_path: Path, save_path: Path, data: dict[str, s def create_model_description(fmu: FmiModel, src_path: Path): - # Compute XML structure for FMU + """ + Compute XML structure for FMU and save it in a file. + + Args + ---- + fmu (FmiModel): The FMI model. + src_path (Path): The path to save the model description file. + """ + xml_structure = generate_model_description(fmu_model=fmu) # Save in file xml_structure.write(src_path / "modelDescription.xml", encoding="utf-8") -# Creating all the directories needed to put all the FMU files in def make_fmu_dirs(src_path: Path): + """ + Create all the directories needed to put all the FMU files in. + + Args + ---- + src_path (Path): The path to the FMU source directory. + """ + sources_path = src_path / "sources" resources_path = src_path / "resources" sources_path.mkdir(parents=True, exist_ok=True) resources_path.mkdir(parents=True, exist_ok=True) -# Creating and formatting all needed c++ files for FMU generation def create_files_from_templates(data: dict[str, str], fmu_src: Path): + """ + Create and format all needed C++ files for FMU generation. + + Args + ---- + data (dict[str, str]): The data containing the values to format the template files. + fmu_src (Path): The path to the FMU source directory. + """ + sources_path = fmu_src / "sources" file_names = ["fmu.cpp", "model_definitions.h"] @@ -65,8 +97,21 @@ def create_files_from_templates(data: dict[str, str], fmu_src: Path): format_template_file(template_path, save_path, data) -# Function for generating the key value pairs needed to format the template files to valid c++ def format_template_data(onnx: ONNXModel, fmi_model: FmiModel, model_component: ModelComponent) -> dict[str, str]: + """ + Generate the key-value pairs needed to format the template files to valid C++. + + Args + ---- + onnx (ONNXModel): The ONNX model. + fmi_model (FmiModel): The FMI model. + model_component (ModelComponent): The model component. + + Returns + ------- + dict[str, str]: The formatted template data. + """ + # Work out template mapping between ONNX and FMU ports inputs, outputs, state_init = fmi_model.get_template_mapping() state_output_indexes = [ @@ -138,15 +183,18 @@ def format_template_data(onnx: ONNXModel, fmi_model: FmiModel, model_component: def validate_interface_spec( spec: str, ) -> tuple[Optional[ValidationError], ModelComponent]: - """Parse and validate JSON data from interface file. + """ + Parse and validate JSON data from the interface file. - Args: - spec (str): Contents of JSON file. + Args + ---- + spec (str): Contents of the JSON file. Returns ------- - The pydantic model instance that contains all the interface information. + tuple[Optional[ValidationError], ModelComponent]: The validation error (if any) and the validated model component. The pydantic model instance that contains all the interface information. """ + parsed_spec = ModelComponent.model_validate_json(json_data=spec, strict=True) try: @@ -160,6 +208,20 @@ def validate_interface_spec( def generate_fmu_files( fmu_src_path: os.PathLike[str], onnx_path: os.PathLike[str], interface_spec_path: os.PathLike[str] ): + """ + Generate FMU files based on the FMU source, ONNX model, and interface specification. + + Args + ---- + fmu_src_path (os.PathLike[str]): The path to the FMU source directory. + onnx_path (os.PathLike[str]): The path to the ONNX model file. + interface_spec_path (os.PathLike[str]): The path to the interface specification file. + + Returns + ------- + FmiModel: The FMI model. + """ + # Create Path instances for the path to the spec and ONNX file. onnx_path = Path(onnx_path) interface_spec_path = Path(interface_spec_path) @@ -194,6 +256,17 @@ def generate_fmu_files( def validate_fmu_source_files(fmu_path: os.PathLike[str]): + """ + Validate the FMU source files. + + Args + ---- + fmu_path (os.PathLike[str]): The path to the FMU source directory. + + Raises + ------ + FileNotFoundError: If required files are missing in the FMU source directory. + """ fmu_path = Path(fmu_path) files_should_exist: List[str] = [ @@ -206,7 +279,7 @@ def validate_fmu_source_files(fmu_path: os.PathLike[str]): if len(files_not_exists) > 0: raise FileNotFoundError( - f"The files {files_not_exists} are not contained in the provided fmu source path ({fmu_path})" + f"The files {files_not_exists} are not contained in the provided FMU source path ({fmu_path})" ) resources_dir = fmu_path / "resources" @@ -215,7 +288,7 @@ def validate_fmu_source_files(fmu_path: os.PathLike[str]): if num_onnx_files < 1: raise FileNotFoundError( - f"There is no *.onnx file in the resource folder in the provided fmu source path ({fmu_path})" + f"There is no *.onnx file in the resource folder in the provided FMU source path ({fmu_path})" ) @@ -224,6 +297,20 @@ def build_fmu( fmu_build_path: os.PathLike[str], fmu_save_path: os.PathLike[str], ): + """ + Build the FMU. + + Args + ---- + fmu_src_path (os.PathLike[str]): The path to the FMU source directory. + fmu_build_path (os.PathLike[str]): The path to the FMU build directory. + fmu_save_path (os.PathLike[str]): The path to save the built FMU. + + Raises + ------ + FileNotFoundError: If required files are missing in the FMU source directory. + """ + fmu_src_path = Path(fmu_src_path) validate_fmu_source_files(fmu_src_path) fmu_name = fmu_src_path.stem diff --git a/src/mlfmu/utils/fmi_builder.py b/src/mlfmu/utils/fmi_builder.py index 428d40a..89b2e7d 100644 --- a/src/mlfmu/utils/fmi_builder.py +++ b/src/mlfmu/utils/fmi_builder.py @@ -14,7 +14,8 @@ def requires_start(var: FmiVariable) -> bool: - """Test if a variable requires a start attribute. + """ + Test if a variable requires a start attribute. Returns ------- @@ -28,9 +29,11 @@ def requires_start(var: FmiVariable) -> bool: def generate_model_description(fmu_model: FmiModel) -> ElementTree: - """Generate FMU modelDescription as XML. + """ + Generate FMU modelDescription as XML. - Args: + Args + ---- fmu_model (FmiModel): Object representation of FMI slave instance Returns diff --git a/src/mlfmu/utils/interface.py b/src/mlfmu/utils/interface.py index b779798..d82c3b2 100644 --- a/src/mlfmu/utils/interface.py +++ b/src/mlfmu/utils/interface.py @@ -23,6 +23,14 @@ def generate_interface_schema( model: Union[BaseModel, ModelMetaclass], schema_dir: Union[str, os.PathLike[str], None] = None, ): + """ + Generate a JSON interface schema file for the given model. + + Args + ---- + model (Union[BaseModel, ModelMetaclass]): The pydantic model for which to generate the schema. + schema_dir (Union[str, os.PathLike[str], None], optional): The directory where the schema file will be saved. Defaults to None. + """ schema_dir_default = Path.cwd() / "docs/schema" schema_dir = schema_dir or schema_dir_default # Make sure schema_dir argument is of type Path. If not, cast it to Path type. @@ -50,6 +58,15 @@ def generate_interface_docs( schema_dir: Union[str, os.PathLike[str], None] = None, docs_dir: Union[str, os.PathLike[str], None] = None, ): + """ + Generate HTML documentation for the JSON interface schema files in the schema directory. + + Args + ---- + schema_dir (Union[str, os.PathLike[str], None], optional): The directory where the schema files are located. Defaults to None. + docs_dir (Union[str, os.PathLike[str], None], optional): The directory where the documentation files will be saved. Defaults to None. + """ + schema_dir_default = Path.cwd() / "docs/schema" schema_dir = schema_dir or schema_dir_default @@ -99,6 +116,15 @@ def publish_interface_schema( schema_dir: Union[str, os.PathLike[str], None] = None, docs_dir: Union[str, os.PathLike[str], None] = None, ): + """ + Publish the JSON schema and HTML documentation for the interface. + + Args + ---- + schema_dir (Union[str, os.PathLike[str], None], optional): The directory where the schema file will be saved. Defaults to None. + docs_dir (Union[str, os.PathLike[str], None], optional): The directory where the documentation files will be saved. Defaults to None. + """ + # Generate JSON schema generate_interface_schema(model=ModelComponent, schema_dir=schema_dir) diff --git a/src/mlfmu/utils/logger.py b/src/mlfmu/utils/logger.py index 4e0a8bd..4ad2210 100644 --- a/src/mlfmu/utils/logger.py +++ b/src/mlfmu/utils/logger.py @@ -13,6 +13,20 @@ def configure_logging( log_file: Union[Path, None] = None, log_level_file: str = "WARNING", ): # sourcery skip: extract-duplicate-method, extract-method + """ + Configure the logging settings for the application. + + Args + ---- + log_level_console (str): The log level for console output. Defaults to "WARNING". + log_file (Union[Path, None]): The path to the log file. Defaults to None. + log_level_file (str): The log level for file output. Defaults to "WARNING". + + Raises + ------ + ValueError: If an invalid log level is provided for console or file. + """ + log_level_console_numeric = getattr(logging, log_level_console.upper(), None) if not isinstance(log_level_console_numeric, int): raise ValueError(f"Invalid log level to console: {log_level_console_numeric}") diff --git a/src/mlfmu/utils/signals.py b/src/mlfmu/utils/signals.py index 8d964fc..2835f02 100644 --- a/src/mlfmu/utils/signals.py +++ b/src/mlfmu/utils/signals.py @@ -2,15 +2,18 @@ def range_list_expanded(list_of_ranges: List[str]) -> List[int]: - """Expand ranges specified in interface. They should be formatted as [starts_index:end_index]. + """ + Expand ranges specified in interface. They should be formatted as [starts_index:end_index]. - Args: + Args + ---- list_of_ranges (List[str]): List of indexes or ranges. Returns ------- A list of all indexes covered in ranges or individual indexes """ + indexes: List[int] = [] for val in list_of_ranges: diff --git a/tests/cli/test_mlfmu_cli.py b/tests/cli/test_mlfmu_cli.py index 36710e3..fb4454c 100644 --- a/tests/cli/test_mlfmu_cli.py +++ b/tests/cli/test_mlfmu_cli.py @@ -25,7 +25,7 @@ class CliArgs: command: str = "" -@pytest.mark.parametrize( +@pytest.mark.parametrize( # type: ignore "inputs, expected", [ ([], ArgumentError), @@ -45,17 +45,34 @@ class CliArgs: def test_cli( inputs: List[str], expected: Union[CliArgs, type], - monkeypatch: MonkeyPatch, + monkeypatch: MonkeyPatch, # type: ignore ): + """ + Test the command-line interface (CLI) of the 'mlfmu' program. + + Args + ---- + inputs (List[str]): A list of input arguments to be passed to the CLI. + expected (Union[CliArgs, type]): The expected output of the CLI. It can be either an instance of the `CliArgs` class or a subclass of `Exception`. + monkeypatch (MonkeyPatch): A pytest fixture that allows patching of objects at runtime. + + Raises + ------ + AssertionError: If the `expected` argument is neither an instance of `CliArgs` nor a subclass of `Exception`. + """ + # sourcery skip: no-conditionals-in-tests # sourcery skip: no-loop-in-tests + # Prepare - monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) + monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) # type: ignore parser = _argparser() + # Execute if isinstance(expected, CliArgs): args_expected: CliArgs = expected args = parser.parse_args() + # Assert args print(args) print(args_expected) @@ -63,8 +80,9 @@ def test_cli( assert args.__getattribute__(key) == args_expected.__getattribute__(key) elif issubclass(expected, Exception): exception: type = expected + # Assert that expected exception is raised - with pytest.raises((exception, SystemExit)): + with pytest.raises((exception, SystemExit)): # type: ignore args = parser.parse_args() else: raise AssertionError() @@ -81,7 +99,7 @@ class ConfigureLoggingArgs: log_level_file: str = field(default_factory=lambda: "WARNING") -@pytest.mark.parametrize( +@pytest.mark.parametrize( # type: ignore "inputs, expected", [ ([], ArgumentError), @@ -112,12 +130,27 @@ class ConfigureLoggingArgs: def test_logging_configuration( inputs: List[str], expected: Union[ConfigureLoggingArgs, type], - monkeypatch: MonkeyPatch, + monkeypatch: MonkeyPatch, # type: ignore ): + """ + Test the logging configuration of the `main` function in the `mlfmu` module. + + Args + ---- + inputs (List[str]): The list of input arguments to be passed to the `main` function. + expected (Union[ConfigureLoggingArgs, type]): The expected output of the `main` function. It can be an instance of `ConfigureLoggingArgs` or a subclass of `Exception`. + monkeypatch (MonkeyPatch): The monkeypatch fixture provided by pytest. + + Raises + ---- + AssertionError: If the `expected` argument is neither an instance of `ConfigureLoggingArgs` nor a subclass of `Exception`. + """ + # sourcery skip: no-conditionals-in-tests # sourcery skip: no-loop-in-tests + # Prepare - monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) + monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) # type: ignore args: ConfigureLoggingArgs = ConfigureLoggingArgs() def fake_configure_logging( @@ -138,8 +171,8 @@ def fake_run( ): pass - monkeypatch.setattr(mlfmu, "configure_logging", fake_configure_logging) - monkeypatch.setattr(mlfmu, "run", fake_run) + monkeypatch.setattr(mlfmu, "configure_logging", fake_configure_logging) # type: ignore + monkeypatch.setattr(mlfmu, "run", fake_run) # type: ignore # Execute if isinstance(expected, ConfigureLoggingArgs): args_expected: ConfigureLoggingArgs = expected @@ -150,7 +183,7 @@ def fake_run( elif issubclass(expected, Exception): exception: type = expected # Assert that expected exception is raised - with pytest.raises((exception, SystemExit)): + with pytest.raises((exception, SystemExit)): # type: ignore main() else: raise AssertionError() @@ -169,7 +202,7 @@ class ApiArgs: source_folder: Optional[str] = None -@pytest.mark.parametrize( +@pytest.mark.parametrize( # type: ignore "inputs, expected", [ ([], ArgumentError), @@ -179,12 +212,26 @@ class ApiArgs: def test_api_invokation( inputs: List[str], expected: Union[ApiArgs, type], - monkeypatch: MonkeyPatch, + monkeypatch: MonkeyPatch, # type: ignore ): # sourcery skip: no-conditionals-in-tests # sourcery skip: no-loop-in-tests + """ + Test the invocation of the API function. + + Args + ---- + inputs (List[str]): The list of input arguments. + expected (Union[ApiArgs, type]): The expected output, either an instance of ApiArgs or an exception type. + monkeypatch (MonkeyPatch): The monkeypatch object for patching sys.argv. + + Raises + ---- + AssertionError: If the expected output is neither an instance of ApiArgs nor an exception type. + """ + # Prepare - monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) + monkeypatch.setattr(sys, "argv", ["mlfmu"] + inputs) # type: ignore args: ApiArgs = ApiArgs() def fake_run( @@ -200,7 +247,7 @@ def fake_run( args.fmu_path = fmu_path args.source_folder = source_folder - monkeypatch.setattr(mlfmu, "run", fake_run) + monkeypatch.setattr(mlfmu, "run", fake_run) # type: ignore # Execute if isinstance(expected, ApiArgs): args_expected: ApiArgs = expected @@ -211,7 +258,7 @@ def fake_run( elif issubclass(expected, Exception): exception: type = expected # Assert that expected exception is raised - with pytest.raises((exception, SystemExit)): + with pytest.raises((exception, SystemExit)): # type: ignore main() else: raise AssertionError() diff --git a/tests/conftest.py b/tests/conftest.py index 129f993..6c55706 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,11 +10,19 @@ @pytest.fixture(scope="package", autouse=True) def chdir(): + """ + Fixture that changes the current working directory to the 'test_working_directory' folder. + This fixture is automatically used for the entire package. + """ os.chdir(Path(__file__).parent.absolute() / "test_working_directory") @pytest.fixture(scope="package", autouse=True) def test_dir(): + """ + Fixture that returns the absolute path of the directory containing the current file. + This fixture is automatically used for the entire package. + """ return Path(__file__).parent.absolute() @@ -28,12 +36,19 @@ def test_dir(): @pytest.fixture(autouse=True) def default_setup_and_teardown(caplog: LogCaptureFixture): + """ + Fixture that performs setup and teardown actions before and after each test function. + It removes the output directories and files specified in 'output_dirs' and 'output_files' lists. + """ _remove_output_dirs_and_files() yield _remove_output_dirs_and_files() def _remove_output_dirs_and_files(): + """ + Helper function that removes the output directories and files specified in 'output_dirs' and 'output_files' lists. + """ for folder in output_dirs: rmtree(folder, ignore_errors=True) for pattern in output_files: @@ -44,10 +59,15 @@ def _remove_output_dirs_and_files(): @pytest.fixture(autouse=True) def setup_logging(caplog: LogCaptureFixture): + """ + Fixture that sets up logging for each test function. + It sets the log level to 'INFO' and clears the log capture. + """ caplog.set_level("INFO") caplog.clear() @pytest.fixture(autouse=True) def logger(): + """Fixture that returns the logger object.""" return logging.getLogger() From 35fb83c8181b57f2a0213556e63ff71770631a18 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:31:43 +0200 Subject: [PATCH 2/9] Address some of Jorge's comments to new docstrings --- .../fmu_build/templates/fmu/fmu_template.cpp | 2 +- .../fmu_build/templates/onnx_fmu/onnxFmu.cpp | 15 ++++++++------- src/mlfmu/types/fmu_component.py | 11 ++++++----- src/mlfmu/utils/builder.py | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp index 94e6ffb..70eedfe 100644 --- a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp +++ b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp @@ -19,7 +19,7 @@ class /** * \brief Constructs a new {FmuName} object. * - * \param fmuResourceLocation The location of the FMU resource. + * \param fmuResourceLocation The location of the resources of the FMU. */ {FmuName}(cppfmu::FMIString fmuResourceLocation) : OnnxFmu(fmuResourceLocation) {} diff --git a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp index 77b77aa..c42794e 100644 --- a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp +++ b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp @@ -45,9 +45,10 @@ std::wstring OnnxFmu::formatOnnxPath(cppfmu::FMIString fmuResourceLocation) } /** - * \brief Creates a session for the ONNX FMU. + * \brief Creates a session to the ONNX model. * - * This function creates a session for the ONNX FMU using the specified ONNX model file. + * This function creates a session to the ONNX model, using the specified ONNX model file. + * This loads the weights of the model such that we can run predictions in the doStep function. * * \note The ONNX model file path must be set before calling this function. * \throws std::runtime_error if the session creation fails. @@ -168,11 +169,11 @@ void OnnxFmu::GetBoolean(const cppfmu::FMIValueReference vr[], std::size_t nvr, } /** - * \brief Sets the ONNX inputs for the ONNX FMU. + * \brief Sets the ONNX inputs for the ONNX FMU, matching FMU variables with inputs of ONNX model. * - * This function sets the ONNX inputs for the ONNX FMU by iterating over the ONNX input - * value-reference index pairs and assigning the corresponding FMU variable's real value - * to the ONNX input. + * This function matches the FMU variables with the inputs to the ONNX model. + * It iterates over the ONNX input value-reference index pairs and assigns the corresponding FMU + * variable's real value to the ONNX input. * * \returns `true` if the ONNX inputs are successfully set, `false` otherwise. */ @@ -230,7 +231,7 @@ bool OnnxFmu::GetOnnxOutputs() } /** - * \brief Initializes the ONNX states of the ONNX FMU. + * \brief Initializes the ONNX states of the ONNX model. * * This function initializes the ONNX states of the ONNX FMU by assigning the initial values * of the ONNX states from the corresponding variables in the FMU. diff --git a/src/mlfmu/types/fmu_component.py b/src/mlfmu/types/fmu_component.py index 3b9cc5f..87e7db7 100644 --- a/src/mlfmu/types/fmu_component.py +++ b/src/mlfmu/types/fmu_component.py @@ -102,9 +102,9 @@ class InternalState(BaseModelConfig): If this field is set, parameters for initialization will be automatically generated for these states. initialization_variable (Optional[str]): The name of an input or parameter in the same model interface that should be used to initialize this state. - agent_output_indexes (List[str]): Index or range of indices of agent outputs that will be stored as + agent_output_indexes (List[str]): Index or range of indices of agent (ONNX model) outputs that will be stored as internal states and will be fed as inputs in the next time step. Note: the FMU signal and the - agent outputs need to have the same length. + ONNX (agent) outputs need to have the same length. """ name: Optional[str] = Field( @@ -174,8 +174,8 @@ class InputVariable(Variable): Attributes ---------- - agent_input_indexes (List[str]): Index or range of indices of agent inputs to which this FMU signal shall be linked to. - Note: The FMU signal and the agent inputs need to have the same length. + agent_input_indexes (List[str]): Index or range of indices of ONNX (agent) inputs to which this FMU signal shall be linked. + Note: The FMU signal and the ONNX (agent) inputs need to have the same length. Examples -------- @@ -301,7 +301,8 @@ class FmiVariable: class ModelComponent(BaseModelConfig): """ - Represents a simulation model component. + Represents a simulation model component, used to generate the JSON schema for the model interface. + We define the structure of the FMU and how the inputs and outputs of the ONNX model correspond to the FMU variables. Attributes ---------- diff --git a/src/mlfmu/utils/builder.py b/src/mlfmu/utils/builder.py index 955a729..bb98c69 100644 --- a/src/mlfmu/utils/builder.py +++ b/src/mlfmu/utils/builder.py @@ -43,7 +43,7 @@ def format_template_file(template_path: Path, save_path: Path, data: dict[str, s def create_model_description(fmu: FmiModel, src_path: Path): """ - Compute XML structure for FMU and save it in a file. + Generate modelDescription.xml structure for FMU, and save it in a file. Args ---- From d2741b2bd9b118a1511390769a3c010bde58b7c3 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:52:59 +0200 Subject: [PATCH 3/9] Address Jorge's comments re. agent_state_init_indexes and class args --- src/mlfmu/types/fmu_component.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mlfmu/types/fmu_component.py b/src/mlfmu/types/fmu_component.py index 87e7db7..355cf9f 100644 --- a/src/mlfmu/types/fmu_component.py +++ b/src/mlfmu/types/fmu_component.py @@ -229,13 +229,13 @@ class FmiInputVariable(InputVariable): ---------- causality (FmiCausality): The causality of the input variable. variable_references (List[int]): List of variable references. - agent_state_init_indexes (List[List[int]]): List of agent state initialization indexes. + agent_state_init_indexes (List[List[int]]): List of state initialization indexes for ONNX model - concerns mapping of FMU input variables to ONNX states. Args ---- **kwargs: Additional keyword arguments to initialize the input variable. - - causality (FmiCausality): The causality of the input variable. - - variable_references (List[int]): List of variable references. + - causality (FmiCausality). Default: FmiCausality.INPUT + - variable_references (List[int]). Default: [] """ causality: FmiCausality @@ -261,8 +261,8 @@ class FmiOutputVariable(OutputVariable): Args ---- **kwargs: Additional keyword arguments to initialize the output variable. - - causality (FmiCausality): The causality of the output variable. Defaults to FmiCausality.OUTPUT. - - variable_references (List[int]): The list of variable references associated with the output variable. Defaults to an empty list. + - causality (FmiCausality). Default: FmiCausality.OUTPUT + - variable_references (List[int]). Default: [] """ causality: FmiCausality @@ -497,7 +497,9 @@ def add_variable_references( def add_state_initialization_parameters(self, states: List[InternalState]): """ - Generate or modifies FmuInputVariables for initialization of states for the InternalState objects that have set start_value and name or have set initialization_variable. Any generated parameters are appended to self.parameters. + Generate or modifies FmuInputVariables for initialization of states for the InternalState objects + that have set start_value and name or have set initialization_variable. + Any generated parameters are appended to self.parameters. Args ---- From 1b8cb3a0ffa0051ce700577c5a8302ead28431e5 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:53:12 +0200 Subject: [PATCH 4/9] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7682d73..c24a2da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog format is based on [Keep a Changelog](https://keepachangelog.com/e ### Changed +* Added missing docstrings for py/cpp/h files with help of Github Copilot * Moved CMake + conan + c++ package files and folders with cpp code inside src folder to be included in package * Replace pkg_resources with importlib.metadata for getting packages version to work in python 3.12 * Replace deprecated root_validator with model_validator in pydanitc class From 1a30427847d042b62c168b62b98cf9eb27c8ea6a Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:39:52 +0200 Subject: [PATCH 5/9] Update src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp undo linting, fix templating, Kristoffer's suggestion Co-authored-by: KristofferSkare <52509111+KristofferSkare@users.noreply.github.com> --- src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp index 70eedfe..01230f4 100644 --- a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp +++ b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp @@ -11,10 +11,7 @@ * * This class is derived from the OnnxFmu class and provides functionality specific to the {FmuName} FMU. */ -class -{ - FmuName -} : public OnnxFmu {{ +class {FmuName} : public OnnxFmu {{ public : /** * \brief Constructs a new {FmuName} object. From 91a103a76655e373fffd7afd3ac2b0ab6e09fe93 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:40:06 +0200 Subject: [PATCH 6/9] Update src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp undo linting, fix templating, Kristoffer's suggestion Co-authored-by: KristofferSkare <52509111+KristofferSkare@users.noreply.github.com> --- src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp index 01230f4..1bcb5bb 100644 --- a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp +++ b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp @@ -18,7 +18,7 @@ class {FmuName} : public OnnxFmu {{ * * \param fmuResourceLocation The location of the resources of the FMU. */ - {FmuName}(cppfmu::FMIString fmuResourceLocation) : OnnxFmu(fmuResourceLocation) {} + {FmuName}(cppfmu::FMIString fmuResourceLocation) : OnnxFmu(fmuResourceLocation) {{}} private : // Add private members and functions here From 6ffef305be5ee597eee296ba04b2903b708f59b6 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:40:19 +0200 Subject: [PATCH 7/9] Update src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp undo linting, fix templating, Kristoffer's suggestion Co-authored-by: KristofferSkare <52509111+KristofferSkare@users.noreply.github.com> --- .../fmu_build/templates/fmu/fmu_template.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp index 1bcb5bb..1b81bce 100644 --- a/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp +++ b/src/mlfmu/fmu_build/templates/fmu/fmu_template.cpp @@ -44,13 +44,9 @@ cppfmu::UniquePtr CppfmuInstantiateSlave( cppfmu::FMIString /*instanceName*/, cppfmu::FMIString fmuGUID, cppfmu::FMIString fmuResourceLocation, cppfmu::FMIString /*mimeType*/, cppfmu::FMIReal /*timeout*/, cppfmu::FMIBoolean /*visible*/, cppfmu::FMIBoolean /*interactive*/, cppfmu::Memory memory, cppfmu::Logger /*logger*/) -{ - { - if (std::strcmp(fmuGUID, FMU_UUID) != 0) { - { - throw std::runtime_error("FMU GUID mismatch"); - } - } - return cppfmu::AllocateUnique<{FmuName}>(memory, fmuResourceLocation); - } -} +{{ + if (std::strcmp(fmuGUID, FMU_UUID) != 0) {{ + throw std::runtime_error("FMU GUID mismatch"); + }} + return cppfmu::AllocateUnique<{FmuName}>(memory, fmuResourceLocation); +}} From a0699d6b678d7763a41e147852fa5747e23b1d51 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:40:46 +0200 Subject: [PATCH 8/9] Update src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp Kristoffer's suggested rewrite Co-authored-by: KristofferSkare <52509111+KristofferSkare@users.noreply.github.com> --- src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp index c42794e..aa6e024 100644 --- a/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp +++ b/src/mlfmu/fmu_build/templates/onnx_fmu/onnxFmu.cpp @@ -45,7 +45,7 @@ std::wstring OnnxFmu::formatOnnxPath(cppfmu::FMIString fmuResourceLocation) } /** - * \brief Creates a session to the ONNX model. + * \brief Creates a onnx runtime session for the model. * * This function creates a session to the ONNX model, using the specified ONNX model file. * This loads the weights of the model such that we can run predictions in the doStep function. From a50a6bd40dbe073370f5e8b7b06aee77ddbb5b62 Mon Sep 17 00:00:00 2001 From: Stephanie Kemna <6518317+StephanieKemna@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:46:04 +0200 Subject: [PATCH 9/9] remove examples from python package template, which are not used --- src/mlfmu/api.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/src/mlfmu/api.py b/src/mlfmu/api.py index 21050b0..aee4fc0 100644 --- a/src/mlfmu/api.py +++ b/src/mlfmu/api.py @@ -439,40 +439,6 @@ def run(self): return - @property - def run_number(self) -> int: - """Example for a read only property.""" - return self._run_number - - @property - def max_number_of_runs(self) -> int: - """ - Example for a read/write property implemented through a pair of explicit - getter and setter methods (see below for the related setter method). - """ - return self._max_number_of_runs - - @max_number_of_runs.setter - def max_number_of_runs(self, value: int): - """ - Setter method that belongs to above getter method. - - Note that implementing specific getter- and setter methods is in most cases not necessary. - The same can be achieved by simply making the instance variable a public attribute. - I.e., declaring the instance variable in __init__() not as - self._max_number_of_runs: int = .. # (-> private instance variable) - but as - self.max_number_of_runs: int = .. # (-> public attribute) - - However, in some cases the additional effort of implementing explicit getter- and setter- methods - as in this example can be reasoned, for instance if you have a need for increased control - and want be able to cancel or alter code execution, or write log messages whenever a property - gets reads or written from outside. - """ - - self._max_number_of_runs = value - return - def _run_process(self): """Execute a single run of the mlfmu process.""" self._run_number += 1