Manipulate a function

Tutorial by Tobias Thummerer, Johannes Stoljar

🚧 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 to the example

This example shows how to overwrite a FMI function with a custom C-function. For this the FMU model is simulated first without changes. Then the function fmi2GetReal() is overwritten and simulated again. Both simulations are displayed in a graph to show the change caused by overwriting the function. The model used is a one-dimensional spring pendulum with friction. The object-orientated structure of the SpringFrictionPendulum1D can be seen in the following graphic.

svg

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 FMI: fmiSetFctGetReal
using FMIZoo
using FMICore
using Plots

Simulation setup

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

tStart = 0.0
tStop = 8.0

Import FMU

Next, the FMU model from FMIZoo.jl is loaded.

# we use an FMU from the FMIZoo.jl
fmu = fmiLoad("SpringFrictionPendulum1D", "Dymola", "2022x"; type=:ME)

Simulate FMU

In the next steps the recorded value is defined. The recorded value is the position of the mass. In the function fmiSimulateME() the FMU is simulated in model-exchange mode (ME) with an adaptive step size. In addition, the start and end time and the recorded variables are specified.

# an array of value references... or just one
vrs = ["mass.s"]

simData = fmiSimulate(fmu, (tStart, tStop); recordValues=vrs)

Plotting FMU

After the simulation is finished, the result of the FMU for the model-exchange mode can be plotted. In the plot for the FMU it can be seen that the oscillation continues to decrease due to the effect of the friction. If you simulate long enough, the oscillation comes to a standstill in a certain time.

fig = plot(simData, states=false)

Override Function

After overwriting a function, the previous one is no longer accessible. The original function fmi2GetReal() is cached by storing the address of the pointer. The addresses of the pointers are kept in the FMU and are thus accessible.

# save, where the original `fmi2GetReal` function was stored, so we can access it in our new function
originalGetReal = fmu.cGetReal

To overwrite the function fmi2GetReal!(), the function header of the new custom function must be identical to the previous one. The function header looks like fmi2GetReal!(cfunc::Ptr{Nothing}, c::fmi2Component, vr::Union{Array{fmi2ValueReference}, Ptr{fmi2ValueReference}}, nvr::Csize_t, value::Union{Array{fmi2Real}, Ptr{fmi2Real}})::fmi2Status. The information how the FMI2 function are structured can be seen from FMICore.jl, the api of fmi2GetReal! or the FMI2.0.3-specification.

In the new implementation the original function is called by the previously stored pointer. Next there is a special handling if value is a pointer to an array. In this case the pointer is treated as an array, so that the entries are accessible. Otherwise, each value in value is multiplied by two. Finally, the original state of the original function is output.

function myGetReal!(c::fmi2Component, vr::Union{Array{fmi2ValueReference}, Ptr{fmi2ValueReference}}, 
                    nvr::Csize_t, value::Union{Array{fmi2Real}, Ptr{fmi2Real}})
    # first, we do what the original function does
    status = fmi2GetReal!(originalGetReal, c, vr, nvr, value)

    # if we have a pointer to an array, we must interprete it as array to access elements
    if isa(value, Ptr{fmi2Real})
        value = unsafe_wrap(Array{fmi2Real}, value, nvr, own=false)
    end

    # now, we multiply every value by two (just for fun!)
    for i in 1:nvr 
        value[i] *= 2.0 
    end 

    # return the original status
    return status
end

In the next command the original function is overwritten with the new defined function, for which the command fmiSetFctGetReal() is called.

# no we overwrite the original function
fmiSetFctGetReal(fmu, myGetReal!)

Simulate and Plot FMU with modified function

As before, the identical command is called here for simulation. This is also a model exchange simulation. Immediately afterwards, the results are added to the previous graph as a dashed line.

simData = fmiSimulate(fmu, (tStart, tStop); recordValues=vrs)
plot!(fig, simData; states=false, style=:dash)

As expected by overwriting the function, all values are doubled.

Unload FMU

After plotting the data, the FMU is unloaded and all unpacked data on disc is removed.

fmiUnload(fmu)

Summary

In this tutorial it is shown how an existing function of the library can be replaced by an own implementation.