Skip to content

Commit fa246ed

Browse files
feat: add FMUComponent with support for v2 ME FMUs
1 parent 4792360 commit fa246ed

File tree

3 files changed

+220
-1
lines changed

3 files changed

+220
-1
lines changed

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
6464
BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665"
6565
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
6666
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
67+
FMI = "14a09403-18e3-468f-ad8a-74f8dda2d9ac"
6768
HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327"
6869
InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57"
6970
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
@@ -72,9 +73,10 @@ LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
7273
MTKBifurcationKitExt = "BifurcationKit"
7374
MTKChainRulesCoreExt = "ChainRulesCore"
7475
MTKDeepDiffsExt = "DeepDiffs"
76+
MTKFMIExt = "FMI"
7577
MTKHomotopyContinuationExt = "HomotopyContinuation"
76-
MTKLabelledArraysExt = "LabelledArrays"
7778
MTKInfiniteOptExt = "InfiniteOpt"
79+
MTKLabelledArraysExt = "LabelledArrays"
7880

7981
[compat]
8082
AbstractTrees = "0.3, 0.4"
@@ -105,6 +107,7 @@ FindFirstFunctions = "1"
105107
ForwardDiff = "0.10.3"
106108
FunctionWrappers = "1.1"
107109
FunctionWrappersWrappers = "0.1"
110+
FMI = "0.14"
108111
Graphs = "1.5.2"
109112
HomotopyContinuation = "2.11"
110113
InfiniteOpt = "0.5"

ext/MTKFMIExt.jl

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
module MTKFMIExt
2+
3+
using ModelingToolkit
4+
using ModelingToolkit: t_nounits as t, D_nounits as D
5+
import ModelingToolkit as MTK
6+
import FMI
7+
8+
macro statuscheck(expr)
9+
@assert Meta.isexpr(expr, :call)
10+
fn = expr.args[1]
11+
@assert Meta.isexpr(fn, :.)
12+
@assert fn.args[1] == :FMI
13+
fnname = fn.args[2]
14+
15+
instance = expr.args[2]
16+
17+
return quote
18+
status = $expr
19+
fnname = $fnname
20+
if (status isa Tuple && status[1] == FMI.fmi2True) ||
21+
(!(status isa Tuple) && status != FMI.fmi2StatusOK &&
22+
status != FMI.fmi2StatusWarning)
23+
if status != FMI.fmi2StatusFatal
24+
FMI.fmi2Terminate(wrapper.instance)
25+
end
26+
FMI.fmi2FreeInstance!(wrapper.instance)
27+
wrapper.instance = nothing
28+
error("FMU Error: status $status")
29+
end
30+
end |> esc
31+
end
32+
33+
function MTK.FMIComponent(::Val{2}, ::Val{:ME}; fmu = nothing, tolerance = 1e-6, name)
34+
value_references = Dict()
35+
defs = Dict()
36+
states = []
37+
diffvars = []
38+
observed = Equation[]
39+
stateT = Float64
40+
for (valRef, snames) in FMI.getStateValueReferencesAndNames(fmu)
41+
stateT = FMI.dataTypeForValueReference(fmu, valRef)
42+
snames = map(parseFMIVariableName, snames)
43+
vars = [MTK.unwrap(only(@variables $sname(t)::stateT)) for sname in snames]
44+
for i in eachindex(vars)
45+
if i == 1
46+
push!(diffvars, vars[i])
47+
else
48+
push!(observed, vars[i] ~ vars[1])
49+
end
50+
value_references[vars[i]] = valRef
51+
end
52+
append!(states, vars)
53+
end
54+
55+
inputs = []
56+
for (valRef, snames) in FMI.getInputValueReferencesAndNames(fmu)
57+
snames = map(parseFMIVariableName, snames)
58+
vars = [MTK.unwrap(only(@variables $sname(t)::stateT)) for sname in snames]
59+
for i in eachindex(vars)
60+
if i == 1
61+
push!(inputs, vars[i])
62+
else
63+
push!(observed, vars[i] ~ vars[1])
64+
end
65+
value_references[vars[i]] = valRef
66+
end
67+
append!(states, vars)
68+
end
69+
70+
outputs = []
71+
for (valRef, snames) in FMI.getOutputValueReferencesAndNames(fmu)
72+
snames = map(parseFMIVariableName, snames)
73+
vars = [MTK.unwrap(only(@variables $sname(t)::stateT)) for sname in snames]
74+
for i in eachindex(vars)
75+
if i == 1
76+
push!(outputs, vars[i])
77+
else
78+
push!(observed, vars[i] ~ vars[1])
79+
end
80+
value_references[vars[i]] = valRef
81+
end
82+
append!(states, vars)
83+
end
84+
85+
params = []
86+
parameter_dependencies = Equation[]
87+
for (valRef, pnames) in FMI.getParameterValueReferencesAndNames(fmu)
88+
defval = FMI.getStartValue(fmu, valRef)
89+
T = FMI.dataTypeForValueReference(fmu, valRef)
90+
pnames = map(parseFMIVariableName, pnames)
91+
vars = [MTK.unwrap(only(@parameters $pname::T)) for pname in pnames]
92+
for i in eachindex(vars)
93+
if i == 1
94+
push!(params, vars[i])
95+
else
96+
push!(parameter_dependencies, vars[i] ~ vars[1])
97+
end
98+
value_references[vars[i]] = valRef
99+
end
100+
defs[vars[1]] = defval
101+
end
102+
103+
input_value_references = UInt32[value_references[var] for var in inputs]
104+
param_value_references = UInt32[value_references[var] for var in params]
105+
@parameters wrapper::FMI2InstanceWrapper = FMI2InstanceWrapper(
106+
fmu, param_value_references, input_value_references, tolerance)
107+
108+
output_value_references = UInt32[value_references[var] for var in outputs]
109+
buffer_length = length(diffvars) + length(outputs)
110+
_functor = FMI2MEFunctor(zeros(buffer_length), output_value_references)
111+
@parameters (functor::(typeof(_functor)))(..)[1:buffer_length] = _functor
112+
call_expr = functor(wrapper, copy(diffvars), copy(inputs), copy(params), t)
113+
114+
diffeqs = Equation[]
115+
for (i, var) in enumerate([D.(diffvars); outputs])
116+
push!(diffeqs, var ~ call_expr[i])
117+
end
118+
119+
finalize_affect = MTK.FunctionalAffect(fmi2MEFinalize!, [], [wrapper], [])
120+
step_affect = MTK.FunctionalAffect(fmi2MEStep!, [], [wrapper], [])
121+
instance_management_callback = MTK.SymbolicDiscreteCallback(
122+
(t != t - 1), step_affect; finalize = finalize_affect)
123+
124+
push!(params, wrapper, functor)
125+
eqs = [observed; diffeqs]
126+
return ODESystem(eqs, t, states, params; parameter_dependencies, defaults = defs,
127+
discrete_events = [instance_management_callback], name)
128+
end
129+
130+
function parseFMIVariableName(name::AbstractString)
131+
return Symbol(replace(name, "." => "__"))
132+
end
133+
134+
mutable struct FMI2InstanceWrapper
135+
const fmu::FMI.FMU2
136+
const param_value_references::Vector{UInt32}
137+
const input_value_references::Vector{UInt32}
138+
const tolerance::FMI.fmi2Real
139+
instance::Union{FMI.FMU2Component, Nothing}
140+
end
141+
142+
function FMI2InstanceWrapper(fmu, params, inputs, tolerance)
143+
FMI2InstanceWrapper(fmu, params, inputs, tolerance, nothing)
144+
end
145+
146+
function get_instance!(wrapper::FMI2InstanceWrapper, states, inputs, params, t)
147+
if wrapper.instance === nothing
148+
wrapper.instance = FMI.fmi2Instantiate!(wrapper.fmu)::FMI.FMU2Component
149+
if !isempty(params)
150+
@statuscheck FMI.fmi2SetReal(wrapper.instance, wrapper.param_value_references,
151+
Csize_t(length(wrapper.param_value_references)), params)
152+
end
153+
@statuscheck FMI.fmi2SetupExperiment(
154+
wrapper.instance, FMI.fmi2True, wrapper.tolerance, t, FMI.fmi2False, t)
155+
@statuscheck FMI.fmi2EnterInitializationMode(wrapper.instance)
156+
if !isempty(inputs)
157+
@statuscheck FMI.fmi2SetReal(wrapper.instance, wrapper.input_value_references,
158+
Csize_t(length(wrapper.param_value_references)), inputs)
159+
end
160+
@statuscheck FMI.fmi2ExitInitializationMode(wrapper.instance)
161+
eventInfo = FMI.fmi2NewDiscreteStates(wrapper.instance)
162+
@assert eventInfo.newDiscreteStatesNeeded == FMI.fmi2False
163+
# TODO: Support FMU events
164+
@statuscheck FMI.fmi2EnterContinuousTimeMode(wrapper.instance)
165+
end
166+
instance = wrapper.instance
167+
@statuscheck FMI.fmi2SetTime(instance, t)
168+
@statuscheck FMI.fmi2SetContinuousStates(instance, states)
169+
if !isempty(inputs)
170+
@statuscheck FMI.fmi2SetReal(instance, wrapper.input_value_references,
171+
Csize_t(length(wrapper.param_value_references)), inputs)
172+
end
173+
174+
return instance
175+
end
176+
177+
function complete_step!(wrapper::FMI2InstanceWrapper)
178+
wrapper.instance === nothing && return
179+
@statuscheck FMI.fmi2CompletedIntegratorStep(wrapper.instance, FMI.fmi2True)
180+
end
181+
182+
function reset_instance!(wrapper::FMI2InstanceWrapper)
183+
wrapper.instance === nothing && return
184+
@statuscheck FMI.fmi2Reset(wrapper.instance)
185+
end
186+
187+
struct FMI2MEFunctor{T}
188+
return_buffer::Vector{T}
189+
output_value_references::Vector{UInt32}
190+
end
191+
192+
function (fn::FMI2MEFunctor)(wrapper::FMI2InstanceWrapper, states, inputs, params, t)
193+
instance = get_instance!(wrapper, states, inputs, params, t)
194+
195+
states_buffer = zeros(length(states))
196+
@statuscheck FMI.fmi2GetDerivatives!(instance, states_buffer)
197+
outputs_buffer = zeros(length(fn.output_value_references))
198+
FMI.fmi2GetReal!(instance, fn.output_value_references, outputs_buffer)
199+
return [states_buffer; outputs_buffer]
200+
end
201+
202+
function fmi2MEStep!(integrator, u, p, ctx)
203+
wrapper_idx = p[1]
204+
wrapper = integrator.ps[wrapper_idx]
205+
complete_step!(wrapper)
206+
end
207+
208+
function fmi2MEFinalize!(integrator, u, p, ctx)
209+
wrapper_idx = p[1]
210+
wrapper = integrator.ps[wrapper_idx]
211+
reset_instance!(wrapper)
212+
end
213+
214+
end # module

src/ModelingToolkit.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,6 @@ export MTKParameters, reorder_dimension_by_tunables!, reorder_dimension_by_tunab
291291

292292
export HomotopyContinuationProblem
293293

294+
function FMIComponent end
295+
294296
end # module

0 commit comments

Comments
 (0)