Skip to content

Commit 5e3e75d

Browse files
feat: support v3 CS FMUs
1 parent 88e99b1 commit 5e3e75d

File tree

2 files changed

+112
-7
lines changed

2 files changed

+112
-7
lines changed

ext/MTKFMIExt.jl

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,16 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6,
144144

145145
push!(params, wrapper, functor)
146146
push!(states, __mtk_internal_u)
147-
elseif type == :CS && Ver == 2
147+
elseif type == :CS
148148
state_value_references = UInt32[value_references[var] for var in diffvars]
149149
state_and_output_value_references = vcat(
150150
state_value_references, output_value_references)
151-
_functor = FMI2CSFunctor(state_and_output_value_references,
152-
state_value_references, output_value_references)
151+
_functor = if Ver == 2
152+
FMI2CSFunctor(state_and_output_value_references,
153+
state_value_references, output_value_references)
154+
else
155+
FMI3CSFunctor(state_value_references, output_value_references)
156+
end
153157
@parameters (functor::(typeof(_functor)))(..)[1:(length(__mtk_internal_u) + length(__mtk_internal_o))] = _functor
154158
for (i, x) in enumerate(collect(__mtk_internal_o))
155159
push!(initialization_eqs,
@@ -168,11 +172,11 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6,
168172
if symbolic_type(__mtk_internal_u) != NotSymbolic()
169173
cb_modified = (cb_modified..., states = __mtk_internal_u)
170174
end
171-
initialize_affect = MTK.ImperativeAffect(fmi2CSInitialize!; observed = cb_observed,
175+
initialize_affect = MTK.ImperativeAffect(fmiCSInitialize!; observed = cb_observed,
172176
modified = cb_modified, ctx = _functor)
173177
finalize_affect = MTK.FunctionalAffect(fmiFinalize!, [], [wrapper], [])
174178
step_affect = MTK.ImperativeAffect(
175-
fmi2CSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor)
179+
fmiCSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor)
176180
instance_management_callback = MTK.SymbolicDiscreteCallback(
177181
communication_step_size, step_affect; initialize = initialize_affect, finalize = finalize_affect
178182
)
@@ -318,6 +322,16 @@ function get_instance_ME!(wrapper::FMI3InstanceWrapper, states, inputs, params,
318322
return wrapper.instance
319323
end
320324

325+
function get_instance_CS!(wrapper::FMI3InstanceWrapper, states, inputs, params, t)
326+
if wrapper.instance === nothing
327+
wrapper.instance = FMI.fmi3InstantiateCoSimulation!(
328+
wrapper.fmu; eventModeUsed = false)::FMI.FMU3Instance
329+
get_instance_common!(wrapper, states, inputs, params, t)
330+
@statuscheck FMI.fmi3ExitInitializationMode(wrapper.instance)
331+
end
332+
return wrapper.instance
333+
end
334+
321335
function complete_step!(wrapper::FMI3InstanceWrapper)
322336
wrapper.instance === nothing && return
323337
enterEventMode = Ref(FMI.fmi3False)
@@ -440,7 +454,7 @@ end
440454
ndims = 1
441455
end
442456

443-
function fmi2CSInitialize!(m, o, ctx::FMI2CSFunctor, integrator)
457+
function fmiCSInitialize!(m, o, ctx::FMI2CSFunctor, integrator)
444458
states = isdefined(m, :states) ? m.states : ()
445459
inputs = o.inputs
446460
params = o.params
@@ -449,6 +463,7 @@ function fmi2CSInitialize!(m, o, ctx::FMI2CSFunctor, integrator)
449463
if wrapper.instance !== nothing
450464
reset_instance!(wrapper)
451465
end
466+
452467
instance = get_instance_common!(wrapper, states, inputs, params, t)
453468
@statuscheck FMI.fmi2ExitInitializationMode(instance)
454469
if isdefined(m, :states)
@@ -461,7 +476,7 @@ function fmi2CSInitialize!(m, o, ctx::FMI2CSFunctor, integrator)
461476
return m
462477
end
463478

464-
function fmi2CSStep!(m, o, ctx::FMI2CSFunctor, integrator)
479+
function fmiCSStep!(m, o, ctx::FMI2CSFunctor, integrator)
465480
wrapper = o.wrapper
466481
states = isdefined(m, :states) ? m.states : ()
467482
inputs = o.inputs
@@ -482,4 +497,79 @@ function fmi2CSStep!(m, o, ctx::FMI2CSFunctor, integrator)
482497
return m
483498
end
484499

500+
struct FMI3CSFunctor
501+
state_value_references::Vector{UInt32}
502+
output_value_references::Vector{UInt32}
503+
end
504+
505+
function (fn::FMI3CSFunctor)(wrapper::FMI3InstanceWrapper, states, inputs, params, t)
506+
states = states isa SubArray ? copy(states) : states
507+
inputs = inputs isa SubArray ? copy(inputs) : inputs
508+
params = params isa SubArray ? copy(params) : params
509+
instance = get_instance_CS!(wrapper, states, inputs, params, t)
510+
if isempty(fn.output_value_references)
511+
return eltype(states)[]
512+
else
513+
return FMI.fmi3GetFloat64(instance, fn.output_value_references)
514+
end
515+
end
516+
517+
@register_array_symbolic (fn::FMI3CSFunctor)(
518+
wrapper::FMI3InstanceWrapper, states::Vector{<:Real},
519+
inputs::Vector{<:Real}, params::Vector{<:Real}, t::Real) begin
520+
size = (length(states) + length(fn.output_value_references),)
521+
eltype = eltype(states)
522+
ndims = 1
523+
end
524+
525+
function fmiCSInitialize!(m, o, ctx::FMI3CSFunctor, integrator)
526+
states = isdefined(m, :states) ? m.states : ()
527+
inputs = o.inputs
528+
params = o.params
529+
t = o.t
530+
wrapper = o.wrapper
531+
if wrapper.instance !== nothing
532+
reset_instance!(wrapper)
533+
end
534+
instance = get_instance_CS!(wrapper, states, inputs, params, t)
535+
if isdefined(m, :states)
536+
@statuscheck FMI.fmi3GetFloat64!(instance, ctx.state_value_references, m.states)
537+
end
538+
if isdefined(m, :outputs)
539+
@statuscheck FMI.fmi3GetFloat64!(instance, ctx.output_value_references, m.outputs)
540+
end
541+
542+
return m
543+
end
544+
545+
function fmiCSStep!(m, o, ctx::FMI3CSFunctor, integrator)
546+
wrapper = o.wrapper
547+
states = isdefined(m, :states) ? m.states : ()
548+
inputs = o.inputs
549+
params = o.params
550+
t = o.t
551+
dt = o.dt
552+
553+
instance = get_instance_CS!(wrapper, states, inputs, params, integrator.t)
554+
eventEncountered = Ref(FMI.fmi3False)
555+
terminateSimulation = Ref(FMI.fmi3False)
556+
earlyReturn = Ref(FMI.fmi3False)
557+
lastSuccessfulTime = Ref(zero(FMI.fmi3Float64))
558+
@statuscheck FMI.fmi3DoStep!(
559+
instance, integrator.t - dt, dt, FMI.fmi3True, eventEncountered,
560+
terminateSimulation, earlyReturn, lastSuccessfulTime)
561+
@assert eventEncountered[] == FMI.fmi3False
562+
@assert terminateSimulation[] == FMI.fmi3False
563+
@assert earlyReturn[] == FMI.fmi3False
564+
565+
if isdefined(m, :states)
566+
@statuscheck FMI.fmi3GetFloat64!(instance, ctx.state_value_references, m.states)
567+
end
568+
if isdefined(m, :outputs)
569+
@statuscheck FMI.fmi3GetFloat64!(instance, ctx.output_value_references, m.outputs)
570+
end
571+
572+
return m
573+
end
574+
485575
end # module

test/extensions/fmi.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ import ModelingToolkit as MTK
5252
# repeated solve works
5353
@test_nowarn solve(prob, Tsit5())
5454
end
55+
@testset "v3, CS" begin
56+
fmu = loadFMU("SpringPendulum1D", "Dymola", "2023x", "3.0"; type = :CS)
57+
@named inner = MTK.FMIComponent(
58+
Val(3); fmu, communication_step_size = 0.001, type = :CS)
59+
@variables x(t) = 1.0
60+
@mtkbuild sys = ODESystem([D(x) ~ x], t; systems = [inner])
61+
62+
prob = ODEProblem{true, SciMLBase.FullSpecialize}(
63+
sys, [sys.inner.mass__s => 0.5, sys.inner.mass__v => 0.0], (0.0, 8.0))
64+
sol = solve(prob, Tsit5(); reltol = 1e-8, abstol = 1e-8)
65+
@test SciMLBase.successful_retcode(sol)
66+
67+
@test sol(0.0:0.1:8.0;
68+
idxs = [sys.inner.mass__s, sys.inner.mass__v]).ucollect.(truesol.values.saveval) rtol=1e-2
69+
end
5570
end
5671

5772
@testset "IO Model" begin

0 commit comments

Comments
 (0)