Callback Interface

Callbacks describe event handling and solver-side actions that run during a differential equation solve. They are passed through the common callback keyword, usually as a single callback or as a CallbackSet.

Continuous callbacks locate events inside a solver step by monitoring zero crossings. Discrete callbacks are evaluated at solver-controlled points and trigger when their condition is true. Vector continuous callbacks combine many continuous events into one condition function.

Interface Rules

  • Callback condition functions inspect the proposed state, time, and integrator without mutating the integrator.
  • Callback affect! functions may mutate the integrator. State changes that introduce discontinuities should normally use save_positions = (true, true).
  • Continuous callbacks and vector continuous callbacks are processed before discrete callbacks. Discrete callbacks are then applied in order.
  • For DAEs, callback effects may require consistent reinitialization. The callback initializealg keyword controls this per callback when it is not inherited from solve.
  • ModelingToolkit-generated callbacks may carry saved_clock_partitions. SciMLBase's discrete-save hooks use this metadata to keep time-series parameters synchronized with callback effects.

The root-finding mode for continuous events is selected by SciMLBase.LeftRootFind, SciMLBase.RightRootFind, or SciMLBase.NoRootFind.

Callback-specific discrete-save hooks extend SciMLBase.save_discretes!, which is documented with the symbolic save-index interface.

API

SciMLBase.DECallbackType
abstract type DECallback

Common supertype for callback objects accepted by differential-equation solvers.

Concrete callbacks describe user or solver actions that run during integration, such as event handling, state modification, saving, or termination. Callback sets are represented by CallbackSet, while event callbacks generally subtype AbstractContinuousCallback or AbstractDiscreteCallback.

source
SciMLBase.AbstractContinuousCallbackType
abstract type AbstractContinuousCallback <: SciMLBase.DECallback

Base interface for callbacks that locate events inside a solver step.

Continuous callbacks define a condition whose zero crossing is detected by the solver, optionally using interpolation and root finding, before applying an effect to the integrator. Concrete subtypes must document their condition and effect signatures and any assumptions about interpolation or event direction.

source
SciMLBase.AbstractDiscreteCallbackType
abstract type AbstractDiscreteCallback <: SciMLBase.DECallback

Base interface for callbacks evaluated at solver-controlled points without continuous root finding.

Discrete callbacks usually test a condition at accepted steps, initialization, or other solver-defined synchronization points, then apply an effect to the integrator. Concrete subtypes must document when their condition is evaluated and which integrator mutations their effects may perform.

source
SciMLBase.RootfindOptType
RootfindOpt

Select how a continuous callback localizes a detected zero crossing.

Values

  • NoRootFind: apply the callback at the detected step endpoint without root localization.
  • LeftRootFind: localize the event and use the solution's left-limit value.
  • RightRootFind: localize the event and use the solution's right-limit value.

Usage

callback = ContinuousCallback(condition, affect!; rootfind = LeftRootFind)

The rootfind keyword of ContinuousCallback and VectorContinuousCallback accepts one of these values. Bool values remain accepted for compatibility and convert to LeftRootFind or NoRootFind.

source
SciMLBase.ContinuousCallbackType
ContinuousCallback(
    condition, affect!, affect_neg!;
    initialize = INITIALIZE_DEFAULT,
    finalize = FINALIZE_DEFAULT,
    idxs = nothing,
    rootfind = LeftRootFind,
    save_positions = (true, true),
    interp_points = 10,
    abstol = 10eps(), reltol = 0, repeat_nudge = 1 // 100,
    initializealg = nothing, maybe_discontinuity = true
)
ContinuousCallback(
    condition, affect!;
    initialize = INITIALIZE_DEFAULT,
    finalize = FINALIZE_DEFAULT,
    idxs = nothing,
    rootfind = LeftRootFind,
    save_positions = (true, true),
    affect_neg! = affect!,
    interp_points = 10,
    abstol = 10eps(), reltol = 0, repeat_nudge = 1 // 100,
    initializealg = nothing, maybe_discontinuity = true
)

Contains a single callback whose condition is a continuous function. The callback is triggered when this function evaluates to 0.

Arguments

  • condition: This is a function condition(u,t,integrator) for declaring when the callback should be used. A callback is initiated if the condition hits 0 within the time interval. See the Integrator Interface documentation for information about integrator.
  • affect!: This is the function affect!(integrator) where one is allowed to modify the current state of the integrator. If you do not pass an affect_neg! function, it is called when condition is found to be 0 (at a root) and the cross is either an upcrossing (from negative to positive) or a downcrossing (from positive to negative). You need to explicitly pass nothing as the affect_neg! argument if it should only be called at upcrossings, e.g. ContinuousCallback(condition, affect!, nothing). For more information on what can be done, see the Integrator Interface manual page. Modifications to u are safe in this function.
  • affect_neg!=affect!: This is the function affect_neg!(integrator) where one is allowed to modify the current state of the integrator. This is called when condition is found to be 0 (at a root) and the cross is a downcrossing (from positive to negative). For more information on what can be done, see the Integrator Interface manual page. Modifications to u are safe in this function.
  • rootfind=LeftRootFind: This is a flag to specify the type of rootfinding to do for finding event location. If this is set to LeftRootFind, the solution will be backtracked to the point where condition==0 and if the solution isn't exact, the left limit of root is used. If set to RightRootFind, the solution would be set to the right limit of the root. Otherwise, the systems and the affect! will occur at t+dt. Note that these enums are not exported, and thus one needs to reference them as SciMLBase.LeftRootFind, SciMLBase.RightRootFind, or SciMLBase.NoRootFind.
  • interp_points=10: The number of interpolated points to check the condition. The condition is found by checking whether any interpolation point / endpoint has a different sign. If interp_points=0, then conditions will only be noticed if the sign of condition is different at t than at t+dt. This behavior is not robust when the solution is oscillatory, and thus it's recommended that one use some interpolation points (they're cheap to compute!). 0 within the time interval.
  • save_positions=(true,true): Boolean tuple for whether to save before and after the affect!. This saving will occur just before and after the event, only at event times, and does not depend on options like saveat, save_everystep, etc. (i.e. if saveat=[1.0,2.0,3.0], this can still add a save point at 2.1 if true). For discontinuous changes like a modification to u to be handled correctly (without error), one should set save_positions=(true,true).
  • idxs=nothing: The components which will be interpolated into the condition. Defaults to nothing which means u will be all components.
  • initialize: This is a function (c,u,t,integrator) which can be used to initialize the state of the callback c. It should modify the argument c and the return is ignored.
  • finalize: This is a function (c,u,t,integrator) which can be used to finalize the state of the callback c. It can modify the argument c and the return is ignored.
  • abstol=10eps(): Tolerance for repeated event prevention. If the callback was just triggered and the new starting condition is less than the tolerance from its value at the root, then the next testing point will be nudged to avoid repeats. If the callback does not mutate the integrator in a way that affect the condition, this can be safely set to 0.0. reltol is deprecated.
  • repeat_nudge = 1//100: This is used to set the next testing point after a previously found zero. Defaults to 1//100, which means after a callback, the next sign check will take place at t + dt*1//100 instead of at t to avoid repeats.
  • initializealg = nothing: In the context of a DAE, this is the algorithm that is used to run initialization after the effect. The default of nothing defers to the initialization algorithm provided in the solve.
  • maybe_discontinuity = true: Declares whether the condition time could have a discontinuity or the affect! could introduce a discontinuity. Defaults to true. This is only used if discontinuity detection is enabled in the controller (i.e. discontinuity_handling = true).
Warning

The effect of using a callback with a DAE needs to be done with care because the solution u needs to satisfy the algebraic constraints before taking the next step. For this reason, a consistent initialization calculation must be run after running the callback. If the chosen initialization alg is BrownFullBasicInit() (the default for solve), then the initialization will change the algebraic variables to satisfy the conditions. Thus if x is an algebraic variable and the callback performs x+=1, the initialization may "revert" the change to satisfy the constraints. This behavior can be removed by setting initializealg = CheckInit(), which simply checks that the state u is consistent, but requires that the result of the affect! satisfies the constraints (or else errors). It is not recommended that NoInit() is used as that will lead to an unstable step following initialization. This warning can be ignored for non-DAE ODEs.

Extended help

  • saved_clock_partitions: An iterable of clock partition indices to save after the callback triggers. MTK-only API.
  • initialize_save_discretes = true: Whether callback initialization should save the discrete parameter partitions listed in saved_clock_partitions when save_positions[2] is true.
source
SciMLBase.VectorContinuousCallbackType
VectorContinuousCallback(
    condition, affect!, len;
    initialize = INITIALIZE_DEFAULT,
    finalize = FINALIZE_DEFAULT,
    idxs = nothing,
    rootfind = LeftRootFind,
    save_positions = (true, true),
    interp_points = 10,
    abstol = 10eps(), reltol = 0, repeat_nudge = 1 // 100,
    initializealg = nothing, maybe_discontinuity = true
)

This is also a subtype of AbstractContinuousCallback. CallbackSet is not feasible when you have many callbacks, as it doesn't scale well. For this reason, we have VectorContinuousCallback - it allows you to have a single callback for multiple events.

VectorContinuousCallback intentionally does not have an affect_neg! callback. Its affect! receives the triggering event index and is responsible for applying the appropriate effect for that event.

Arguments

  • condition: This is a function condition(out, u, t, integrator) which should save the condition value in the array out at the right index. Maximum index of out should be specified in the len property of callback. So, this way you can have a chain of len events, which would cause the ith event to trigger when out[i] = 0.
  • affect!: This is a function affect!(integrator, event_index) which lets you modify integrator and it tells you about which event occurred using event_idx i.e. gives you index i for which out[i] came out to be zero.
  • len: Number of callbacks chained. This is compulsory to be specified.

Rest of the arguments have the same meaning as in ContinuousCallback.

Extended help

  • saved_clock_partitions: An iterable of len elements, where the ith element is an iterable of clock partition indices to save when the ith event triggers. MTK-only API.
  • initialize_save_discretes = true: Whether callback initialization should save the discrete parameter partitions listed in saved_clock_partitions when save_positions[2] is true.
source
SciMLBase.DiscreteCallbackType
DiscreteCallback(
    condition, affect!;
    initialize = INITIALIZE_DEFAULT,
    finalize = FINALIZE_DEFAULT,
    save_positions = (true, true),
    initializealg = nothing
)

Arguments

  • condition: This is a function condition(u,t,integrator) for declaring when the callback should be used. A callback is initiated if the condition evaluates to true. See the Integrator Interface documentation for information about integrator.
  • affect!: This is the function affect!(integrator) where one is allowed to modify the current state of the integrator. For more information on what can be done, see the Integrator Interface manual page.
  • save_positions: Boolean tuple for whether to save before and after the affect!. This saving will occur just before and after the event, only at event times, and does not depend on options like saveat, save_everystep, etc. (i.e. if saveat=[1.0,2.0,3.0], this can still add a save point at 2.1 if true). For discontinuous changes like a modification to u to be handled correctly (without error), one should set save_positions=(true,true).
  • initialize: This is a function (c,u,t,integrator) which can be used to initialize the state of the callback c. It should modify the argument c and the return is ignored.
  • finalize: This is a function (c,u,t,integrator) which can be used to finalize the state of the callback c. It can modify the argument c and the return is ignored.
  • initializealg = nothing: In the context of a DAE, this is the algorithm that is used to run initialization after the effect. The default of nothing defers to the initialization algorithm provided in the solve.
Warning

The effect of using a callback with a DAE needs to be done with care because the solution u needs to satisfy the algebraic constraints before taking the next step. For this reason, a consistent initialization calculation must be run after running the callback. If the chosen initialization alg is BrownFullBasicInit() (the default for solve), then the initialization will change the algebraic variables to satisfy the conditions. Thus if x is an algebraic variable and the callback performs x+=1, the initialization may "revert" the change to satisfy the constraints. This behavior can be removed by setting initializealg = CheckInit(), which simply checks that the state u is consistent, but requires that the result of the affect! satisfies the constraints (or else errors). It is not recommended that NoInit() is used as that will lead to an unstable step following initialization. This warning can be ignored for non-DAE ODEs.

Extended help

  • saved_clock_partitions: An iterable of clock partition indices to save after the callback triggers. MTK-only API.
  • initialize_save_discretes = true: Whether callback initialization should save the discrete parameter partitions listed in saved_clock_partitions when save_positions[2] is true.
source
SciMLBase.CallbackSetType
struct CallbackSet{T1<:Union{Tuple, AbstractVector}, T2<:Union{Tuple, AbstractVector}} <: SciMLBase.DECallback

Container for the callbacks attached to a differential equation solve.

Multiple callbacks can be chained together to form a CallbackSet. A CallbackSet is constructed by passing ContinuousCallback, DiscreteCallback, VectorContinuousCallback, nothing, or other CallbackSet instances:

CallbackSet(cb1, cb2, cb3)

You can pass as many callbacks as needed. Nested callback sets are flattened into two ordered collections, continuous_callbacks and discrete_callbacks. Public constructors use tuples; solver paths may use vectors when callback types must be erased to reuse compilation.

When a solver encounters multiple callbacks, the following rules apply:

  • ContinuousCallbacks and VectorContinuousCallbacks are applied before DiscreteCallbacks. (This is because they often implement event-finding that will backtrack the timestep to smaller than dt).
  • For ContinuousCallbacks and VectorContinuousCallbacks, the event times are found by rootfinding and only the first ContinuousCallback or VectorContinuousCallback affect is applied.
  • The DiscreteCallbacks are then applied in order. Note that the ordering only matters for the conditions: if a previous callback modifies u in such a way that the next callback no longer evaluates condition to true, its affect will not be applied.
source
SciMLBase.split_callbacksFunction
split_callbacks(cs, ds, args...)

Split callbacks into continuous and discrete callback tuples.

cs and ds are the tuples accumulated so far. Additional arguments may be AbstractContinuousCallback, AbstractDiscreteCallback, CallbackSet, or nothing. Nested callback sets are flattened while preserving the order within each category. This is the helper used by the vararg CallbackSet constructor.

source
SciMLBase.save_final_discretes!Function
save_final_discretes!(
    integrator::SciMLBase.DEIntegrator,
    cb::Union{ContinuousCallback, DiscreteCallback, VectorContinuousCallback}
)

Save callback-associated discrete parameter partitions after finalization.

This helper is used at the final time of the simulation. It saves only when the callback has a non-default finalize hook and save_positions[2] is true, so finalizer-induced changes to time-series parameters are reflected in the solution's discrete storage.

source
SciMLBase.save_discretes_if_enabled!Function
save_discretes_if_enabled!(
    integrator::SciMLBase.DEIntegrator,
    cb::Union{ContinuousCallback, DiscreteCallback, VectorContinuousCallback};
    skip_duplicates
)

Save callback-associated discrete parameter partitions during initialization when enabled.

Initialization saves occur only when both save_positions[2] and initialize_save_discretes are true. This lets callbacks participate in symbolic discrete-parameter saving while allowing solver authors or generated callbacks to opt out of the initial discrete save.

Keyword arguments

  • skip_duplicates: Skip saving variables that have already been saved at the current time.
source