Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.10.2 #87

Merged
merged 16 commits into from
Jul 3, 2023
10 changes: 5 additions & 5 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "FMIFlux"
uuid = "fabad875-0d53-4e47-9446-963b74cae21f"
version = "0.10.1"
version = "0.10.2"

[deps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
Expand All @@ -18,15 +18,15 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"

[compat]
ChainRulesCore = "1.15.0"
ChainRulesCore = "1.16.0"
Colors = "0.12.8"
DiffEqCallbacks = "2.26.0"
DifferentialEquations = "7.7.0"
FMIImport = "0.15.2"
Flux = "0.13.14"
FMIImport = "0.15.6"
Flux = "0.13.16"
Optim = "1.7.0"
ProgressMeter = "1.7.0"
Requires = "1.3.0"
SciMLSensitivity = "7.27.0"
SciMLSensitivity = "7.31.0"
ThreadPools = "2.1.1"
julia = "1.6"
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@

## What Platforms are supported?
[*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl) is tested (and testing) under Julia versions *1.6* (LTS) and *1.8* (latest) on Windows (latest) and Ubuntu (latest). MacOS should work, but untested.
However, please use Julia versions *>= 1.7* if possible, because [*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl) runs **a lot** faster with these newer Julia versions.
[*FMIFlux.jl*](https://github.com/ThummeTo/FMIFlux.jl) currently only works with FMI2-FMUs.
All shipped examples are tested under Julia version *1.8* (latest) on Windows (latest).
All shipped examples are automatically tested under Julia version *1.8* (latest) on Windows (latest).

## What FMI.jl-Library should I use?
![FMI.jl Family](https://github.com/ThummeTo/FMI.jl/blob/main/docs/src/assets/FMI_JL_family.png?raw=true "FMI.jl Family")
Expand Down
54 changes: 29 additions & 25 deletions src/FMIFlux.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,40 @@ if VERSION < v"1.7.0"
end

# ToDo: Quick-fixes until patch release SciMLSensitivity v0.7.29
import SciMLSensitivity: FakeIntegrator, u_modified!, TrackedAffect
import SciMLSensitivity: FakeIntegrator, u_modified!, TrackedAffect, set_u!
function u_modified!(::FakeIntegrator, ::Bool)
end

# ToDo: Quick-fixes until patch release SciMLSensitivity v0.7.28
function Base.hasproperty(f::TrackedAffect, s::Symbol)
if hasfield(TrackedAffect, s)
return true
else
_affect = getfield(f, :affect!)
return hasfield(typeof(_affect), s)
end
end
function Base.getproperty(f::TrackedAffect, s::Symbol)
if hasfield(TrackedAffect, s)
return getfield(f, s)
else
_affect = getfield(f, :affect!)
return getfield(_affect, s)
end
end
function Base.setproperty!(f::TrackedAffect, s::Symbol, value)
if hasfield(TrackedAffect, s)
return setfield!(f, s, value)
else
_affect = getfield(f, :affect!)
return setfield!(_affect, s, value)
end
function set_u!(integrator::FakeIntegrator, u)
#integrator.u = u
end

# ToDo: Quick-fixes until patch release SciMLSensitivity v0.7.28
# function Base.hasproperty(f::TrackedAffect, s::Symbol)
# if hasfield(TrackedAffect, s)
# return true
# else
# _affect = getfield(f, :affect!)
# return hasfield(typeof(_affect), s)
# end
# end
# function Base.getproperty(f::TrackedAffect, s::Symbol)
# if hasfield(TrackedAffect, s)
# return getfield(f, s)
# else
# _affect = getfield(f, :affect!)
# return getfield(_affect, s)
# end
# end
# function Base.setproperty!(f::TrackedAffect, s::Symbol, value)
# if hasfield(TrackedAffect, s)
# return setfield!(f, s, value)
# else
# _affect = getfield(f, :affect!)
# return setfield!(_affect, s, value)
# end
# end

using Requires, Flux

using FMIImport
Expand Down
102 changes: 53 additions & 49 deletions src/batch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,40 +94,40 @@ mutable struct FMU2EvaluationBatchElement <: FMU2BatchElement
end
end

function run!(neuralFMU::ME_NeuralFMU, batchElement::FMU2SolutionBatchElement; lastBatchElement=nothing, kwargs...)
function startStateCallback(fmu, batchElement)
#print("Setting state ... ")

ignore_derivatives() do
c = getCurrentComponent(fmu)

if batchElement.initialState != nothing
fmi2SetFMUstate(c, batchElement.initialState)
c.eventInfo = deepcopy(batchElement.initialEventInfo)
c.t = batchElement.tStart
else
batchElement.initialState = fmi2GetFMUstate(c)
batchElement.initialEventInfo = deepcopy(c.eventInfo)
@warn "Batch element does not provide a `initialState`, I try to simulate anyway. InitialState is overwritten."
end
end

function startStateCallback(fmu, batchElement)
#print("Setting state ... ")

c = getCurrentComponent(fmu)

if batchElement.initialState != nothing
fmi2SetFMUstate(c, batchElement.initialState)
c.eventInfo = deepcopy(batchElement.initialEventInfo)
c.t = batchElement.tStart
else
batchElement.initialState = fmi2GetFMUstate(c)
batchElement.initialEventInfo = deepcopy(c.eventInfo)
@warn "Batch element does not provide a `initialState`, I try to simulate anyway. InitialState is overwritten."
end
end
function stopStateCallback(fmu, batchElement)
#print("\nGetting state ... ")

function stopStateCallback(fmu, batchElement)
#print("\nGetting state ... ")

c = getCurrentComponent(fmu)

if batchElement.initialState != nothing
fmi2GetFMUstate!(c, Ref(batchElement.initialState))
else
batchElement.initialState = fmi2GetFMUstate(c)
end
batchElement.initialEventInfo = deepcopy(c.eventInfo)

#println("done @ $(batchElement.initialState) in componentState: $(c.state)!")
end
c = getCurrentComponent(fmu)

if batchElement.initialState != nothing
fmi2GetFMUstate!(c, Ref(batchElement.initialState))
else
batchElement.initialState = fmi2GetFMUstate(c)
end
batchElement.initialEventInfo = deepcopy(c.eventInfo)

#println("done @ $(batchElement.initialState) in componentState: $(c.state)!")
end

function run!(neuralFMU::ME_NeuralFMU, batchElement::FMU2SolutionBatchElement; lastBatchElement=nothing, kwargs...)

ignore_derivatives() do

neuralFMU.customCallbacksAfter = []
neuralFMU.customCallbacksBefore = []
Expand Down Expand Up @@ -163,8 +163,12 @@ function run!(neuralFMU::ME_NeuralFMU, batchElement::FMU2SolutionBatchElement; l
return batchElement.solution
end

function run!(model, batchElement::FMU2EvaluationBatchElement)
batchElement.result = collect(model(f)[batchElement.indicesModel] for f in batchElement.features)
function run!(model, batchElement::FMU2EvaluationBatchElement, p=nothing)
if isnothing(p) # implicite parameter model
batchElement.result = collect(model(f)[batchElement.indicesModel] for f in batchElement.features)
else # explicite parameter model
batchElement.result = collect(model(p)(f)[batchElement.indicesModel] for f in batchElement.features)
end
end

function plot(batchElement::FMU2SolutionBatchElement; targets::Bool=true, kwargs...)
Expand Down Expand Up @@ -297,8 +301,8 @@ function batchDataSolution(neuralFMU::NeuralFMU, x0_fun, train_t::AbstractArray{
iStop = timeToIndex(train_t, tStart + batchDuration)

startElement = FMIFlux.FMU2SolutionBatchElement()
startElement.tStart = tStart
startElement.tStop = tStart + batchDuration
startElement.tStart = train_t[iStart]
startElement.tStop = train_t[iStop]
startElement.xStart = x0_fun(tStart)

startElement.saveat = train_t[iStart:iStop]
Expand All @@ -314,12 +318,12 @@ function batchDataSolution(neuralFMU::NeuralFMU, x0_fun, train_t::AbstractArray{
FMIFlux.run!(neuralFMU, batch[i-1]; lastBatchElement=batch[i], solverKwargs...)

# overwrite start state
batch[i].tStart = tStart + (i-1) * batchDuration
batch[i].tStop = tStart + i * batchDuration
iStart = timeToIndex(train_t, tStart + (i-1) * batchDuration)
iStop = timeToIndex(train_t, tStart + i * batchDuration)
batch[i].tStart = train_t[iStart]
batch[i].tStop = train_t[iStop]
batch[i].xStart = x0_fun(batch[i].tStart)

iStart = timeToIndex(train_t, batch[i].tStart)
iStop = timeToIndex(train_t, batch[i].tStop)

batch[i].saveat = train_t[iStart:iStop]
batch[i].targets = targets[iStart:iStop]

Expand All @@ -335,7 +339,7 @@ function batchDataSolution(neuralFMU::NeuralFMU, x0_fun, train_t::AbstractArray{
end

function batchDataEvaluation(train_t::AbstractArray{<:Real}, targets::AbstractArray, features::Union{AbstractArray, Nothing}=nothing;
batchDuration::Real=(train_t[end]-train_t[1]), indicesModel=1:length(targets[1]), plot::Bool=false)
batchDuration::Real=(train_t[end]-train_t[1]), indicesModel=1:length(targets[1]), plot::Bool=false, round_digits=3)

batch = Array{FMIFlux.FMU2EvaluationBatchElement,1}()

Expand All @@ -347,8 +351,8 @@ function batchDataEvaluation(train_t::AbstractArray{<:Real}, targets::AbstractAr
iStop = timeToIndex(train_t, tStart + batchDuration)

startElement = FMIFlux.FMU2EvaluationBatchElement()
startElement.tStart = tStart
startElement.tStop = tStart + batchDuration
startElement.tStart = train_t[iStart]
startElement.tStop = train_t[iStop]

startElement.saveat = train_t[iStart:iStop]
startElement.targets = targets[iStart:iStop]
Expand All @@ -364,12 +368,12 @@ function batchDataEvaluation(train_t::AbstractArray{<:Real}, targets::AbstractAr
for i in 2:floor(Integer, (train_t[end]-train_t[1])/batchDuration)
push!(batch, FMIFlux.FMU2EvaluationBatchElement())

# overwrite start state
batch[i].tStart = tStart + (i-1) * batchDuration
batch[i].tStop = tStart + i * batchDuration

iStart = timeToIndex(train_t, batch[i].tStart)
iStop = timeToIndex(train_t, batch[i].tStop)
iStart = timeToIndex(train_t, tStart + (i-1) * batchDuration)
iStop = timeToIndex(train_t, tStart + i * batchDuration)

batch[i].tStart = train_t[iStart]
batch[i].tStop = train_t[iStop]

batch[i].saveat = train_t[iStart:iStop]
batch[i].targets = targets[iStart:iStop]
if features != nothing
Expand Down
47 changes: 40 additions & 7 deletions src/layers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@
#

using Statistics: mean, std
import FMIImport: fmi2Real, fmi2ValueReferenceFormat

### FMUParameterRegistrator ###

"""
ToDo.
"""
struct FMUParameterRegistrator
fmu::FMU2
p_refs::AbstractArray{<:fmi2ValueReference}
p::AbstractArray{<:Real}

function FMUParameterRegistrator(fmu::FMU2, p_refs::fmi2ValueReferenceFormat, p::AbstractArray{<:Real})
@assert length(p_refs) == length(p) "`p_refs` and `p` need to be the same length!"
p_refs = prepareValueReference(fmu, p_refs)
fmu.optim_p_refs = p_refs
fmu.optim_p = p
return new(fmu, p_refs, p)
end
end
export FMUParameterRegistrator

function (l::FMUParameterRegistrator)(x)
l.fmu.optim_p = l.p
l.fmu.optim_p_refs = l.p_refs
return x
end

Flux.@functor FMUParameterRegistrator (p, )

### SHIFTSCALE ###

Expand Down Expand Up @@ -94,23 +123,27 @@ Flux.@functor ScaleShift (scale, shift)

struct ScaleSum{T}
scale::AbstractArray{T}
groups::Union{AbstractVector{<:AbstractVector{<:Integer}}, Nothing}

function ScaleSum{T}(scale::AbstractArray{T}) where {T}
inst = new(scale)
function ScaleSum{T}(scale::AbstractArray{T}, groups::Union{AbstractVector{<:AbstractVector{<:Integer}}, Nothing}=nothing) where {T}
inst = new(scale, groups)
return inst
end

function ScaleSum(scale::AbstractArray{T}) where {T}
return ScaleSum{T}(scale)
function ScaleSum(scale::AbstractArray{T}, groups::Union{AbstractVector{<:AbstractVector{<:Integer}}, Nothing}=nothing) where {T}
return ScaleSum{T}(scale, groups)
end
end
export ScaleSum

function (l::ScaleSum)(x)

x_proc = sum(x .* l.scale)

return [x_proc]
if isnothing(l.groups)
x_proc = sum(x .* l.scale)
return [x_proc]
else
return collect(sum(x[g] .* l.scale[g]) for g in l.groups)
end
end

Flux.@functor ScaleSum (scale, )
Expand Down
21 changes: 10 additions & 11 deletions src/losses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ end

function loss(model, batchElement::FMU2BatchElement;
logLoss::Bool=true,
lossFct=Flux.Losses.mse)
lossFct=Flux.Losses.mse, p=nothing)

model = nfmu.neuralODE.model[layers]

loss = 0.0

# evaluate model
result = run!(model, batchElement)
result = run!(model, batchElement, p=p)

# for i in 1:length(batchElement.targets[1])
# targets_model = collect(r[batchElement.indicesModel[i]] for r in batchElement.result)
Expand Down Expand Up @@ -71,22 +71,21 @@ function loss(nfmu::NeuralFMU, batch::AbstractArray{<:FMU2BatchElement};

solution = run!(nfmu, batch[batchIndex]; lastBatchElement=lastBatchElement, progressDescr="Sim. Batch $(batchIndex)/$(length(batch)) |", kwargs...)

if solution.success != true
@warn "Solving the NeuralFMU as part of the loss function failed. This is often because the ODE cannot be solved. Did you initialize the NeuralFMU model? Maybe additional errors are printed before this assertion."
if solution.success
return loss!(batch[batchIndex], lossFct; logLoss=logLoss)
else
@warn "Solving the NeuralFMU as part of the loss function failed. This is often because the ODE cannot be solved. Did you initialize the NeuralFMU model? Often additional solver errors/warnings are printed before this warning."
return Inf
end

loss = loss!(batch[batchIndex], lossFct; logLoss=logLoss)

return loss
end

function loss(model, batch::AbstractArray{<:FMU2BatchElement};
batchIndex::Integer=rand(1:length(batch)),
lossFct=Flux.Losses.mse,
logLoss::Bool=true)
logLoss::Bool=true, p=nothing)

run!(model, batch[batchIndex])
run!(model, batch[batchIndex], p)

loss = loss!(batch[batchIndex], lossFct; logLoss=logLoss)

Expand Down Expand Up @@ -127,7 +126,7 @@ function batch_loss(neuralFMU::ME_NeuralFMU, batch::AbstractArray{<:FMU2BatchEle
return accu
end

function batch_loss(model, batch::AbstractArray{<:FMU2BatchElement}; update::Bool=false, logLoss::Bool=false, lossFct=nothing)
function batch_loss(model, batch::AbstractArray{<:FMU2BatchElement}; update::Bool=false, logLoss::Bool=false, lossFct=nothing, p=nothing)

accu = 0.0

Expand All @@ -137,7 +136,7 @@ function batch_loss(model, batch::AbstractArray{<:FMU2BatchElement}; update::Boo
for i in 1:numBatch
b = batch[i]

run!(model, b)
run!(model, b, p)

accu += loss!(b, lossFct; logLoss=logLoss)
end
Expand Down
Loading