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)
2-element Vector{Float64}:
 0.0
 1.0

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 = loadFMU(pathToFMU)
info(fmu)
#################### Begin information for FMU ####################
	Model name:			IO
	FMI-Version:			2.0
	GUID:				{889089a6-481b-41a6-a282-f6ce02a33aa6}
	Generation tool:		Dymola Version 2022x (64-bit), 2021-10-08
	Generation time:		2022-05-19T06:53:52Z
	Var. naming conv.:		structured
	Event indicators:		4
	Inputs:				3
		352321536 ["u_real"]
		352321537 ["u_boolean"]
		352321538 ["u_integer"]


	Outputs:			3
		335544320 ["y_real"]
		335544321 ["y_boolean"]
		335544322 ["y_integer"]
	States:				0
	Parameters:			5
		16777216 ["p_real"]
		16777217 ["p_integer"]
		16777218 ["p_boolean"]
		16777219 ["p_enumeration"]
		134217728 ["p_string"]
	Supports Co-Simulation:		true
		Model identifier:	IO
		Get/Set State:		true
		Serialize State:	true
		Dir. Derivatives:	true
		Var. com. steps:	true
		Input interpol.:	true
		Max order out. der.:	1
	Supports Model-Exchange:	true
		Model identifier:	IO
		Get/Set State:		true
		Serialize State:	true
		Dir. Derivatives:	true
##################### End information for 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
Dict{String, Any}()

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!().

c = fmi2Instantiate!(fmu; loggingOn=true)
FMU:            IO
    InstanceName:   IO
    Address:        Ptr{Nothing} @0x0000019fabc4c410
    State:          0
    Logging:        true
    FMU time:       -Inf
    FMU states:     nothing

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

fmi2SetupExperiment(c, tStart, tStop)
0x00000000

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"]
4-element Vector{String}:
 "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.

fmi2EnterInitializationMode(c)
0x00000000

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

getValue(c, params)
4-element Vector{Any}:
 0.0
 0
 0
  "Hello World!"

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

fmi2ExitInitializationMode(c)
0x00000000

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
generateRandomNumbers (generic function with 1 method)

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

paramsVal = generateRandomNumbers()
(82.27945474196719, false, 25, "Random number 34.97807074595338!")

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.

fmi2Terminate(c)
fmi2Reset(c)
fmi2SetupExperiment(c, tStart, tStop)
0x00000000

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 setValue() 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.

setValue(c, params, collect(paramsVal))
4-element Vector{UInt32}:
 0x00000000
 0x00000000
 0x00000000
 0x00000000

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

fmi2EnterInitializationMode(c)
# getValue(c, params)
fmi2ExitInitializationMode(c)
0x00000000

Now the FMU has been initialized correctly, the FMU can be simulated. The simulate() 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 simulate() function is not capable of outputting string values, so p_string is omitted.

simData = simulate(c, (tStart, tStop); recordValues=params[1:3], saveat=tSave, 
                        instantiate=false, setup=false, freeInstance=false, terminate=false, reset=false)
Model name:
	IO
Success:
	true
f(x)-Evaluations:
	In-place: 0
	Out-of-place: 0
Jacobian-Evaluations:
	∂ẋ_∂p: 0
	∂ẋ_∂x: 0
	∂ẋ_∂u: 0
	∂y_∂p: 0
	∂y_∂x: 0
	∂y_∂u: 0
	∂e_∂p: 0
	∂e_∂x: 0
	∂e_∂u: 0
	∂xr_∂xl: 0
Gradient-Evaluations:
	∂ẋ_∂t: 0
	∂y_∂t: 0
	∂e_∂t: 0
Callback-Evaluations:
	Condition (event-indicators): 0
	Time-Choice (event-instances): 0
	Affect (event-handling): 0
	Save values: 0
	Steps completed: 0
Values [2]:
	0.0	(82.27945474196719, 0.0, 25.0)
	1.0	(82.27945474196719, 0.0, 25.0)
Events [0]:

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.

fmi2Terminate(c)
fmi2Reset(c)
fmi2SetupExperiment(c, tStart, tStop)
0x00000000

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()
(14.70939091105291, true, 62, "Random number 63.50571246361321!")

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.

fmi2SetReal(c, "p_real", rndReal)
fmi2SetBoolean(c, "p_boolean", rndBoolean)
fmi2SetInteger(c, "p_integer", rndInteger)
fmi2SetString(c, "p_string", rndString)
0x00000000

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

  • fmi2SetReal() ⇔ fmi2GetReal()
  • fmi2SetBoolean() ⇔ fmi2GetBoolean()
  • fmi2SetInteger() ⇔ fmi2GetInteger()
  • fmi2SetString() ⇔ fmi2GetString().

As before, the FMU must be in initialization mode.

fmi2EnterInitializationMode(c)
# fmi2GetReal(c, "u_real")
# fmi2GetBoolean(c, "u_boolean")
# fmi2GetInteger(c, "u_integer")
# fmi2GetString(c, "p_string")
fmi2ExitInitializationMode(c)
0x00000000

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 = simulate(c, (tStart, tStop); recordValues=params[1:3], saveat=tSave, 
                        instantiate=false, setup=false)
Model name:
	IO
Success:
	true
f(x)-Evaluations:
	In-place: 0
	Out-of-place: 0
Jacobian-Evaluations:
	∂ẋ_∂p: 0
	∂ẋ_∂x: 0
	∂ẋ_∂u: 0
	∂y_∂p: 0
	∂y_∂x: 0
	∂y_∂u: 0
	∂e_∂p: 0
	∂e_∂x: 0
	∂e_∂u: 0
	∂xr_∂xl: 0
Gradient-Evaluations:
	∂ẋ_∂t: 0
	∂y_∂t: 0
	∂e_∂t: 0
Callback-Evaluations:
	Condition (event-indicators): 0
	Time-Choice (event-instances): 0
	Affect (event-handling): 0
	Save values: 0
	Steps completed: 0
Values [2]:
	0.0	(14.70939091105291, 1.0, 62.0)
	1.0	(14.70939091105291, 1.0, 62.0)
Events [0]:

Unload FMU

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

unloadFMU(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.

loadFMU() → fmiInstantiate!() → fmiSetupExperiment() → fmiSetXXX() → fmiEnterInitializationMode() → fmiGetXXX() → fmiExitInitializationMode() → simualte() → unloadFMU()