The purpose of this library is enabling the creation of FMU files that can be later used in many compatible simulation packages such as OpenModelica, Matlab, Dymola, ...
This library is heavily based on fmusdk.
You need Nim v2.0. You can use choosenim in order to install Nim.
To intall the fmu.nim
library, as usual, with nimble:
nimble install https://github.com/SciNim/fmu.nim
Right now, you can create both Model Exchange and Co-Simulation models using FMI2.0.
The five examples from fmusdk
are working.
Besides, it can create easily multiplatform FMU's (they can work both in windows and linux).
It creates:
- the libraries:
.so
and/or.dll
- the folder structure as per the specification
- the XML with the model details
Then it packs everything in a zip file and changes the extension into .fmu
.
Go to the examples
folder.
The following command will generate inc.so
(in Linux) and embed it into inc.fmu
as a Model Exchange:
$ nim c -r -d:fmu2me inc
In order to create a co-simulation replace
-d:fmu2me
with-d:fmu2cs
. The source code using one or the other looks like:
when defined(fmu2me):
values.exportFmu("values.fmu")
when defined(fmu2cs):
values.exportFmu("values.fmu", fmi2CoSimulation)
In order to test the models, one can use FMUComplianceChecker:
$ .../fmuCheck.linux64 -h 1 -s 11 -f -l 6 -e values.log values.fmu
where:
-h 1
: steps every second-s 11
: during 11 seconds-f
: forces showing all the values (inputs, outputs, locals)-l 6
: logs every detail-e values.log
: stores the logging undervalues.log
values.fmu
: selects the FMU to be used
or with fmusim (from fmusdk
package):
$ ./fmusim_me inc.fmu 5 0.1
this will simulate during 5seconds using 0.1 second steps. it will create the file
results.csv
.
If you compile like this:
nim c -r -d:fmu2me inc
you will get a FMU that is compatible only with the platform in which it was compile.
But if you compile using zig
:
nim c -r -d:fmu2me -d:zig inc
you will get a FMU compatible with windows and linux (amd64 in both cases).
Install zig
as you would do in windows or linux. (In windows, you need to have the binary reachable in the path; in Linux your package manager will take care of that).
Then you need to install with nimble
(Nim package manager) the package: zigcc
.
nimble install zigcc
(this is the same for both windows and linux; in windows you might need to install git
)
In general terms, we are making Nim to export C functions fulfilling the naming conventions of the FMU standard.
In Linux, open:
$ OMEdit
Then import the created FMU:
Create a new Model (we will use the imported FMU in it):
Then drag-n-drop the FMU into the model:
Add an output for the integer. For that, drag-n-drop IntegerOutput
on the model:
and connect the FMU instance to IntegerOutput
:
Configure the simulation:
At this point, OpenModelica ask you to store the model somewhere (example.mo
file). Select whereever it suits you.
A new model requires the info such as:
id = "inc"
guid = "{8c4e810f-3df3-4a00-8276-176fa3c9f008}"
We will call the model
template with these values. The model
template is defined in src/fmu.nim
. This calls model2
template (another template within src/fmu.nim
). They both are responsible for structuring the code: the custom defined functions plus the other functions required by the standard.
All this will create two modes of operations for the code:
- When compiled as a library, it will create a library such as
inc.so
. - When compiled as an app it uses
src/lib/fmubuilder.nim
:
- Creates the folder structure
- Creates the XML
- Compress everything into a
.fmu
file.
Called by fmi2Instantiate
with signature setStartValues(comp:Fmu)
.
In fmusdk
, it initialize everything. This is no needed here. Right now this would only be needed in case of requiring to setup something during the FMU instantiation phase.
Calculate the values of the FMU (Functional Mock-up Unit) variables at a specific time step during simulation.
This function is defined by the user and called from getters.nim
(fmi2GetReal
, fmi2GetInteger
, fmi2GetBoolean
, fmi2GetString
) and common.nim
(fmi2ExitInitializationMode
).
Lazy set values (given it is only called when isDirtyValues == true
in the model) for all variable that are computed from other variables.
Defines:
proc calculateValues*(comp: FmuRef) =
if comp.state == modelInitializationMode:
# set first time event
comp.eventInfo.nextEventTimeDefined = fmi2True
comp.eventInfo.nextEventTime = 1 + comp.time
In this case, calculateValues
creates new temporal events. In particular, creates in the InitializationMode
state a new event 1s after the start.
Used to set the next time event, if any.
Reacts to events. In the inc.nim
example, the first event was created in calculateValues
.
In the inc.nim
case, eventUpdate
reacts to the event by creating a new event.
- TODO: To talk about:
and about:
type fmi2EventInfo* {.bycopy.} = object newDiscreteStatesNeeded*: fmi2Boolean terminateSimulation*: fmi2Boolean nominalsOfContinuousStatesChanged*: fmi2Boolean valuesOfContinuousStatesChanged*: fmi2Boolean nextEventTimeDefined*: fmi2Boolean nextEventTime*: fmi2Real
comp.isNewEventIteration
This is a user defined function with signature proc getReal*(comp: FmuRef; key: string):float
. It is called by getters.nim
in fmi2GetReal
.
It is responsible for calculating the float values.
Defined as:
proc getReal*(comp: FmuRef;
key: string): float =
case key
of "myFloat": comp["myfloat"].valueR
of "myFloatDerivative": -comp["myfloat"].valueR
else: 0.0
- unit testing for the examples
- To simulate within Nim. This would prevent the need for
fmuChecker
.
-
Exporting FMUs:
-
Importing FMUs:
- FMI4cpp: in order to simulate with FMI.