Skip to content

Commit

Permalink
v1.1.0 (#47)
Browse files Browse the repository at this point in the history
* OpenScaling example adaption

* FMU2 constructor for Flux

* snapshot corrections

* added `tspan` keyword for plot

* updated version

* formatting...

---------

Co-authored-by: Tobias Thummerer <tobias.thummerer@uni-a.de>
  • Loading branch information
ThummeTo and Tobias Thummerer authored Feb 11, 2025
1 parent 5c7192f commit 7c67e17
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 33 deletions.
16 changes: 8 additions & 8 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "FMIBase"
uuid = "900ee838-d029-460e-b485-d98a826ceef2"
authors = ["TT <tobias.thummerer@informatik.uni-augsburg.de>", "LM <lars.mikelsons@informatik.uni-augsburg.de>"]
version = "1.0.10"
version = "1.1.0"

[deps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
Expand Down Expand Up @@ -45,18 +45,18 @@ ReverseDiffExt = ["ReverseDiff"]

[compat]
CSV = "0.10"
ChainRulesCore = "1.20 - 1.24"
ChainRulesCore = "1.20"
DataFrames = "1"
DiffEqCallbacks = "2.0 - 3.9"
EzXML = "1.1 - 1.2"
DiffEqCallbacks = "2.0, 3.0, 4.0"
EzXML = "1.1"
FMICore = "1.0"
MAT = "0.10"
JLD2 = "0.4"
ForwardDiff = "0.10"
MAT = "0.10.0"
JLD2 = "0.4.0, 0.5.0"
ForwardDiff = "0.10.0"
PackageExtensionCompat = "1.0.0"
Plots = "1"
ProgressMeter = "1.7"
Reexport = "1.0 - 1.2"
Reexport = "1.0"
Requires = "1.3.0"
ReverseDiff = "1"
SciMLBase = "1.0, 2.0" # v1 is required to keep Julia 1.6.7 (LTS) running!
Expand Down
86 changes: 74 additions & 12 deletions ext/PlotsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,31 @@ using FMIBase, Plots
import FMIBase: unsense

"""
Plots.plot(solution::FMUSolution; plotkwargs...)
Plots.plot(solution::FMUSolution;
states::Union{Bool, Nothing}=nothing,
values::Union{Bool, Nothing}=nothing,
stateEvents::Union{Bool, Nothing}=nothing,
timeEvents::Union{Bool, Nothing}=nothing,
stateIndices=nothing,
valueIndices=nothing,
maxLabelLength=64,
plotkwargs...)
Plots the `solution` of a FMU simulation and returns the corresponding figure.
(requires package Plots.jl)
Plots the `solution` of a FMU simulation and returns a new figure.
# Arguments
- `fig::Plots.Plot`: Figure to plot into
- `solution::FMUSolution`: Struct containing information about the solution values, success, states and events of a specific FMU simulation.
# Keywords
- `plotkwargs...`: Arguments, that are passed on to Plots.plot!
- `states::Union{Bool, Nothing}=nothing`: controls if states should be plotted (default = nothing: plot states from `solution`, if they exist)
- `values::Union{Bool, Nothing}=nothing`: controls if values should be plotted (default = nothing: plot values from `solution`, if they exist)
- `stateEvents::Union{Bool, Nothing}=nothing`: controls if stateEvents should be plotted (default = nothing: plot stateEvents from `solution`, if at least one and at most 100 exist)
- `timeEvents::Union{Bool, Nothing}=nothing`: controls if timeEvents should be plotted (default = nothing: plot timeEvents from `solution`, if at least one and at most 100 exist)
- `stateIndices=nothing`: controls which states will be plotted by index in state vector (default = nothing: plot all states)
- `valueIndices=nothing`: controls which values will be plotted by index (default = nothing: plot all values)
- `maxLabelLength::Integer=64`: controls the maximum length for legend labels (too long labels are cut from front)
- `maxStateEvents::Integer=100`: controls, how many state events are plotted before suppressing plotting state events
- `maxTimeEvents::Integer=100`: controls, how many time events are plotted before suppressing plotting state events
- `plotkwargs...`: Arguments, that are passed on to Plots.plot
"""
function Plots.plot(solution::FMUSolution; plotkwargs...)
fig = Plots.plot(; xlabel = "t [s]")
Expand Down Expand Up @@ -65,6 +80,7 @@ function Plots.plot!(
maxLabelLength::Integer = 64,
maxStateEvents::Integer = 100,
maxTimeEvents::Integer = 100,
tspan::Union{Tuple{Real,Real},Nothing} = nothing,
plotkwargs...,
)

Expand Down Expand Up @@ -98,7 +114,7 @@ function Plots.plot!(
end

if numStateEvents > maxStateEvents
@info "fmiPlot(...): Number of state events ($(numStateEvents)) exceeding 100, disabling automatic plotting of state events (can be forced with keyword `stateEvents=true`)."
@info "plot(::FMUSolution, ...): Number of state events ($(numStateEvents)) exceeding 100, disabling automatic plotting of state events (can be forced with keyword `stateEvents=true`)."
stateEvents = false
end
end
Expand All @@ -113,7 +129,7 @@ function Plots.plot!(
end

if numTimeEvents > maxTimeEvents
@info "fmiPlot(...): Number of time events ($(numTimeEvents)) exceeding 100, disabling automatic plotting of time events (can be forced with keyword `timeEvents=true`)."
@info "plot(::FMUSolution, ...): Number of time events ($(numTimeEvents)) exceeding 100, disabling automatic plotting of time events (can be forced with keyword `timeEvents=true`)."
timeEvents = false
end
end
Expand All @@ -131,9 +147,55 @@ function Plots.plot!(
plot_min = Inf
plot_max = -Inf

# plot states
t = nothing
num_t = nothing

if values
t = collect(unsense(e) for e in solution.values.t)
num_t = length(solution.values.saveval)
end

if states
t = collect(unsense(e) for e in solution.states.t)
num_t = length(solution.states.u)
end

ts = 1
te = num_t

# calculating the indices for start and stop in tspan
if !isnothing(tspan)
start, stop = tspan

ts = 1
te = num_t

# if start < t[1]
# # @warn "Given tspan start $(start) is less than solution start $(t[1]), correcting it."
# start = t[1]
# end

# if stop > t[end]
# # @warn "Given tspan stop $(start) is greater than solution stop $(t[end]), correcting it."
# stop = t[end]
# end

for i = 1:num_t
if t[i] <= start
ts = max(i + 1, ts)
end
if t[i] >= stop
te = min(i, te)
end
end

t = t[ts:te]
num_t = te - ts + 1
end

# plot states
if states
t = collect(unsense(e) for e in solution.states.t[ts:te])
numValues = length(solution.states.u[1])

for v = 1:numValues
Expand All @@ -142,7 +204,7 @@ function Plots.plot!(
vrNames = valueReferenceToString(instance.fmu, vr)
vrName = length(vrNames) > 0 ? vrNames[1] : "?"

vals = collect(unsense(data[v]) for data in solution.states.u)
vals = collect(unsense(data[v]) for data in solution.states.u[ts:te])

plot_min = min(plot_min, vals...)
plot_max = max(plot_max, vals...)
Expand All @@ -161,7 +223,7 @@ function Plots.plot!(

# plot recorded values
if values
t = collect(unsense(e) for e in solution.values.t)
t = collect(unsense(e) for e in solution.values.t[ts:te])
numValues = length(solution.values.saveval[1])

for v = 1:numValues
Expand All @@ -175,7 +237,7 @@ function Plots.plot!(
vrName = length(vrNames) > 0 ? vrNames[1] : "?"
end

vals = collect(unsense(data[v]) for data in solution.values.saveval)
vals = collect(unsense(data[v]) for data in solution.values.saveval[ts:te])

plot_min = min(plot_min, vals...)
plot_max = max(plot_max, vals...)
Expand Down
5 changes: 5 additions & 0 deletions src/FMI2/struct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@ mutable struct FMU2 <: FMU

return inst
end

# required for creation of FMU layers in Flux.jl
function FMU2(args...)
return new(args...)
end
end
export FMU2

Expand Down
28 changes: 24 additions & 4 deletions src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ end
"""
ToDo
"""
function setDiscreteStates(c::FMU2Component, x_d::AbstractArray{<:fmi2Real}; kwargs...)
setReal(c, c.fmu.modelDescription.discreteStateValueReferences, x_d; kwargs...)
function setDiscreteStates(c::FMU2Component, x_d::AbstractArray{<:Any}; kwargs...)
setValue(c, c.fmu.modelDescription.discreteStateValueReferences, x_d; kwargs...)
return nothing
end
function setDiscreteStates(c::FMU3Instance, x_d::AbstractArray{<:fmi3Float64}; kwargs...)
setReal(c, c.fmu.modelDescription.discreteStateValueReferences, x_d; kwargs...)
function setDiscreteStates(c::FMU3Instance, x_d::AbstractArray{<:Any}; kwargs...)
setValue(c, c.fmu.modelDescription.discreteStateValueReferences, x_d; kwargs...)
return nothing
end

Expand Down Expand Up @@ -467,3 +467,23 @@ end
function getNextEventTime(c::FMU3Instance)
return c.nextEventTime
end

"""
ToDo
"""
function enterEventMode(c::FMU2Component)
return fmi2EnterEventMode(c)
end
function enterEventMode(c::FMU3Instance)
return fmi3EnterEventMode(c)
end

"""
ToDo
"""
function enterContinuousTimeMode(c::FMU2Component)
return fmi2EnterContinuousTimeMode(c)
end
function enterContinuousTimeMode(c::FMU3Instance)
return fmi3EnterContinuousTimeMode(c)
end
4 changes: 2 additions & 2 deletions src/eval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function (c::FMUInstance)(
end
end

@debug "dispatching on eval! $((c.cRef, dx, dx_refs, y, y_refs, x, u, u_refs, p, p_refs, ec, ec_idcs, t))"
# @debug "dispatching on eval! $((c.cRef, dx, dx_refs, y, y_refs, x, u, u_refs, p, p_refs, ec, ec_idcs, t))"

# [Note] not necessary:
#c.output = FMU2ADOutput{Real}(; initType=Real)
Expand Down Expand Up @@ -242,7 +242,7 @@ function eval!(
t::Float64,
)

@debug "eval! $((cRef, dx, dx_refs, y, y_refs, x, u, u_refs, p, p_refs, ec, ec_idcs, t))"
# @debug "eval! $((cRef, dx, dx_refs, y, y_refs, x, u, u_refs, p, p_refs, ec, ec_idcs, t))"

c = unsafe_pointer_to_objref(Ptr{Nothing}(cRef))

Expand Down
4 changes: 2 additions & 2 deletions src/setup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import DiffEqCallbacks:

function setupSolver!(fmu::FMU, tspan, kwargs)

t_start = tspan[1]
t_stop = tspan[end]
t_start = isnothing(tspan) ? nothing : tspan[1]
t_stop = isnothing(tspan) ? nothing : tspan[end]

if isnothing(t_start)
t_start = getDefaultStartTime(fmu)
Expand Down
18 changes: 13 additions & 5 deletions src/snapshot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ end

function snapshot!(c::FMUInstance)
s = FMUSnapshot(c)
# is automatically pushed to instance within `FMUSnapshot`
return s
end
function snapshot!(sol::FMUSolution)
Expand Down Expand Up @@ -53,11 +54,12 @@ function hasSnapshot(c::Union{FMUInstance,FMUSolution}, t::Float64; atol = 0.0)
return false
end

function getSnapshot(c::FMUInstance, t::Float64; kwargs...)
return getSnapshot(c.fmu, t; kwargs...)
end

function getSnapshot(c::Union{FMU,FMUSolution}, t::Float64; exact::Bool = false, atol = 0.0)
function getSnapshot(
c::Union{FMUInstance,FMUSolution},
t::Float64;
exact::Bool = false,
atol = 0.0,
)
# [Note] only take exact fit if we are at 0, otherwise take the next left,
# because we are saving snapshots for the right root of events.

Expand Down Expand Up @@ -116,8 +118,12 @@ function update!(c::FMUInstance, s::FMUSnapshot)
s.state = c.state
s.instance = c

@debug "Updating snapshot t=$(s.t) [$(s.fmuState)]"

getFMUstate!(c, Ref(s.fmuState))

@debug "... to t=$(s.t) [$(s.fmuState)]"

s.x_c = isnothing(c.x) ? nothing : copy(c.x)
s.x_d = isnothing(c.x_d) ? nothing : copy(c.x_d)
return nothing
Expand Down Expand Up @@ -162,6 +168,8 @@ export apply!

function freeSnapshot!(s::FMUSnapshot)
#@async println("cleanup!")
@debug "Freeing snapshot t=$(s.t) [$(s.fmuState)]"

freeFMUstate!(s.instance, Ref(s.fmuState))
s.fmuState = nothing

Expand Down
2 changes: 2 additions & 0 deletions src/struct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ mutable struct FMUSnapshot{E,C,D,I,S}

inst = new{E,C,D,I,S}(t, eventInfo, state, instance, fmuState, x_c, x_d)

@debug "New snapshot #$(length(c.snapshots)+1) t=$(t), x_c=$(x_c) [$(fmuState)]"

# if !isnothing(fmuState)
# inst = finalizer((_inst) -> cleanup!(c, _inst), inst)
# end
Expand Down

2 comments on commit 7c67e17

@ThummeTo
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

  • added more flexible plotting functionality
  • fixed snapshot bug in FMIFlux

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/124811

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.1.0 -m "<description of version>" 7c67e1716c93aab797c58825ee3fb7aaadeed719
git push origin v1.1.0

Please sign in to comment.