Deploying a Double Pendulum Model with Output Transform

This tutorial will demonstrate how to deploy a double pendulum model with a transformation of the outputs. The system evolves with states as polar coordinates of the position, but we will add a transformation such that the FMU outputs are Cartesian coordinates.

We start with importing FMUGeneration and OrdinaryDiffEq into our environment.

using FMUGeneration
using OrdinaryDiffEq

Let us clear our deployment workspace before we begin with the tutorial.

clear_deployment_workspace()
[ Info: Cleared Deployment Workspace!

We first define the system and the transforming function using @define. The function double_pendulum is our system. The function polar2cart is the transforming function that converts the polar coordinates into cartesian. This example is taken from the DifferentialEquations.jl docs

@define begin
    function double_pendulum(u, x, p, t)
        m₁, m₂, L₁, L₂, g = p
        du1 = u[2]
        du2 = -((g * (2 * m₁ + m₂) * sin(u[1]) +
                     m₂ * (g * sin(u[1] - 2 * u[3]) +
                      2 * (L₂ * u[4]^2 + L₁ * u[2]^2 * cos(u[1] - u[3])) * sin(u[1] - u[3]))) /
                    (2 * L₁ * (m₁ + m₂ - m₂ * cos(u[1] - u[3])^2)))
        du3 = u[4]
        du4 = (((m₁ + m₂) * (L₁ * u[2]^2 + g * cos(u[1])) +
                    L₂ * m₂ * u[4]^2 * cos(u[1] - u[3])) * sin(u[1] - u[3])) /
                  (L₂ * (m₁ + m₂ - m₂ * cos(u[1] - u[3])^2))
        [du1, du2, du3, du4]
    end

    function polar2cart(u, x, p, t)
        l1 = p[3]
        l2 = p[4]
        vars = (2, 4)
        p1 = l1 * u[vars[1]]
        p2 = l2 * u[vars[2]]
        x1 = l1 * sin(p1)
        y1 = l1 * -cos(p1)
        [x1 + l2 * sin.(p2), y1 - l2 * cos.(p2)]
    end
end

Now that we have defined our system and transformation into the FMU Package, let us define the initial states, default parameters, and timespan for the system.

initial_states = [0, π / 3, 0, 3pi / 5]
m₁, m₂, L₁, L₂, g = 1, 2, 1, 2, 9.8
default_parameters = [m₁, m₂, L₁, L₂, g]
tspan = (0.0, 50.0)
tend = tspan[end]

param_names = ["m1", "m2", "L1", "L2", "g"]
state_names = ["alpha", "Lalpha", "beta", "Lbeta"]
output_names = ["x", "y"]
2-element Vector{String}:
 "x"
 "y"

We call set_default and pass the keyword arguments necessary.

set_default(; initial_states = initial_states, default_parameters = default_parameters, tspan = tspan)

Now we define our continuous model, in this case, it is the double_pendulum function we have defined before.

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

And then, we define the output transform that would convert the polar coordinates to cartesian coordinates.

@output_transform polar2cart(u, x, p, t)

We will add OrdinaryDiffEq package 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.

We then generate the FMU Package code using generate_fmu_code

generate_fmu_code()

We will import the package and get the package name using @import_fmu_pkg

pkg_name, FMI2Binary = @import_fmu_pkg FMUGeneration.lib_path

Now lets define the XML metadeta for the FMU with generateXML

Note

We donot have inputs for this system and hence we define an empty vector for inputs.

inputs = NamedTuple[]

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", unit = "unit c$i", description = "desc c$i")
            for (i, o) in enumerate(["x", "y"])]

generateXML(inputs, params, conts, outputs, tend; sysname = "double_pendulum")

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()
Pkg.rm(String(pkg_name)) # hide
nothing # hide