Deploying a Lotka-Volterra FMU model

In this example we will demonstrate how to deploy a Lotka-Volterra model as a FMU using FMUGeneration.jl. We start by loading FMUGeneration and OrdinaryDiffEq into our environment.

using FMUGeneration
using OrdinaryDiffEq

We will start by clearing our deployment workspace. This is a necessary step if we are debugging a build and we iterate through it. This ensures that we have removed any pre-existing information in the cache.

clear_deployment_workspace()
[ Info: Cleared Deployment Workspace!

For the tutorial, we chose a Lotka Volterra system with controls. This system is a pair of non-linear differential equations used to describe the dynamics of a biological system. The system has a set of parameters α, β, γ, δ and x₁ , x₂ are time-dependent controls.

\[\frac{du₁}{dt} = \alpha u₁ - \beta u₁u₂ + x₁ \]

\[\frac{du₂}{dt} = \delta u₁u₂ - \gamma u₂ - x₂\]

FMUGeneration.jl first creates a package for the FMU containing the necessary functions, models, and variables, then builds a sysimage.

We use @define macro to define the above system in the FMU package as follows:

@define function lv(u, x, p, t)
    x₁, x₂ = x
    u₁, u₂ = u
    α, β, γ, δ = p
    du₁ = α * u₁ - β * u₁ * u₂ + x₁
    du₂ = δ * u₁ * u₂ - γ * u₂ - x₂
    du = [du₁, du₂]
end

We then assign this lv function as our continuous model, that will be deployed as a FMU. This is done using @continuous_model. Note that the first step defines a function for the package, and this step assigns the function as the continuous model.

@continuous_model lv(u, x, p, t)

Once we have defined our system and continuous model, we need to define some defaults for the system. These include initial states, initial inputs, default parameters, and the timespan. This is done using set_default

initial_states = [1.0, 1.0]
initial_inputs = [0.01, 0.01]
default_parameters = [2.0, 1.875, 2.0, 1.875]
tspan = (0.0, 10.0)

set_default(; initial_states,
    initial_inputs,
    default_parameters,
    tspan
    )

We then add necessary packages to the FMU package using @add_packages

@add_packages(["OrdinaryDiffEq", "SciMLBase"])
[ Info: Pkg is already available in the sysimage.
[ Info: Base64 is already available in the sysimage.
[ Info: Future is already available in the sysimage.
[ Info: Sockets is already available in the sysimage.
[ Info: Markdown is already available in the sysimage.
[ Info: Tar is already available in the sysimage.
[ Info: UUIDs is already available in the sysimage.
[ Info: SHA is already available in the sysimage.
[ Info: LazyArtifacts is already available in the sysimage.
[ Info: LinearAlgebra is already available in the sysimage.
[ Info: ArgTools is already available in the sysimage.
[ Info: LibGit2 is already available in the sysimage.
[ Info: Artifacts is already available in the sysimage.
[ Info: Dates is already available in the sysimage.
[ Info: NetworkOptions is already available in the sysimage.
[ Info: MKL_jll is not explicitly added to the environment.
[ Info: Printf is already available in the sysimage.
[ Info: nghttp2_jll is already available in the sysimage.
[ Info: Test is already available in the sysimage.
[ Info: Random is already available in the sysimage.
[ Info: Libdl is already available in the sysimage.
[ Info: Serialization is already available in the sysimage.
[ Info: REPL is already available in the sysimage.
[ Info: libblastrampoline_jll is already available in the sysimage.
[ Info: MozillaCACerts_jll is already available in the sysimage.
[ Info: Mmap is already available in the sysimage.
[ Info: LibCURL_jll is already available in the sysimage.
[ Info: Logging is already available in the sysimage.
[ Info: TOML is already available in the sysimage.
[ Info: Downloads is already available in the sysimage.
[ Info: OpenBLAS_jll is already available in the sysimage.
[ Info: Distributed is already available in the sysimage.
[ Info: FileWatching is already available in the sysimage.
[ Info: LibCURL is already available in the sysimage.
[ Info: SharedArrays is already available in the sysimage.
[ Info: SparseArrays is already available in the sysimage.
[ Info: Unicode is already available in the sysimage.
[ Info: InteractiveUtils is already available in the sysimage.
[ Info: p7zip_jll is already available in the sysimage.

And then, we generate the FMU package code using generate_fmu_code. This generates a FMU Package with the functions, variables and continuous model defined above.

generate_fmu_code()
Note

For adding multiple threads to speed up matrix calculations use num_threads keyword argument. The default is num_threads = 4

To import the generated FMU package we use @import_fmu_pkg. This would also return the name of the generated package and the package itself.

Note

The path to the generated FMU package on disk can be accessed using FMUGeneration.lib_path

pkg_name, FMI2Binary = @import_fmu_pkg FMUGeneration.lib_path

We now define the XML metadata for the FMU using generateXML. This metadata includes inputs, parameters, continuous variables, outputs and end time Each of these should be a Vector{Tuple} with fields: name, unit, description and start (initial value).


state_names = ["u1", "u2"]
param_names = ["alpha", "beta", "gamma", "delta"]
input_names = ["x1", "x2"]

inputs = [(name = "$(input_names[i])", unit = "unit i$i", description = "desc i$i", start = initial_inputs[i])
            for i in 1:length(initial_inputs)]

params = [(name = "$(param_names[i])", description = "desc p$i", start = default_parameters[i]) for i in 1:length(default_parameters)]

conts = [(name = "$(state_names[i])", unit = "unit c$i", description = "desc c$i", start = initial_states[i])
            for i in 1:length(initial_states)]

outputs = [(name = "o$i", unit = "unit c$i", description = "desc c$i")
            for i in 1:length(initial_states)]
    
generateXML(inputs, params, conts, outputs, tspan[2]; sysname = "lv")

Now that we have defined our model, generated the FMU package, and generated the metadata, we are ready to compile a FMU. We call compile_fmu

fmu_dir_path, fmu_path, fmu_so_file_path = compile_fmu()

We get the path to the FMU on disk as fmu_path.

Pkg.rm(String(pkg_name)) # hide
nothing # hide