| 
 | 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  | 
0 commit comments