[ANN] SimulationLogs.jl 🪵🪵🪵

TLDR: Place the @log macro in your DifferentialEquations simulation to log intermediate variables.

One of the killer features of Simulink is the ability to log or plot (with a scope) any intermediate signal in a simulation with a click. It’s one of the things that I’ve been sorely missing in my DifferentialEquations.jl simulations. ModelingToolkit gives this ability, but it would be nice to have for non-ModelingToolkit simulations as well.

So let me introduce SimulationLogs.jl. By simply placing the macro @log before any variable declaration in your simulation, you can now access that variable by calling get_log on your solution object. Logged variables can also be plotted by calling the scope function on your solution with the desired variables to be plotted.

For a pretty (I hope) illustrative example, see the PI cruise control example in the docs where I use the scope function to inspect intermediate variables like so:

I’m going to post the FAQ section of the README here in order to head off any of the questions I anticipate people having:

FAQs

How does this work with time stepping and variable caches and all that?

Despite the name, @log doesn’t actually log anything while the simulation is running. The “logging” happens by calculating values from the stored solutions when get_log is called.

Wait, how does that work?

There is a global SimulationLog that is turned off by default. When it is off, the @log macro basically doesn’t do anything. The get_log function turns on the global log and then calls your simulation function (derivative function, vector field… whatever you want to call it) for each time point (these can be supplied, but will default to the saved time points). A copy of the global simulation log is passed as an output to the user, after which the global log then gets erased and turned back off.

Will logging variables slow my simulation down?

Nope. There is no runtime overhead because no logging is actually happening during the simulation.

How does this work when the same @log gets called multiple times in the same time step (e.g. in a subfunction that gets called more than once)?

It doesn’t. Don’t do that. There are some good ways we could handle this, but they aren’t implemented right now.

What if my parameters are changed during the simulation or my simulation depends on some changing global state?

Then this probably won’t work.

21 Likes

Side note: If you’re going to use this right now, don’t get too comfortable with the current syntax. I just came up with this package last night so things definitely aren’t in their stable state yet.

This will be so useful! Scoping signals for debugging has always been a pain in the current framework. Thanks for creating this package!

1 Like

Update: It’s registered now.

More updates:

  • Added support for models with DiscreteCallbacks (including parameters changed with DiscreteCallbacks). You just have to pass the callback/callback set in to get_log with the keyword callback. Alternatively, just use the new logged_solve in place of your old solve and it will give you a solution that has a property .log with all of your logged values in it.
  • Can now handle multiple @log calls within a single time step. Instead of a vector of values, it will log an nxm Matrix where n is the number of time steps and m is the number of times that variable was logged per time step.