1
1
module MTKFMIExt
2
2
3
3
using ModelingToolkit
4
+ using SymbolicIndexingInterface
4
5
using ModelingToolkit: t_nounits as t, D_nounits as D
5
6
import ModelingToolkit as MTK
6
7
import FMI
@@ -17,20 +18,24 @@ macro statuscheck(expr)
17
18
return quote
18
19
status = $ expr
19
20
fnname = $ fnname
20
- if (status isa Tuple && status[1 ] == FMI. fmi2True) ||
21
- (! (status isa Tuple) && status != FMI. fmi2StatusOK &&
22
- status != FMI. fmi2StatusWarning)
21
+ if status != = nothing && ( (status isa Tuple && status[1 ] == FMI. fmi2True) ||
22
+ (! (status isa Tuple) && status != FMI. fmi2StatusOK &&
23
+ status != FMI. fmi2StatusWarning) )
23
24
if status != FMI. fmi2StatusFatal
24
25
FMI. fmi2Terminate (wrapper. instance)
25
26
end
26
27
FMI. fmi2FreeInstance! (wrapper. instance)
27
28
wrapper. instance = nothing
28
- error (" FMU Error: status $status " )
29
+ error (" FMU Error in $fnname : status $status " )
29
30
end
30
31
end |> esc
31
32
end
32
33
33
- function MTK. FMIComponent (:: Val{2} , :: Val{:ME} ; fmu = nothing , tolerance = 1e-6 , name)
34
+ function MTK. FMIComponent (:: Val{2} ; fmu = nothing , tolerance = 1e-6 ,
35
+ communication_step_size = nothing , type, name)
36
+ if type == :CS && communication_step_size === nothing
37
+ throw (ArgumentError (" `communication_step_size` must be specified for Co-Simulation FMUs." ))
38
+ end
34
39
value_references = Dict ()
35
40
defs = Dict ()
36
41
states = []
@@ -40,10 +45,12 @@ function MTK.FMIComponent(::Val{2}, ::Val{:ME}; fmu = nothing, tolerance = 1e-6,
40
45
value_references, diffvars, states, observed)
41
46
if isempty (diffvars)
42
47
__mtk_internal_u = []
43
- else
44
- @variables __mtk_internal_u (t)[1 : length (diffvars)]
48
+ elseif type == :ME
49
+ @variables __mtk_internal_u (t)[1 : length (diffvars)] [guess = diffvars]
50
+ push! (observed, __mtk_internal_u ~ copy (diffvars))
51
+ elseif type == :CS
52
+ @parameters __mtk_internal_u (t)[1 : length (diffvars)]= missing [guess = diffvars]
45
53
push! (observed, __mtk_internal_u ~ copy (diffvars))
46
- push! (states, __mtk_internal_u)
47
54
end
48
55
49
56
inputs = []
@@ -52,16 +59,22 @@ function MTK.FMIComponent(::Val{2}, ::Val{:ME}; fmu = nothing, tolerance = 1e-6,
52
59
if isempty (inputs)
53
60
__mtk_internal_x = []
54
61
else
55
- @variables __mtk_internal_x (t)[1 : length (inputs)]
62
+ @variables __mtk_internal_x (t)[1 : length (inputs)] [guess = inputs]
56
63
push! (observed, __mtk_internal_x ~ copy (inputs))
57
64
push! (states, __mtk_internal_x)
58
65
end
59
66
60
67
outputs = []
61
68
fmi_variables_to_mtk_variables! (fmu, FMI. getOutputValueReferencesAndNames (fmu),
62
69
value_references, outputs, states, observed)
63
- # @variables __mtk_internal_o(t)[1:length(outputs)]
64
- # push!(observed, __mtk_internal_o ~ outputs)
70
+ if type == :CS
71
+ if isempty (outputs)
72
+ __mtk_internal_o = []
73
+ else
74
+ @parameters __mtk_internal_o (t)[1 : length (outputs)]= missing [guess = zeros (length (outputs))]
75
+ push! (observed, __mtk_internal_o ~ outputs)
76
+ end
77
+ end
65
78
66
79
params = []
67
80
parameter_dependencies = Equation[]
@@ -82,24 +95,69 @@ function MTK.FMIComponent(::Val{2}, ::Val{:ME}; fmu = nothing, tolerance = 1e-6,
82
95
83
96
output_value_references = UInt32[value_references[var] for var in outputs]
84
97
buffer_length = length (diffvars) + length (outputs)
85
- _functor = FMI2MEFunctor (zeros (buffer_length), output_value_references)
86
- @parameters (functor:: (typeof(_functor) ))(.. )[1 : buffer_length] = _functor
87
- call_expr = functor (wrapper, __mtk_internal_u, __mtk_internal_x, __mtk_internal_p, t)
88
98
89
- diffeqs = Equation[]
90
- for (i, var) in enumerate ([D .(diffvars); outputs])
91
- push! (diffeqs, var ~ call_expr[i])
92
- end
99
+ initialization_eqs = Equation[]
100
+
101
+ if type == :ME
102
+ _functor = FMI2MEFunctor (zeros (buffer_length), output_value_references)
103
+ @parameters (functor:: (typeof(_functor) ))(.. )[1 : buffer_length] = _functor
104
+ call_expr = functor (
105
+ wrapper, __mtk_internal_u, __mtk_internal_x, __mtk_internal_p, t)
106
+
107
+ diffeqs = Equation[]
108
+ for (i, var) in enumerate ([D .(diffvars); outputs])
109
+ push! (diffeqs, var ~ call_expr[i])
110
+ end
111
+
112
+ finalize_affect = MTK. FunctionalAffect (fmi2Finalize!, [], [wrapper], [])
113
+ step_affect = MTK. FunctionalAffect (fmi2MEStep!, [], [wrapper], [])
114
+ instance_management_callback = MTK. SymbolicDiscreteCallback (
115
+ (t != t - 1 ), step_affect; finalize = finalize_affect)
116
+
117
+ push! (params, wrapper, functor)
118
+ push! (states, __mtk_internal_u)
119
+ elseif type == :CS
120
+ state_value_references = UInt32[value_references[var] for var in diffvars]
121
+ state_and_output_value_references = vcat (
122
+ state_value_references, output_value_references)
123
+ _functor = FMI2CSFunctor (state_and_output_value_references,
124
+ state_value_references, output_value_references)
125
+ @parameters (functor:: (typeof(_functor) ))(.. )[1 : (length (__mtk_internal_u) + length (__mtk_internal_o))] = _functor
126
+ for (i, x) in enumerate (collect (__mtk_internal_o))
127
+ push! (initialization_eqs,
128
+ x ~ functor (
129
+ wrapper, __mtk_internal_u, __mtk_internal_x, __mtk_internal_p, t)[i])
130
+ end
93
131
94
- finalize_affect = MTK. FunctionalAffect (fmi2MEFinalize!, [], [wrapper], [])
95
- step_affect = MTK. FunctionalAffect (fmi2MEStep!, [], [wrapper], [])
96
- instance_management_callback = MTK. SymbolicDiscreteCallback (
97
- (t != t - 1 ), step_affect; finalize = finalize_affect)
132
+ diffeqs = Equation[]
133
+
134
+ cb_observed = (; inputs = __mtk_internal_x, params = copy (params),
135
+ t, wrapper, dt = communication_step_size)
136
+ cb_modified = (;)
137
+ if symbolic_type (__mtk_internal_o) != NotSymbolic ()
138
+ cb_modified = (cb_modified... , outputs = __mtk_internal_o)
139
+ end
140
+ if symbolic_type (__mtk_internal_u) != NotSymbolic ()
141
+ cb_modified = (cb_modified... , states = __mtk_internal_u)
142
+ end
143
+ initialize_affect = MTK. ImperativeAffect (fmi2CSInitialize!; observed = cb_observed,
144
+ modified = cb_modified, ctx = _functor)
145
+ finalize_affect = MTK. FunctionalAffect (fmi2Finalize!, [], [wrapper], [])
146
+ step_affect = MTK. ImperativeAffect (
147
+ fmi2CSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor)
148
+ instance_management_callback = MTK. SymbolicDiscreteCallback (
149
+ communication_step_size, step_affect; initialize = initialize_affect, finalize = finalize_affect
150
+ )
151
+
152
+ symbolic_type (__mtk_internal_o) == NotSymbolic () || push! (params, __mtk_internal_o)
153
+ symbolic_type (__mtk_internal_u) == NotSymbolic () || push! (params, __mtk_internal_u)
154
+
155
+ push! (params, wrapper, functor)
156
+ end
98
157
99
- push! (params, wrapper, functor)
100
158
eqs = [observed; diffeqs]
101
159
return ODESystem (eqs, t, states, params; parameter_dependencies, defaults = defs,
102
- discrete_events = [instance_management_callback], name)
160
+ discrete_events = [instance_management_callback], name, initialization_eqs )
103
161
end
104
162
105
163
function fmi_variables_to_mtk_variables! (fmu, varmap, value_references, truevars, allvars,
@@ -142,35 +200,42 @@ function FMI2InstanceWrapper(fmu, params, inputs, tolerance)
142
200
FMI2InstanceWrapper (fmu, params, inputs, tolerance, nothing )
143
201
end
144
202
145
- function get_instance! (wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
203
+ function get_instance_common! (wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
204
+ wrapper. instance = FMI. fmi2Instantiate! (wrapper. fmu):: FMI.FMU2Component
205
+ if ! isempty (params)
206
+ @statuscheck FMI. fmi2SetReal (wrapper. instance, wrapper. param_value_references,
207
+ Csize_t (length (wrapper. param_value_references)), params)
208
+ end
209
+ @statuscheck FMI. fmi2SetupExperiment (
210
+ wrapper. instance, FMI. fmi2True, wrapper. tolerance, t, FMI. fmi2False, t)
211
+ @statuscheck FMI. fmi2EnterInitializationMode (wrapper. instance)
212
+ if ! isempty (inputs)
213
+ @statuscheck FMI. fmi2SetReal (wrapper. instance, wrapper. input_value_references,
214
+ Csize_t (length (wrapper. param_value_references)), inputs)
215
+ end
216
+
217
+ return wrapper. instance
218
+ end
219
+
220
+ function get_instance_ME! (wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
146
221
if wrapper. instance === nothing
147
- wrapper. instance = FMI. fmi2Instantiate! (wrapper. fmu):: FMI.FMU2Component
148
- if ! isempty (params)
149
- @statuscheck FMI. fmi2SetReal (wrapper. instance, wrapper. param_value_references,
150
- Csize_t (length (wrapper. param_value_references)), params)
151
- end
152
- @statuscheck FMI. fmi2SetupExperiment (
153
- wrapper. instance, FMI. fmi2True, wrapper. tolerance, t, FMI. fmi2False, t)
154
- @statuscheck FMI. fmi2EnterInitializationMode (wrapper. instance)
155
- if ! isempty (inputs)
156
- @statuscheck FMI. fmi2SetReal (wrapper. instance, wrapper. input_value_references,
157
- Csize_t (length (wrapper. param_value_references)), inputs)
158
- end
222
+ get_instance_common! (wrapper, states, inputs, params, t)
159
223
@statuscheck FMI. fmi2ExitInitializationMode (wrapper. instance)
160
224
eventInfo = FMI. fmi2NewDiscreteStates (wrapper. instance)
161
225
@assert eventInfo. newDiscreteStatesNeeded == FMI. fmi2False
162
226
# TODO : Support FMU events
163
227
@statuscheck FMI. fmi2EnterContinuousTimeMode (wrapper. instance)
164
228
end
165
- instance = wrapper. instance
166
- @statuscheck FMI. fmi2SetTime (instance, t)
167
- @statuscheck FMI. fmi2SetContinuousStates (instance, states)
168
- if ! isempty (inputs)
169
- @statuscheck FMI. fmi2SetReal (instance, wrapper. input_value_references,
170
- Csize_t (length (wrapper. param_value_references)), inputs)
171
- end
172
229
173
- return instance
230
+ return wrapper. instance
231
+ end
232
+
233
+ function get_instance_CS! (wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
234
+ if wrapper. instance === nothing
235
+ get_instance_common! (wrapper, states, inputs, params, t)
236
+ @statuscheck FMI. fmi2ExitInitializationMode (wrapper. instance)
237
+ end
238
+ return wrapper. instance
174
239
end
175
240
176
241
function complete_step! (wrapper:: FMI2InstanceWrapper )
198
263
ndims = 1
199
264
end
200
265
266
+ function update_instance_ME! (wrapper:: FMI2InstanceWrapper , states, inputs, t)
267
+ instance = wrapper. instance
268
+ @statuscheck FMI. fmi2SetTime (instance, t)
269
+ @statuscheck FMI. fmi2SetContinuousStates (instance, states)
270
+ if ! isempty (inputs)
271
+ @statuscheck FMI. fmi2SetReal (instance, wrapper. input_value_references,
272
+ Csize_t (length (wrapper. param_value_references)), inputs)
273
+ end
274
+ end
275
+
201
276
function (fn:: FMI2MEFunctor )(wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
202
- instance = get_instance! (wrapper, states, inputs, params, t)
277
+ instance = get_instance_ME! (wrapper, states, inputs, params, t)
278
+ update_instance_ME! (wrapper, states, inputs, t)
203
279
204
280
states_buffer = zeros (length (states))
205
281
@statuscheck FMI. fmi2GetDerivatives! (instance, states_buffer)
@@ -214,10 +290,78 @@ function fmi2MEStep!(integrator, u, p, ctx)
214
290
complete_step! (wrapper)
215
291
end
216
292
217
- function fmi2MEFinalize ! (integrator, u, p, ctx)
293
+ function fmi2Finalize ! (integrator, u, p, ctx)
218
294
wrapper_idx = p[1 ]
219
295
wrapper = integrator. ps[wrapper_idx]
220
296
reset_instance! (wrapper)
221
297
end
222
298
299
+ struct FMI2CSFunctor
300
+ state_and_output_value_references:: Vector{UInt32}
301
+ state_value_references:: Vector{UInt32}
302
+ output_value_references:: Vector{UInt32}
303
+ end
304
+
305
+ function (fn:: FMI2CSFunctor )(wrapper:: FMI2InstanceWrapper , states, inputs, params, t)
306
+ states = states isa SubArray ? copy (states) : states
307
+ inputs = inputs isa SubArray ? copy (inputs) : inputs
308
+ params = params isa SubArray ? copy (params) : params
309
+ instance = get_instance_CS! (wrapper, states, inputs, params, t)
310
+ if isempty (fn. output_value_references)
311
+ return eltype (states)[]
312
+ else
313
+ return FMI. fmi2GetReal (instance, fn. output_value_references)
314
+ end
315
+ end
316
+
317
+ @register_array_symbolic (fn:: FMI2CSFunctor )(
318
+ wrapper:: FMI2InstanceWrapper , states:: Vector{<:Real} ,
319
+ inputs:: Vector{<:Real} , params:: Vector{<:Real} , t:: Real ) begin
320
+ size = (length (states) + length (fn. output_value_references),)
321
+ eltype = eltype (states)
322
+ ndims = 1
323
+ end
324
+
325
+ function fmi2CSInitialize! (m, o, ctx:: FMI2CSFunctor , integrator)
326
+ states = isdefined (m, :states ) ? m. states : ()
327
+ inputs = o. inputs
328
+ params = o. params
329
+ t = o. t
330
+ wrapper = o. wrapper
331
+ if wrapper. instance != = nothing
332
+ reset_instance! (wrapper)
333
+ end
334
+ instance = get_instance_common! (wrapper, states, inputs, params, t)
335
+ @statuscheck FMI. fmi2ExitInitializationMode (instance)
336
+ if isdefined (m, :states )
337
+ @statuscheck FMI. fmi2GetReal! (instance, ctx. state_value_references, m. states)
338
+ end
339
+ if isdefined (m, :outputs )
340
+ @statuscheck FMI. fmi2GetReal! (instance, ctx. output_value_references, m. outputs)
341
+ end
342
+
343
+ return m
344
+ end
345
+
346
+ function fmi2CSStep! (m, o, ctx:: FMI2CSFunctor , integrator)
347
+ wrapper = o. wrapper
348
+ states = isdefined (m, :states ) ? m. states : ()
349
+ inputs = o. inputs
350
+ params = o. params
351
+ t = o. t
352
+ dt = o. dt
353
+
354
+ instance = get_instance_CS! (wrapper, states, inputs, params, integrator. t)
355
+ @statuscheck FMI. fmi2DoStep (instance, integrator. t - dt, dt, FMI. fmi2True)
356
+
357
+ if isdefined (m, :states )
358
+ @statuscheck FMI. fmi2GetReal! (instance, ctx. state_value_references, m. states)
359
+ end
360
+ if isdefined (m, :outputs )
361
+ @statuscheck FMI. fmi2GetReal! (instance, ctx. output_value_references, m. outputs)
362
+ end
363
+
364
+ return m
365
+ end
366
+
223
367
end # module
0 commit comments