diff --git a/Project.toml b/Project.toml index 061405f..f16535f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,23 +1,17 @@ name = "FMIImport" uuid = "9fcbc62e-52a0-44e9-a616-1359a0008194" authors = ["TT ", "LM ", "JK "] -version = "0.15.8" +version = "0.16.0" [deps] -ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" FMICore = "8af89139-c281-408e-bce2-3005eb87462f" -ForwardDiffChainRules = "c9556dd2-1aed-4cfe-8560-1557cf593001" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" [compat] -ChainRulesCore = "1.16" EzXML = "1.1.0" -FMICore = "0.17.3" -ForwardDiffChainRules = "0.1.1" -SciMLSensitivity = "7.35, 7.36" +FMICore = "0.18.0" ZipFile = "0.10.0" julia = "1.6" diff --git a/src/FMI2/c.jl b/src/FMI2/c.jl index 5215672..e20625c 100644 --- a/src/FMI2/c.jl +++ b/src/FMI2/c.jl @@ -21,6 +21,8 @@ import FMICore: fmi2DoStep, fmi2CancelStep, fmi2GetStatus!, fmi2GetRealStatus!, import FMICore: fmi2SetTime, fmi2SetContinuousStates, fmi2EnterEventMode, fmi2NewDiscreteStates!, fmi2EnterContinuousTimeMode, fmi2CompletedIntegratorStep! import FMICore: fmi2GetDerivatives!, fmi2GetEventIndicators!, fmi2GetContinuousStates!, fmi2GetNominalsOfContinuousStates! +using FMICore: invalidate!, check_invalidate! + """ Source: FMISpec2.0.2[p.21]: 2.1.5 Creation, Destruction and Logging of FMU Instances @@ -234,9 +236,10 @@ end # helper function checkStatus(c::FMU2Component, status::fmi2Status) - @assert (status != fmi2StatusWarning) || !c.fmu.executionConfig.assertOnWarning "Assert on `fmi2StatusWarning`. See stack for errors." + if status == fmi2StatusWarning + @assert !c.fmu.executionConfig.assertOnWarning "Assert on `fmi2StatusWarning`. See stack for errors." - if status == fmi2StatusError + elseif status == fmi2StatusError c.state = fmi2ComponentStateError @assert !c.fmu.executionConfig.assertOnError "Assert on `fmi2StatusError`. See stack for errors." @@ -561,6 +564,7 @@ function fmi2GetReal!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, n return status end + """ fmi2SetReal(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Real}) @@ -597,14 +601,23 @@ function fmi2SetReal(c::FMU2Component, c.compAddr, vr, nvr, value) checkStatus(c, status) - if track - if status == fmi2StatusOK - for j in (c.A, c.B, c.C, c.D, c.E, c.F) - if any(collect(v in j.∂f_refs for v in vr)) - FMICore.invalidate!(j) - end - end - end + if track && status == fmi2StatusOK + check_invalidate!(vr, c.∂ẋ_∂x) + check_invalidate!(vr, c.∂ẋ_∂u) + check_invalidate!(vr, c.∂ẋ_∂p) + + check_invalidate!(vr, c.∂y_∂x) + check_invalidate!(vr, c.∂y_∂u) + check_invalidate!(vr, c.∂y_∂p) + + check_invalidate!(vr, c.∂e_∂x) + check_invalidate!(vr, c.∂e_∂u) + check_invalidate!(vr, c.∂e_∂p) + + # [NOTE] No need to check for: + # check_invalidate!(vr, c.∂ẋ_∂t) + # check_invalidate!(vr, c.∂y_∂t) + # check_invalidate!(vr, c.∂e_∂t) end return status @@ -650,6 +663,7 @@ function fmi2GetInteger!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference} return status end + """ fmi2SetInteger(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Integer}) @@ -687,6 +701,7 @@ function fmi2SetInteger(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, return status end + """ fmi2GetBoolean!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) @@ -726,6 +741,7 @@ function fmi2GetBoolean!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference} return status end + """ fmi2SetBoolean(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::AbstractArray{fmi2Boolean}) @@ -761,6 +777,7 @@ function fmi2SetBoolean(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, return status end + """ fmi2GetString!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) @@ -800,6 +817,7 @@ function fmi2GetString!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, return status end + """ fmi2SetString(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, value::Union{AbstractArray{Ptr{Cchar}}, AbstractArray{Ptr{UInt8}}}) @@ -837,6 +855,7 @@ function fmi2SetString(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, return status end + """ fmi2GetFMUstate!(c::FMU2Component, FMUstate::Ref{fmi2FMUstate}) @@ -1015,6 +1034,7 @@ function fmi2SerializeFMUstate!(c::FMU2Component, FMUstate::fmi2FMUstate, serial return status end + """ fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArray{fmi2Byte}, size::Csize_t, FMUstate::Ref{fmi2FMUstate}) @@ -1054,6 +1074,7 @@ function fmi2DeSerializeFMUstate!(c::FMU2Component, serializedState::AbstractArr return status end + """ fmi2GetDirectionalDerivative!(c::FMU2Component, vUnknown_ref::AbstractArray{fmi2ValueReference}, @@ -1156,7 +1177,7 @@ More detailed: See also [`fmi2SetRealInputDerivatives`](@ref). """ -function fmi2SetRealInputDerivatives(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, order::AbstractArray{fmi2Integer}, value::AbstractArray{fmi2Real}) +function fmi2SetRealInputDerivatives(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) status = fmi2SetRealInputDerivatives(c.fmu.cSetRealInputDerivatives, c.compAddr, vr, nvr, order, value) @@ -1164,6 +1185,7 @@ function fmi2SetRealInputDerivatives(c::FMU2Component, vr::AbstractArray{fmi2Val return status end + """ fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, @@ -1195,7 +1217,7 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions - FMISpec2.0.2[p.104]: 4.2.1 Transfer of Input / Output Values and Parameters """ -function fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::AbstractArray{fmi2ValueReference}, nvr::Csize_t, order::AbstractArray{fmi2Integer}, value::AbstractArray{fmi2Real}) +function fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::Array{fmi2ValueReference}, nvr::Csize_t, order::Array{fmi2Integer}, value::Array{fmi2Real}) status = fmi2GetRealOutputDerivatives!(c.fmu.cGetRealOutputDerivatives, c.compAddr, vr, nvr, order, value) @@ -1203,6 +1225,7 @@ function fmi2GetRealOutputDerivatives!(c::FMU2Component, vr::AbstractArray{fmi2 return status end + """ fmi2DoStep(c::FMU2Component, currentCommunicationPoint::fmi2Real, @@ -1491,7 +1514,6 @@ function fmi2GetStringStatus!(c::FMU2Component, s::fmi2StatusKind, value::fmi2St end # Model Exchange specific Functions -#TOD0 """ fmi2SetTime(c::FMU2Component, time::fmi2Real; @@ -1558,19 +1580,15 @@ function fmi2SetTime(c::FMU2Component, time::fmi2Real; soft::Bool=false, track:: if status == fmi2StatusOK c.t = time - FMICore.invalidate!(c.A) - FMICore.invalidate!(c.B) - FMICore.invalidate!(c.C) - FMICore.invalidate!(c.D) - FMICore.invalidate!(c.E) - FMICore.invalidate!(c.F) + invalidate!(c.∂ẋ_∂t) + invalidate!(c.∂y_∂t) + invalidate!(c.∂e_∂t) end end return status end -#TODO """ fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}, @@ -1618,13 +1636,11 @@ function fmi2SetContinuousStates(c::FMU2Component, if track if status == fmi2StatusOK - c.x = copy(x) + isnothing(c.x) ? (c.x = copy(x);) : copyto!(c.x, x) - FMICore.invalidate!(c.A) - FMICore.invalidate!(c.C) - - FMICore.invalidate!(c.E) - FMICore.invalidate!(c.F) + invalidate!(c.∂ẋ_∂x) + invalidate!(c.∂y_∂x) + invalidate!(c.∂e_∂x) end end @@ -1857,6 +1873,7 @@ function fmi2GetDerivatives!(c::FMU2Component, status = fmi2GetDerivatives!(c.fmu.cGetDerivatives, c.compAddr, derivatives, nx) checkStatus(c, status) + return status end diff --git a/src/FMI2/convert.jl b/src/FMI2/convert.jl index 87db3a8..6abdce4 100644 --- a/src/FMI2/convert.jl +++ b/src/FMI2/convert.jl @@ -3,46 +3,32 @@ # Licensed under the MIT license. See LICENSE file in the project root for details. # -using ChainRulesCore: ignore_derivatives -import SciMLSensitivity.ForwardDiff +# ToDo: Fix this: import SciMLSensitivity.ForwardDiff -# ToDo: Replace by multiple dispatch version ... # Receives one or an array of value references in an arbitrary format (see fmi2ValueReferenceFormat) and converts it into an Array{fmi2ValueReference} (if not already). -function prepareValueReference(md::fmi2ModelDescription, vr::fmi2ValueReferenceFormat) - tvr = typeof(vr) - if isa(vr, AbstractArray{fmi2ValueReference,1}) - return vr - elseif tvr == fmi2ValueReference - return [vr] - elseif tvr == String - return [fmi2StringToValueReference(md, vr)] - elseif isa(vr, AbstractArray{String,1}) - return fmi2StringToValueReference(md, vr) - elseif tvr == Int64 - return [fmi2ValueReference(vr)] - elseif isa(vr, AbstractArray{Int64,1}) - return fmi2ValueReference.(vr) - elseif tvr == Nothing +prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{fmi2ValueReference}) = vr +prepareValueReference(md::fmi2ModelDescription, vr::fmi2ValueReference) = [vr] +prepareValueReference(md::fmi2ModelDescription, vr::String) = [fmi2StringToValueReference(md, vr)] +prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{String}) = fmi2StringToValueReference(md, vr) +prepareValueReference(md::fmi2ModelDescription, vr::AbstractVector{<:Integer}) = fmi2ValueReference.(vr) +prepareValueReference(md::fmi2ModelDescription, vr::Integer) = [fmi2ValueReference(vr)] +prepareValueReference(md::fmi2ModelDescription, vr::Nothing) = fmi2ValueReference[] +function prepareValueReference(md::fmi2ModelDescription, vr::Symbol) + if vr == :states + return md.stateValueReferences + elseif vr == :derivatives + return md.derivativeValueReferences + elseif vr == :inputs + return md.inputValueReferences + elseif vr == :outputs + return md.outputValueReferences + elseif vr == :all + return md.valueReferences + elseif vr == :none return Array{fmi2ValueReference,1}() - elseif tvr == Symbol - if vr == :states - return md.stateValueReferences - elseif vr == :derivatives - return md.derivativeValueReferences - elseif vr == :inputs - return md.inputValueReferences - elseif vr == :outputs - return md.outputValueReferences - elseif vr == :all - return md.valueReferences - elseif vr == :none - return Array{fmi2ValueReference,1}() - else - @assert false "Unknwon symbol `$vr`, can't convert to value reference." - end + else + @assert false "Unknwon symbol `$vr`, can't convert to value reference." end - - @assert false "prepareValueReference(...): Unknown value reference structure `$tvr`." end function prepareValueReference(fmu::FMU2, vr::fmi2ValueReferenceFormat) prepareValueReference(fmu.modelDescription, vr) @@ -69,16 +55,17 @@ Returns an array of ValueReferences coresponding to the variable names. See also [`fmi2StringToValueReference`](@ref). """ function fmi2StringToValueReference(md::fmi2ModelDescription, names::AbstractArray{String}) - vr = Array{fmi2ValueReference}(undef,0) - for name in names - reference = fmi2StringToValueReference(md, name) - if reference == nothing - @warn "Value reference for variable '$name' not found, skipping." - else - push!(vr, reference) - end - end - vr + # vr = Array{fmi2ValueReference}(undef,0) + # for name in names + # reference = fmi2StringToValueReference(md, name) + # if reference == nothing + # @warn "Value reference for variable '$name' not found, skipping." + # else + # push!(vr, reference) + # end + # end + # vr + return broadcast(fmi2StringToValueReference, (md,), names) end """ diff --git a/src/FMI2/ext.jl b/src/FMI2/ext.jl index 0d64955..2a9cf79 100644 --- a/src/FMI2/ext.jl +++ b/src/FMI2/ext.jl @@ -281,7 +281,6 @@ function loadBinary(fmu::FMU2) fmu.cSetInteger = dlsym(fmu.libHandle, :fmi2SetInteger) fmu.cGetBoolean = dlsym(fmu.libHandle, :fmi2GetBoolean) fmu.cSetBoolean = dlsym(fmu.libHandle, :fmi2SetBoolean) - fmu.cGetString = dlsym_opt(fmu.libHandle, :fmi2GetString) fmu.cSetString = dlsym_opt(fmu.libHandle, :fmi2SetString) @@ -329,6 +328,73 @@ function loadBinary(fmu::FMU2) end end +function unloadBinary(fmu::FMU2) + + # retrieve functions + fmu.cInstantiate = @cfunction(FMICore.unload_fmi2Instantiate, fmi2Component, (fmi2String, fmi2Type, fmi2String, fmi2String, Ptr{fmi2CallbackFunctions}, fmi2Boolean, fmi2Boolean)) + fmu.cGetTypesPlatform = @cfunction(FMICore.unload_fmi2GetTypesPlatform, fmi2String, ()) + fmu.cGetVersion = @cfunction(FMICore.unload_fmi2GetVersion, fmi2String, ()) + fmu.cFreeInstance = @cfunction(FMICore.unload_fmi2FreeInstance, Cvoid, (fmi2Component,)) + fmu.cSetDebugLogging = @cfunction(FMICore.unload_fmi2SetDebugLogging, fmi2Status, (fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String})) + fmu.cSetupExperiment = @cfunction(FMICore.unload_fmi2SetupExperiment, fmi2Status, (fmi2Component, fmi2Boolean, fmi2Real, fmi2Real, fmi2Boolean, fmi2Real)) + fmu.cEnterInitializationMode = @cfunction(FMICore.unload_fmi2EnterInitializationMode, fmi2Status, (fmi2Component,)) + fmu.cExitInitializationMode = @cfunction(FMICore.unload_fmi2ExitInitializationMode, fmi2Status, (fmi2Component,)) + fmu.cTerminate = @cfunction(FMICore.unload_fmi2Terminate, fmi2Status, (fmi2Component,)) + fmu.cReset = @cfunction(FMICore.unload_fmi2Reset, fmi2Status, (fmi2Component,)) + fmu.cGetReal = @cfunction(FMICore.unload_fmi2GetReal, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real})) + fmu.cSetReal = @cfunction(FMICore.unload_fmi2SetReal, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Real})) + fmu.cGetInteger = @cfunction(FMICore.unload_fmi2GetInteger, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer})) + fmu.cSetInteger = @cfunction(FMICore.unload_fmi2SetInteger, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Integer})) + fmu.cGetBoolean = @cfunction(FMICore.unload_fmi2GetBoolean, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean})) + fmu.cSetBoolean = @cfunction(FMICore.unload_fmi2SetBoolean, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2Boolean})) + fmu.cGetString = @cfunction(FMICore.unload_fmi2GetString, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String})) + fmu.cSetString = @cfunction(FMICore.unload_fmi2SetString, fmi2Status, (fmi2Component, Ptr{fmi2ValueReference}, Csize_t, Ptr{fmi2String})) + + # ToDo: Implement for pecial functions! + # if fmi2CanGetSetState(fmu.modelDescription) + # fmu.cGetFMUstate = FMICore.unload_fmi2GetFMUstate + # fmu.cSetFMUstate = FMICore.unload_fmi2SetFMUstate + # fmu.cFreeFMUstate = FMICore.unload_fmi2FreeFMUstate + # end + + # if fmi2CanSerializeFMUstate(fmu.modelDescription) + # fmu.cSerializedFMUstateSize = FMICore.unload_fmi2SerializedFMUstateSize + # fmu.cSerializeFMUstate = FMICore.unload_fmi2SerializeFMUstate + # fmu.cDeSerializeFMUstate = FMICore.unload_fmi2DeSerializeFMUstate + # end + + # if fmi2ProvidesDirectionalDerivative(fmu.modelDescription) + # fmu.cGetDirectionalDerivative = FMICore.unload_fmi2GetDirectionalDerivative + # end + + # ToDo: CS specific function calls + # if fmi2IsCoSimulation(fmu.modelDescription) + # fmu.cSetRealInputDerivatives = FMICore.unload_fmi2SetRealInputDerivatives + # fmu.cGetRealOutputDerivatives = FMICore.unload_fmi2GetRealOutputDerivatives + # fmu.cDoStep = FMICore.unload_fmi2DoStep + # fmu.cCancelStep = FMICore.unload_fmi2CancelStep + # fmu.cGetStatus = FMICore.unload_fmi2GetStatus + # fmu.cGetRealStatus = FMICore.unload_fmi2GetRealStatus + # fmu.cGetIntegerStatus = FMICore.unload_fmi2GetIntegerStatus + # fmu.cGetBooleanStatus = FMICore.unload_fmi2GetBooleanStatus + # fmu.cGetStringStatus = FMICore.unload_fmi2GetStringStatus + # end + + # ME specific function calls + if fmi2IsModelExchange(fmu.modelDescription) + fmu.cEnterContinuousTimeMode = @cfunction(FMICore.unload_fmi2EnterContinuousTimeMode, fmi2Status, (fmi2Component,)) + fmu.cGetContinuousStates = @cfunction(FMICore.unload_fmi2GetContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) + fmu.cGetDerivatives = @cfunction(FMICore.unload_fmi2GetDerivatives, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) + fmu.cSetTime = @cfunction(FMICore.unload_fmi2SetTime, fmi2Status, (fmi2Component, fmi2Real)) + fmu.cSetContinuousStates = @cfunction(FMICore.unload_fmi2SetContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) + fmu.cCompletedIntegratorStep = @cfunction(FMICore.unload_fmi2CompletedIntegratorStep, fmi2Status, (fmi2Component, fmi2Boolean, Ptr{fmi2Boolean}, Ptr{fmi2Boolean})) + fmu.cEnterEventMode = @cfunction(FMICore.unload_fmi2EnterEventMode, fmi2Status, (fmi2Component,)) + fmu.cNewDiscreteStates = @cfunction(FMICore.unload_fmi2NewDiscreteStates, fmi2Status, (fmi2Component, Ptr{fmi2EventInfo})) + fmu.cGetEventIndicators = @cfunction(FMICore.unload_fmi2GetEventIndicators, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) + fmu.cGetNominalsOfContinuousStates= @cfunction(FMICore.unload_fmi2GetNominalsOfContinuousStates, fmi2Status, (fmi2Component, Ptr{fmi2Real}, Csize_t)) + end +end + lk_fmi2Instantiate = ReentrantLock() """ fmi2Instantiate!(fmu::FMU2; @@ -437,7 +503,7 @@ function fmi2Instantiate!(fmu::FMU2; compAddr = fmi2Instantiate(fmu.cInstantiate, pointer(instanceName), type, pointer(guidStr), pointer(fmu.fmuResourceLocation), Ptr{fmi2CallbackFunctions}(pointer_from_objref(callbackFunctions)), fmi2Boolean(visible), fmi2Boolean(loggingOn)) if compAddr == Ptr{Cvoid}(C_NULL) - @error "fmi2Instantiate!(...): Instantiation failed!" + @error "fmi2Instantiate!(...): Instantiation failed, see error messages above.\nIf no error messages, enable FMU debug logging.\nIf logging is on and no messages are printed before this, the FMU might not log errors." return nothing end @@ -457,7 +523,7 @@ function fmi2Instantiate!(fmu::FMU2; component.callbackFunctions = callbackFunctions component.instanceName = instanceName component.type = type - + if pushComponents push!(fmu.components, component) end @@ -466,22 +532,28 @@ function fmi2Instantiate!(fmu::FMU2; component.componentEnvironment = compEnv component.loggingOn = loggingOn component.visible = visible - component.jacobianUpdate! = fmi2SampleJacobian! - # setting a jacobian update function dependent on DIrectionalDerivatives-Functionality is present in the FMU - updFct = nothing - if fmi2ProvidesDirectionalDerivative(fmu) - updFct = (jac, ∂f_refs, ∂x_refs) -> fmi2GetJacobian!(jac.mtx, component, ∂f_refs, ∂x_refs) - else - updFct = (jac, ∂f_refs, ∂x_refs) -> fmi2SampleJacobian!(jac.mtx, component, ∂f_refs, ∂x_refs) - end + # Jacobians - component.A = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences, updFct) - component.B = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.inputValueReferences, updFct) - component.C = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.outputValueReferences, fmu.modelDescription.stateValueReferences, updFct) - component.D = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.outputValueReferences, fmu.modelDescription.inputValueReferences, updFct) - component.E = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.derivativeValueReferences, isnothing(fmu.optim_p_refs) ? Array{fmi2ValueReference,1}() : fmu.optim_p_refs, updFct) - component.F = FMICore.FMUJacobian{fmi2Real, fmi2ValueReference}(fmu.modelDescription.outputValueReferences, isnothing(fmu.optim_p_refs) ? Array{fmi2ValueReference,1}() : fmu.optim_p_refs, updFct) + # smpFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2SampleJacobian!(mtx, component, ∂f_refs, ∂x_refs) + # updFct = nothing + # if fmi2ProvidesDirectionalDerivative(fmu) + # updFct = (mtx, ∂f_refs, ∂x_refs) -> fmi2GetJacobian!(mtx, component, ∂f_refs, ∂x_refs) + # else + # updFct = smpFct + # end + + # component.∂ẋ_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂ẋ_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂ẋ_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + + # component.∂y_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂y_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + # component.∂y_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, updFct) + + # component.∂e_∂x = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) + # component.∂e_∂u = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) + # component.∂e_∂p = FMICore.FMU2Jacobian{fmi2Real, fmi2ValueReference}(component, smpFct) # register component for current thread fmu.threadComponents[Threads.threadid()] = component @@ -490,17 +562,6 @@ function fmi2Instantiate!(fmu::FMU2; return getCurrentComponent(fmu) end -function hasCurrentComponent(fmu::FMU2) - tid = Threads.threadid() - return haskey(fmu.threadComponents, tid) && fmu.threadComponents[tid] != nothing -end - -function getCurrentComponent(fmu::FMU2) - tid = Threads.threadid() - @assert hasCurrentComponent(fmu) ["No FMU instance allocated (in current thread with ID `$(tid)`), have you already called fmiInstantiate?"] - return fmu.threadComponents[tid] -end - """ fmi2Reload(fmu::FMU2) @@ -526,19 +587,26 @@ Free the allocated memory, close the binaries and remove temporary zip and unzip # Arguments - `fmu::FMU2`: Mutable struct representing a FMU and all it instantiated instances in the FMI 2.0.2 Standard. -- `cleanUp::Bool= true`: Defines if the file, link, or empty directory should be deleted. +- `cleanUp::Bool= true`: Defines if the file and directory should be deleted. + +# Keywords +- `secure_pointers=true` whether pointers to C-functions should be overwritten with dummies with Julia assertions, instead of pointing to dead memory (slower, but more user safe) """ -function fmi2Unload(fmu::FMU2, cleanUp::Bool = true) +function fmi2Unload(fmu::FMU2, cleanUp::Bool = true; secure_pointers::Bool=true) while length(fmu.components) > 0 fmi2FreeInstance!(fmu.components[end]) end - dlclose(fmu.libHandle) - # the components are removed from the component list via call to fmi2FreeInstance! @assert length(fmu.components) == 0 "fmi2Unload(...): Failure during deleting components, $(length(fmu.components)) remaining in stack." + if secure_pointers + unloadBinary(fmu) + end + + dlclose(fmu.libHandle) + if cleanUp try rm(fmu.path; recursive = true, force = true) @@ -551,13 +619,13 @@ end """ fmi2SampleJacobian(c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, + vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, vKnown_ref::AbstractArray{fmi2ValueReference}, steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) This function samples the directional derivative by manipulating corresponding values (central differences). -Computes the directional derivatives of an FMU. An FMU has different Modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: +Computes the directional derivatives of an FMU. An FMU has different modes and in every Mode an FMU might be described by different equations and different unknowns. The precise definitions are given in the mathematical descriptions of Model Exchange (section 3.1) and Co-Simulation (section 4.1). In every Mode, the general form of the FMU equations are: 𝐯_unknown = 𝐡(𝐯_known, 𝐯_rest) - `v_unknown`: vector of unknown Real variables computed in the actual Mode: @@ -602,8 +670,8 @@ end """ function fmi2SampleJacobian!(mtx::Matrix{<:Real}, c::FMU2Component, - vUnknown_ref::AbstractArray{fmi2ValueReference}, - vKnown_ref::AbstractArray{fmi2ValueReference}, + vUnknown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, + vKnown_ref::Union{AbstractArray{fmi2ValueReference}, Symbol}, steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) This function samples the directional derivative by manipulating corresponding values (central differences) and saves in-place. @@ -681,6 +749,90 @@ function fmi2SampleJacobian!(mtx::Matrix{<:Real}, nothing end +function fmi2SampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::Symbol, + vKnown_ref::AbstractArray{fmi2ValueReference}, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + @assert vUnknown_ref == :indicators "vUnknown_ref::Symbol must be `:indicators`!" + + step = 0.0 + + len_vUnknown_ref = c.fmu.modelDescription.numberOfEventIndicators + + negValues = zeros(len_vUnknown_ref) + posValues = zeros(len_vUnknown_ref) + + for i in 1:length(vKnown_ref) + vKnown = vKnown_ref[i] + origValue = fmi2GetReal(c, vKnown) + + if steps === nothing + step = max(2.0 * eps(Float32(origValue)), 1e-12) + else + step = steps[i] + end + + fmi2SetReal(c, vKnown, origValue - step; track=false) + fmi2GetEventIndicators!(c, negValues) + + fmi2SetReal(c, vKnown, origValue + step; track=false) + fmi2GetEventIndicators!(c, posValues) + + fmi2SetReal(c, vKnown, origValue; track=false) + + if len_vUnknown_ref == 1 + mtx[1,i] = (posValues-negValues) ./ (step * 2.0) + else + mtx[:,i] = (posValues-negValues) ./ (step * 2.0) + end + end + + nothing +end + +function fmi2SampleJacobian!(mtx::Matrix{<:Real}, + c::FMU2Component, + vUnknown_ref::AbstractArray{fmi2ValueReference}, + vKnown_ref::Symbol, + steps::Union{AbstractArray{fmi2Real}, Nothing} = nothing) + + @assert vKnown_ref == :time "vKnown_ref::Symbol must be `:time`!" + + step = 0.0 + + negValues = zeros(length(vUnknown_ref)) + posValues = zeros(length(vUnknown_ref)) + + for i in 1:length(vKnown_ref) + vKnown = vKnown_ref[i] + origValue = fmi2GetReal(c, vKnown) + + if steps === nothing + step = max(2.0 * eps(Float32(origValue)), 1e-12) + else + step = steps[i] + end + + fmi2SetReal(c, vKnown, origValue - step; track=false) + fmi2GetEventIndicators!(c, negValues) + + fmi2SetReal(c, vKnown, origValue + step; track=false) + fmi2GetEventIndicators!(c, posValues) + + fmi2SetReal(c, vKnown, origValue; track=false) + + if length(vUnknown_ref) == 1 + mtx[1,i] = (posValues-negValues) ./ (step * 2.0) + else + mtx[:,i] = (posValues-negValues) ./ (step * 2.0) + end + end + + nothing +end + """ fmi2GetJacobian(comp::FMU2Component, rdx::AbstractArray{fmi2ValueReference}, @@ -782,12 +934,11 @@ function fmi2GetJacobian!(jac::AbstractMatrix{fmi2Real}, # end if length(sensitive_rdx) > 0 - # doesn't work because indexed-views can`t be passed by reference (to ccalls) - #try + fmi2GetDirectionalDerivative!(comp, sensitive_rdx, [rx[i]], view(jac, sensitive_rdx_inds, i)) - #catch e + # jac[sensitive_rdx_inds, i] = fmi2GetDirectionalDerivative(comp, sensitive_rdx, [rx[i]]) - #end + end end diff --git a/src/FMI2/int.jl b/src/FMI2/int.jl index a250fa0..0549383 100644 --- a/src/FMI2/int.jl +++ b/src/FMI2/int.jl @@ -227,15 +227,12 @@ More detailed: - FMISpec2.0.2[p.18]: 2.1.3 Status Returned by Functions See also [`fmi2SetReal`](@ref). """ -function fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Union{AbstractArray{<:Real}, <:Real}; kwargs...) - - vr = prepareValueReference(c, vr) - values = prepareValue(values) +function fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::AbstractVector{fmi2Real}; kwargs...) @assert length(vr) == length(values) "fmi2SetReal(...): `vr` ($(length(vr))) and `values` ($(length(values))) need to be the same length." - nvr = Csize_t(length(vr)) - fmi2SetReal(c, vr, nvr, Array{fmi2Real}(values); kwargs...) + fmi2SetReal(c, prepareValueReference(c, vr), nvr, prepareValue(values); kwargs...) end +fmi2SetReal(c::FMU2Component, vr::fmi2ValueReferenceFormat, values::Real; kwargs...) = fmi2SetReal(c, prepareValueReference(c, vr), prepareValue(values); kwargs...) """ fmi2GetInteger(c::FMU2Component, vr::fmi2ValueReferenceFormat) @@ -1118,14 +1115,15 @@ More detailed: - FMISpec2.0.2[p.83]: 3.2.1 Providing Independent Variables and Re-initialization of Caching See also [`fmi2SetContinuousStates`](@ref). """ -function fmi2SetContinuousStates(c::FMU2Component, x::Union{AbstractArray{Float32}, AbstractArray{Float64}}; kwargs...) +function fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{fmi2Real}; kwargs...) nx = Csize_t(length(x)) - status = fmi2SetContinuousStates(c, Array{fmi2Real}(x), nx; kwargs...) + status = fmi2SetContinuousStates(c, x, nx; kwargs...) if status == fmi2StatusOK - c.x = x + fast_copy!(c, :x, x) end return status end +fmi2SetContinuousStates(c::FMU2Component, x::AbstractArray{Float32}; kwargs...) = fmi2SetContinuousStates(c, Array{fmi2Real}(x); kwargs...) """ fmi2NewDiscreteStates(c::FMU2Component) @@ -1186,16 +1184,13 @@ More detailed: See also [`fmi2CompletedIntegratorStep`](@ref). """ function fmi2CompletedIntegratorStep(c::FMU2Component, - noSetFMUStatePriorToCurrentPoint::fmi2Boolean) - enterEventMode = zeros(fmi2Boolean, 1) - terminateSimulation = zeros(fmi2Boolean, 1) - + noSetFMUStatePriorToCurrentPoint::fmi2Boolean) status = fmi2CompletedIntegratorStep!(c, noSetFMUStatePriorToCurrentPoint, - pointer(enterEventMode), - pointer(terminateSimulation)) + c._ptr_enterEventMode, + c._ptr_terminateSimulation) - return (status, enterEventMode[1], terminateSimulation[1]) + return (status, c._enterEventMode, c._terminateSimulation) end """ @@ -1251,7 +1246,7 @@ See also [`fmi2GetDerivatives!`](@ref). function fmi2GetDerivatives!(c::FMU2Component, derivatives::AbstractArray{fmi2Real}) status = fmi2GetDerivatives!(c, derivatives, Csize_t(length(derivatives))) if status == fmi2StatusOK - c.ẋ = derivatives + fast_copy!(c, :ẋ, derivatives) end return status end @@ -1362,4 +1357,4 @@ function fmi2GetStatus(c::FMU2Component, s::fmi2StatusKind) end status, value[1] -end \ No newline at end of file +end diff --git a/src/FMI2/sens.jl b/src/FMI2/sens.jl deleted file mode 100644 index be38e7a..0000000 --- a/src/FMI2/sens.jl +++ /dev/null @@ -1,796 +0,0 @@ -# -# Copyright (c) 2022 Tobias Thummerer, Lars Mikelsons -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -# What is included in the file `FMI2_sens.jl`? -# - calling function for FMU2 and FMU2Component -# - ForwardDiff- and ChainRulesCore-Sensitivities over FMUs -# - ToDo: colouring of dependency types (known from model description) for fast jacobian build-ups - -import SciMLSensitivity.ForwardDiff -import SciMLSensitivity.ReverseDiff -using ChainRulesCore -import ForwardDiffChainRules: @ForwardDiff_frule -import SciMLSensitivity.ReverseDiff: @grad_from_chainrules -import ChainRulesCore: ZeroTangent, NoTangent, @thunk -import FMICore: fmi2ValueReference - -# in FMI2 we can use fmi2GetDirectionalDerivative for JVP-computations -function fmi2JVP!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, seed) - - if c.fmu.executionConfig.JVPBuiltInDerivatives && fmi2ProvidesDirectionalDerivative(c.fmu.modelDescription) - jac = getfield(c, mtxCache) - if jac.b == nothing || size(jac.b) != (length(seed),) - jac.b = zeros(length(seed)) - end - - fmi2GetDirectionalDerivative!(c, ∂f_refs, ∂x_refs, jac.b, seed) - return jac.b - else - jac = getfield(c, mtxCache) - - return FMICore.jvp!(jac, seed; ∂f_refs=∂f_refs, ∂x_refs=∂x_refs) - end -end - -# in FMI2 there is no helper for VJP-computations (but in FMI3) ... -function fmi2VJP!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, seed) - - jac = getfield(c, mtxCache) - return FMICore.vjp!(jac, seed; ∂f_refs=∂f_refs, ∂x_refs=∂x_refs) -end - -""" - - (fmu::FMU2)(;dx::Union{AbstractVector{<:Real}, Nothing}=nothing, - y::Union{AbstractVector{<:Real}, Nothing}=nothing, - y_refs::Union{AbstractVector{<:fmi2ValueReference}, Nothing}=nothing, - x::Union{AbstractVector{<:Real}, Nothing}=nothing, - u::Union{AbstractVector{<:Real}, Nothing}=nothing, - u_refs::Union{AbstractVector{<:fmi2ValueReference}, Nothing}=nothing, - t::Union{Real, Nothing}=nothing) - -Evaluates a `FMU2` by setting the component state `x`, inputs `u` and/or time `t`. If no component is available, one is allocated. The result of the evaluation might be the system output `y` and/or state-derivative `dx`. -Not all options are available for any FMU type, e.g. setting state is not supported for CS-FMUs. Assertions will be generated for wrong use. - -# Keywords -- `dx`: An array to store the state-derivatives in. If not provided but necessary, a suitable array is allocated and returned. Not supported by CS-FMUs. -- `y`: An array to store the system outputs in. If not provided but requested, a suitable array is allocated and returned. -- `y_refs`: An array of value references to indicate which system outputs shall be returned. -- `x`: An array containing the states to be set. Not supported by CS-FMUs. -- `u`: An array containing the inputs to be set. -- `u_refs`: An array of value references to indicate which system inputs want to be set. -- `p`: An array of FMU parameters to be set. -- `p_refs`: An array of parameter references to indicate which system parameter sensitivities need to be determined. -- `t`: A scalar value holding the system time to be set. - -# Returns (as Tuple) -- `y::Union{AbstractVector{<:Real}, Nothing}`: The system output `y` (if requested, otherwise `nothing`). -- `dx::Union{AbstractVector{<:Real}, Nothing}`: The system state-derivaitve (if ME-FMU, otherwise `nothing`). -""" -function (fmu::FMU2)(;dx::AbstractVector{<:Real}=Vector{fmi2Real}(), - y::AbstractVector{<:Real}=Vector{fmi2Real}(), - y_refs::AbstractVector{<:fmi2ValueReference}=Vector{fmi2ValueReference}(), - x::AbstractVector{<:Real}=Vector{fmi2Real}(), - u::AbstractVector{<:Real}=Vector{fmi2Real}(), - u_refs::AbstractVector{<:fmi2ValueReference}=Vector{fmi2ValueReference}(), - p::AbstractVector{<:Real}=fmu.optim_p, - p_refs::AbstractVector{<:fmi2ValueReference}=fmu.optim_p_refs, - t::Real=-1.0) - - c = nothing - - if hasCurrentComponent(fmu) - c = getCurrentComponent(fmu) - else - logWarn(fmu, "No FMU2Component found. Allocating one.") - c = fmi2Instantiate!(fmu) - fmi2EnterInitializationMode(c) - fmi2ExitInitializationMode(c) - end - - if t == -1.0 - t = c.next_t - end - - c(;dx=dx, y=y, y_refs=y_refs, x=x, u=u, u_refs=u_refs, p=p, p_refs=p_refs, t=t) -end - -""" - - (c::FMU2Component)(;dx::Union{AbstractVector{<:Real}, Nothing}=nothing, - y::Union{AbstractVector{<:Real}, Nothing}=nothing, - y_refs::Union{AbstractVector{<:fmi2ValueReference}, Nothing}=nothing, - x::Union{AbstractVector{<:Real}, Nothing}=nothing, - u::Union{AbstractVector{<:Real}, Nothing}=nothing, - u_refs::Union{AbstractVector{<:fmi2ValueReference}, Nothing}=nothing, - t::Union{Real, Nothing}=nothing) - -Evaluates a `FMU2Component` by setting the component state `x`, inputs `u` and/or time `t`. The result of the evaluation might be the system output `y` and/or state-derivative `dx`. -Not all options are available for any FMU type, e.g. setting state is not supported for CS-FMUs. Assertions will be generated for wrong use. - -# Keywords -- `dx`: An array to store the state-derivatives in. If not provided but necessary, a suitable array is allocated and returned. Not supported by CS-FMUs. -- `y`: An array to store the system outputs in. If not provided but requested, a suitable array is allocated and returned. -- `y_refs`: An array of value references to indicate which system outputs shall be returned. -- `x`: An array containing the states to be set. Not supported by CS-FMUs. -- `u`: An array containing the inputs to be set. -- `u_refs`: An array of value references to indicate which system inputs want to be set. -- `p`: An array of FMU parameters to be set. -- `p_refs`: An array of parameter references to indicate which system parameter sensitivities need to be determined. -- `t`: A scalar value holding the system time to be set. - -# Returns (as Tuple) -- `y::Union{AbstractVector{<:Real}, Nothing}`: The system output `y` (if requested, otherwise `nothing`). -- `dx::Union{AbstractVector{<:Real}, Nothing}`: The system state-derivaitve (if ME-FMU, otherwise `nothing`). -""" -function (c::FMU2Component)(;dx::AbstractVector{<:Real}=Vector{fmi2Real}(), - y::AbstractVector{<:Real}=Vector{fmi2Real}(), - y_refs::AbstractVector{<:fmi2ValueReference}=Vector{fmi2ValueReference}(), - x::AbstractVector{<:Real}=Vector{fmi2Real}(), - u::AbstractVector{<:Real}=Vector{fmi2Real}(), - u_refs::AbstractVector{<:fmi2ValueReference}=Vector{fmi2ValueReference}(), - p::AbstractVector{<:Real}=c.fmu.optim_p, - p_refs::AbstractVector{<:fmi2ValueReference}=c.fmu.optim_p_refs, - t::Real=c.next_t) - - if length(y_refs) > 0 - if length(y) <= 0 - y = zeros(fmi2Real, length(y_refs)) - end - end - - @assert (length(y) == length(y_refs)) "Length of `y` must match length of `y_refs`." - @assert (length(u) == length(u_refs)) "Length of `u` must match length of `u_refs`." - @assert (length(p) == length(p_refs)) "Length of `p` must match length of `p_refs`." - - if fmi2IsModelExchange(c.fmu) - - if c.type == fmi2TypeModelExchange::fmi2Type - if length(dx) <= 0 - dx = zeros(fmi2Real, fmi2GetNumberOfStates(c.fmu.modelDescription)) - end - end - end - - if fmi2IsCoSimulation(c.fmu) - if c.type == fmi2TypeCoSimulation::fmi2Type - @assert length(dx) <= 0 "Keyword `dx != nothing` is invalid for CS-FMUs. Setting a state-derivative is not possible in CS." - @assert length(x) <= 0 "Keyword `x != nothing` is invalid for CS-FMUs. Setting a state is not possible in CS." - @assert t < 0.0 "Keyword `t != nothing` is invalid for CS-FMUs. Setting explicit time is not possible in CS." - end - end - - # ToDo: This is necessary, because ForwardDiffChainRules.jl can't handle arguments with type `Ptr{Nothing}`. - cRef = nothing - ignore_derivatives() do - cRef = pointer_from_objref(c) - cRef = UInt64(cRef) - end - - return eval!(cRef, dx, y, y_refs, x, u, u_refs, p, p_refs, t) -end - -function eval!(cRef::UInt64, - dx::AbstractVector{<:Real}, - y::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x::AbstractVector{<:Real}, - u::AbstractVector{<:Real}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p::AbstractVector{<:Real}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t::Real) - - c = unsafe_pointer_to_objref(Ptr{Nothing}(cRef)) - - @assert (!isdual(x) && !istracked(x)) "eval!(...): Wrong dispatched: `x` is ForwardDiff.Dual/ReverseDiff.TrackedReal, please open an issue with MWE." - @assert (!isdual(u) && !istracked(u)) "eval!(...): Wrong dispatched: `u` is ForwardDiff.Dual/ReverseDiff.TrackedReal, please open an issue with MWE." - @assert (!isdual(t) && !istracked(t)) "eval!(...): Wrong dispatched: `t` is ForwardDiff.Dual/ReverseDiff.TrackedReal, please open an issue with MWE." - @assert (!isdual(p) && !istracked(p)) "eval!(...): Wrong dispatched: `p` is ForwardDiff.Dual/ReverseDiff.TrackedReal, please open an issue with MWE." - - x = unsense(x) - t = unsense(t) - u = unsense(u) - # p = unsense(p) # no need to unsense `p` because it is not beeing used further - - # set state - if length(x) > 0 && !c.fmu.isZeroState - fmi2SetContinuousStates(c, x) - end - - # set time - if t >= 0.0 - fmi2SetTime(c, t) - end - - # set input - if length(u) > 0 - fmi2SetReal(c, u_refs, u) - end - - # get derivative - if length(dx) > 0 - if isdual(dx) - - dx_tmp = nothing - - if c.fmu.isZeroState - dx_tmp = [1.0] - else - dx_tmp = collect(ForwardDiff.value(e) for e in dx) - fmi2GetDerivatives!(c, dx_tmp) - end - - T, V, N = fd_eltypes(dx) - dx[:] = collect(ForwardDiff.Dual{T, V, N}(dx_tmp[i], ForwardDiff.partials(dx[i]) ) for i in 1:length(dx)) - - elseif istracked(dx) - - dx_tmp = zeros(fmi2Real, length(dx)) - fmi2GetDerivatives!(c, dx_tmp) - - for e in 1:length(dx) - dx[e].value = dx_tmp[e] - end - else - if c.fmu.isZeroState - dx[:] = [1.0] - else - fmi2GetDerivatives!(c, dx) - end - end - end - - # get output - if length(y) > 0 - if isdual(y) - #@info "y is dual!" - y_tmp = collect(ForwardDiff.value(e) for e in y) - fmi2GetReal!(c, y_refs, y_tmp) - T, V, N = fd_eltypes(y) - y[:] = collect(ForwardDiff.Dual{T, V, N}(y_tmp[i], ForwardDiff.partials(y[i]) ) for i in 1:length(y)) - else - if !isa(y, AbstractVector{fmi2Real}) - y = convert(Vector{fmi2Real}, y) - end - fmi2GetReal!(c, y_refs, y) - end - end - - if c.fmu.executionConfig.concat_y_dx - return vcat(y, dx) # [y..., dx...] - else - return y, dx - end -end - -function ChainRulesCore.frule(Δtuple, - ::typeof(eval!), - cRef, - dx, - y, - y_refs, - x, - u, - u_refs, - p, - p_refs, - t) - - Δtuple = undual(Δtuple) - Δself, ΔcRef, Δdx, Δy, Δy_refs, Δx, Δu, Δu_refs, Δp, Δp_refs, Δt = Δtuple - - ### ToDo: Somehow, ForwardDiff enters with all types beeing Float64, this needs to be corrected. - - cRef = undual(cRef) - if typeof(cRef) != UInt64 - cRef = UInt64(cRef) - end - - t = undual(t) - u = undual(u) - x = undual(x) - - p = undual(p) - - y_refs = undual(y_refs) - y_refs = convert(Array{UInt32,1}, y_refs) - - u_refs = undual(u_refs) - u_refs = convert(Array{UInt32,1}, u_refs) - - p_refs = undual(p_refs) - p_refs = convert(Array{UInt32,1}, p_refs) - - ### - - c = unsafe_pointer_to_objref(Ptr{Nothing}(cRef)) - - outputs = (length(y_refs) > 0) - inputs = (length(u_refs) > 0) - derivatives = (length(dx) > 0) - states = (length(x) > 0) - times = (t >= 0.0) - parameters = (length(p_refs) > 0) - - Ω = eval!(cRef, dx, y, y_refs, x, u, u_refs, p, p_refs, t) - - # time, states and inputs where already set in `eval!`, no need to repeat it here - - ∂y = ZeroTangent() - ∂dx = ZeroTangent() - - if Δx != NoTangent() && length(Δx) > 0 - - if !isa(Δx, AbstractVector{fmi2Real}) - Δx = convert(Vector{fmi2Real}, Δx) - end - - if states - if derivatives - ∂dx += fmi2JVP!(c, :A, c.fmu.modelDescription.derivativeValueReferences, c.fmu.modelDescription.stateValueReferences, Δx) - c.solution.evals_∂ẋ_∂x += 1 - #@info "$(Δx)" - end - - if outputs - ∂y += fmi2JVP!(c, :C, y_refs, c.fmu.modelDescription.stateValueReferences, Δx) - c.solution.evals_∂y_∂x += 1 - end - end - end - - - if Δu != NoTangent() && length(Δu) > 0 - - if !isa(Δu, AbstractVector{fmi2Real}) - Δu = convert(Vector{fmi2Real}, Δu) - end - - if inputs - if derivatives - ∂dx += fmi2JVP!(c, :B, c.fmu.modelDescription.derivativeValueReferences, u_refs, Δu) - c.solution.evals_∂ẋ_∂u += 1 - end - - if outputs - ∂y += fmi2JVP!(c, :D, y_refs, u_refs, Δu) - c.solution.evals_∂y_∂u += 1 - end - end - end - - if Δp != NoTangent() && length(Δp) > 0 - - if !isa(Δp, AbstractVector{fmi2Real}) - Δp = convert(Vector{fmi2Real}, Δp) - end - - if parameters - if derivatives - ∂dx += fmi2JVP!(c, :E, c.fmu.modelDescription.derivativeValueReferences, p_refs, Δp) - c.solution.evals_∂ẋ_∂p += 1 - end - - if outputs - ∂y += fmi2JVP!(c, :F, y_refs, p_refs, Δp) - c.solution.evals_∂y_∂p += 1 - end - end - end - - if c.fmu.executionConfig.eval_t_gradients - # partial time derivatives are not part of the FMI standard, so must be sampled in any case - if Δt != NoTangent() && times && (derivatives || outputs) - - dt = 1e-6 # ToDo: Find a better value, e.g. based on the current solver step size - - dx1 = nothing - dx2 = nothing - y1 = nothing - y2 = nothing - - if derivatives - dx1 = zeros(fmi2Real, length(c.fmu.modelDescription.derivativeValueReferences)) - dx2 = zeros(fmi2Real, length(c.fmu.modelDescription.derivativeValueReferences)) - fmi2GetDerivatives!(c, dx1) - end - - if outputs - y1 = zeros(fmi2Real, length(y)) - y2 = zeros(fmi2Real, length(y)) - fmi2GetReal!(c, y_refs, y1) - end - - fmi2SetTime(c, t + dt; track=false) - - if derivatives - fmi2GetDerivatives!(c, dx2) - - ∂dx_t = (dx2-dx1)/dt - ∂dx += ∂dx_t * Δt - - c.solution.evals_∂ẋ_∂t += 1 - end - - if outputs - fmi2GetReal!(c, y_refs, y2) - - ∂y_t = (y2-y1)/dt - ∂y += ∂y_t * Δt - - c.solution.evals_∂y_∂t += 1 - end - - fmi2SetTime(c, t; track=false) - end - end - - @debug "frule: ∂y=$(∂y) ∂dx=$(∂dx)" - - ∂Ω = nothing - if c.fmu.executionConfig.concat_y_dx - ∂Ω = vcat(∂y, ∂dx) # [∂y..., ∂dx...] - else - ∂Ω = (∂y, ∂dx) - end - - return Ω, ∂Ω -end - -function isZeroTangent(d) - return false -end - -function isZeroTangent(d::ZeroTangent) - return true -end - -function isZeroTangent(d::AbstractArray{<:ZeroTangent}) - return true -end - -function ChainRulesCore.rrule(::typeof(eval!), - cRef, - dx, - y, - y_refs, - x, - u, - u_refs, - p, - p_refs, - t) - - @assert !isa(cRef, FMU2Component) "Wrong dispatched!" - - c = unsafe_pointer_to_objref(Ptr{Nothing}(cRef)) - - outputs = (length(y_refs) > 0) - inputs = (length(u_refs) > 0) - derivatives = (length(dx) > 0) - states = (length(x) > 0) - times = (t >= 0.0) - parameters = (length(p_refs) > 0) - - Ω = eval!(cRef, dx, y, y_refs, x, u, u_refs, p, p_refs, t) - - # if !inputs - # Ω = eval!(cRef, dx, y, y_refs, x, t) - # elseif !states - # Ω = eval!(cRef, dx, y, y_refs, u, u_refs, t) - # else - # Ω = eval!(cRef, dx, y, y_refs, x, u, u_refs, t) - # end - - ############## - - function eval_pullback(r̄) - - ȳ = nothing - d̄x = nothing - - if c.fmu.executionConfig.concat_y_dx - ylen = (isnothing(y_refs) ? 0 : length(y_refs)) - ȳ = r̄[1:ylen] - d̄x = r̄[ylen+1:end] - else - ȳ, d̄x = r̄ - end - - outputs = outputs && !isZeroTangent(ȳ) - derivatives = derivatives && !isZeroTangent(d̄x) - - if !isa(ȳ, AbstractArray) - ȳ = collect(ȳ) # [ȳ...] - end - - if !isa(d̄x, AbstractArray) - d̄x = collect(d̄x) # [d̄x...] - end - - # between building and using the pullback maybe the time, state or inputs where changed, so we need to re-set them - - if states && c.x != x - fmi2SetContinuousStates(c, x) - end - - if inputs ## && c.u != u - fmi2SetReal(c, u_refs, u) - end - - if times && c.t != t - fmi2SetTime(c, t) - end - - n_dx_x = ZeroTangent() - n_dx_u = ZeroTangent() - n_dx_p = ZeroTangent() - n_dx_t = ZeroTangent() - - n_y_x = ZeroTangent() - n_y_u = ZeroTangent() - n_y_p = ZeroTangent() - n_y_t = ZeroTangent() - - @debug "rrule pullback ȳ, d̄x = $(ȳ), $(d̄x)" - - dx_refs = c.fmu.modelDescription.derivativeValueReferences - x_refs = c.fmu.modelDescription.stateValueReferences - - if derivatives - if states - n_dx_x = fmi2VJP!(c, :A, dx_refs, x_refs, d̄x) - c.solution.evals_∂ẋ_∂x += 1 - end - - if inputs - n_dx_u = fmi2VJP!(c, :B, dx_refs, u_refs, d̄x) - c.solution.evals_∂ẋ_∂u += 1 - end - - if parameters - n_dx_p = fmi2VJP!(c, :E, dx_refs, p_refs, d̄x) - c.solution.evals_∂ẋ_∂p += 1 - - # if rand(1:100) == 1 - # #@info "$(c.E.mtx)" - # #@info "$(p_refs)" - # @info "$(fmi2GetJacobian(c, dx_refs, p_refs))" - # end - end - end - - if outputs - if states - n_y_x = fmi2VJP!(c, :C, y_refs, x_refs, ȳ) - c.solution.evals_∂y_∂x += 1 - end - - if inputs - n_y_u = fmi2VJP!(c, :D, y_refs, u_refs, ȳ) - c.solution.evals_∂y_∂u += 1 - end - - if parameters - n_y_p = fmi2VJP!(c, :F, y_refs, p_refs, ȳ) - c.solution.evals_∂y_∂p += 1 - end - end - - if c.fmu.executionConfig.eval_t_gradients - # sample time partials - # in rrule this should be done even if no new time is actively set - if (derivatives || outputs) # && times - - # if no time is actively set, use the component current time for sampling - if !times - t = c.t - end - - dt = 1e-6 # ToDo: better value - - dx1 = nothing - dx2 = nothing - y1 = nothing - y2 = nothing - - if derivatives - dx1 = zeros(fmi2Real, length(dx_refs)) - dx2 = zeros(fmi2Real, length(dx_refs)) - fmi2GetDerivatives!(c, dx1) - end - - if outputs - y1 = zeros(fmi2Real, length(y)) - y2 = zeros(fmi2Real, length(y)) - fmi2GetReal!(c, y_refs, y1) - end - - fmi2SetTime(c, t + dt; track=false) - - if derivatives - fmi2GetDerivatives!(c, dx2) - - ∂dx_t = (dx2-dx1) / dt - n_dx_t = ∂dx_t' * d̄x - - c.solution.evals_∂ẋ_∂t += 1 - end - - if outputs - fmi2GetReal!(c, y_refs, y2) - - ∂y_t = (y2-y1) / dt - n_y_t = ∂y_t' * ȳ - - c.solution.evals_∂y_∂t += 1 - end - - fmi2SetTime(c, t; track=false) - end - end - - # write back - f̄ = NoTangent() - c̄Ref = ZeroTangent() - d̄x = ZeroTangent() - ȳ = ZeroTangent() - ȳ_refs = ZeroTangent() - t̄ = n_y_t + n_dx_t - - x̄ = n_y_x + n_dx_x - - ū = n_y_u + n_dx_u - ū_refs = ZeroTangent() - - p̄ = n_y_p + n_dx_p - p̄_refs = ZeroTangent() - - @debug "rrule: $((f̄, c̄Ref, d̄x, ȳ, ȳ_refs, x̄, ū, ū_refs, p̄, p̄_refs, t̄))" - - return (f̄, c̄Ref, d̄x, ȳ, ȳ_refs, x̄, ū, ū_refs, p̄, p̄_refs, t̄) - end - - return (Ω, eval_pullback) -end - -# dx, y, x, u, t -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:ForwardDiff.Dual}, - y ::AbstractVector{<:ForwardDiff.Dual}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:ForwardDiff.Dual}, - u ::AbstractVector{<:ForwardDiff.Dual}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::ForwardDiff.Dual) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:ReverseDiff.TrackedReal}, - y ::AbstractVector{<:ReverseDiff.TrackedReal}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:ReverseDiff.TrackedReal}, - u ::AbstractVector{<:ReverseDiff.TrackedReal}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:UInt32}, - t ::ReverseDiff.TrackedReal) - -# x, p -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:ForwardDiff.Dual}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:ForwardDiff.Dual}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::Real) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:ReverseDiff.TrackedReal}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:ReverseDiff.TrackedReal}, - p_refs::AbstractVector{<:UInt32}, - t ::Real) - -# t -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::ForwardDiff.Dual) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:UInt32}, - t ::ReverseDiff.TrackedReal) - -# x -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:ForwardDiff.Dual}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::Real) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:ReverseDiff.TrackedReal}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:UInt32}, - t ::Real) - -# u -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:ForwardDiff.Dual}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::Real) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:ReverseDiff.TrackedReal}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:Real}, - p_refs::AbstractVector{<:UInt32}, - t ::Real) - -# p -@ForwardDiff_frule eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:fmi2ValueReference}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:fmi2ValueReference}, - p ::AbstractVector{<:ForwardDiff.Dual}, - p_refs::AbstractVector{<:fmi2ValueReference}, - t ::Real) - -@grad_from_chainrules eval!(cRef::UInt64, - dx ::AbstractVector{<:Real}, - y ::AbstractVector{<:Real}, - y_refs::AbstractVector{<:UInt32}, - x ::AbstractVector{<:Real}, - u ::AbstractVector{<:Real}, - u_refs::AbstractVector{<:UInt32}, - p ::AbstractVector{<:ReverseDiff.TrackedReal}, - p_refs::AbstractVector{<:UInt32}, - t ::Real) \ No newline at end of file diff --git a/src/FMI3/ext.jl b/src/FMI3/ext.jl index 0b92cf9..61b0c53 100644 --- a/src/FMI3/ext.jl +++ b/src/FMI3/ext.jl @@ -1110,46 +1110,46 @@ function fmi3Get!(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, dstArray::A mv = fmi3ModelVariablesForValueReference(inst.fmu.modelDescription, vr) mv = mv[1] # TODO change if dataytype is elimnated - if isa(mv, FMICore.mvFloat32) + if isa(mv, FMICore.fmi3VariableFloat32) #@assert isa(dstArray[i], Real) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetFloat32(inst, vr) - elseif isa(mv, FMICore.mvFloat64) + elseif isa(mv, FMICore.fmi3VariableFloat64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetFloat64(inst, vr) - elseif isa(mv, FMICore.mvInt8) + elseif isa(mv, FMICore.fmi3VariableInt8) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetInt8(inst, vr) - elseif isa(mv, FMICore.mvInt16) + elseif isa(mv, FMICore.fmi3VariableInt16) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetInt16(inst, vr) - elseif isa(mv, FMICore.mvInt32) + elseif isa(mv, FMICore.fmi3VariableInt32) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetInt32(inst, vr) - elseif isa(mv, FMICore.mvInt64) + elseif isa(mv, FMICore.fmi3VariableInt64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetInt64(inst, vr) - elseif isa(mv, FMICore.mvUInt8) + elseif isa(mv, FMICore.fmi3VariableUInt8) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetUInt8(inst, vr) - elseif isa(mv, FMICore.mvUInt16) + elseif isa(mv, FMICore.fmi3VariableUInt16) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetUInt16(inst, vr) - elseif isa(mv, FMICore.mvUInt32) + elseif isa(mv, FMICore.fmi3VariableUInt32) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetUInt32(inst, vr) - elseif isa(mv, FMICore.mvUInt64) + elseif isa(mv, FMICore.fmi3VariableUInt64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetUInt64(inst, vr) - elseif isa(mv, FMICore.mvBoolean) + elseif isa(mv, FMICore.fmi3VariableBoolean) #@assert isa(dstArray[i], Union{Real, Bool}) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetBoolean(inst, vr) - elseif isa(mv, FMICore.mvString) + elseif isa(mv, FMICore.fmi3VariableString) #@assert isa(dstArray[i], String) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetString(inst, vr) - elseif isa(mv, FMICore.mvBinary) + elseif isa(mv, FMICore.fmi3VariableBinary) #@assert isa(dstArray[i], String) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." dstArray[i] = fmi3GetBinary(inst, vr) - elseif isa(mv, FMICore.mvEnumeration) + elseif isa(mv, FMICore.fmi3VariableEnumeration) @warn "fmi3Get!(...): Currently not implemented for fmi3Enum." else @assert isa(dstArray[i], Real) "fmi3Get!(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(mv.datatype.datatype)`." @@ -1229,46 +1229,46 @@ function fmi3Set(inst::FMU3Instance, vrs::fmi3ValueReferenceFormat, srcArray::Ar vr = vrs[i] mv = fmi3ModelVariablesForValueReference(inst.fmu.modelDescription, vr) mv = mv[1] - if isa(mv, FMICore.mvFloat32) + if isa(mv, FMICore.fmi3VariableFloat32) #@assert isa(dstArray[i], Real) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Real`, is `$(typeof(dstArray[i]))`." fmi3SetFloat32(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.mvFloat64) + elseif isa(mv, FMICore.fmi3VariableFloat64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetFloat64(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.mvInt8) + elseif isa(mv, FMICore.fmi3VariableInt8) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetInt8(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvInt16) + elseif isa(mv, FMICore.fmi3VariableInt16) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetInt16(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvInt32) + elseif isa(mv, FMICore.fmi3VariableInt32) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetInt32(inst, vr, Int32(srcArray[i])) - elseif isa(mv, FMICore.mvInt64) + elseif isa(mv, FMICore.fmi3VariableInt64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetInt64(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvUInt8) + elseif isa(mv, FMICore.fmi3VariableUInt8) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetUInt8(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvUInt16) + elseif isa(mv, FMICore.fmi3VariableUInt16) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetUInt16(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvUInt32) + elseif isa(mv, FMICore.fmi3VariableUInt32) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetUInt32(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvUInt64) + elseif isa(mv, FMICore.fmi3VariableUInt64) #@assert isa(dstArray[i], Union{Real, Integer}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Integer`, is `$(typeof(dstArray[i]))`." fmi3SetUInt64(inst, vr, Integer(srcArray[i])) - elseif isa(mv, FMICore.mvBoolean) + elseif isa(mv, FMICore.fmi3VariableBoolean) #@assert isa(dstArray[i], Union{Real, Bool}) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `Bool`, is `$(typeof(dstArray[i]))`." fmi3SetBoolean(inst, vr, Bool(srcArray[i])) - elseif isa(mv, FMICore.mvString) + elseif isa(mv, FMICore.fmi3VariableString) #@assert isa(dstArray[i], String) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." fmi3SetString(inst, vr, srcArray[i]) - elseif isa(mv, FMICore.mvBinary) + elseif isa(mv, FMICore.fmi3VariableBinary) #@assert isa(dstArray[i], String) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), should be `String`, is `$(typeof(dstArray[i]))`." fmi3SetBinary(inst, vr, Csize_t(length(srcArray[i])), pointer(srcArray[i])) # TODO fix this - elseif isa(mv, FMICore.mvEnumeration) + elseif isa(mv, FMICore.fmi3VariableEnumeration) @warn "fmi3Set!(...): Currently not implemented for fmi3Enum." else @assert isa(dstArray[i], Real) "fmi3Set!(...): Unknown data type for value reference `$(vr)` at index $(i), is `$(typeof(mv))`." diff --git a/src/FMI3/md.jl b/src/FMI3/md.jl index 57ad4e8..d7bee73 100644 --- a/src/FMI3/md.jl +++ b/src/FMI3/md.jl @@ -13,7 +13,7 @@ using EzXML using FMICore: fmi3ModelDescriptionModelExchange, fmi3ModelDescriptionCoSimulation, fmi3ModelDescriptionDefaultExperiment -using FMICore: mvFloat32, mvFloat64, mvInt8, mvUInt8, mvInt16, mvUInt16, mvInt32, mvUInt32, mvInt64, mvUInt64, mvBoolean, mvString, mvBinary, mvClock, mvEnumeration +using FMICore: fmi3VariableFloat32, fmi3VariableFloat64, fmi3VariableInt8, fmi3VariableUInt8, fmi3VariableInt16, fmi3VariableUInt16, fmi3VariableInt32, fmi3VariableUInt32, fmi3VariableInt64, fmi3VariableUInt64, fmi3VariableBoolean, fmi3VariableString, fmi3VariableBinary, fmi3VariableClock, fmi3VariableEnumeration using FMICore: fmi3ModelDescriptionModelStructure using FMICore: fmi3DependencyKind ###################################### @@ -34,7 +34,7 @@ function fmi3LoadModelDescription(pathToModellDescription::String) md.intermediateUpdateValueReferences = Array{fmi3ValueReference}(undef, 0) md.numberOfEventIndicators = 0 - md.enumerations = [] + #ToDo: md.enumerations = [] typedefinitions = nothing modelvariables = nothing modelstructure = nothing @@ -67,42 +67,40 @@ function fmi3LoadModelDescription(pathToModellDescription::String) md.valueReferenceIndicies = Dict{UInt, UInt}() for node in eachelement(root) - if node.name == "CoSimulation" || node.name == "ModelExchange" || node.name == "ScheduledExecution" - if node.name == "CoSimulation" - md.coSimulation = fmi3ModelDescriptionCoSimulation() - md.coSimulation.modelIdentifier = node["modelIdentifier"] - md.coSimulation.canHandleVariableCommunicationStepSize = parseNodeBoolean(node, "canHandleVariableCommunicationStepSize" ; onfail=false) - md.coSimulation.canInterpolateInputs = parseNodeBoolean(node, "canInterpolateInputs" ; onfail=false) - md.coSimulation.maxOutputDerivativeOrder = parseNodeInteger(node, "maxOutputDerivativeOrder" ; onfail=nothing) - md.coSimulation.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.coSimulation.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.coSimulation.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.coSimulation.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) - md.coSimulation.hasEventMode = parseNodeBoolean(node, "hasEventMode" ; onfail=false) - end - - if node.name == "ModelExchange" - md.modelExchange = fmi3ModelDescriptionModelExchange() - md.modelExchange.modelIdentifier = node["modelIdentifier"] - md.modelExchange.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.modelExchange.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.modelExchange.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.modelExchange.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) - end + if node.name == "CoSimulation" + md.coSimulation = fmi3ModelDescriptionCoSimulation() + md.coSimulation.modelIdentifier = node["modelIdentifier"] + md.coSimulation.canHandleVariableCommunicationStepSize = parseNodeBoolean(node, "canHandleVariableCommunicationStepSize" ; onfail=false) + md.coSimulation.canInterpolateInputs = parseNodeBoolean(node, "canInterpolateInputs" ; onfail=false) + md.coSimulation.maxOutputDerivativeOrder = parseNodeInteger(node, "maxOutputDerivativeOrder" ; onfail=nothing) + md.coSimulation.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) + md.coSimulation.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) + md.coSimulation.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) + md.coSimulation.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) + md.coSimulation.hasEventMode = parseNodeBoolean(node, "hasEventMode" ; onfail=false) + + elseif node.name == "ModelExchange" + md.modelExchange = fmi3ModelDescriptionModelExchange() + md.modelExchange.modelIdentifier = node["modelIdentifier"] + md.modelExchange.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) + md.modelExchange.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) + md.modelExchange.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) + md.modelExchange.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) + + elseif node.name == "ScheduledExecution" + md.scheduledExecution = FMICore.fmi3ModelDescriptionScheduledExecution() + md.scheduledExecution.modelIdentifier = node["modelIdentifier"] + md.scheduledExecution.needsExecutionTool = parseNodeBoolean(node, "needsExecutionTool" ; onfail=false) + md.scheduledExecution.canBeInstantiatedOnlyOncePerProcess = parseNodeBoolean(node, "canBeInstantiatedOnlyOncePerProcess" ; onfail=false) + md.scheduledExecution.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) + md.scheduledExecution.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) + md.scheduledExecution.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) + md.scheduledExecution.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) + md.scheduledExecution.providesPerElementDependencies = parseNodeBoolean(node, "providesPerElementDependencies" ; onfail=false) - if node.name == "ScheduledExecution" - md.scheduledExecution = FMICore.fmi3ModelDescriptionScheduledExecution() - md.scheduledExecution.modelIdentifier = node["modelIdentifier"] - md.scheduledExecution.needsExecutionTool = parseNodeBoolean(node, "needsExecutionTool" ; onfail=false) - md.scheduledExecution.canBeInstantiatedOnlyOncePerProcess = parseNodeBoolean(node, "canBeInstantiatedOnlyOncePerProcess" ; onfail=false) - md.scheduledExecution.canGetAndSetFMUstate = parseNodeBoolean(node, "canGetAndSetFMUState" ; onfail=false) - md.scheduledExecution.canSerializeFMUstate = parseNodeBoolean(node, "canSerializeFMUState" ; onfail=false) - md.scheduledExecution.providesDirectionalDerivatives = parseNodeBoolean(node, "providesDirectionalDerivatives" ; onfail=false) - md.scheduledExecution.providesAdjointDerivatives = parseNodeBoolean(node, "providesAdjointDerivatives" ; onfail=false) - md.scheduledExecution.providesPerElementDependencies = parseNodeBoolean(node, "providesPerElementDependencies" ; onfail=false) - end elseif node.name == "TypeDefinitions" - md.enumerations = createEnum(node) + #ToDo: md.enumerations = createEnum(node) + @warn "ToDo: FMU has TypeDefinitions, but this is not implemented yet." elseif node.name == "ModelVariables" md.modelVariables = parseModelVariables(node, md) @@ -120,6 +118,9 @@ function fmi3LoadModelDescription(pathToModellDescription::String) md.defaultExperiment.stopTime = parseNodeReal(node, "stopTime") md.defaultExperiment.tolerance = parseNodeReal(node, "tolerance"; onfail = md.defaultExperiment.tolerance) md.defaultExperiment.stepSize = parseNodeReal(node, "stepSize") + + else + @warn "Unknwon node named `$(node.name)`" end end @@ -161,38 +162,38 @@ function parseModelVariables(nodes::EzXML.Node, md::fmi3ModelDescription) typename = node.name if typename == "Float32" - modelVariables[index] = mvFloat32(name, valueReference) + modelVariables[index] = fmi3VariableFloat32(name, valueReference) elseif typename == "Float64" - modelVariables[index] = mvFloat64(name, valueReference) + modelVariables[index] = fmi3VariableFloat64(name, valueReference) elseif typename == "Int8" - modelVariables[index] = mvInt8(name, valueReference) + modelVariables[index] = fmi3VariableInt8(name, valueReference) elseif typename == "UInt8" - modelVariables[index] = mvUInt8(name, valueReference) + modelVariables[index] = fmi3VariableUInt8(name, valueReference) elseif typename == "Int16" - modelVariables[index] = mvInt16(name, valueReference) + modelVariables[index] = fmi3VariableInt16(name, valueReference) elseif typename == "UInt16" - modelVariables[index] = mvUInt16(name, valueReference) + modelVariables[index] = fmi3VariableUInt16(name, valueReference) elseif typename == "Int32" - modelVariables[index] = mvInt32(name, valueReference) + modelVariables[index] = fmi3VariableInt32(name, valueReference) elseif typename == "UInt32" - modelVariables[index] = mvUInt32(name, valueReference) + modelVariables[index] = fmi3VariableUInt32(name, valueReference) elseif typename == "Int64" - modelVariables[index] = mvInt64(name, valueReference) + modelVariables[index] = fmi3VariableInt64(name, valueReference) elseif typename == "UInt64" - modelVariables[index] = mvUInt64(name, valueReference) + modelVariables[index] = fmi3VariableUInt64(name, valueReference) elseif typename == "Boolean" - modelVariables[index] = mvBoolean(name, valueReference) + modelVariables[index] = fmi3VariableBoolean(name, valueReference) elseif typename == "String" - modelVariables[index] = mvString(name, valueReference) + modelVariables[index] = fmi3VariableString(name, valueReference) elseif typename == "Binary" - modelVariables[index] = mvBinary(name, valueReference) + modelVariables[index] = fmi3VariableBinary(name, valueReference) elseif typename == "Clock" - modelVariables[index] = mvClock(name, valueReference) + modelVariables[index] = fmi3VariableClock(name, valueReference) elseif typename == "Enumeration" - modelVariables[index] = mvEnumeration(name, valueReference) + modelVariables[index] = fmi3VariableEnumeration(name, valueReference) else @warn "Unknown data type `$(typename)`." - # tODO how to handle unknown types + # ToDo: how to handle unknown types end # modelVariables[index] = fmi3Variable(name, valueReference) diff --git a/src/FMIImport.jl b/src/FMIImport.jl index d274598..144b2b4 100644 --- a/src/FMIImport.jl +++ b/src/FMIImport.jl @@ -5,10 +5,12 @@ module FMIImport -import SciMLSensitivity using FMICore - using FMICore: fmi2Component, fmi3Instance +using FMICore: fast_copy! + +using FMICore.Requires +import FMICore.ChainRulesCore: ignore_derivatives # functions that have (currently) no better place @@ -17,17 +19,10 @@ prepareValue(value) = [value] prepareValue(value::AbstractVector) = value export prepareValue, prepareValueReference -# wildcards for how a user can pass a fmi[X]ValueReference -fmi2ValueReferenceFormat = Union{Nothing, String, AbstractArray{String,1}, fmi2ValueReference, AbstractArray{fmi2ValueReference,1}, Int64, AbstractArray{Int64,1}, Symbol} -fmi3ValueReferenceFormat = Union{Nothing, String, AbstractArray{String,1}, fmi3ValueReference, AbstractArray{fmi3ValueReference,1}, Int64, AbstractArray{Int64,1}} -export fmi2ValueReferenceFormat, fmi3ValueReferenceFormat - using EzXML -include("utils.jl") include("parse.jl") ### FMI2 ### - include("FMI2/prep.jl") include("FMI2/convert.jl") include("FMI2/c.jl") @@ -35,9 +30,6 @@ include("FMI2/int.jl") include("FMI2/ext.jl") include("FMI2/md.jl") include("FMI2/fmu_to_md.jl") -include("FMI2/sens.jl") - -export getCurrentComponent, hasCurrentComponent # FMI2_c.jl export fmi2CallbackLogger, fmi2CallbackAllocateMemory, fmi2CallbackFreeMemory, fmi2CallbackStepFinished @@ -123,7 +115,6 @@ export fmi3GetFMUState, fmi3SerializedFMUStateSize, fmi3SerializeFMUState, fmi3D export fmi3GetDirectionalDerivative export fmi3GetStartValue, fmi3SampleDirectionalDerivative, fmi3CompletedIntegratorStep - # FMI3_ext.jl export fmi3Unzip, fmi3Load, fmi3Unload, fmi3InstantiateModelExchange!, fmi3InstantiateCoSimulation!, fmi3InstantiateScheduledExecution! export fmi3Get, fmi3Get!, fmi3Set diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index ab0ecae..0000000 --- a/src/utils.jl +++ /dev/null @@ -1,186 +0,0 @@ -# -# Copyright (c) 2022 Tobias Thummerer, Lars Mikelsons -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -import SciMLSensitivity.ForwardDiff -import SciMLSensitivity.ReverseDiff - -# check if scalar/vector is ForwardDiff.Dual -function isdual(e) - return false -end -function isdual(e::ForwardDiff.Dual{T, V, N}) where {T, V, N} - return true -end -function isdual(e::AbstractVector{<:ForwardDiff.Dual{T, V, N}}) where {T, V, N} - return true -end - -# check if scalar/vector is ForwardDiff.Dual -function istracked(e) - return false -end -function istracked(e::ReverseDiff.TrackedReal) - return true -end -function istracked(e::AbstractVector{<:ReverseDiff.TrackedReal}) - return true -end - -# check types (Tag, Variable, Number) of ForwardDiff.Dual scalar/vector -function fd_eltypes(e::ForwardDiff.Dual{T, V, N}) where {T, V, N} - return (T, V, N) -end -function fd_eltypes(e::AbstractVector{<:ForwardDiff.Dual{T, V, N}}) where {T, V, N} - return (T, V, N) -end - -# overwrites a ForwardDiff.Dual in-place -# inheritates partials -function fd_set!(dst::AbstractArray{<:Real}, src::AbstractArray{<:Real}) - if isdual(src) - if isdual(dst) - dst[:] = src - - # for i in 1:length(dst) - # dst[i] = src[i] - # end - else - dst[:] = collect(ForwardDiff.value(e) for e in src) - - # for i in 1:length(dst) - # dst[i] = ForwardDiff.value(e) - # end - end - - else - if isdual(dst) - T, V, N = fd_eltypes(dst) - - dst[:] = collect(ForwardDiff.Dual{T, V, N}(V(src[i]), ForwardDiff.partials(dst[i]) ) for i in 1:length(dst)) - - # for i in 1:length(dst) - # dst[i] = ForwardDiff.Dual{T, V, N}(V(src[i]), ForwardDiff.partials(dst[i]) ) - # end - else - dst[:] = src - - # for i in 1:length(dst) - # dst[i] = src[i] - # end - end - end - - return nothing -end - -function rd_set!(dst::AbstractArray{<:Real}, src::AbstractArray{<:Real}) - - @assert length(dst) == length(src) "rd_set! dimension mismatch" - - if istracked(src) - if istracked(dst) - dst[:] = src - - # for i in 1:length(dst) - # dst[i] = src[i] - # end - else - dst[:] = collect(ReverseDiff.value(e) for e in src) - - # for i in 1:length(dst) - # dst[i] = ForwardDiff.value(e) - # end - end - - else - if istracked(dst) - - #@info "dst [$(length(dst))]: $dst" - #@info "src [$(length(src))]: $src" - #@info "$(collect(dst[i] for i in 1:length(dst)))" - - dst[:] = collect(ReverseDiff.TrackedReal(ReverseDiff.value(src[i]), 0.0 ) for i in 1:length(dst)) - #dst[:] = collect(ReverseDiff.TrackedReal(ReverseDiff.value(src[i]), ReverseDiff.deriv(dst[i]), ReverseDiff.tape(dst[i]) ) for i in 1:length(dst)) - #dst[:] = collect(ReverseDiff.TrackedReal(ReverseDiff.value(src[i]), ReverseDiff.deriv(dst[i]) ) for i in 1:length(dst)) - - # for i in 1:length(dst) - # dst[i] = ForwardDiff.Dual{T, V, N}(V(src[i]), ForwardDiff.partials(dst[i]) ) - # end - else - dst[:] = src - - # for i in 1:length(dst) - # dst[i] = src[i] - # end - end - end - - return nothing -end - -# makes Reals from ForwardDiff.Dual scalar/vector -function undual(e::AbstractArray) - return collect(undual(c) for c in e) -end -function undual(e::Tuple) - return (collect(undual(c) for c in e)...,) -end -function undual(e::ForwardDiff.Dual) - return ForwardDiff.value(e) -end -function undual(::Nothing) - return nothing -end -function undual(e) - return e -end - -# makes Reals from ReverseDiff.TrackedXXX scalar/vector -function untrack(e::AbstractArray) - return collect(untrack(c) for c in e) -end -function untrack(e::Tuple) - return (collect(untrack(c) for c in e)...,) -end -function untrack(e::ReverseDiff.TrackedReal) - return ReverseDiff.value(e) -end -function untrack(e::ReverseDiff.TrackedArray) - return ReverseDiff.value(e) -end -function untrack(::Nothing) - return nothing -end -function untrack(e) - return e -end - -# makes Reals from ForwardDiff/ReverseDiff.TrackedXXX scalar/vector -function unsense(e::AbstractArray) - return collect(unsense(c) for c in e) -end -function unsense(e::Tuple) - return (collect(unsense(c) for c in e)...,) -end -function unsense(e::ReverseDiff.TrackedReal) - return ReverseDiff.value(e) -end -function unsense(e::ReverseDiff.TrackedArray) - return ReverseDiff.value(e) -end -function unsense(e::ForwardDiff.Dual) - return ForwardDiff.value(e) -end -function unsense(::Nothing) - return nothing -end -function unsense(e) - return e -end - -# checks if integrator has NaNs (that is not good...) -function assert_integrator_valid(integrator) - @assert !isnan(integrator.opts.internalnorm(integrator.u, integrator.t)) "NaN in `integrator.u` @ $(integrator.t)." -end diff --git a/test/FMI2/sensitivities.jl b/test/FMI2/sensitivities.jl deleted file mode 100644 index d537ff0..0000000 --- a/test/FMI2/sensitivities.jl +++ /dev/null @@ -1,305 +0,0 @@ -# -# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher -# Licensed under the MIT license. See LICENSE file in the project root for details. -# - -import SciMLSensitivity.ForwardDiff -import SciMLSensitivity.Zygote -import SciMLSensitivity.ReverseDiff - -# load demo FMU -fmu = fmi2Load("SpringPendulumExtForce1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]; type=:ME) - -# enable time gradient evaluation (disabled by default for performance reasons) -fmu.executionConfig.eval_t_gradients = true - -# prepare (allocate) an FMU instance -c, x0 = FMIImport.prepareSolveFMU(fmu, nothing, fmu.type, nothing, nothing, nothing, nothing, nothing, nothing, 0.0, 0.0, nothing) - -x = [1.0, 1.0] -x_refs = fmu.modelDescription.stateValueReferences -u = [2.0] -u_refs = fmu.modelDescription.inputValueReferences -y = [0.0, 0.0] -y_refs = fmu.modelDescription.outputValueReferences -p_refs = fmu.modelDescription.parameterValueReferences -p = zeros(length(p_refs)) -dx = [0.0, 0.0] -t = 0.0 - -function reset!(c::FMIImport.FMU2Component) - c.solution.evals_∂ẋ_∂x = 0 - c.solution.evals_∂ẋ_∂u = 0 - c.solution.evals_∂y_∂x = 0 - c.solution.evals_∂y_∂u = 0 - c.solution.evals_∂ẋ_∂t = 0 - c.solution.evals_∂y_∂t = 0 - c.solution.evals_∂ẋ_∂p = 0 - c.solution.evals_∂y_∂p = 0 - - @test length(dx) == length(fmu.modelDescription.derivativeValueReferences) - @test length(y) == length(fmu.modelDescription.outputValueReferences) -end - -# evaluation: set state, get state derivative (out-of-place) -ydx = fmu(;x=x) - -# evaluation: set state, get state derivative in-place -ydx = fmu(;x=x, dx=dx) - -# evaluation: set state and inputs, get state derivative (out-of-place) -ydx = fmu(;x=x, u=u, u_refs=u_refs) - -# evaluation: set state and inputs, get state derivative (out-of-place) and outputs (in-place) -ydx = fmu(;x=x, u=u, u_refs=u_refs, y=y, y_refs=y_refs) - -# evaluation: set state and inputs, get state derivative (in-place) and outputs (in-place) -ydx = fmu(;x=x, u=u, u_refs=u_refs, y=y, y_refs=y_refs, dx=dx) - -# evaluation: set state and inputs, parameters, get state derivative (in-place) and outputs (in-place) -ydx = fmu(;x=x, u=u, u_refs=u_refs, y=y, y_refs=y_refs, dx=dx, p=p, p_refs=p_refs) - -# known results -atol= 1e-8 -A = [0.0 1.0; -10.0 0.0] -B = [0.0; 1.0] -C = [0.0 1.0; -10.0 0.0] -D = [0.0; 1.0] -E = [0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 10.0 0.1 10.0 -3.0 5.0] -F = [0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 10.0 0.1 10.0 -3.0 5.0] -dx_t = [0.0, 0.0] -y_t = [0.0, 0.0] - -# Test build-in derivatives (slow) only for jacobian A -fmu.executionConfig.JVPBuiltInDerivatives = true - -_f = _x -> fmu(;x=_x) -_f(x) -j_fwd = ForwardDiff.jacobian(_f, x) -j_zyg = Zygote.jacobian(_f, x)[1] -j_rwd = ReverseDiff.jacobian(_f, x) -j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) -j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) - -@test isapprox(j_fwd, A; atol=atol) -@test isapprox(j_zyg, A; atol=atol) -@test isapprox(j_rwd, A; atol=atol) -@test isapprox(j_smp, A; atol=atol) -@test isapprox(j_get, A; atol=atol) - -# End: Test build-in derivatives (slow) only for jacobian A -fmu.executionConfig.JVPBuiltInDerivatives = false -reset!(c) - -# Jacobian A=∂dx/∂x -_f = _x -> fmu(;x=_x) -_f(x) -j_fwd = ForwardDiff.jacobian(_f, x) -j_zyg = Zygote.jacobian(_f, x)[1] -j_rwd = ReverseDiff.jacobian(_f, x) -j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) -j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) - -@test isapprox(j_fwd, A; atol=atol) -@test isapprox(j_zyg, A; atol=atol) -@test isapprox(j_rwd, A; atol=atol) -@test isapprox(j_smp, A; atol=atol) -@test isapprox(j_get, A; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 4+2 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂y_∂x == 0 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 2+2 -@test c.solution.evals_∂y_∂t == 0 -reset!(c) - -# Jacobian B=∂dx/∂u -_f = _u -> fmu(;x=x, u=_u, u_refs=u_refs) -_f(u) -j_fwd = ForwardDiff.jacobian(_f, u) -j_zyg = Zygote.jacobian(_f, u)[1] -j_rwd = ReverseDiff.jacobian(_f, u) -j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) -j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) - -@test isapprox(j_fwd, B; atol=atol) -@test isapprox(j_zyg, B; atol=atol) -@test isapprox(j_rwd, B; atol=atol) -@test isapprox(j_smp, B; atol=atol) -@test isapprox(j_get, B; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 3+2 -@test c.solution.evals_∂ẋ_∂u == 3+2 -@test c.solution.evals_∂y_∂x == 0 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 2+2 -@test c.solution.evals_∂y_∂t == 0 -reset!(c) - -# Jacobian C=∂y/∂x (in-place) -_f = _x -> fmu(;x=_x, y=y, y_refs=y_refs)[1:length(y_refs)] -_f(x) -j_fwd = ForwardDiff.jacobian(_f, x) -j_zyg = Zygote.jacobian(_f, x)[1] -j_rwd = ReverseDiff.jacobian(_f, x) -j_smp = fmi2SampleJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) -j_get = fmi2GetJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) - -@test isapprox(j_fwd, C; atol=atol) -@test isapprox(j_zyg, C; atol=atol) -@test isapprox(j_rwd, C; atol=atol) -@test isapprox(j_smp, C; atol=atol) -@test isapprox(j_get, C; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 2+4 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂y_∂x == 2+4 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 4 -@test c.solution.evals_∂y_∂t == 4 -reset!(c) - -# Jacobian C=∂y/∂x (out-of-place) -_f = _x -> fmu(;x=_x, y_refs=y_refs)[1:length(y_refs)] -_f(x) -j_fwd = ForwardDiff.jacobian(_f, x) -j_zyg = Zygote.jacobian(_f, x)[1] -j_rwd = ReverseDiff.jacobian(_f, x) - -@test isapprox(j_fwd, C; atol=atol) -@test isapprox(j_zyg, C; atol=atol) -@test isapprox(j_rwd, C; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 6 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂y_∂x == 6 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 4 -@test c.solution.evals_∂y_∂t == 4 -reset!(c) - -# Jacobian D=∂y/∂u (in-place) -_f = _u -> fmu(;x=x, u=_u, u_refs=u_refs, y=y, y_refs=y_refs)[1:length(y_refs)] -_f(u) -j_fwd = ForwardDiff.jacobian(_f, u) -j_zyg = Zygote.jacobian(_f, u)[1] -j_rwd = ReverseDiff.jacobian(_f, u) -j_smp = fmi2SampleJacobian(c, y_refs, u_refs) -j_get = fmi2GetJacobian(c, y_refs, u_refs) - -@test isapprox(j_fwd, D; atol=atol) -@test isapprox(j_zyg, D; atol=atol) -@test isapprox(j_rwd, D; atol=atol) -@test isapprox(j_smp, D; atol=atol) -@test isapprox(j_get, D; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 5 -@test c.solution.evals_∂ẋ_∂u == 5 -@test c.solution.evals_∂y_∂x == 5 -@test c.solution.evals_∂y_∂u == 5 -@test c.solution.evals_∂ẋ_∂t == 4 -@test c.solution.evals_∂y_∂t == 4 -reset!(c) - -# Jacobian D=∂y/∂u (out-of-place) -_f = _u -> fmu(;x=x, u=_u, u_refs=u_refs, y_refs=y_refs)[1:length(y_refs)] -_f(u) -j_fwd = ForwardDiff.jacobian(_f, u) -j_zyg = Zygote.jacobian(_f, u)[1] -j_rwd = ReverseDiff.jacobian(_f, u) - -@test isapprox(j_fwd, D; atol=atol) -@test isapprox(j_zyg, D; atol=atol) -@test isapprox(j_rwd, D; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 5 -@test c.solution.evals_∂ẋ_∂u == 5 -@test c.solution.evals_∂y_∂x == 5 -@test c.solution.evals_∂y_∂u == 5 -@test c.solution.evals_∂ẋ_∂t == 4 -@test c.solution.evals_∂y_∂t == 4 -reset!(c) - -# explicit time derivative ∂dx/∂t -_f = _t -> fmu(;x=x, t=_t) -_f(t) -j_fwd = ForwardDiff.derivative(_f, t) -j_zyg = Zygote.jacobian(_f, t)[1] - -# ReverseDiff has no `derivative` function for scalars -_f = _t -> fmu(;x=x, t=_t[1]) -j_rwd = ReverseDiff.jacobian(_f, [t]) - -@test isapprox(j_fwd, dx_t; atol=atol) -@test isapprox(j_zyg, dx_t; atol=atol) -@test isapprox(j_rwd, dx_t; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 5 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂y_∂x == 0 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 5 -@test c.solution.evals_∂y_∂t == 0 -reset!(c) - -# explicit time derivative ∂y/∂t -_f = _t -> fmu(;x=x, y=y, y_refs=y_refs, t=_t)[1:length(y_refs)] -_f(t) -j_fwd = ForwardDiff.derivative(_f, t) -j_zyg = Zygote.jacobian(_f, t)[1] - -# ReverseDiff has no `derivative` function for scalars -_f = _t -> fmu(;x=x, t=_t[1]) -j_rwd = ReverseDiff.jacobian(_f, [t]) - -@test isapprox(j_fwd, y_t; atol=atol) -@test isapprox(j_zyg, y_t; atol=atol) -@test isapprox(j_rwd, y_t; atol=atol) - -@test c.solution.evals_∂ẋ_∂x == 5 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂y_∂x == 3 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂ẋ_∂t == 5 -@test c.solution.evals_∂y_∂t == 3 -reset!(c) - -# Jacobian E=∂dx/∂p -_f = _p -> fmu(;x=x, p=_p, p_refs=p_refs) -_f(p) -j_fwd = ForwardDiff.jacobian(_f, p) -j_zyg = Zygote.jacobian(_f, p)[1] -j_rwd = ReverseDiff.jacobian(_f, p) -j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.parameterValueReferences) -j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.parameterValueReferences) - -@test isapprox(j_fwd, E; atol=atol) -@test isapprox(j_zyg, E; atol=atol) -@test isapprox(j_rwd, E; atol=atol) -@test isapprox(j_smp, E; atol=atol) -@test isapprox(j_get, E; atol=atol) - -reset!(c) - -# Jacobian F=∂y/∂p -_f = _p -> fmu(;p=_p, p_refs=p_refs, y_refs=y_refs)[1:length(y_refs)] -_f(p) -j_fwd = ForwardDiff.jacobian(_f, p) -j_zyg = Zygote.jacobian(_f, p)[1] -j_rwd = ReverseDiff.jacobian(_f, p) -j_smp = fmi2SampleJacobian(c, fmu.modelDescription.outputValueReferences, fmu.modelDescription.parameterValueReferences) -j_get = fmi2GetJacobian(c, fmu.modelDescription.outputValueReferences, fmu.modelDescription.parameterValueReferences) - -@test isapprox(j_fwd, F; atol=atol) -@test isapprox(j_zyg, F; atol=atol) -@test isapprox(j_rwd, F; atol=atol) -@test isapprox(j_smp, F; atol=atol) -@test isapprox(j_get, F; atol=atol) - -reset!(c) - -# clean up -fmi2Unload(fmu) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index e2baa38..4ac2bff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,8 +12,7 @@ using FMIImport.FMICore: fmi2Integer, fmi2Boolean, fmi2Real, fmi2String using FMIImport.FMICore: fmi3Float32, fmi3Float64, fmi3Int8, fmi3UInt8, fmi3Int16, fmi3UInt16, fmi3Int32, fmi3UInt32, fmi3Int64, fmi3UInt64 using FMIImport.FMICore: fmi3Boolean, fmi3String, fmi3Binary -import FMIImport.FMICore: FMU2_EXECUTION_CONFIGURATION_NO_FREEING, FMU2_EXECUTION_CONFIGURATION_NO_RESET, FMU2_EXECUTION_CONFIGURATION_RESET, FMU2_EXECUTION_CONFIGURATION_NOTHING -import FMIImport.FMICore: FMU3_EXECUTION_CONFIGURATION_NO_FREEING, FMU3_EXECUTION_CONFIGURATION_NO_RESET, FMU3_EXECUTION_CONFIGURATION_RESET +import FMIImport.FMICore: FMU2_EXECUTION_CONFIGURATIONS, FMU3_EXECUTION_CONFIGURATIONS exportingToolsWindows = [("Dymola", "2022x")] exportingToolsLinux = [("Dymola", "2022x")] @@ -23,7 +22,7 @@ function runtestsFMI2(exportingTool) ENV["EXPORTINGVERSION"] = exportingTool[2] # enable assertions for warnings/errors for all default execution configurations - for exec in [FMU2_EXECUTION_CONFIGURATION_NO_FREEING, FMU2_EXECUTION_CONFIGURATION_NO_RESET, FMU2_EXECUTION_CONFIGURATION_RESET, FMU2_EXECUTION_CONFIGURATION_NOTHING] + for exec in FMU2_EXECUTION_CONFIGURATIONS exec.assertOnError = true exec.assertOnWarning = true end @@ -38,10 +37,6 @@ function runtestsFMI2(exportingTool) end end - @testset "Sensitivities" begin - include("FMI2/sensitivities.jl") - end - @testset "Model Description Parsing" begin include("FMI2/model_description.jl") end @@ -60,7 +55,7 @@ function runtestsFMI3(exportingTool) ENV["EXPORTINGVERSION"] = exportingTool[2] # enable assertions for warnings/errors for all default execution configurations - for exec in [FMU3_EXECUTION_CONFIGURATION_NO_FREEING, FMU3_EXECUTION_CONFIGURATION_NO_RESET, FMU3_EXECUTION_CONFIGURATION_RESET] + for exec in FMU3_EXECUTION_CONFIGURATIONS exec.assertOnError = true exec.assertOnWarning = true end