Callbacks
Many of the advanced features of Trixi.jl, such as adaptive mesh refinement, are implemented as callbacks. A callback is an algorithmic entity that gets passed to the ODE solver and is called at specific points during execution to perform certain tasks. Callbacks in Trixi.jl are either called after each time step (step callbacks) or after each stage of the ODE solver (stage callbacks).

The advantage of callbacks over hard-coding all features is that it allows to extend Trixi.jl without modifying the internal source code. Trixi.jl provides callbacks for time step control, adaptive mesh refinement, I/O, and more.
Step callbacks
CFL-based time step control
Time step control can be performed with a StepsizeCallback. An example making use of this can be found at examples/tree_2d_dgsem/elixir_advection_basic.jl
Adaptive mesh refinement
Trixi.jl uses a hierarchical Cartesian mesh which can be locally refined in a solution-adaptive way. This can be used to speed up simulations with minimal loss in overall accuracy. Adaptive mesh refinement (AMR) can be used by passing an AMRCallback to the ODE solver. The AMRCallback requires a controller such as ControllerThreeLevel or ControllerThreeLevelCombined to tell the AMR algorithm which cells to refine/coarsen.
An example elixir using AMR can be found at examples/tree_2d_dgsem/elixir_advection_amr.jl.
Analyzing the numerical solution
The AnalysisCallback can be used to analyze the numerical solution, e.g. calculate errors or user-specified integrals, and print the results to the screen. The results can also be saved in a file. An example can be found at examples/tree_2d_dgsem/elixir_euler_vortex.jl. Note that the errors (e.g. L2 error or Linf error) are computed with respect to the initial condition. The percentage of the simulation time refers to the ratio of the current time and the final time, i.e. it does not consider the maximal number of iterations. So the simulation could finish before 100% are reached. Note that, e.g., due to AMR or smaller time step sizes, the simulation can actually take longer than the percentage indicates. In Performance metrics of the AnalysisCallback you can find a detailed description of the different performance metrics the AnalysisCallback computes.
I/O
Solution and restart files
To save the solution in regular intervals you can use a SaveSolutionCallback. It is also possible to create restart files using the SaveRestartCallback. An example making use of these can be found at examples/tree_2d_dgsem/elixir_advection_extended.jl. An example showing how to restart a simulation from a restart file can be found at examples/tree_2d_dgsem/elixir_advection_restart.jl.
Time series
Sometimes it is useful to record the evaluations of state variables over time at a given set of points. This can be achieved by the TimeSeriesCallback, which is used, e.g., in examples/tree_2d_dgsem/elixir_acoustics_gaussian_source.jl. The TimeSeriesCallback constructor expects a semidiscretization and a list of points at which the solution should be recorded in regular time step intervals. After the last time step, the entire record is stored in an HDF5 file.
For the points, two different input formats are supported: You can either provide them as a list of tuples, which is handy if you specify them by hand on the REPL. Alternatively, you can provide them as a two-dimensional array, where the first dimension is the point number and the second dimension is the coordinate dimension. This is especially useful when reading them from a file.
For example, to record the primitive variables at the points (0.0, 0.0) and (-1.0, 0.5) every five timesteps and storing the collected data in the file tseries.h5, you can create the TimeSeriesCallback as
time_series = TimeSeriesCallback(semi, [(0.0, 0.0), (-1.0, 0.5)];
interval=5,
solution_variables=cons2prim,
filename="tseries.h5")For a full list of possible arguments, please check the documentation for the TimeSeriesCallback. As an alternative to specifying the point coordinates directly in the elixir or on the REPL, you can read them from a file. For instance, with a text file points.dat with content
0.0 0.0
-1.0 0.5you can create a time series callback with
using DelimitedFiles: readdlm
time_series = TimeSeriesCallback(semi, readdlm("points.dat"))To plot the individual point data series over time, you can create a PlotData1D from the TimeSeriesCallback and a given point ID. For example, executing
julia> using Trixi, Plots
julia> trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_acoustics_gaussian_source.jl"))
julia> pd1 = PlotData1D(time_series, 1)
julia> pd2 = PlotData1D(time_series, 2)
julia> plot(pd1["p_prime"]); plot!(pd2["p_prime"], xguide="t")will yield the following plot:

Miscellaneous
- The
AliveCallbackprints some information to the screen to show that a simulation is still running. - The
SummaryCallbackprints a human-readable summary of the simulation setup and controls the automated performance measurements, including an output of the recorded timers after a simulation. - The
VisualizationCallbackcan be used for in-situ visualization. See Visualizing results during a simulation. - The
TrivialCallbackdoes nothing and can be used to easily disable some callbacks viatrixi_include.
Equation-specific callbacks
Some callbacks provided by Trixi.jl implement specific features for certain equations:
- The
LBMCollisionCallbackimplements the Lattice-Boltzmann method (LBM) collision operator and should only be used when solving the Lattice-Boltzmann equations. See e.g.examples/tree_2d_dgsem/elixir_lbm_constant.jl - The
SteadyStateCallbackterminates the time integration when the residual steady state falls below a certain threshold. This checks the convergence of the potential $\phi$ for hyperbolic diffusion. See e.g.examples/tree_2d_dgsem/elixir_hypdiff_nonperiodic.jl. - The
GlmSpeedCallbackupdates the divergence cleaning wave speedc_hfor the ideal GLM-MHD equations. See e.g.examples/tree_2d_dgsem/elixir_mhd_alfven_wave.jl.
Usage of step callbacks
Step callbacks are passed to the solve method from the ODE solver via the keyword argument callback. If you want to use a single callback cb, pass it as callback=cb. When using two or more callbacks, you need to turn them into a CallbackSet first by calling callbacks = CallbackSet(cb1, cb2) and passing it as callback = callbacks.
There are some restrictions regarding the order of callbacks in a CallbackSet.
The callbacks are called after each time step but some callbacks actually belong to the next time step. Therefore, the callbacks should be ordered in the following way:
- Callbacks that belong to the current time step:
SummaryCallbackcontrols, among other things, timers and should thus be firstSteadyStateCallbackmay mark a time step as the last oneAnalysisCallbackmay do some checks that mark a time step as the last oneAliveCallbackshould be nearbyAnalysisCallbackSaveSolutionCallback/SaveRestartCallbackshould save the current solution before it is degraded by AMRVisualizationCallbackshould be called before the mesh is adapted
- Callbacks that belong to the next time step:
AMRCallbackStepsizeCallbackmust be called afterAMRCallbackto accommodate potential changes to the meshGlmSpeedCallbackmust be called afterStepsizeCallbackbecause the step size affects the value ofc_hLBMCollisionCallbackis already part of the calculations of the next time step and should therefore be called afterStepsizeCallback
Stage callbacks
PositivityPreservingLimiterZhangShu is a positivity-preserving limiter, used to enforce physical constraints. An example elixir using this feature can be found at examples/tree_2d_dgsem/elixir_euler_positivity.jl.
Implementing new callbacks
Since Trixi.jl is compatible with OrdinaryDiffEq.jl, both packages share the same callback interface. A detailed description of it can be found in the OrdinaryDiffEq.jl documentation. Step callbacks are just called callbacks. Stage callbacks are called stage_limiter!.
An example elixir showing how to implement a new simple stage callback and a new simple step callback can be found at examples/tree_2d_dgsem/elixir_advection_callbacks.jl.