From 1e4f7c1bd28f499a50fc63814910a843dc2ad94c Mon Sep 17 00:00:00 2001 From: TT Date: Tue, 28 May 2024 11:05:50 +0200 Subject: [PATCH] fixes for FMIBase --- Project.toml | 2 +- src/sense.jl | 61 +++-- test/{FMI2 => }/jacobians_gradients.jl | 331 ++++++++++++------------- test/runtests.jl | 82 +++--- test/{FMI2 => }/solution.jl | 0 5 files changed, 248 insertions(+), 228 deletions(-) rename test/{FMI2 => }/jacobians_gradients.jl (62%) rename test/{FMI2 => }/solution.jl (100%) diff --git a/Project.toml b/Project.toml index b22db9c..a847dae 100644 --- a/Project.toml +++ b/Project.toml @@ -11,5 +11,5 @@ SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" [compat] FMIBase = "1.0.0" ForwardDiffChainRules = "0.2.0" -SciMLSensitivity = "7.0 - 7.58" +SciMLSensitivity = "7.0 - 7.59" julia = "1.6" diff --git a/src/sense.jl b/src/sense.jl index 8a6e6e0..3549d8e 100644 --- a/src/sense.jl +++ b/src/sense.jl @@ -5,10 +5,10 @@ import FMIBase: eval!, invalidate!, check_invalidate! using FMIBase: getDirectionalDerivative!, getAdjointDerivative! -using FMIBase: setContinuousStates, setInputs, setReal, setTime, setReal, getReal!, getEventIndicators! +using FMIBase: setContinuousStates, setInputs, setReal, setTime, setReal, getReal!, getEventIndicators!, getRealType # in FMI2 and FMI3 we can use fmi2GetDirectionalDerivative for JVP-computations -function jvp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) +function jvp!(c::FMUInstance, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) jac = getfield(c, mtxCache) if isnothing(jac) @@ -23,14 +23,14 @@ function jvp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) jac.x_refs = ∂x_refs if c.fmu.executionConfig.JVPBuiltInDerivatives && providesDirectionalDerivatives(c.fmu) && !isa(jac.f_refs, Tuple) && !isa(jac.x_refs, Symbol) - getDirectionalDerivative!(c, ∂f_refs, ∂x_refs, jac.jvp, seed) + getDirectionalDerivative!(c, ∂f_refs, ∂x_refs, seed, jac.jvp) return jac.jvp else return jvp!(jac, x, seed) end end -function gvp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) +function gvp!(c::FMUInstance, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) grad = getfield(c, mtxCache) if isnothing(grad) @@ -45,7 +45,7 @@ function gvp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) grad.x_refs = ∂x_refs if c.fmu.executionConfig.JVPBuiltInDerivatives && providesDirectionalDerivatives(c.fmu) && !isa(grad.f_refs, Tuple) && !isa(grad.x_refs, Symbol) - getDirectionalDerivative!(c, ∂f_refs, ∂x_refs, grad.gvp, [seed]) + getDirectionalDerivative!(c, ∂f_refs, ∂x_refs, [seed], grad.gvp) return grad.gvp else return gvp!(grad, x, seed) @@ -53,7 +53,7 @@ function gvp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) end # in FMI2 there is no helper for VJP-computations (but in FMI3) ... -function vjp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) +function vjp!(c::FMUInstance, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) jac = getfield(c, mtxCache) if isnothing(jac) @@ -68,14 +68,14 @@ function vjp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) jac.x_refs = ∂x_refs if c.fmu.executionConfig.VJPBuiltInDerivatives && providesAdjointDerivatives(c.fmu) && !isa(jac.f_refs, Tuple) && !isa(jac.x_refs, Symbol) - getAdjointDerivative!(c, ∂f_refs, ∂x_refs, jac.vjp, seed) + getAdjointDerivative!(c, ∂f_refs, ∂x_refs, seed, jac.vjp) return jac.vjp else return vjp!(jac, x, seed) end end -function vgp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) +function vgp!(c::FMUInstance, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) grad = getfield(c, mtxCache) if isnothing(grad) @@ -90,7 +90,7 @@ function vgp!(c::FMU2Component, mtxCache::Symbol, ∂f_refs, ∂x_refs, x, seed) grad.x_refs = ∂x_refs if c.fmu.executionConfig.VJPBuiltInDerivatives && providesAdjointDerivatives(c.fmu) && !isa(grad.f_refs, Tuple) && !isa(grad.x_refs, Symbol) - getAdjointDerivative!(c, ∂f_refs, ∂x_refs, grad.vgp, [seed]) + getAdjointDerivative!(c, ∂f_refs, ∂x_refs, [seed], grad.vgp) return grad.vgp else return vgp!(grad, x, seed) @@ -308,7 +308,7 @@ function ChainRulesCore.rrule(::typeof(FMIBase.eval!), ec_idcs, t) - @assert !isa(cRef, FMU2Component) "Wrong dispatched!" + @assert !isa(cRef, FMUInstance) "Wrong dispatched!" @debug "rrule start: $((cRef, dx, dx_refs, y, y_refs, x, u, u_refs, p, p_refs, ec, ec_idcs, t))" @@ -998,9 +998,9 @@ end # FiniteDiff Jacobians -abstract type FMU2Sensitivities end +abstract type FMUSensitivities end -mutable struct FMUJacobian{C, T, F} <: FMU2Sensitivities +mutable struct FMUJacobian{C, T, F} <: FMUSensitivities valid::Bool colored::Bool component::C @@ -1065,7 +1065,7 @@ mutable struct FMUJacobian{C, T, F} <: FMU2Sensitivities end -mutable struct FMUGradient{C, T, F} <: FMU2Sensitivities +mutable struct FMUGradient{C, T, F} <: FMUSensitivities valid::Bool colored::Bool component::C @@ -1152,12 +1152,12 @@ function f_∂v_∂t(jac::FMUGradient, dx, x) return dx end -function FMIBase.invalidate!(sens::FMU2Sensitivities) +function FMIBase.invalidate!(sens::FMUSensitivities) sens.valid = false return nothing end -function FMIBase.check_invalidate!(vrs, sens::FMU2Sensitivities) +function FMIBase.check_invalidate!(vrs, sens::FMUSensitivities) if !sens.valid return end @@ -1175,22 +1175,41 @@ function FMIBase.check_invalidate!(vrs, sens::FMU2Sensitivities) return nothing end -function uncolor!(jac::FMU2Sensitivities) +function uncolor!(jac::FMUSensitivities) jac.colored = false return nothing end +function onehot(c::FMUInstance, len::Integer, i::Integer) # [ToDo] this could be solved without allocations + ret = zeros(getRealType(c), len) + ret[i] = 1.0 + return ret +end + function validate!(jac::FMUJacobian, x::AbstractVector) + rows = length(jac.f_refs) + cols = length(jac.x_refs) + if jac.component.fmu.executionConfig.sensitivity_strategy == :FMIDirectionalDerivative && providesDirectionalDerivatives(jac.component.fmu) && !isa(jac.f_refs, Tuple) && !isa(jac.x_refs, Symbol) # ToDo: use directional derivatives with sparsitiy information! - # ToDo: Optimize allocation (ones) - for i in 1:length(jac.x_refs) - getDirectionalDerivative!(jac.component, jac.f_refs, [jac.x_refs[i]], view(jac.mtx, 1:length(jac.f_refs), i), ones(length(jac.f_refs))) + # ToDo: Optimize allocation (onehot) + # [Note] Jacobian is sampled column by column + for i in 1:cols + getDirectionalDerivative!(jac.component, jac.f_refs, jac.x_refs, onehot(jac.component, cols, i), view(jac.mtx, 1:rows, i)) + end + elseif jac.component.fmu.executionConfig.sensitivity_strategy == :FMIAdjointDerivative && providesAdjointDerivatives(jac.component.fmu) && !isa(jac.f_refs, Tuple) && !isa(jac.x_refs, Symbol) + # ToDo: use directional derivatives with sparsitiy information! + # ToDo: Optimize allocation (onehot) + # [Note] Jacobian is sampled row by row + for i in 1:rows + getAdjointDerivative!(jac.component, jac.f_refs, jac.x_refs, onehot(jac.component, rows, i), view(jac.mtx, 1:cols, i)) end else #if jac.component.fmu.executionConfig.sensitivity_strategy == :FiniteDiff # cache = FiniteDiff.JacobianCache(x) FiniteDiff.finite_difference_jacobian!(jac.mtx, (_x, _dx) -> (jac.f(jac, _x, _dx)), x) # , cache) + # else + # @assert false "Unknown sensitivity strategy `$(jac.component.fmu.executionConfig.sensitivity_strategy)`." end jac.validations += 1 @@ -1202,7 +1221,7 @@ function validate!(grad::FMUGradient, x::Real) if grad.component.fmu.executionConfig.sensitivity_strategy == :FMIDirectionalDerivative && providesDirectionalDerivatives(grad.component.fmu) && !isa(grad.f_refs, Tuple) && !isa(grad.x_refs, Symbol) # ToDo: use directional derivatives with sparsitiy information! - getDirectionalDerivative!(grad.component, grad.f_refs, grad.x_refs, grad.vec, ones(length(jac.f_refs))) + getDirectionalDerivative!(grad.component, grad.f_refs, grad.x_refs, ones(length(jac.f_refs)), grad.vec) else #if grad.component.fmu.executionConfig.sensitivity_strategy == :FiniteDiff # cache = FiniteDiff.GradientCache(x) FiniteDiff.finite_difference_gradient!(grad.vec, (_x, _dx) -> (grad.f(grad, _x, _dx)), x) # , cache) @@ -1213,7 +1232,7 @@ function validate!(grad::FMUGradient, x::Real) return nothing end -function color!(sens::FMU2Sensitivities) +function color!(sens::FMUSensitivities) # ToDo # colors = SparseDiffTools.matrix_colors(sparsejac) diff --git a/test/FMI2/jacobians_gradients.jl b/test/jacobians_gradients.jl similarity index 62% rename from test/FMI2/jacobians_gradients.jl rename to test/jacobians_gradients.jl index 8831bdf..7688995 100644 --- a/test/FMI2/jacobians_gradients.jl +++ b/test/jacobians_gradients.jl @@ -11,30 +11,31 @@ import FMISensitivity.ReverseDiff using FMISensitivity.FMIBase using FMISensitivity.FMIBase.FMICore +using FMISensitivity.FMIBase: getContinuousStates, getReal, getRealType, getEventIndicators, getDirectionalDerivative + CHECK_ZYGOTE = false # load demo FMU -fmu = loadFMU("SpringPendulumExtForce1D", EXPORTINGTOOL, EXPORTINGVERSION; type=:ME) +c, fmu = getFMUStruct("SpringFrictionPendulumExtForce1D", :ME) +const FMU_SUPPORTS_PARAMETER_SAMPLING = false # 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) - x_refs = fmu.modelDescription.stateValueReferences -x = fmi2GetContinuousStates(c) -dx = fmi2GetReal(c, c.fmu.modelDescription.derivativeValueReferences) +x = getContinuousStates(c) +dx_refs = c.fmu.modelDescription.derivativeValueReferences +dx = getReal(c, dx_refs) u_refs = fmu.modelDescription.inputValueReferences -u = [0.0] +u = zeros(getRealType(fmu), length(u_refs)) y_refs = fmu.modelDescription.outputValueReferences -y = fmi2GetReal(c, y_refs) +y = getReal(c, y_refs) p_refs = fmu.modelDescription.parameterValueReferences -p = fmi2GetReal(c, p_refs) -e = fmi2GetEventIndicators(c) +p = getReal(c, p_refs) +e = getEventIndicators(c) t = 0.0 -reset! = function(c::FMIImport.FMU2Component) +reset! = function(c::FMUInstance) c.solution.evals_∂ẋ_∂x = 0 c.solution.evals_∂ẋ_∂u = 0 c.solution.evals_∂ẋ_∂p = 0 @@ -80,47 +81,51 @@ ydx = fmu(;x=x, u=u, u_refs=u_refs, y=y, y_refs=y_refs, dx=dx, dx_refs=:all, p=p @test length(ydx) == 4 # known results -atol= 1e-7 -∂ẋ_∂x = [0.0 1.0; -10.0 0.0] +atol= 1e-3 # 1e-7 +∂ẋ_∂x = [0.0 1.0; -10.0 -0.05] ∂ẋ_∂u = [0.0; 1.0] -∂ẋ_∂p = [0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 10.0 0.6 10.0 -6.0 5.0] -∂y_∂x = [0.0 1.0; -10.0 0.0] +∂ẋ_∂p = [0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0; + 0.0375 0.0 0.0 10.0 0.6 10.0 0.0 0.0 0.0 5.0 -5.25 0.0] +∂y_∂x = [0.0 1.0; -10.0 -0.05] ∂y_∂u = [0.0; 1.0] -∂y_∂p = [0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 10.0 0.6 10.0 -6.0 5.0] +∂y_∂p = [0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0; + 0.0375 0.0 0.0 10.0 0.6 10.0 0.0 0.0 0.0 5.0 -5.25 0.0] ∂ẋ_∂t = [0.0, 0.0] ∂y_∂t = [0.0, 0.0] -∂e_∂x = [0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 -1.0 0.0; -0.0 0.0 0.0 0.0 -1.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; -0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 -1.0 0.0; - 0.0 0.0 0.0 0.0 -1.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 0.0 0.0; - 0.0 0.0 0.0 0.0 -132.390625 0.0; - 0.0 0.0 0.0 0.0 -132.390625 0.0; - 0.0 0.0 0.0 0.0 132.389404296875 0.0; - 0.0 0.0 0.0 0.0 132.389404296875 0.0; - 0.0 0.0 0.0 1.0 0.0 0.0; - 0.0 0.0 0.0 1.0 0.0 0.0] - -# Test build-in derivatives (slow) only for jacobian A +∂e_∂x = [0.0 0.0; + 0.0 0.0; + 1.0 0.0; + 1.0 0.0; + -10.0 -0.05; + -10.0 -0.05; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 1.0; + 0.0 1.0; + -10.0 -0.05; + -10.0 -0.05; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 0.0 0.0; + 1.0 0.0; + 1.0 0.0; + 1.0 0.0; + 1.0 0.0] + +# Test build-in directional derivatives (slow) only for jacobian A fmu.executionConfig.JVPBuiltInDerivatives = true _f = _x -> fmu(;x=_x, dx_refs=:all) @@ -128,8 +133,8 @@ _f(x) j_fwd = ForwardDiff.jacobian(_f, x) j_rwd = ReverseDiff.jacobian(_f, x) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing -# j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) -# j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +# j_smp = sampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +# j_get = getJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) @test isapprox(j_fwd, ∂ẋ_∂x; atol=atol) @test isapprox(j_rwd, ∂ẋ_∂x; atol=atol) @@ -140,7 +145,23 @@ j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing # End: Test build-in derivatives (slow) only for jacobian A fmu.executionConfig.JVPBuiltInDerivatives = false -@test c.solution.evals_∂ẋ_∂x == (CHECK_ZYGOTE ? 6 : 4) +# Test build-in adjoint derivatives (slow) only for jacobian A +fmu.executionConfig.VJPBuiltInDerivatives = true + +_f = _x -> fmu(;x=_x, dx_refs=:all) +_f(x) +j_fwd = ForwardDiff.jacobian(_f, x) +j_rwd = ReverseDiff.jacobian(_f, x) +j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing + +@test isapprox(j_fwd, ∂ẋ_∂x; atol=atol) +@test isapprox(j_rwd, ∂ẋ_∂x; atol=atol) +@test CHECK_ZYGOTE ? isapprox(j_zyg, ∂ẋ_∂x; atol=atol) : true + +# End: Test build-in derivatives (slow) only for jacobian A +fmu.executionConfig.VJPBuiltInDerivatives = false + +@test c.solution.evals_∂ẋ_∂x == (CHECK_ZYGOTE ? 10 : 8) @test c.solution.evals_∂ẋ_∂u == 0 @test c.solution.evals_∂ẋ_∂p == 0 @test c.solution.evals_∂ẋ_∂t == 0 @@ -162,8 +183,8 @@ _f(x) j_fwd = ForwardDiff.jacobian(_f, x) j_rwd = ReverseDiff.jacobian(_f, x) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing -#j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) -#j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +#j_smp = sampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +#j_get = getJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) @test isapprox(j_fwd, ∂ẋ_∂x; atol=atol) @test isapprox(j_rwd, ∂ẋ_∂x; atol=atol) @@ -193,8 +214,8 @@ _f(x) j_fwd = ForwardDiff.jacobian(_f, x) j_rwd = ReverseDiff.jacobian(_f, x) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing -#j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) -#j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +#j_smp = sampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) +#j_get = getJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.stateValueReferences) @test isapprox(j_fwd, ∂ẋ_∂x; atol=atol) @test isapprox(j_rwd, ∂ẋ_∂x; atol=atol) @@ -224,8 +245,8 @@ _f(u) j_fwd = ForwardDiff.jacobian(_f, u) j_rwd = ReverseDiff.jacobian(_f, u) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, u)[1] : nothing -#j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) -#j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) +#j_smp = fsampleJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) +#j_get = getJacobian(c, fmu.modelDescription.derivativeValueReferences, u_refs) @test isapprox(j_fwd, ∂ẋ_∂u; atol=atol) @test isapprox(j_rwd, ∂ẋ_∂u; atol=atol) @@ -255,8 +276,8 @@ _f(x) j_fwd = ForwardDiff.jacobian(_f, x) j_rwd = ReverseDiff.jacobian(_f, x) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing -#j_smp = fmi2SampleJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) -#j_get = fmi2GetJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) +#j_smp = sampleJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) +#j_get = getJacobian(c, y_refs, fmu.modelDescription.stateValueReferences) @test isapprox(j_fwd, ∂y_∂x; atol=atol) @test isapprox(j_rwd, ∂y_∂x; atol=atol) @@ -286,8 +307,8 @@ _f(u) j_fwd = ForwardDiff.jacobian(_f, u) j_rwd = ReverseDiff.jacobian(_f, u) j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, u)[1] : nothing -#j_smp = fmi2SampleJacobian(c, y_refs, u_refs) -#j_get = fmi2GetJacobian(c, y_refs, u_refs) +#j_smp = sampleJacobian(c, y_refs, u_refs) +#j_get = getJacobian(c, y_refs, u_refs) @test isapprox(j_fwd, ∂y_∂u; atol=atol) @test isapprox(j_rwd, ∂y_∂u; atol=atol) @@ -374,136 +395,98 @@ j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, t)[1] : nothing reset!(c) # Jacobian ∂ẋ/∂p -_f = _p -> fmu(;p=_p, p_refs=p_refs, dx_refs=:all) -_f(p) -j_fwd = ForwardDiff.jacobian(_f, p) -j_rwd = ReverseDiff.jacobian(_f, p) -j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, p)[1] : nothing -#j_smp = fmi2SampleJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.parameterValueReferences) -#j_get = fmi2GetJacobian(c, fmu.modelDescription.derivativeValueReferences, fmu.modelDescription.parameterValueReferences) - -@test isapprox(j_fwd, ∂ẋ_∂p; atol=atol) -@test isapprox(j_rwd, ∂ẋ_∂p; atol=atol) -@test CHECK_ZYGOTE ? isapprox(j_zyg, ∂ẋ_∂p; atol=atol) : true -#@test isapprox(j_smp, ∂ẋ_∂p; atol=atol) -#@test isapprox(j_get, ∂ẋ_∂p; atol=atol) +#if FMU_SUPPORTS_PARAMETER_SAMPLING + _f = _p -> fmu(;p=_p, p_refs=p_refs, dx_refs=:all) + _f(p) + j_fwd = ForwardDiff.jacobian(_f, p) + j_rwd = ReverseDiff.jacobian(_f, p) + j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, p)[1] : nothing -@test c.solution.evals_∂ẋ_∂x == 0 -@test c.solution.evals_∂ẋ_∂u == 0 -@test c.solution.evals_∂ẋ_∂p == (CHECK_ZYGOTE ? 10 : 8) -@test c.solution.evals_∂ẋ_∂t == 0 + @test isapprox(j_fwd, ∂ẋ_∂p; atol=atol) + @test isapprox(j_rwd, ∂ẋ_∂p; atol=atol) + @test CHECK_ZYGOTE ? isapprox(j_zyg, ∂ẋ_∂p; atol=atol) : true -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂y_∂p == 0 -@test c.solution.evals_∂y_∂x == 0 -@test c.solution.evals_∂y_∂t == 0 + @test c.solution.evals_∂ẋ_∂x == 0 + @test c.solution.evals_∂ẋ_∂u == 0 + @test c.solution.evals_∂ẋ_∂p == (CHECK_ZYGOTE ? 16 : 14) + @test c.solution.evals_∂ẋ_∂t == 0 -@test c.solution.evals_∂e_∂x == 0 -@test c.solution.evals_∂e_∂u == 0 -@test c.solution.evals_∂e_∂p == 0 -@test c.solution.evals_∂e_∂t == 0 -reset!(c) + @test c.solution.evals_∂y_∂u == 0 + @test c.solution.evals_∂y_∂p == 0 + @test c.solution.evals_∂y_∂x == 0 + @test c.solution.evals_∂y_∂t == 0 + + @test c.solution.evals_∂e_∂x == 0 + @test c.solution.evals_∂e_∂u == 0 + @test c.solution.evals_∂e_∂p == 0 + @test c.solution.evals_∂e_∂t == 0 + reset!(c) +#end # Jacobian ∂y/∂p -_f = _p -> fmu(;p=_p, p_refs=p_refs, y_refs=y_refs) -_f(p) -j_fwd = ForwardDiff.jacobian(_f, p) -j_rwd = ReverseDiff.jacobian(_f, p) -j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, p)[1] : nothing -#j_smp = fmi2SampleJacobian(c, fmu.modelDescription.outputValueReferences, fmu.modelDescription.parameterValueReferences) -#j_get = fmi2GetJacobian(c, fmu.modelDescription.outputValueReferences, fmu.modelDescription.parameterValueReferences) - -@test isapprox(j_fwd, ∂y_∂p; atol=atol) -@test isapprox(j_rwd, ∂y_∂p; atol=atol) -@test CHECK_ZYGOTE ? isapprox(j_zyg, ∂y_∂p; atol=atol) : true -#@test isapprox(j_smp, ∂y_∂p; atol=atol) -#@test isapprox(j_get, ∂y_∂p; atol=atol) +#if FMU_SUPPORTS_PARAMETER_SAMPLING + _f = _p -> fmu(;p=_p, p_refs=p_refs, y_refs=y_refs) + _f(p) + j_fwd = ForwardDiff.jacobian(_f, p) + j_rwd = ReverseDiff.jacobian(_f, p) + j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, p)[1] : nothing + + @test isapprox(j_fwd, ∂y_∂p; atol=atol) + @test isapprox(j_rwd, ∂y_∂p; atol=atol) + @test CHECK_ZYGOTE ? isapprox(j_zyg, ∂y_∂p; atol=atol) : true + + @test c.solution.evals_∂ẋ_∂x == 0 + @test c.solution.evals_∂ẋ_∂u == 0 + @test c.solution.evals_∂ẋ_∂p == 0 + @test c.solution.evals_∂ẋ_∂t == 0 + + @test c.solution.evals_∂y_∂u == 0 + @test c.solution.evals_∂y_∂p == (CHECK_ZYGOTE ? 16 : 14) + @test c.solution.evals_∂y_∂x == 0 + @test c.solution.evals_∂y_∂t == 0 + + @test c.solution.evals_∂e_∂x == 0 + @test c.solution.evals_∂e_∂u == 0 + @test c.solution.evals_∂e_∂p == 0 + @test c.solution.evals_∂e_∂t == 0 + reset!(c) +#end + +# Jacobian ∂e/∂x +_f = function(_x) + ec_idcs = collect(UInt32(i) for i in 1:fmu.modelDescription.numberOfEventIndicators) + + ret = fmu(; ec_idcs=ec_idcs, x=_x) + + return ret.ec +end +_f(x) +j_fwd = ForwardDiff.jacobian(_f, x) +j_rwd = ReverseDiff.jacobian(_f, x) +j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing +# j_fid = FiniteDiff.finite_difference_jacobian(_f, x) +# j_smp = sampleJacobian(c, :indicators, fmu.modelDescription.parameterValueReferences) +# no option to get sensitivitities directly in FMI2/FMI3... + +@test isapprox(j_fwd, ∂e_∂x; atol=atol) +@test isapprox(j_rwd, ∂e_∂x; atol=atol) +@test CHECK_ZYGOTE ? isapprox(j_zyg, j_rwd; atol=atol) : true @test c.solution.evals_∂ẋ_∂x == 0 @test c.solution.evals_∂ẋ_∂u == 0 @test c.solution.evals_∂ẋ_∂p == 0 @test c.solution.evals_∂ẋ_∂t == 0 -@test c.solution.evals_∂y_∂u == 0 -@test c.solution.evals_∂y_∂p == (CHECK_ZYGOTE ? 10 : 8) @test c.solution.evals_∂y_∂x == 0 +@test c.solution.evals_∂y_∂u == 0 +@test c.solution.evals_∂y_∂p == 0 @test c.solution.evals_∂y_∂t == 0 -@test c.solution.evals_∂e_∂x == 0 +@test c.solution.evals_∂e_∂x == (CHECK_ZYGOTE ? 62 : 34) @test c.solution.evals_∂e_∂u == 0 @test c.solution.evals_∂e_∂p == 0 @test c.solution.evals_∂e_∂t == 0 reset!(c) # clean up -unloadFMU(fmu) - -########## Event Indicators Check ########### - -# [ToDo] Enable Test for Linux too (by providing a FMU) - -if Sys.iswindows() - # load demo FMU - fmu = loadFMU("VLDM", EXPORTINGTOOL, "2020x"; type=:ME) - data = FMIZoo.VLDM(:train) - - # 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; parameters=data.params) - - x_refs = fmu.modelDescription.stateValueReferences - x = fmi2GetContinuousStates(c) - dx = fmi2GetReal(c, c.fmu.modelDescription.derivativeValueReferences) - u_refs = fmu.modelDescription.inputValueReferences - u = zeros(fmi2Real, 0) - y_refs = fmu.modelDescription.outputValueReferences - y = zeros(fmi2Real, 0) - p_refs = copy(fmu.modelDescription.parameterValueReferences) - - # remove some parameters - deleteat!(p_refs, findall(x -> (x >= UInt32(134217728) && x <= UInt32(134217737)), p_refs)) - p = fmi2GetReal(c, p_refs) - e = fmi2GetEventIndicators(c) - t = 0.0 - - # Jacobian ∂e/∂x - _f = function(_x) - ec_idcs = collect(UInt32(i) for i in 1:fmu.modelDescription.numberOfEventIndicators) - - ret = fmu(; ec_idcs=ec_idcs, x=_x) - - return ret.ec - end - _f(x) - j_fwd = ForwardDiff.jacobian(_f, x) - j_rwd = ReverseDiff.jacobian(_f, x) - j_zyg = CHECK_ZYGOTE ? Zygote.jacobian(_f, x)[1] : nothing - # j_fid = FiniteDiff.finite_difference_jacobian(_f, x) - # j_smp = fmi2SampleJacobian(c, :indicators, fmu.modelDescription.parameterValueReferences) - # no option to get sensitivitities directly in FMI2... - - @test isapprox(j_fwd, ∂e_∂x; atol=atol) - @test isapprox(j_rwd, ∂e_∂x; atol=atol) - @test CHECK_ZYGOTE ? isapprox(j_zyg, j_rwd; atol=atol) : true - - @test c.solution.evals_∂ẋ_∂x == 0 - @test c.solution.evals_∂ẋ_∂u == 0 - @test c.solution.evals_∂ẋ_∂p == 0 - @test c.solution.evals_∂ẋ_∂t == 0 - - @test c.solution.evals_∂y_∂x == 0 - @test c.solution.evals_∂y_∂u == 0 - @test c.solution.evals_∂y_∂p == 0 - @test c.solution.evals_∂y_∂t == 0 - - @test c.solution.evals_∂e_∂x == (CHECK_ZYGOTE ? 62 : 34) - @test c.solution.evals_∂e_∂u == 0 - @test c.solution.evals_∂e_∂p == 0 - @test c.solution.evals_∂e_∂t == 0 - reset!(c) - - # clean up - unloadFMU(fmu) -end \ No newline at end of file +unloadFMU(fmu) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 9c15ec8..9a25faa 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,30 +8,11 @@ using Test import Random using FMIZoo -using FMIImport.FMIBase.FMICore: fmi2Integer, fmi2Boolean, fmi2Real, fmi2String -using FMIImport.FMIBase: FMU_EXECUTION_CONFIGURATIONS +using FMISensitivity +using FMISensitivity.FMIBase +using FMISensitivity.FMIBase: FMU_EXECUTION_CONFIGURATIONS -exportingToolsWindows = [("Dymola", "2022x")] -exportingToolsLinux = [("Dymola", "2022x")] - -global EXPORTINGTOOL -global EXPORTINGVERSION - -function runtestsFMI2(exportingTool) - global EXPORTINGTOOL, EXPORTINGVERSION - EXPORTINGTOOL = exportingTool[1] - EXPORTINGVERSION = exportingTool[2] - - @testset "Testing FMUs exported from $exportingTool" begin - @testset "Jacobians / Gradients" begin - include("FMI2/jacobians_gradients.jl") - end - - @testset "Solution" begin - include("FMI2/solution.jl") - end - end -end +fmuStructs = ("FMU", "FMUCOMPONENT") # enable assertions for warnings/errors for all default execution configurations for exec in FMU_EXECUTION_CONFIGURATIONS @@ -39,17 +20,54 @@ for exec in FMU_EXECUTION_CONFIGURATIONS exec.assertOnWarning = true end +function getFMUStruct(modelname, mode, tool=ENV["EXPORTINGTOOL"], version=ENV["EXPORTINGVERSION"], fmiversion=ENV["FMIVERSION"], fmustruct=ENV["FMUSTRUCT"]; kwargs...) + + # choose FMU or FMUComponent + if endswith(modelname, ".fmu") + fmu = loadFMU(modelname; kwargs...) + else + fmu = loadFMU(modelname, tool, version, fmiversion; kwargs...) + end + + if fmustruct == "FMU" + return fmu, fmu + + elseif fmustruct == "FMUCOMPONENT" + inst, _ = FMIImport.prepareSolveFMU(fmu, nothing, mode; loggingOn=true) + @test !isnothing(inst) + return inst, fmu + + else + @assert false "Unknown fmuStruct, variable `FMUSTRUCT` = `$(fmustruct)`" + end +end + @testset "FMIImport.jl" begin - if Sys.iswindows() - @info "Automated testing is supported on Windows." - for exportingTool in exportingToolsWindows - runtestsFMI2(exportingTool) - end - elseif Sys.islinux() - @info "Automated testing is supported on Linux." - for exportingTool in exportingToolsLinux - runtestsFMI2(exportingTool) + if Sys.iswindows() || Sys.islinux() + @info "Automated testing is supported on Windows/Linux." + + ENV["EXPORTINGTOOL"] = "Dymola" + ENV["EXPORTINGVERSION"] = "2023x" + + for fmiversion in (2.0, 3.0) + ENV["FMIVERSION"] = fmiversion + + @testset "Testing FMI $(ENV["FMIVERSION"]) FMUs exported from $(ENV["EXPORTINGTOOL"]) $(ENV["EXPORTINGVERSION"])" begin + + ENV["FMUSTRUCT"] = "FMUCOMPONENT" + + @testset "Functions for $(ENV["FMUSTRUCT"])" begin + @testset "Jacobians / Gradients" begin + include("jacobians_gradients.jl") + end + + @testset "Solution" begin + include("solution.jl") + end + end + end end + elseif Sys.isapple() @warn "Test-sets are currrently using Windows- and Linux-FMUs, automated testing for macOS is currently not supported." end diff --git a/test/FMI2/solution.jl b/test/solution.jl similarity index 100% rename from test/FMI2/solution.jl rename to test/solution.jl