Parameterize a FMU

Tutorial by Tobias Thummerer, Johannes Stoljar

Last update: 09.08.2023

🚧 This tutorial is under revision and will be replaced by an up-to-date version soon 🚧

License

# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher, Johannes Stoljar
# Licensed under the MIT license. 
# See LICENSE (https://github.com/thummeto/FMI.jl/blob/main/LICENSE) file in the project root for details.

Introduction

This example shows how to parameterize a FMU. We will show to possible ways to parameterize: The default option using the parameterization feature of fmiSimulate, fmiSimulateME or fmiSimulateCS. Second, a custom parameterization routine for advanced users.

Other formats

Besides, this Jupyter Notebook there is also a Julia file with the same name, which contains only the code cells and for the documentation there is a Markdown file corresponding to the notebook.

Code section

To run the example, the previously installed packages must be included.

# imports
using FMI
using FMIZoo

Simulation setup

Next, the start time and end time of the simulation are set.

tStart = 0.0
tStop = 1.0
tSave = collect(tStart:tStop)

Import FMU

In the next lines of code the FMU model from FMIZoo.jl is loaded and the information about the FMU is shown.

# we use an FMU from the FMIZoo.jl
# just replace this line with a local path if you want to use your own FMU
pathToFMU = get_model_filename("IO", "Dymola", "2022x")

fmu = fmiLoad(pathToFMU)
fmiInfo(fmu)

Option A: Integrated parameterization feature of FMI.jl

If you are using the commands for simulation integrated in FMI.jl, the parameters and initial conditions are set at the correct locations during the initialization process of your FMU. This is the recommended way of parameterizing your model, if you don't have very uncommon requirements regarding initialization.

dict = Dict{String, Any}()
dict

Option B: Custom parameterization routine

If you have special requirements for initialization and parameterization, you can write your very own parameterization routine.

Instantiate and Setup FMU

Next it is necessary to create an instance of the FMU. This is achieved by the command fmiInstantiate!().

fmiInstantiate!(fmu; loggingOn=true)

In the following code block, start and end time for the simulation is set by the fmiSetupExperiment() command.

fmiSetupExperiment(fmu, tStart, tStop)

Parameterize FMU

In this example, for each data type (real, boolean, integer and string) a corresponding input or parameter is selected. From here on, the inputs and parameters will be referred to as parameters for simplicity.

params = ["p_real", "p_boolean", "p_integer", "p_string"]

At the beginning we want to display the initial state of these parameters, for which the FMU must be in initialization mode. The next function fmiEnterInitializationMode() informs the FMU to enter the initialization mode. Before calling this function, the variables can be set. Furthermore, fmiSetupExperiment() must be called at least once before calling fmiEnterInitializationMode(), in order that the start time is defined.

fmiEnterInitializationMode(fmu)

The initial state of these parameters are displayed with the function fmiGet().

fmiGet(fmu, params)

The initialization mode is terminated with the function fmiExitInitializationMode(). (For the model exchange FMU type, this function switches off all initialization equations, and enters the event mode implicitly.)

fmiExitInitializationMode(fmu)

In the next step, a function is defined that generates a random value for each parameter. For the parameter p_string a random number is inserted into the string. All parameters are combined to a tuple and output.

function generateRandomNumbers()
    rndReal = 100 * rand()
    rndBoolean = rand() > 0.5
    rndInteger = round(Integer, 100 * rand())
    rndString = "Random number $(100 * rand())!"

    return rndReal, rndBoolean, rndInteger, rndString
end

The previously defined function is called and the results are displayed in the console.

paramsVal = generateRandomNumbers()

First variant

To show the first variant, it is necessary to terminate and reset the FMU instance. Then, as before, the setup command must be called for the FMU.

fmiTerminate(fmu)
fmiReset(fmu)
fmiSetupExperiment(fmu, tStart, tStop)

In the next step it is possible to set the parameters for the FMU. With the first variant it is quickly possible to set all parameters at once. Even different data types can be set with only one command. The command fmiSet() selects itself which function is chosen for which data type. As long as the output of the function gives the status code 0, setting the parameters has worked.

fmiSet(fmu, params, collect(paramsVal))

After setting the parameters, it can be checked whether the corresponding parameters were set correctly. For this the function fmiGet() can be used as above. To be able to call the function fmiGet() the FMU must be in initialization mode.

fmiEnterInitializationMode(fmu)
# fmiGet(fmu, params)
fmiExitInitializationMode(fmu)

Now the FMU has been initialized correctly, the FMU can be simulated. The fmiSimulate() command is used for this purpose. It must be pointed out that the keywords instantiate=false, setup=false must be set. The keyword instantiate=false prevents the simulation command from creating a new FMU instance, otherwise our parameterization will be lost. The keyword setup=false prevents the FMU from calling the initialization mode again. The additionally listed keyword freeInstance=false prevents that the instance is removed after the simulation. This is only needed in this example, because we want to continue working on the created instance. Another keyword is the recordValues=parmas[1:3], which saves: p_real, p_boolean and p_integer as output. It should be noted that the fmiSimulate() function is not capable of outputting string values, so p_string is omitted.

simData = fmiSimulate(fmu, (tStart, tStop); recordValues=params[1:3], saveat=tSave, 
                        instantiate=false, setup=false, freeInstance=false, terminate=false, reset=false)

Second variant

To show the second variant, it is necessary to terminate and reset the FMU instance. Then, as before, the setup command must be called for the FMU.

fmiTerminate(fmu)
fmiReset(fmu)
fmiSetupExperiment(fmu, tStart, tStop)

To make sure that the functions work it is necessary to generate random numbers again. As shown already, we call the defined function generateRandomNumbers() and output the values.

rndReal, rndBoolean, rndInteger, rndString = generateRandomNumbers()

In the second variant, the value for each data type is set separately by the corresponding command. By this variant one has the maximum control and can be sure that also the correct data type is set.

fmiSetReal(fmu, "p_real", rndReal)
fmiSetBoolean(fmu, "p_boolean", rndBoolean)
fmiSetInteger(fmu, "p_integer", rndInteger)
fmiSetString(fmu, "p_string", rndString)

To illustrate the functionality of the parameterization with the separate functions, the corresponding get function can be also called separately for each data type:

  • fmiSetReal() ⇔ fmiGetReal()
  • fmiSetBoolean() ⇔ fmiGetBoolean()
  • fmiSetInteger() ⇔ fmiGetInteger()
  • fmiSetString() ⇔ fmiGetString().

As before, the FMU must be in initialization mode.

fmiEnterInitializationMode(fmu)
# fmiGetReal(fmu, "u_real")
# fmiGetBoolean(fmu, "u_boolean")
# fmiGetInteger(fmu, "u_integer")
# fmiGetString(fmu, "p_string")
fmiExitInitializationMode(fmu)

From here on, you may want to simulate the FMU. Please note, that with the default executionConfig, it is necessary to prevent a new instantiation using the keyword instantiate=false. Otherwise, a new instance is allocated for the simulation-call and the parameters set for the previous instance are not transfered.

simData = fmiSimulate(fmu, (tStart, tStop); recordValues=params[1:3], saveat=tSave, 
                        instantiate=false, setup=false)

Unload FMU

The FMU will be unloaded and all unpacked data on disc will be removed.

fmiUnload(fmu)

Summary

Based on this tutorial it can be seen that there are two different variants to set and get parameters.These examples should make it clear to the user how parameters can also be set with different data types. As a small reminder, the sequence of commands for the manual parameterization of an FMU is summarized again.

fmiLoad() → fmiInstantiate!() → fmiSetupExperiment() → fmiSetXXX() → fmiEnterInitializationMode() → fmiGetXXX() → fmiExitInitializationMode() → fmiSimualte() → fmiUnload()