diff --git a/Project.toml b/Project.toml index 422cd9ec..b883451f 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.10.0" +version = "0.10.1" [deps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" @@ -20,7 +20,7 @@ ChainRulesCore = "1.15.0" DiffEqCallbacks = "2.24.0" DifferentialEquations = "7.5.0" FMIExport = "0.1.0" -FMIImport = "0.11.0" +FMIImport = "0.12.0" ForwardDiff = "0.10.0" ProgressMeter = "1.7.0" Requires = "1.3.0" diff --git a/README.md b/README.md index c521410d..497cd93f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ (@v1.6) pkg> add FMI ``` -(3)\. If you want to check that everything works correctly, you can run the tests bundled with [*FMI.jl*](https://github.com/ThummeTo/FMI.jl): +3\. If you want to check that everything works correctly, you can run the tests bundled with [*FMI.jl*](https://github.com/ThummeTo/FMI.jl): ```julia-repl (@v1.6) pkg> test FMI ``` @@ -97,12 +97,15 @@ To keep dependencies nice and clean, the original package [*FMI.jl*](https://git - ... ## What Platforms are supported? -[*FMI.jl*](https://github.com/ThummeTo/FMI.jl) is tested (and testing) under Julia Versions *1.6.5 LTS* (64-bit) and *latest* (64-bit) on Windows *latest* (64-bit) and Ubuntu *latest* (64-bit). Mac and Julia (32-bit) should work, but untested. +[*FMI.jl*](https://github.com/ThummeTo/FMI.jl) is tested (and testing) under Julia Versions *1.6.6 LTS* (64-bit) and *latest* (64-bit) on Windows *latest* (64-bit) and Ubuntu *latest* (64-bit). Mac and Julia (32-bit) should work, but untested. -## How to cite? Related publications? +## How to cite? Tobias Thummerer, Lars Mikelsons and Josef Kircher. 2021. **NeuralFMU: towards structural integration of FMUs into neural networks.** Martin Sjölund, Lena Buffoni, Adrian Pop and Lennart Ochel (Ed.). Proceedings of 14th Modelica Conference 2021, Linköping, Sweden, September 20-24, 2021. Linköping University Electronic Press, Linköping (Linköping Electronic Conference Proceedings ; 181), 297-306. [DOI: 10.3384/ecp21181297](https://doi.org/10.3384/ecp21181297) -Tobias Thummerer, Johannes Tintenherr, Lars Mikelsons 2021 **Hybrid modeling of the human cardiovascular system using NeuralFMUs** Journal of Physics: Conference Series 2090, 1, 012155. [DOI: 10.1088/1742-6596/2090/1/012155](https://doi.org/10.1088/1742-6596/2090/1/012155) +## Related publications? +Tobias Thummerer, Johannes Stoljar and Lars Mikelsons. 2022. **NeuralFMU: presenting a workflow for integrating hybrid NeuralODEs into real-world applications.** Electronics 11, 19, 3202. [DOI: 10.3390/electronics11193202](https://doi.org/10.3390/electronics11193202) + +Tobias Thummerer, Johannes Tintenherr, Lars Mikelsons. 2021 **Hybrid modeling of the human cardiovascular system using NeuralFMUs** Journal of Physics: Conference Series 2090, 1, 012155. [DOI: 10.1088/1742-6596/2090/1/012155](https://doi.org/10.1088/1742-6596/2090/1/012155) ## Interested in Hybrid Modelling in Julia using FMUs? See [*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl). diff --git a/src/FMI2_sim.jl b/src/FMI2_sim.jl index 4794a527..042a2460 100644 --- a/src/FMI2_sim.jl +++ b/src/FMI2_sim.jl @@ -130,6 +130,7 @@ function affectFMU!(c::FMU2Component, integrator, idx, inputFunction, inputValue u_modified!(integrator, true) #set_proposed_dt!(integrator, 1e-10) else + u_modified!(integrator, false) @debug "affectFMU!(...): Handled event at t=$(integrator.t), no new state." end @@ -152,8 +153,12 @@ function stepCompleted(c::FMU2Component, x, t, integrator, inputFunction, inputV @assert c.state == fmi2ComponentStateContinuousTimeMode "stepCompleted(...): Must be in continuous time mode." #@info "Step completed" - if progressMeter !== nothing - ProgressMeter.update!(progressMeter, floor(Integer, 1000.0*(t-tStart)/(tStop-tStart)) ) + if progressMeter !== nothing + stat = 1000.0*(t-tStart)/(tStop-tStart) + if !isnan(stat) + stat = floor(Integer, stat) + ProgressMeter.update!(progressMeter, stat) + end end (status, enterEventMode, terminateSimulation) = fmi2CompletedIntegratorStep(c, fmi2True) @@ -197,34 +202,36 @@ function fx(c::FMU2Component, p::AbstractArray, t::Real) - if isa(t, ForwardDiff.Dual) - t = ForwardDiff.value(t) - end + # if isa(t, ForwardDiff.Dual) + # t = ForwardDiff.value(t) + # end - fmi2SetContinuousStates(c, x) - fmi2SetTime(c, t) + # fmi2SetContinuousStates(c, x) + # fmi2SetTime(c, t) - if all(isa.(dx, ForwardDiff.Dual)) - dx_tmp = collect(ForwardDiff.value(e) for e in dx) - fmi2GetDerivatives!(c, dx_tmp) - 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)) - else - fmi2GetDerivatives!(c, dx) - end + # if all(isa.(dx, ForwardDiff.Dual)) + # dx_tmp = collect(ForwardDiff.value(e) for e in dx) + # fmi2GetDerivatives!(c, dx_tmp) + # 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)) + # else + # fmi2GetDerivatives!(c, dx) + # end + + y, dx = FMIImport.eval!(c, dx, nothing, nothing, x, nothing, nothing, t) return dx end # ForwardDiff-Dispatch for fx -function fx(comp::FMU2Component, - dx::AbstractArray{<:Real}, - x::AbstractArray{<:ForwardDiff.Dual{Tx, Vx, Nx}}, - p::AbstractArray, - t::Real) where {Tx, Vx, Nx} +# function fx(comp::FMU2Component, +# dx::AbstractArray{<:Real}, +# x::AbstractArray{<:ForwardDiff.Dual{Tx, Vx, Nx}}, +# p::AbstractArray, +# t::Real) where {Tx, Vx, Nx} - return _fx_fd(comp, dx, x, p, t) -end +# return _fx_fd(comp, dx, x, p, t) +# end # function _fx_fd(TVNx, comp, dx, x, p, t) @@ -758,6 +765,18 @@ function fmi2SimulateME(c::FMU2Component, t_start::Union{Real, Nothing} = nothin fmi2SimulateME(c.fmu, c, t_start, t_stop; kwargs...) end +# sets up the ODEProblem for simulating a ME-FMU +function setupODEProblem(c::FMU2Component, x0::AbstractArray{fmi2Real}, t_start::fmi2Real, t_stop::fmi2Real; p=[], customFx=nothing) + if customFx === nothing + customFx = (dx, x, p, t) -> fx(c, dx, x, p, t) + end + + p = [] + c.problem = ODEProblem(customFx, x0, (t_start, t_stop), p,) + + return c.problem +end + """ Simulates a FMU instance for the given simulation time interval. State- and Time-Events are handled correctly. @@ -869,7 +888,9 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, t_s dtmax = (t_stop-t_start)/100.0 end - c, x0 = prepareFMU(fmu, c, fmi2TypeModelExchange, instantiate, terminate, reset, setup, parameters, t_start, t_stop, tolerance; x0=x0, inputFunction=_inputFunction, inputValueReferences=inputValueReferences) + # argument `tolerance=nothing` here, because ME-FMUs doesn't support tolerance control (no solver included) + # tolerance for the solver is set-up later in this function + c, x0 = prepareFMU(fmu, c, fmi2TypeModelExchange, instantiate, terminate, reset, setup, parameters, t_start, t_stop, nothing; x0=x0, inputFunction=_inputFunction, inputValueReferences=inputValueReferences) # 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." @@ -886,12 +907,7 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, t_s c.fmu.hasStateEvents = (c.fmu.modelDescription.numberOfEventIndicators > 0) c.fmu.hasTimeEvents = (c.eventInfo.nextEventTimeDefined == fmi2True) - if customFx === nothing - customFx = (dx, x, p, t) -> fx(c, dx, x, p, t) - end - - p = [] - problem = ODEProblem(customFx, x0, (t_start, t_stop), p,) + setupODEProblem(c, x0, t_start, t_stop; customFx=customFx) progressMeter = nothing if showProgress @@ -945,7 +961,7 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, t_s # if auto_dt == true # @assert solver !== nothing "fmi2SimulateME(...): `auto_dt=true` but no solver specified, this is not allowed." - # tmpIntegrator = init(problem, solver) + # tmpIntegrator = init(c.problem, solver) # dt = auto_dt_reset!(tmpIntegrator) # end @@ -964,9 +980,9 @@ function fmi2SimulateME(fmu::FMU2, c::Union{FMU2Component, Nothing}=nothing, t_s end if solver === nothing - fmusol.states = solve(problem; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) + fmusol.states = solve(c.problem; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) else - fmusol.states = solve(problem, solver; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) + fmusol.states = solve(c.problem, solver; callback = CallbackSet(cbs...), dtmax=dtmax, solveKwargs..., kwargs...) end fmusol.success = (fmusol.states.retcode == :Success) diff --git a/test/runtests.jl b/test/runtests.jl index abbcd9ea..337ed9b2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,6 +12,12 @@ exportingToolsWindows = [("Dymola", "2022x")] exportingToolsLinux = [("Dymola", "2022x")] fmuStructs = ["FMU", "FMUCOMPONENT"] +# 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] + exec.assertOnError = true + exec.assertOnWarning = true +end + function runtests(exportingTool) ENV["EXPORTINGTOOL"] = exportingTool[1] ENV["EXPORTINGVERSION"] = exportingTool[2] diff --git a/test/sim_ME.jl b/test/sim_ME.jl index 3e3adc20..f5595378 100644 --- a/test/sim_ME.jl +++ b/test/sim_ME.jl @@ -171,17 +171,17 @@ elseif envFMUSTRUCT == "FMUCOMPONENT" end @assert fmuStruct != nothing "Unknown fmuStruct, environment variable `FMUSTRUCT` = `$envFMUSTRUCT`" -# ToDo: autodiff=true not working currently! -# solution = fmiSimulateME(fmuStruct, t_start, t_stop; inputValueReferences=["extForce"], inputFunction=extForce, solver=Rosenbrock23(autodiff=true), dtmax=0.001) # dtmax to force resolution -# @test length(solution.states.u) > 0 -# @test length(solution.states.t) > 0 +# ToDo: test `autodiff=true` +solution = fmiSimulateME(fmuStruct, t_start, t_stop; solver=Rosenbrock23(autodiff=false), dtmax=0.001) # dtmax to force resolution +@test length(solution.states.u) > 0 +@test length(solution.states.t) > 0 -# @test solution.states.t[1] == t_start -# @test solution.states.t[end] == t_stop +@test solution.states.t[1] == t_start +@test solution.states.t[end] == t_stop -# # reference values from Simulation in Dymola2020x (Dassl) -# @test solution.states.u[1] == [0.5, 0.0] -# @test sum(abs.(solution.states.u[end] - [0.613371, 0.188633])) < 0.01 +# reference values (no force) from Simulation in Dymola2020x (Dassl) +@test solution.states.u[1] == [0.5, 0.0] +@test sum(abs.(solution.states.u[end] - [0.509219, 0.314074])) < 0.01 fmiUnload(myFMU) # case 3c: ME-FMU without events, but with input signal (implicit solver: Rosenbrock23, no autodiff)