diff --git a/Project.toml b/Project.toml index 331ea290..aae166a0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "FMI" uuid = "14a09403-18e3-468f-ad8a-74f8dda2d9ac" authors = ["TT ", "LM ", "JK "] -version = "0.12.2" +version = "0.12.3" [deps] DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def" @@ -17,7 +17,7 @@ ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" DiffEqCallbacks = "2.26.0" DifferentialEquations = "7.7.0" FMIExport = "0.2.0" -FMIImport = "0.15.5" +FMIImport = "0.15.6" ProgressMeter = "1.7.0" Requires = "1.3.0" ThreadPools = "2.1.1" diff --git a/src/FMI2/sim.jl b/src/FMI2/sim.jl index f92ab777..1ac0068c 100644 --- a/src/FMI2/sim.jl +++ b/src/FMI2/sim.jl @@ -27,6 +27,8 @@ function time_choice(c::FMU2Component, integrator, tStart, tStop) #@info "TC" + c.solution.evals_timechoice += 1 + if c.eventInfo.nextEventTimeDefined == fmi2True if c.eventInfo.nextEventTime >= tStart && c.eventInfo.nextEventTime <= tStop @@ -47,6 +49,8 @@ function condition(c::FMU2Component, out::AbstractArray{<:Real}, x, t, integrato @assert c.state == fmi2ComponentStateContinuousTimeMode "condition(...): Must be called in mode continuous time." + c.solution.evals_condition += 1 + t = undual(t) x = undual(x) @@ -66,6 +70,8 @@ function affectFMU!(c::FMU2Component, integrator, idx, inputFunction, inputValue @assert c.state == fmi2ComponentStateContinuousTimeMode "affectFMU!(...): Must be in continuous time mode!" + c.solution.evals_affect += 1 + # there are fx-evaluations before the event is handled, reset the FMU state to the current integrator step fmi2SetContinuousStates(c, integrator.u; force=true) fmi2SetTime(c, integrator.t; force=true) @@ -112,6 +118,9 @@ function stepCompleted(c::FMU2Component, x, t, integrator, inputFunction, inputV @assert c.state == fmi2ComponentStateContinuousTimeMode "stepCompleted(...): Must be in continuous time mode." #@info "Step completed" + + c.solution.evals_stepcompleted += 1 + if progressMeter !== nothing stat = 1000.0*(t-tStart)/(tStop-tStart) if !isnan(stat) @@ -140,10 +149,14 @@ function saveValues(c::FMU2Component, recordValues, x, t, integrator, inputFunct @assert c.state == fmi2ComponentStateContinuousTimeMode "saveValues(...): Must be in continuous time mode." + c.solution.evals_savevalues += 1 + #x_old = fmi2GetContinuousStates(c) #t_old = c.t - fmi2SetContinuousStates(c, x) + if !c.fmu.isZeroState + fmi2SetContinuousStates(c, x) + end fmi2SetTime(c, t) if inputFunction != nothing fmi2SetReal(c, inputValues, inputFunction(c, x, t)) @@ -152,7 +165,7 @@ function saveValues(c::FMU2Component, recordValues, x, t, integrator, inputFunct #fmi2SetContinuousStates(c, x_old) #fmi2SetTime(c, t_old) - return (fmiGetReal(c, recordValues)...,) + return (fmiGet(c, recordValues)...,) end function fx(c::FMU2Component, @@ -161,7 +174,13 @@ function fx(c::FMU2Component, p::AbstractArray, t::Real) - _, dx = c(;dx=dx, x=x, t=t) + c.solution.evals_fx_inplace += 1 + + if c.fmu.executionConfig.concat_y_dx + dx[:] = c(;dx=dx, x=x, t=t) + else + _, dx[:] = c(;dx=dx, x=x, t=t) + end return dx end @@ -171,7 +190,15 @@ function fx(c::FMU2Component, p::AbstractArray, t::Real) - _, dx = c(;x=x, t=t) + c.solution.evals_fx_outofplace += 1 + + dx = nothing + + if c.fmu.executionConfig.concat_y_dx + dx = c(;x=x, t=t) + else + _, dx = c(;x=x, t=t) + end return dx end @@ -216,7 +243,7 @@ State- and Time-Events are handled correctly. Via the optional keyword arguemnts `inputValues` and `inputFunction`, a custom input function `f(c, u, t)`, `f(c, t)`, `f(u, t)`, `f(c, u)` or `f(t)` with `c` current component, `u` current state and `t` current time can be defined, that should return a array of values for `fmi2SetReal(..., inputValues, inputFunction(...))`. Keywords: - - solver: Any Julia-supported ODE-solver (default is Tsit5) + - solver: Any Julia-supported ODE-solver (default is the DifferentialEquations.jl default solver, currently `AutoTsit5(Rosenbrock23())`) - customFx: [deprecated] Ability to give a custom state derivative function ẋ=f(x,t) - recordValues: Array of variables (strings or variableIdentifiers) to record. Results are returned as `DiffEqCallbacks.SavedValues` - saveat: Time points to save values at (interpolated) @@ -332,6 +359,11 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tsp c, x0 = prepareSolveFMU(fmu, c, fmi2TypeModelExchange, instantiate, freeInstance, terminate, reset, setup, parameters, t_start, t_stop, nothing; x0=x0, inputs=inputs, handleEvents=FMI.handleEvents) fmusol = c.solution + # Zero state FMU: add dummy state + if c.fmu.isZeroState + x0 = [0.0] + end + # from here on, we are in event mode, if `setup=false` this is the job of the user #@assert c.state == fmi2ComponentStateEventMode "FMU needs to be in event mode after setup." @@ -385,7 +417,8 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tsp end if savingValues - fmusol.values = SavedValues(Float64, Tuple{collect(Float64 for i in 1:length(recordValues))...}) + dtypes = collect(fmi2DataTypeForValueReference(c.fmu.modelDescription, vr) for vr in recordValues) + fmusol.values = SavedValues(Float64, Tuple{dtypes...}) fmusol.valueReferences = copy(recordValues) if saveat === nothing @@ -432,6 +465,11 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, tsp @warn "FMU simulation failed with solver return code `$(fmusol.states.retcode)`, please check log for hints." end + # ZeroStateFMU: remove dummy state + if c.fmu.isZeroState + c.solution.states = nothing + end + # cleanup progress meter if showProgress ProgressMeter.finish!(progressMeter) diff --git a/test/FMI2/sim_zero_state.jl b/test/FMI2/sim_zero_state.jl new file mode 100644 index 00000000..017c0d59 --- /dev/null +++ b/test/FMI2/sim_zero_state.jl @@ -0,0 +1,44 @@ +# +# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher +# Licensed under the MIT license. See LICENSE file in the project root for details. +# + +using DifferentialEquations: Tsit5 + +t_start = 0.0 +t_stop = 8.0 +solver = Tsit5() +dtmax = 0.01 + +function extForce_t(t) + [sin(t)] +end + +myFMU = fmiLoad("SpringPendulumExtForce1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"]) + +# make a dummy zero-state FMU by overwriting the state field (ToDo: Use an actual zero state FMU from FMIZoo.jl) +myFMU.modelDescription.stateValueReferences = [] +myFMU.modelDescription.derivativeValueReferences = [] +myFMU.modelDescription.numberOfEventIndicators = 0 +myFMU.isZeroState = true + +comp = fmiInstantiate!(myFMU; loggingOn=false) +@test comp != 0 + +# choose FMU or FMUComponent +fmuStruct = nothing +envFMUSTRUCT = ENV["FMUSTRUCT"] +if envFMUSTRUCT == "FMU" + fmuStruct = myFMU +elseif envFMUSTRUCT == "FMUCOMPONENT" + fmuStruct = comp +end +@assert fmuStruct != nothing "Unknown fmuStruct, environment variable `FMUSTRUCT` = `$envFMUSTRUCT`" + +solution = fmiSimulateME(fmuStruct, (t_start, t_stop); solver=solver, dtmax=dtmax, recordValues=["a"], inputValueReferences=myFMU.modelDescription.inputValueReferences, inputFunction=extForce_t) +@test isnothing(solution.states) + +@test solution.values.t[1] == t_start +@test solution.values.t[end] == t_stop + +fmiUnload(myFMU) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index f152efcd..c0c59968 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -66,6 +66,11 @@ function runtestsFMI2(exportingTool) include("FMI2/cs_me.jl") end + @info "Simulation FMU without states (sim_zero_state.jl)" + @testset "Simulation FMU without states" begin + include("FMI2/sim_zero_state.jl") + end + @info "Loading/Saving simulation results (load_save.jl)" @testset "Loading/Saving simulation results" begin include("FMI2/load_save.jl")