Creation and training of ME-NeuralFMUs

Tutorial by Johannes Stoljar, Tobias Thummerer

Last edit: 15.11.2023

License

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

Motivation

The Julia Package FMIFlux.jl is motivated by the application of hybrid modeling. This package enables the user to integrate his simulation model between neural networks (NeuralFMU). For this, the simulation model must be exported as FMU (functional mock-up unit), which corresponds to a widely used standard. The big advantage of hybrid modeling with artificial neural networks is, that effects that are difficult to model (because they might be unknown) can be easily learned by the neural networks. For this purpose, the NeuralFMU is trained with measurement data containing the not modeled physical effect. The final product is a simulation model including the originally not modeled effects. Another big advantage of the NeuralFMU is that it works with little data, because the FMU already contains the characteristic functionality of the simulation and only the missing effects are added.

NeuralFMUs do not need to be as easy as in this example. Basically a NeuralFMU can combine different ANN topologies that manipulate any FMU-input (system state, system inputs, time) and any FMU-output (system state derivative, system outputs, other system variables). However, for this example a NeuralFMU topology as shown in the following picture is used.

NeuralFMU.svg

NeuralFMU (ME) from [1].

Introduction to the example

In this example, simplified modeling of a one-dimensional spring pendulum (without friction) is compared to a model of the same system that includes a nonlinear friction model. The FMU with the simplified model will be named simpleFMU in the following and the model with the friction will be named realFMU. At the beginning, the actual state of both simulations is shown, whereby clear deviations can be seen in the graphs. The realFMU serves as a reference graph. The simpleFMU is then integrated into a NeuralFMU architecture and a training of the entire network is performed. After the training the final state is compared again to the realFMU. It can be clearly seen that by using the NeuralFMU, learning of the friction process has taken place.

Target group

The example is primarily intended for users who work in the field of first principle and/or hybrid modeling and are further interested in hybrid model building. The example wants to show how simple it is to combine FMUs with machine learning and to illustrate the advantages of this approach.

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.

Getting started

Installation prerequisites

DescriptionCommand
1.Enter Package Manager via]
2.Install FMI viaadd FMI
3.Install FMIFlux viaadd FMIFlux
4.Install FMIZoo viaadd FMIZoo
5.Install DifferentialEquations viaadd DifferentialEquations
6.Install Plots viaadd Plots
7.Install Random viaadd Random

Code section

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

# imports
using FMI
using FMIFlux
using FMIFlux.Flux
using FMIZoo
using DifferentialEquations: Tsit5
import Plots

# set seed
import Random
Random.seed!(42);
┌ Warning: Error requiring `Enzyme` from `LinearSolve`
│   exception =
│    LoadError: ArgumentError: Package LinearSolve does not have Enzyme in its dependencies:
│    - You may have a partially installed environment. Try `Pkg.instantiate()`
│      to ensure all packages in the environment are installed.
│    - Or, if you have LinearSolve checked out for development and have
│      added Enzyme as a dependency but haven't updated your primary
│      environment's manifest file, try `Pkg.resolve()`.
│    - Otherwise you may need to report an issue with LinearSolve
│    Stacktrace:
│      [1] macro expansion
│        @ .\loading.jl:1167 [inlined]
│      [2] macro expansion
│        @ .\lock.jl:223 [inlined]
│      [3] require(into::Module, mod::Symbol)
│        @ Base .\loading.jl:1144
│      [4] include(mod::Module, _path::String)
│        @ Base .\Base.jl:419
│      [5] include(x::String)
│        @ LinearSolve C:\Users\runneradmin\.julia\packages\LinearSolve\qCLK7\src\LinearSolve.jl:1
│      [6] macro expansion
│        @ C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\Requires.jl:40 [inlined]
│      [7] top-level scope
│        @ C:\Users\runneradmin\.julia\packages\LinearSolve\qCLK7\src\init.jl:16
│      [8] eval
│        @ .\boot.jl:368 [inlined]
│      [9] eval
│        @ C:\Users\runneradmin\.julia\packages\LinearSolve\qCLK7\src\LinearSolve.jl:1 [inlined]
│     [10] (::LinearSolve.var"#88#97")()
│        @ LinearSolve C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:101
│     [11] macro expansion
│        @ timing.jl:382 [inlined]
│     [12] err(f::Any, listener::Module, modname::String, file::String, line::Any)
│        @ Requires C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:47
│     [13] (::LinearSolve.var"#87#96")()
│        @ LinearSolve C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:100
│     [14] withpath(f::Any, path::String)
│        @ Requires C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:37
│     [15] (::LinearSolve.var"#86#95")()
│        @ LinearSolve C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:99
│     [16] #invokelatest#2
│        @ .\essentials.jl:729 [inlined]
│     [17] invokelatest
│        @ .\essentials.jl:726 [inlined]
│     [18] foreach(f::typeof(Base.invokelatest), itr::Vector{Function})
│        @ Base .\abstractarray.jl:2774
│     [19] loadpkg(pkg::Base.PkgId)
│        @ Requires C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:27
│     [20] #invokelatest#2
│        @ .\essentials.jl:729 [inlined]
│     [21] invokelatest
│        @ .\essentials.jl:726 [inlined]
│     [22] run_package_callbacks(modkey::Base.PkgId)
│        @ Base .\loading.jl:869
│     [23] _tryrequire_from_serialized(modkey::Base.PkgId, path::String, sourcepath::String, depmods::Vector{Any})
│        @ Base .\loading.jl:944
│     [24] _require_search_from_serialized(pkg::Base.PkgId, sourcepath::String, build_id::UInt64)
│        @ Base .\loading.jl:1028
│     [25] _require(pkg::Base.PkgId)
│        @ Base .\loading.jl:1315
│     [26] _require_prelocked(uuidkey::Base.PkgId)
│        @ Base .\loading.jl:1200
│     [27] macro expansion
│        @ .\loading.jl:1180 [inlined]
│     [28] macro expansion
│        @ .\lock.jl:223 [inlined]
│     [29] require(into::Module, mod::Symbol)
│        @ Base .\loading.jl:1144
│     [30] eval
│        @ .\boot.jl:368 [inlined]
│     [31] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
│        @ Base .\loading.jl:1428
│     [32] softscope_include_string(m::Module, code::String, filename::String)
│        @ SoftGlobalScope C:\Users\runneradmin\.julia\packages\SoftGlobalScope\u4UzH\src\SoftGlobalScope.jl:65
│     [33] execute_request(socket::ZMQ.Socket, msg::IJulia.Msg)
│        @ IJulia C:\Users\runneradmin\.julia\packages\IJulia\Vo51o\src\execute_request.jl:67
│     [34] #invokelatest#2
│        @ .\essentials.jl:729 [inlined]
│     [35] invokelatest
│        @ .\essentials.jl:726 [inlined]
│     [36] eventloop(socket::ZMQ.Socket)
│        @ IJulia C:\Users\runneradmin\.julia\packages\IJulia\Vo51o\src\eventloop.jl:8
│     [37] (::IJulia.var"#15#18")()
│        @ IJulia .\task.jl:484
│    in expression starting at C:\Users\runneradmin\.julia\packages\LinearSolve\qCLK7\ext\LinearSolveEnzymeExt.jl:1
└ @ Requires C:\Users\runneradmin\.julia\packages\Requires\Z8rfN\src\require.jl:51

After importing the packages, the path to the Functional Mock-up Units (FMUs) is set. The FMU is a model exported meeting the Functional Mock-up Interface (FMI) Standard. The FMI is a free standard (fmi-standard.org) that defines a container and an interface to exchange dynamic models using a combination of XML files, binaries and C code zipped into a single file.

The object-orientated structure of the SpringPendulum1D (simpleFMU) can be seen in the following graphic and corresponds to a simple modeling.

svg

In contrast, the model SpringFrictionPendulum1D (realFMU) is somewhat more accurate, because it includes a friction component.

svg

Next, the start time and end time of the simulation are set. Finally, a step size is specified to store the results of the simulation at these time steps.

tStart = 0.0
tStep = 0.01
tStop = 5.0
tSave = collect(tStart:tStep:tStop)
501-element Vector{Float64}:
 0.0
 0.01
 0.02
 0.03
 0.04
 0.05
 0.06
 0.07
 0.08
 0.09
 0.1
 0.11
 0.12
 ⋮
 4.89
 4.9
 4.91
 4.92
 4.93
 4.94
 4.95
 4.96
 4.97
 4.98
 4.99
 5.0

RealFMU

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

realFMU = fmiLoad("SpringFrictionPendulum1D", "Dymola", "2022x")
fmiInfo(realFMU)
#################### Begin information for FMU ####################
	Model name:			SpringFrictionPendulum1D
	FMI-Version:			2.0
	GUID:				{2e178ad3-5e9b-48ec-a7b2-baa5669efc0c}
	Generation tool:		Dymola Version 2022x (64-bit), 2021-10-08
	Generation time:		2022-05-19T06:54:12Z
	Var. naming conv.:		structured
	Event indicators:		24
	Inputs:				0
	Outputs:			0
	States:				2
		33554432 ["mass.s"]
		33554433 ["mass.v", "mass.v_relfric"]
	Supports Co-Simulation:		true
		Model identifier:	SpringFrictionPendulum1D
		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:	SpringFrictionPendulum1D
		Get/Set State:		true
		Serialize State:	true
		Dir. Derivatives:	true
##################### End information for FMU #####################

In the next steps the parameters are defined. The first parameter is the initial position of the mass, which is initilized with $0.5𝑚$. The second parameter is the initial velocity of the mass, which is initialized with $0\frac{m}{s}$. The FMU hase two states: The first state is the position of the mass and the second state is the velocity. In the function fmiSimulate() the realFMU is simulated, still specifying the start and end time, the parameters and which variables are recorded. After the simulation is finished the result of the realFMU can be plotted. This plot also serves as a reference for the other model (simpleFMU).

initStates = ["s0", "v0"]
x₀ = [0.5, 0.0]
params = Dict(zip(initStates, x₀))
vrs = ["mass.s", "mass.v", "mass.a", "mass.f"]

realSimData = fmiSimulate(realFMU, (tStart, tStop); parameters=params, recordValues=vrs, saveat=tSave)
fmiPlot(realSimData)
Simulating CS-FMU ...   0%|█                             |  ETA: N/A

Simulating CS-FMU ... 100%|██████████████████████████████| Time: 0:00:02

svg

The data from the simulation of the realFMU, are divided into position and velocity data. These data will be needed later.

velReal = fmi2GetSolutionValue(realSimData, "mass.v")
posReal = fmi2GetSolutionValue(realSimData, "mass.s")
501-element Vector{Float64}:
 0.5
 0.5002235448486548
 0.5008715291319449
 0.5019478597521578
 0.5034570452098334
 0.5053993458877354
 0.5077764240578201
 0.5105886522837868
 0.5138351439717114
 0.5175150321322992
 0.521627087567517
 0.5261682148972211
 0.5311370185654775
 ⋮
 1.0657564963384756
 1.066930862706352
 1.0679715872270086
 1.068876303469867
 1.0696434085045978
 1.0702725656148622
 1.0707609890298837
 1.071107075846018
 1.0713093338869186
 1.0713672546639146
 1.0713672546629138
 1.071367254661913

After extracting the data, the FMU is cleaned-up.

fmiUnload(realFMU)

SimpleFMU

The following lines load, simulate and plot the simpleFMU just like the realFMU. The differences between both systems can be clearly seen from the plots. In the plot for the realFMU it can be seen that the oscillation continues to decrease due to the effect of the friction. If you simulate long enough, the oscillation would come to a standstill in a certain time. The oscillation in the simpleFMU behaves differently, since the friction was not taken into account here. The oscillation in this model would continue to infinity with the same oscillation amplitude. From this observation the desire of an improvement of this model arises.

simpleFMU = fmiLoad("SpringPendulum1D", "Dymola", "2022x")
fmiInfo(simpleFMU)

vrs = ["mass.s", "mass.v", "mass.a"]
simpleSimData = fmiSimulate(simpleFMU, (tStart, tStop); recordValues=vrs, saveat=tSave, reset=false)
fmiPlot(simpleSimData)
#################### Begin information for FMU ####################
	Model name:			SpringPendulum1D
	FMI-Version:			2.0
	GUID:				{fc15d8c4-758b-48e6-b00e-5bf47b8b14e5}
	Generation tool:		Dymola Version 2022x (64-bit), 2021-10-08
	Generation time:		2022-05-19T06:54:23Z
	Var. naming conv.:		structured
	Event indicators:		0
	Inputs:				0
	Outputs:			0
	States:				2
		33554432 ["mass.s"]
		33554433 ["mass.v"]
	Supports Co-Simulation:		true
		Model identifier:	SpringPendulum1D
		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:	SpringPendulum1D
		Get/Set State:		true
		Serialize State:	true
		Dir. Derivatives:	true
##################### End information for FMU #####################

svg

The data from the simulation of the simpleFMU, are divided into position and velocity data. These data will be needed later to plot the results.

velSimple = fmi2GetSolutionValue(simpleSimData, "mass.v")
posSimple = fmi2GetSolutionValue(simpleSimData, "mass.s")
501-element Vector{Float64}:
 0.5
 0.5003127019074967
 0.5012175433745238
 0.5027172504687035
 0.504812416566759
 0.5075012719497328
 0.5107830165354977
 0.5146534880772458
 0.5191107030735219
 0.5241484264969329
 0.5297629811612266
 0.5359472314461261
 0.5426950964528339
 ⋮
 1.6842615646003007
 1.6884869953422783
 1.6921224800662573
 1.69516502108285
 1.6976144547672483
 1.6994659284032172
 1.7007174453690572
 1.7013675684067706
 1.7014154196220592
 1.7008606804843265
 1.69970552855305
 1.6979508813706

NeuralFMU

Loss function

In order to train our model, a loss function must be implemented. The solver of the NeuralFMU can calculate the gradient of the loss function. The gradient descent is needed to adjust the weights in the neural network so that the sum of the error is reduced and the model becomes more accurate.

The loss function in this implementation consists of the mean squared error (mse) from the real position of the realFMU simulation (posReal) and the position data of the network (posNet). $ e{mse} = \frac{1}{n} \sum\limits{i=0}^n (posReal[i] - posNet[i])^2 $

As it is indicated with the comments, one could also additionally consider the mse from the real velocity (velReal) and the velocity from the network (velNet). The error in this case would be calculated from the sum of both errors.

# loss function for training
function lossSum(p)
    global posReal
    solution = neuralFMU(x₀; p=p)

    posNet = fmi2GetSolutionState(solution, 1; isIndex=true)
    
    FMIFlux.Losses.mse(posReal, posNet) 
end
lossSum (generic function with 1 method)

Callback

To output the loss in certain time intervals, a callback is implemented as a function in the following. Here a counter is incremented, every twentieth pass the loss function is called and the average error is printed out.

# callback function for training
global counter = 0
function callb(p)
    global counter += 1
    if counter % 20 == 1
        avgLoss = lossSum(p[1])
        @info "Loss [$counter]: $(round(avgLoss, digits=5))   Avg displacement in data: $(round(sqrt(avgLoss), digits=5))"
    end
end
callb (generic function with 1 method)

Structure of the NeuralFMU

In the following, the topology of the NeuralFMU is constructed. It consists of an input layer, which then leads into the simpleFMU model. The ME-FMU computes the state derivatives for a given system state. Following the simpleFMU is a dense layer that has exactly as many inputs as the model has states (and therefore state derivatives). The output of this layer consists of 16 output nodes and a tanh activation function. The next layer has 16 input and output nodes with the same activation function. The last layer is again a dense layer with 16 input nodes and the number of states as outputs. Here, it is important that no tanh-activation function follows, because otherwise the pendulums state values would be limited to the interval $[-1;1]$.

# NeuralFMU setup
numStates = fmiGetNumberOfStates(simpleFMU)

net = Chain(x -> simpleFMU(x=x, dx_refs=:all),
            Dense(numStates, 16, tanh),
            Dense(16, 16, tanh),
            Dense(16, numStates))
Chain(
  var"#1#2"(),
  Dense(2 => 16, tanh),                 # 48 parameters
  Dense(16 => 16, tanh),                # 272 parameters
  Dense(16 => 2),                       # 34 parameters
)                   # Total: 6 arrays, 354 parameters, 1.758 KiB.

Definition of the NeuralFMU

The instantiation of the ME-NeuralFMU is done as a one-liner. The FMU (simpleFMU), the structure of the network net, start tStart and end time tStop, the numerical solver Tsit5() and the time steps tSave for saving are specified.

neuralFMU = ME_NeuralFMU(simpleFMU, net, (tStart, tStop), Tsit5(); saveat=tSave);

Plot before training

Here the state trajectory of the simpleFMU is recorded. Doesn't really look like a pendulum yet, but the system is random initialized by default. In the plots later on, the effect of learning can be seen.

solutionBefore = neuralFMU(x₀)
fmiPlot(solutionBefore)
┌ Warning: No solver keyword detected for NeuralFMU.
│ Continuous adjoint method is applied, which requires solving backward in time.
│ This might be not supported by every FMU.
│ (This message is only printed once.)
└ @ FMICore C:\Users\runneradmin\.julia\packages\FMICore\7NIyu\src\printing.jl:38

svg

Training of the NeuralFMU

For the training of the NeuralFMU the parameters are extracted. The known Adam optimizer for minimizing the gradient descent is used as further passing parameters. In addition, the previously defined loss and callback function, as well as the number of epochs are passed.

# train
paramsNet = FMIFlux.params(neuralFMU)

optim = Adam()
FMIFlux.train!(lossSum, neuralFMU, Iterators.repeated((), 300), optim; cb=()->callb(paramsNet)) 
[ Info: Loss [1]: 14.31508   Avg displacement in data: 3.78353


[ Info: Loss [21]: 2.0444   Avg displacement in data: 1.42982


[ Info: Loss [41]: 0.36163   Avg displacement in data: 0.60135


[ Info: Loss [61]: 0.11469   Avg displacement in data: 0.33866


[ Info: Loss [81]: 0.0737   Avg displacement in data: 0.27147


[ Info: Loss [101]: 0.06571   Avg displacement in data: 0.25633


[ Info: Loss [121]: 0.06033   Avg displacement in data: 0.24562


[ Info: Loss [141]: 0.05599   Avg displacement in data: 0.23663


[ Info: Loss [161]: 0.05242   Avg displacement in data: 0.22894


[ Info: Loss [181]: 0.0495   Avg displacement in data: 0.22249


[ Info: Loss [201]: 0.04714   Avg displacement in data: 0.21712


[ Info: Loss [221]: 0.04522   Avg displacement in data: 0.21265


[ Info: Loss [241]: 0.04366   Avg displacement in data: 0.20896


[ Info: Loss [261]: 0.04239   Avg displacement in data: 0.20589


[ Info: Loss [281]: 0.04135   Avg displacement in data: 0.20334

Comparison of the plots

Here three plots are compared with each other and only the position of the mass is considered. The first plot represents the simpleFMU, the second represents the realFMU (reference) and the third plot represents the result after training the NeuralFMU.

# plot results mass.s
solutionAfter = neuralFMU(x₀)

fig = Plots.plot(xlabel="t [s]", ylabel="mass position [m]", linewidth=2,
                 xtickfontsize=12, ytickfontsize=12,
                 xguidefontsize=12, yguidefontsize=12,
                 legendfontsize=8, legend=:topright)

Plots.plot!(fig, tSave, posSimple, label="SimpleFMU", linewidth=2)
Plots.plot!(fig, tSave, posReal, label="RealFMU", linewidth=2)
Plots.plot!(fig, solutionAfter; stateIndices=1:1, values=false, label="NeuralFMU (300 epochs)", linewidth=2)
fig 

svg

Continue training and plotting

As can be seen from the previous figure, the plot of the NeuralFMU has not yet fully converged against the realFMU, so the training of the NeuralFMU is continued. After further training, the plot of NeuralFMU is added to the figure again. The effect of the longer training can be recognized well, since the plot of the NeuralFMU had further converged.

FMIFlux.train!(lossSum, neuralFMU, Iterators.repeated((), 1200), optim; cb=()->callb(paramsNet)) 
# plot results mass.s
solutionAfter = neuralFMU(x₀)
Plots.plot!(fig, solutionAfter; stateIndices=1:1, values=false, label="NeuralFMU (1500 epochs)", linewidth=2)
fig 
[ Info: Loss [301]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [321]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [341]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [361]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [381]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [401]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [421]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [441]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [461]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [481]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [501]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [521]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [541]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [561]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [581]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [601]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [621]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [641]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [661]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [681]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [701]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [721]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [741]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [761]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [781]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [801]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [821]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [841]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [861]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [881]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [901]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [921]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [941]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [961]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [981]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1001]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1021]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1041]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1061]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1081]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1101]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1121]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1141]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1161]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1181]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1201]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1221]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1241]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1261]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1281]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1301]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1321]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1341]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1361]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1381]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1401]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1421]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1441]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1461]: 0.04052   Avg displacement in data: 0.20129


[ Info: Loss [1481]: 0.04052   Avg displacement in data: 0.20129

svg

Finally, the FMU is cleaned-up.

fmiUnload(simpleFMU)

Summary

Based on the plots, it can be seen that the NeuralFMU is able to adapt the friction model of the realFMU. After 300 runs, the curves do not overlap very well, but this can be achieved by longer training (1000 runs) or a better initialization.

Source

[1] Tobias Thummerer, Lars Mikelsons and Josef Kircher. 2021. NeuralFMU: towards structural integration of FMUs into neural networks. Martin Sjölund, Lena Buffoni, Adrian Pop and Lennart Ochel (Ed.). Proceedings of 14th Modelica Conference 2021, Linköping, Sweden, September 20-24, 2021. Linköping University Electronic Press, Linköping (Linköping Electronic Conference Proceedings ; 181), 297-306. DOI: 10.3384/ecp21181297