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
endis good.
@Loggable dynamics! = (dx, x, p, t) -> dx .= -xmay 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
endThis 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
endThis 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_logwith 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
endThis 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, @login this package is based on SavingCallback, while@login 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!

