SimulationLogger.jl is a package providing convenient logging tools for DifferentialEquations.jl.
- FlightSims.jl is a general-purpose numerical simulator, and it uses SimulationLogger.jl as a main logging tool. You can find real examples about how to use this package in FlightSims.jl.
- The privileged name of data logger is now changed from
__LOGGER_DICT__
to__LOGGER__
. - The type of data container, i.e.,
typeof(__LOGGER__)
is now changed to beNamedTuple
.- For Zygote.jl-compatible data logging.
- See
test/macros.jl
. - Unfortunately, it is hard to integrate with DiffEqFlux.jl for now. See DiffEqFlux.jl, #662.
- See
- For Zygote.jl-compatible data logging.
See FlightSims.jl for details.
using SimulationLogger
using DifferentialEquations
using Transducers
using Plots
using Test
function test()
@Loggable function dynamics!(dx, x, p, t; u)
@onlylog state, input = x, u # __LOGGER__.state = x, __LOGGER__.input = u
dx .= u
end
@Loggable function custom_control(x)
@log a = 1
-a*x
end
@Loggable function feedback_dynamics!(dx, x, p, t)
@onlylog time = t # __LOGGER__.time = t
@log x, t # __LOGGER__.x = x
u = @nested_log custom_control(x) # __LOGGER__.a = 1
@log u # __LOGGER__.u = -a*x
@nested_log :linear x
@nested_log :linear dynamics!(dx, x, p, t; u=u)
end
t0, tf = 0.0, 0.1
Δt = 0.01
saved_values = SavedValues(Float64, NamedTuple)
cb = CallbackSet()
if hasmethod(feedback_dynamics!, Tuple{Any, Any, Any, Any, __LOG_INDICATOR__})
# to avoid undefined error when not adding @Loggable
log_func(x, t, integrator::DiffEqBase.DEIntegrator; kwargs...) = feedback_dynamics!(zero.(x), copy(x), integrator.p, t, __LOG_INDICATOR__(); kwargs...)
cb = SavingCallback(log_func, saved_values; saveat=t0:Δt:tf)
end
# # sim
x0 = [1, 2, 3]
tspan = (t0, tf)
prob = ODEProblem(
feedback_dynamics!, x0, tspan;
callback=cb,
)
_ = solve(prob)
ts = saved_values.saveval |> Map(datum -> datum[:t]) |> collect
xs = saved_values.saveval |> Map(datum -> datum[:x]) |> collect
us = saved_values.saveval |> Map(datum -> datum[:u]) |> collect
times = saved_values.saveval |> Map(datum -> datum[:time]) |> collect
states = saved_values.saveval |> Map(datum -> datum[:linear][:state]) |> collect
inputs = saved_values.saveval |> Map(datum -> datum[:linear][:input]) |> collect
as = saved_values.saveval |> Map(datum -> datum[:a]) |> collect
@test ts == saved_values.t
@test ts == times
@test xs == states
@test us == inputs
@test as == ones(length(ts))
p_x = plot(ts, hcat(xs...)')
p_u = plot(ts, hcat(us...)')
dir_log = "figures"
mkpath(dir_log)
savefig(p_x, joinpath(dir_log, "state.png"))
savefig(p_u, joinpath(dir_log, "input.png"))
end
@Loggable
is a macro that makes an ODE function loggable.
@Loggable function dynamics!(dx, x, p, t)
dx .= -x
end
@Loggable
generates additional method for the generic function of the annotated function definition.
The additional method receives __log__indicator__::__LOG_INDICATOR__
as the last argument (other arguments are the same as the original function definition).
- This macro is supposed to be used in front of "function definition". For example,
@Loggable function dynamics!(dx, x, p, t)
dx .= -x
end
is good.
@Loggable dynamics! = (dx, x, p, t) -> dx .= -x
may not work properly.
This macro logs the annotated variable, and also executes the followed expression when both solving DEProblem and logging data.
@Loggable function dynamics!(dx, x, p, t)
@log state = x
@log p # the same as `@log p = p`
dx .= -x
end
This macro logs the annotated variable, and also executes the followed expression only when logging data.
@Loggable function dynamics!(dx, x, p, t)
@log u = x
@onlylog input = u # `input` is not visible in this function when solving DEProblem.
dx .= -u
end
This macro logs (possibly) multiple data in a nested sense.
- nested log with specified name
@Loggable function dynamics!(dx, x, p, t)
@log state = x # __LOGGER__.state = x
dx .= -x
end
@Loggable function feedback_dynamics!(dx, x, p, t)
@log time = t # __LOGGER__.time = t
@nested_log :linear dynamics!(dx, x, p, t) # __LOGGER__.linear = (; state = x)
end
- nested log with no name
@Loggable function dynamics!(dx, x, p, t)
@log state = x # __LOGGER__.state = x
dx .= -x
end
@Loggable function feedback_dynamics!(dx, x, p, t)
@log time = t # __LOGGER__.time = t
@nested_log dynamics!(dx, x, p, t) # __LOGGER__.state = x
end
@nested_log
with assignment
@Loggable function dynamics!(dx, x, p, t; u)
@log state = x # __LOGGER__.state = x
@log input = u # __LOGGER__.input = u
dx .= u
end
@Loggable function control(x)
@log a = 1 # hidden state; internally defined variable
-a*x
end
@Loggable function feedback_dynamics!(dx, x, p, t)
@log time = t # __LOGGER__.time = t
u = @nested_log control(x) # __LOGGER__.a = 1
@nested_log dynamics!(dx, x, p, t; u) # __LOGGER__.state = x
end
This macro logs (possibly) multiple data in a nested sense only when logging data (similar to @onlylog
).
__LOGGER__
is a privileged name to contain variables annotated by logging macros. DO NOT USE THIS NAME IN USUAL CASE.- This package supports only in-place method of DifferentialEquations.jl.
- This basic form of this macro is inspired by SimulationLogs.jl. But there are some differences. For example,
@log
in this package is based on SavingCallback, while@log
in SimulationLogs.jl will save data in the sense of postprocessing. There are two main advantages: this package can 1) log data without repeating the same code within differential equation (DE) functions, and 2) deal with stochastic parameter updates. For more details, see the original question and the idea of this package.
Please feel free to request additional features and report bugs!