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
conditionfunctions 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 usesave_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
initializealgkeyword controls this per callback when it is not inherited fromsolve. - 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.DECallback — Type
abstract type DECallbackCommon 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.
SciMLBase.AbstractContinuousCallback — Type
abstract type AbstractContinuousCallback <: SciMLBase.DECallbackBase 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.
SciMLBase.AbstractDiscreteCallback — Type
abstract type AbstractDiscreteCallback <: SciMLBase.DECallbackBase 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.
SciMLBase.RootfindOpt — Type
RootfindOptSelect 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.
SciMLBase.NoRootFind — Constant
Disable root localization and apply a continuous callback at the detected step endpoint.
SciMLBase.LeftRootFind — Constant
Locate a continuous-callback root and use the solution's left-limit value at the event.
SciMLBase.RightRootFind — Constant
Locate a continuous-callback root and use the solution's right-limit value at the event.
SciMLBase.ContinuousCallback — Type
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 functioncondition(u,t,integrator)for declaring when the callback should be used. A callback is initiated if the condition hits0within the time interval. See the Integrator Interface documentation for information aboutintegrator.affect!: This is the functionaffect!(integrator)where one is allowed to modify the current state of the integrator. If you do not pass anaffect_neg!function, it is called whenconditionis found to be0(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 passnothingas theaffect_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 touare safe in this function.affect_neg!=affect!: This is the functionaffect_neg!(integrator)where one is allowed to modify the current state of the integrator. This is called whenconditionis found to be0(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 touare 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 toLeftRootFind, the solution will be backtracked to the point wherecondition==0and if the solution isn't exact, the left limit of root is used. If set toRightRootFind, the solution would be set to the right limit of the root. Otherwise, the systems and theaffect!will occur att+dt. Note that these enums are not exported, and thus one needs to reference them asSciMLBase.LeftRootFind,SciMLBase.RightRootFind, orSciMLBase.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. Ifinterp_points=0, then conditions will only be noticed if the sign ofconditionis different attthan att+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!).0within the time interval.save_positions=(true,true): Boolean tuple for whether to save before and after theaffect!. This saving will occur just before and after the event, only at event times, and does not depend on options likesaveat,save_everystep, etc. (i.e. ifsaveat=[1.0,2.0,3.0], this can still add a save point at2.1if true). For discontinuous changes like a modification touto be handled correctly (without error), one should setsave_positions=(true,true).idxs=nothing: The components which will be interpolated into the condition. Defaults tonothingwhich meansuwill be all components.initialize: This is a function(c,u,t,integrator)which can be used to initialize the state of the callbackc. It should modify the argumentcand the return is ignored.finalize: This is a function(c,u,t,integrator)which can be used to finalize the state of the callbackc. It can modify the argumentcand 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 to0.0.reltolis deprecated.repeat_nudge = 1//100: This is used to set the next testing point after a previously found zero. Defaults to1//100, which means after a callback, the next sign check will take place att + dt*1//100instead of attto 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 ofnothingdefers to the initialization algorithm provided in thesolve.maybe_discontinuity = true: Declares whether the condition time could have a discontinuity or theaffect!could introduce a discontinuity. Defaults totrue. This is only used if discontinuity detection is enabled in the controller (i.e.discontinuity_handling = true).
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 insaved_clock_partitionswhensave_positions[2]is true.
SciMLBase.VectorContinuousCallback — Type
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 functioncondition(out, u, t, integrator)which should save the condition value in the arrayoutat the right index. Maximum index ofoutshould be specified in thelenproperty of callback. So, this way you can have a chain oflenevents, which would cause theith event to trigger whenout[i] = 0.affect!: This is a functionaffect!(integrator, event_index)which lets you modifyintegratorand it tells you about which event occurred usingevent_idxi.e. gives you indexifor whichout[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 oflenelements, where theith element is an iterable of clock partition indices to save when theith event triggers. MTK-only API.initialize_save_discretes = true: Whether callback initialization should save the discrete parameter partitions listed insaved_clock_partitionswhensave_positions[2]is true.
SciMLBase.DiscreteCallback — Type
DiscreteCallback(
condition, affect!;
initialize = INITIALIZE_DEFAULT,
finalize = FINALIZE_DEFAULT,
save_positions = (true, true),
initializealg = nothing
)Arguments
condition: This is a functioncondition(u,t,integrator)for declaring when the callback should be used. A callback is initiated if the condition evaluates totrue. See the Integrator Interface documentation for information aboutintegrator.affect!: This is the functionaffect!(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 theaffect!. This saving will occur just before and after the event, only at event times, and does not depend on options likesaveat,save_everystep, etc. (i.e. ifsaveat=[1.0,2.0,3.0], this can still add a save point at2.1if true). For discontinuous changes like a modification touto be handled correctly (without error), one should setsave_positions=(true,true).initialize: This is a function(c,u,t,integrator)which can be used to initialize the state of the callbackc. It should modify the argumentcand the return is ignored.finalize: This is a function(c,u,t,integrator)which can be used to finalize the state of the callbackc. It can modify the argumentcand 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 ofnothingdefers to the initialization algorithm provided in thesolve.
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 insaved_clock_partitionswhensave_positions[2]is true.
SciMLBase.CallbackSet — Type
struct CallbackSet{T1<:Union{Tuple, AbstractVector}, T2<:Union{Tuple, AbstractVector}} <: SciMLBase.DECallbackContainer 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 andVectorContinuousCallbacks are applied beforeDiscreteCallbacks. (This is because they often implement event-finding that will backtrack the timestep to smaller thandt).- For
ContinuousCallbacks andVectorContinuousCallbacks, the event times are found by rootfinding and only the firstContinuousCallbackorVectorContinuousCallbackaffect is applied. - The
DiscreteCallbacks are then applied in order. Note that the ordering only matters for the conditions: if a previous callback modifiesuin such a way that the next callback no longer evaluates condition totrue, itsaffectwill not be applied.
SciMLBase.split_callbacks — Function
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.
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.
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.