DifferentialEquations.jl v8 / OrdinaryDiffEq v7 Migration Guide

DifferentialEquations.jl v8 is the coordinated meta-release of this breaking-change wave. The actual breaking changes live in the underlying solver and base packages — most prominently OrdinaryDiffEq v7, SciMLBase v3, DiffEqBase v7, RecursiveArrayTools v4, DelayDiffEq v6, StochasticDiffEq v7, and the cascade of major bumps across every OrdinaryDiffEqXXX / StochasticDiffEqXXX sublib (see SciML/OrdinaryDiffEq.jl#3562 for the cascade and SciML/OrdinaryDiffEq.jl#3565 for the OrdinaryDiffEqCore / DiffEqDevTools revert that kept those two at v4 and v3 respectively).

If you upgrade DifferentialEquations to v8 you will pick up the full set; if you depend on a specific sublib (e.g. OrdinaryDiffEqTsit5) you only get that lib's bump — the migration items below are split by package so you only need to read the parts relevant to what you use.

This page mirrors https://github.com/SciML/OrdinaryDiffEq.jl/blob/master/NEWS.md as of 2026-04-24; that file is the authoritative source of truth — check there for any updates not yet reflected here.


DifferentialEquations.jl v8: scope reduction

The single most user-visible v8 change is what using DifferentialEquations actually loads. Through v7, the umbrella package re-exported the entire SciML solver suite — OrdinaryDiffEq, StochasticDiffEq, DelayDiffEq, BoundaryValueDiffEq, Sundials, JumpProcesses, SteadyStateDiffEq, LinearSolve, NonlinearSolve, Optimization, and more — so a single using DifferentialEquations gave you every solver family in one shot. That broad surface drove up using time and obscured which package each solver actually came from.

In v8, using DifferentialEquations only loads OrdinaryDiffEq. All other solver families have been removed from the umbrella. If your code relied on DifferentialEquations for SDEs, DDEs, BVPs, jumps, steady states, DAE wrappers, or any non-ODE solver, you now need to add the topic-specific package to your project explicitly.

What to import instead

TopicOld (v7 and earlier)New (v8)
ODEsusing DifferentialEquationsusing OrdinaryDiffEq (or a sublib like using OrdinaryDiffEqTsit5: Tsit5)
Stochastic ODEs (SDEs)using DifferentialEquationsusing StochasticDiffEq
Delay ODEs (DDEs)using DifferentialEquationsusing DelayDiffEq
Boundary value problemsusing DifferentialEquationsusing BoundaryValueDiffEq (or one of the BVP sublibs)
Jump problemsusing DifferentialEquationsusing JumpProcesses
Steady stateusing DifferentialEquationsusing SteadyStateDiffEq
DAEs (mass matrix)(already in OrdinaryDiffEq)using OrdinaryDiffEq
DAEs (implicit / IDA)using DifferentialEquationsusing Sundials (or use a topic sublib)
Sundials wrappers (CVODE, IDA, ARKODE)using DifferentialEquationsusing Sundials
Linear solvesusing DifferentialEquationsusing LinearSolve
Nonlinear solvesusing DifferentialEquationsusing NonlinearSolve
Optimizationusing DifferentialEquationsusing Optimization

Example

# v7 — the umbrella covered everything
using DifferentialEquations
prob_ode = ODEProblem(f, u0, tspan)
prob_sde = SDEProblem(f, g, u0, tspan)
prob_dde = DDEProblem(f, u0, h, tspan)
sol_ode = solve(prob_ode, Tsit5())
sol_sde = solve(prob_sde, SOSRI())
sol_dde = solve(prob_dde, MethodOfSteps(Tsit5()))

# v8 — add the topic packages you actually use
using OrdinaryDiffEq      # ODEs (still re-exported by DifferentialEquations)
using StochasticDiffEq    # SDEs — now an explicit dep
using DelayDiffEq         # DDEs — now an explicit dep

prob_ode = ODEProblem(f, u0, tspan)
prob_sde = SDEProblem(f, g, u0, tspan)
prob_dde = DDEProblem(f, u0, h, tspan)
sol_ode = solve(prob_ode, Tsit5())
sol_sde = solve(prob_sde, SOSRI())
sol_dde = solve(prob_dde, MethodOfSteps(Tsit5()))

The problem-type constructors (ODEProblem, SDEProblem, DDEProblem, BVProblem, JumpProblem, SteadyStateProblem, …) are defined in SciMLBase and re-exported by every topic package, so you do not need using SciMLBaseusing StochasticDiffEq already gives you SDEProblem, using DelayDiffEq already gives you DDEProblem, and so on.

Why

  • using time. The pre-v8 umbrella precompile chain pulled in the full SciML solver suite for every user, even ones who only solved ODEs. Most users want exactly one or two topic packages; the umbrella was paying the loading cost of all of them.
  • Honest dependency graph.using DifferentialEquations no longer silently brings in Optimization, LinearSolve, NonlinearSolve, Sundials, etc. The Project.toml of a v8 project now reads as the list of solver families actually being used, which makes version-pinning and upgrade decisions much easier.
  • Independent release cadence. Every topic package can now bump its major version without waiting for the others. StochasticDiffEq, DelayDiffEq, BoundaryValueDiffEq, JumpProcesses, etc. are no longer blocked on a coordinated meta-release — DifferentialEquations is just the ODE umbrella now.

How this relates to the OrdinaryDiffEq v7 changes

The scope-reduction discussed here is independent of the OrdinaryDiffEq v7 breaking changes documented in the rest of this page. OrdinaryDiffEq v7 ships with DifferentialEquations v8, but the two changes are decoupled:

  • You can use OrdinaryDiffEq v7 directly (using OrdinaryDiffEq) without the umbrella package at all. This is the recommended setup for libraries that want minimal load time and an explicit dependency on just the ODE solver family. See the Reduced Compile Time, Optimizing Runtime, and Low Dependency Usage page for the per-sublib import pattern (e.g. using OrdinaryDiffEqTsit5: Tsit5 to load only Tsit5 and its support code), which trims using time even further.
  • You can still use the DifferentialEquations umbrella under v8; it now just means "OrdinaryDiffEq + a few convenience exports" rather than "every solver in SciML."

For the full v7 + v8 breaking-change list — including the typed kwargs, controller refactor, RAT v4 indexing, ADTypes migration, etc. — see the OrdinaryDiffEq NEWS.md PR and the per-package sections below.


OrdinaryDiffEq.jl v7 Breaking Changes

This release bumps to SciMLBase v3, RecursiveArrayTools v4, and includes breaking changes across DiffEqBase, OrdinaryDiffEqCore, and all solver sublibraries.

Themes of the v7 release

Most of the breaking changes fall into a small set of recurring themes. Keep these in mind while reading the migration table — they explain why an individual change exists and often suggest the right migration direction:

  • Time to first solve (TTFS) reduction. Direct deps on Static.jl, StaticArrayInterface.jl, Polyester.jl, and StaticArrays.jl were dropped; using OrdinaryDiffEq now loads only the default solver set; ODEFunction switched to AutoSpecialize. All of this means less code loaded and more precompilation caching on first solve.
  • Type stability everywhere. All Bool solver/solve keyword arguments (autodiff, verbose, alias, lazy, …) were replaced by typed objects. Passing a Bool no longer changes dispatch in ways the compiler cannot specialize on, and the reverse is no longer allowed to silently fall back through slow generic paths.
  • Generality beyond ForwardDiff.chunk_size, diff_type, standardtag, etc. encoded ForwardDiff-specific or FiniteDiff-specific knobs on every solver. They are replaced by the ADTypes interface (AutoForwardDiff, AutoFiniteDiff, AutoEnzyme, AutoZygote, …) so every solver automatically generalizes to any AD backend.
  • Controller is now an object, not a pile of solve kwargs.gamma, beta1, beta2, qmin, qmax, qsteady_min, qsteady_max, qoldinit were moved onto concrete PIController / PIDController / IController / PredictiveController structs, and EEst moved to the controller cache. This is prep work for pluggable controllers and removes a large amount of dead state from the integrator struct.
  • Cleanup of old re-exports / deprecations. Functions like has_destats (now has_stats), sol.destats (now sol.stats), DEAlgorithm/DEProblem/DESolution abstract types, tuples()/intervals(), QuadratureProblem, fastpow, concrete_solve, etc. were on a deprecation path for one or more minor releases. v7 removes them.

The cleanest path is not to jump straight from an old environment onto v7. Most renamed APIs (e.g. DEAlgorithmAbstractDEAlgorithm, u_modified!derivative_discontinuity!, has_destatshas_stats, sol.destatssol.stats, the construct* tableau functions, alias_u0/alias_du0, beta1/beta2, PID kwargs) already exist under their new names in SciMLBase v2 / OrdinaryDiffEq v6 with deprecation warnings. The recommended sequence is:

  1. Stay on SciMLBase v2 / OrdinaryDiffEq v6. Update your code to the new names (has_stats, sol.stats, AbstractDEAlgorithm, derivative_discontinuity!, ODEAliasSpecifier, ODEVerbosity, ADTypes-based autodiff, explicit controller = … objects, new tableau names) while the deprecation shims still exist.
  2. Verify your tests pass on v6 with no deprecation warnings.
  3. Then bump to v7. At this point your code should compile and run against v7 without further changes aside from the genuinely new breakage (RAT v4 array semantics, ensemble prob_func/output_func signature, struct type parameter removals, kwargs that truly no longer exist, default changes like CheckInit and williamson_condition=false).

Doing it in two steps keeps the diff small per step and lets the deprecation warnings on v6 point you at the exact call sites that will break on v7.

Fallback for RAT v4 indexing

If you cannot update sol[i] / length(sol) / eachindex(sol) call sites yet (see the RAT v4 table below), you can opt back into v3 semantics on a per-solution basis by converting the container type to the ragged variant:

using RecursiveArrayToolsRaggedArrays
sol_old = RaggedVectorOfArray(sol)   # indexes like v3: sol_old[i] is the i-th timestep

RecursiveArrayToolsRaggedArrays.jl preserves the previous AbstractVectorOfArray indexing behavior (timestep-first, not element-first). This is the escape hatch for code that assumes sol[i] returns the i-th timestep. It is, however, recommended that you update to the sol.u[i] / sol[:, i] style — the ragged wrapper is a compatibility layer, not the canonical API going forward.


RecursiveArrayTools v4

ODESolution is now an AbstractArray

AbstractVectorOfArray (the parent type of ODESolution, RODESolution, DAESolution, etc.) now subtypes AbstractArray. This changes the semantics of several common operations:

Operationv3 (old)v4 (new)Migration
sol[i]Returns i-th timestep (Vector)Returns i-th scalar element (column-major)Use sol.u[i] or sol[:, i]
length(sol)Number of timestepsprod(size(sol)) (total elements)Use length(sol.t) or length(sol.u)
eachindex(sol)1:nstepsCartesianIndices(size(sol))Use eachindex(sol.u)
iterate(sol)Iterates over timestepsIterates over scalar elementsUse for u in sol.u
first(sol) / last(sol)First/last timestepFirst/last scalar elementUse first(sol.u) / last(sol.u)
map(f, sol)Maps over timestepsMaps over elementsUse map(f, sol.u)
maximum(sol)Maximum over timestepsMaximum over all elementsUse maximum(f, sol.u)

Why: making AbstractVectorOfArray <: AbstractArray lets every generic AbstractArray consumer (LinearAlgebra, broadcasting, Zygote adjoints, StructArrays, etc.) work on solutions without any special casing in SciMLBase, and deletes a large pile of manual method overrides.

Migration shortcut:sol.u[i] is the forward-compatible form under both v3 and v4 — if you change every sol[i]sol.u[i] in your code base now, it works on both versions.

Full fallback: see the "Fallback for RAT v4 indexing" section above — wrapping with RaggedVectorOfArray from RecursiveArrayToolsRaggedArrays.jl restores v3 indexing.

EnsembleSolution indexing

The same AbstractArray migration applies to EnsembleSolution (from EnsembleProblem trajectories) and to EnsembleAnalysis helpers — an ensemble's .u is a Vector{<:ODESolution} and the solution itself subtypes AbstractVectorOfArray:

Operationv3 (old)v4 (new)Migration
sim[j]j-th trajectory (ODESolution)j-th scalar element of the flattened containerUse sim.u[j]
sim[i, j]Trajectory j's i-th timestep (Matrix / Vector)Scalar element at column-major position (i, j)Use sim.u[j].u[i]
sim[i, j, k]Row k of trajectory j's i-th timestepScalar at (i, j, k)Use sim.u[j].u[i][k]
length(sim)Number of trajectoriesprod(size(sim)) (total scalar count)Use length(sim.u)
for sol in simIterate trajectoriesIterate scalar elements (column-major)Use for sol in sim.u

Migration shortcut: as with ODESolution, sim.u[j] / sim.u[j].u[i] / length(sim.u) are the forward-compatible forms that work under both v3 and v4.

EnsembleAnalysis helpers (SciMLBase ≥ 3.4.3):get_timestep, get_timepoint, timeseries_steps_mean/median/quantile/meanvar/meancov/meancor/weighted_meancov, EnsembleSummary, and the weak-error path in calculate_ensemble_errors were updated to iterate sim.u and use length(sim.u[1].t) for the timestep count rather than length(sim) / length(sim.u[1]). If you pin SciMLBase < 3.4.3 you may hit a BoundsError at calculate_ensemble_errors on a weak EnsembleProblem with error_estimate; bump to 3.4.3 or later.

See SciML/OrdinaryDiffEq.jl#3532 and SciML/SciMLBase.jl#1326 for the specific v3→v4 ensemble-indexing migrations.

Other RAT v4 changes

  • zero(VectorOfArray) now preserves container type (e.g. StructVector) via rewrap
  • Ragged arrays: size(sol) reports maximum dimensions; out-of-bounds elements return zero(T)
  • Removed: custom length, eachindex, iterate, first, last, eltype, ndims, axes, any, all, sum, prod, mapreduce, map overrides (all inherited from AbstractArray)
  • Removed: convert(::Type{AbstractArray}, ...) (identity since it IS an AbstractArray)
  • Removed: ==(::AbstractVectorOfArray, ::AbstractArray) override

SciMLBase v3

Renamed APIs

The new names already exist under SciMLBase v2 with deprecation warnings. Update to the v3 names while still on v2 before bumping.

Old (v2, removed in v3)New (v2 + v3)Migration
u_modified!(integrator, bool)derivative_discontinuity!(integrator, bool)Search-and-replace. u_modified! was misleading — the callback system doesn't care whether u changed, it cares whether the derivative is discontinuous.
integrator.u_modifiedintegrator.derivative_discontinuityField access rename.
DEAlgorithmAbstractDEAlgorithmAbstract type name aligned with SciML-wide Abstract… convention.
DEProblemAbstractSciMLProblemSame.
DESolutionAbstractSciMLSolutionSame.
sol.destatssol.statsDrop the de prefix.
has_destats(alg)has_stats(alg)Same renaming applied to the trait function.

Removed deprecations

All of these printed a deprecation warning under SciMLBase v2. They are gone in v3:

  • has_destats function → use has_stats
  • symbol_to_ReturnCode and Symbol-to-ReturnCode conversion → use ReturnCode.* directly (see next section)
  • syms/paramsyms/indepsym kwargs on all SciMLFunction constructors → use the problem's sys / MTK symbolic interface
  • sol.x on AbstractOptimizationSolution → use sol.u
  • prob.lb/prob.ub on IntegralProblem → use prob.domain
  • sol.minimizer/sol.minimum → use sol.u / sol.objective
  • tuples(), intervals() iterator functions → iterate over sol.u / sol.t directly
  • IntegratorTuples, IntegratorIntervals, TimeChoiceIterator types
  • QuadratureProblem alias → use IntegralProblem
  • EnsembleProblem vector-of-problems constructor → use a prob_func
  • IntegralProblemnout/batch kwargs → set on the integrand function directly
  • SciMLBaseMLStyleExt extension (MLStyle dependency removed)

sol.retcode is a ReturnCode.T, not a Symbol

Comparing a solution's retcode against a Symbol no longer works:

# Worked on v1/v2, BROKEN on v3
sol.retcode == :Success

The Symbol return codes were deprecated years ago in favor of the ReturnCode.T enum, with a deprecation warning printed on every use across the entire v2 series. The deprecation shim has now been removed.

Migration: prefer SciMLBase.successful_retcode(sol) over equality against a specific ReturnCode.* value. successful_retcode correctly accepts every success-ish return code (Success, StalledSuccess, ExactSolutionLeft, ExactSolutionRight, FloatingPointLimit, …), not just Success — so a solver that terminated at an exact solution or hit a floating-point limit isn't misclassified as a failure.

OldNew (v3)
sol.retcode == :SuccessSciMLBase.successful_retcode(sol)
sol.retcode == :Failure!SciMLBase.successful_retcode(sol) (or match the specific ReturnCode.Failure if you really need that exact code)
sol.retcode == :MaxIterssol.retcode == ReturnCode.MaxIters
sol.retcode == :Defaultsol.retcode == ReturnCode.Default

The full enum is defined in SciMLBase/src/retcodes.jl and documented at https://docs.sciml.ai/SciMLBase/stable/interfaces/Solutions/#retcodes.

Changed defaults

  • ODEFunction{iip}(f) now uses AutoSpecialize by default (was FullSpecialize). Why: drastically more precompilation caching now "works" out of the box — AutoSpecialize stores a lightly-specialized method that can be reused across different right-hand side function types instead of baking a fresh specialization per-user. Combined with the default-solver-set reduction (below), this is the single biggest TTFS improvement in v7. Migration: if you want the old behavior (e.g. you are benchmarking and need maximum inlining of f), write ODEFunction{iip, FullSpecialize}(f) explicitly.
  • is_discrete_time_domain(nothing) now returns false (was true). Affects only code that called this trait with nothing — replace with an explicit domain.

Ensemble RNG redesign

  • prob_func(prob, i, repeat)prob_func(prob, ctx) where ctx::EnsembleContext
  • output_func(sol, i)output_func(sol, ctx)
  • New seed / rng / rng_func kwargs on solve() for deterministic, thread-count-independent ensemble solves

Why: the old (i, repeat) signature had no way to plumb a reproducible per-trajectory RNG through, so ensemble results depended on thread count and scheduling. EnsembleContext carries an RNG derived from a single top-level seed, so results are reproducible regardless of how many workers run in parallel.

Migration:

# v6
prob_func = (prob, i, repeat) -> remake(prob, u0 = rand(length(prob.u0)))

# v7
prob_func = (prob, ctx) -> remake(prob, u0 = rand(ctx.rng, length(prob.u0)))

Within the new signature, ctx.i is the trajectory index and ctx.repeat is the retry counter if you need them. Pass solve(ensemble_prob, alg; seed=42, trajectories=N) for a reproducible run.

Removed Zygote integer-indexing adjoints for ODESolution

Integer-indexing Zygote adjoints removed; Zygote's generic AbstractArray rules handle this under RAT v4. Migration: nothing to do if you were using sol[i] for AD — the generic path produces the same gradient. Code that explicitly called the ChainRulesCore/Zygote rule by name should delete those references.


OrdinaryDiffEq v7

Package scope reduction

using OrdinaryDiffEq now loads only the default solver set:

  • Included: DefaultODEAlgorithm, Tsit5, AutoTsit5, Vern6Vern9, AutoVern6AutoVern9, Rosenbrock23, Rodas5P, FBDF
  • Not included: all other solvers require explicit import of their sublibrary
# v6
using OrdinaryDiffEq
solve(prob, KenCarp4())  # worked

# v7
using OrdinaryDiffEqSDIRK  # explicit import required
solve(prob, KenCarp4())

Why: TTFS. The old umbrella package loaded every solver family (exponential integrators, symplectic RK, stabilized methods, multirate, Taylor series, …) whether you used them or not. The solver you actually care about, plus its precompilation cache, is now a small fraction of what gets loaded.

Migration: add using OrdinaryDiffEq<Family> for any non-default solver you use. The sublibrary name is predictable — KenCarp*/TRBDF2OrdinaryDiffEqSDIRK, Rosenbrock*/Rodas* except Rosenbrock23/Rodas5POrdinaryDiffEqRosenbrock, RadauIIA*OrdinaryDiffEqFIRK, etc. Every family has its own lib/OrdinaryDiffEq<X> directory in the repo.

Note If you are still manually choosing Rodas5, we highly recommend changing to Rodas5P because it has a very similar performance profile while being much more robust in terms of accuracy.

Algorithm struct type parameters removed

All {CS, AD, FDT, ST, CJ} type parameters removed from algorithm abstract and concrete types. Over 100 structs affected across BDF, SDIRK, Rosenbrock, FIRK, Extrapolation, ExponentialRK, IMEXMultistep, PDIRK, Newmark, StabilizedIRK, and StochasticDiffEqImplicit.

# v6
ImplicitEuler{0, AutoForwardDiff{nothing, Nothing}, Nothing, NLNewton{...}, typeof(DEFAULT_PRECS), Val{:forward}(), true, nothing, typeof(trivial_limiter!)}

# v7
ImplicitEuler{AutoForwardDiff{nothing, Nothing}, Nothing, NLNewton{...}, typeof(trivial_limiter!), Nothing}

Why: the removed parameters (CS = chunk size, FDT = finite diff type, ST = standard tag, CJ = concrete Jacobian) are all now carried by the ADTypes object on the autodiff field, not baked into the algorithm's type. This shrinks the method table, improves compile time, and means a single autodiff = AutoForwardDiff(chunksize=12) now communicates what used to take five type parameters.

Migration: any code dispatching on SomeAlg{CS, AD, FDT, ST, CJ} will break. Most call sites want either SomeAlg (no parameters at all) or dispatch on the autodiff type via alg.autodiff. If you genuinely need to specialize on chunksize, inspect the ADTypes object at runtime.

Removed keyword arguments from algorithm constructors

Across all implicit/Rosenbrock/BDF/SDIRK/FIRK/Exponential constructors:

Removed kwargMigration
chunk_sizeSet via autodiff = AutoForwardDiff(chunksize=N)
diff_typeSet via autodiff = AutoFiniteDiff(fdtype=Val(:central))
standardtagAlways true; remove the kwarg
precsPreconditioners now configured via linsolve kwarg (see LinearSolve.jl docs on the Pl/Pr interface)
controller (on individual algs)Set at solve() level via controller = PIController(...) etc.

Why:chunk_size and diff_type only meant anything to ForwardDiff and FiniteDiff respectively. Hoisting them onto the ADTypes object means every solver now works with any AD backend (Enzyme, Zygote, ReverseDiff, Mooncake, …) by just swapping autodiff=AutoEnzyme() — no per-solver kwarg surface needed. standardtag was always the right default. precs moved under linsolve because LinearSolve now owns the preconditioner abstraction.

autodiff: Bool no longer accepted

# v6
Rosenbrock23(autodiff=true)   # worked
Rosenbrock23(autodiff=false)  # worked

# v7 — must use ADTypes
Rosenbrock23(autodiff=AutoForwardDiff())
Rosenbrock23(autodiff=AutoFiniteDiff())

Why: type stability. autodiff::Bool forced a runtime branch between AD backends that the compiler couldn't specialize through; autodiff::AbstractADType dispatches at compile time. This is also what enables the "generalizes beyond ForwardDiff" story — you can now pass AutoEnzyme(), AutoZygote(), AutoMooncake(), etc., and the solver specializes correctly.

verbose: Bool no longer accepted

# v6
solve(prob, alg, verbose=false)

# v7 — must use ODEVerbosity
solve(prob, alg, verbose=ODEVerbosity(SciMLLogging.None()))

Why: same type-stability reason, plus ODEVerbosity exposes fine-grained control (separate levels for nonlinear solver, linear solver, initialization, etc.) that a single Bool can't express.

alias: Bool no longer accepted

# v6
solve(prob, alg, alias=true)

# v7 — must use ODEAliasSpecifier
solve(prob, alg, alias=ODEAliasSpecifier(alias_u0=true))

Deprecated alias_u0 / alias_du0 keyword shortcuts also removed — they already printed warnings on v6. Update to alias = ODEAliasSpecifier(alias_u0=…, alias_du0=…) on v6 first, then upgrade.

Why: type stability + finer control. alias=true used to mean "alias everything that can be aliased," which was ambiguous when some things could be safely aliased and others couldn't. ODEAliasSpecifier makes each aliasable buffer an explicit opt-in.

Default DAE initialization changed to CheckInit

# v6 — inconsistent initial conditions silently fixed
solve(prob, Rodas5P())

# v7 — errors on inconsistent initial conditions
solve(prob, Rodas5P())  # throws if u0 is inconsistent

# v7 — explicit opt-in to automatic fixing
solve(prob, Rodas5P(), initializealg=BrownFullBasicInit())

Why: silently fixing an inconsistent DAE initial condition produced wrong answers when the user's u0 was actually correct but a modeling bug elsewhere made the system look inconsistent. The new default errors loudly; users who want the old "just fix it" behavior opt in explicitly via initializealg=BrownFullBasicInit().

Controller refactor

PID controller parameters removed from solve()/init():

Removed kwargMigration
gammaPass controller = PIController(gamma=…) or PIDController(...)
beta1, beta2Constructor args on PIController / PIDController
qmin, qmaxSame — on the controller object
qsteady_min, qsteady_maxSame
qoldinitSame

EEst moved from integrator to controller cache:

# v6
integrator.EEst

# v7
OrdinaryDiffEqCore.get_EEst(integrator)
OrdinaryDiffEqCore.set_EEst!(integrator, val)

Fields EEst, qold, q11, erracc, dtacc removed from ODEIntegrator struct.

Why: controllers are now real, pluggable objects rather than a collection of loose numeric knobs on solve. This makes it possible to write a custom controller and just pass controller = MyController(…) without adding yet more kwargs to solve, and keeps controller state (including EEst) inside the controller where it belongs instead of on the integrator.

Migration: if you only ever set gamma, just write controller = PIController(gamma=0.9) (or whatever value). The default controller per algorithm is unchanged; the kwargs are what moved.

lazy keyword: Bool no longer accepted

BS5, Vern6Vern9: lazy must be Val{true}() or Val{false}(), not true/false. Why: type stability — same story as autodiff / verbose / alias.

williamson_condition default changed

All 2N low-storage RK methods: default changed from williamson_condition=true to williamson_condition=false. Why: this optimization only works for mutable Array-style state. Having it on by default silently made the method wrong (or errored) for StaticArrays, GPU arrays, ComponentArrays, etc. Off-by-default is the safe choice; opt in with williamson_condition=true when you know your state is a plain Array.

Threading interface changed

Old (v6)New (v7)
OrdinaryDiffEq.False() / Static.False()Serial() (from FastBroadcast)
OrdinaryDiffEq.True() / Static.True()Threaded() (from FastBroadcast)

All calculate_residuals! thread argument types changed from Union{False, True} to Union{Serial, Threaded}.

Why: this change is what makes the Static.jl dependency removal possible, and Static.jl removal is one of the largest single contributions to v7's TTFS reduction. The Serial/Threaded types from FastBroadcast are semantically identical for this purpose and FastBroadcast is already a transitive dep. Migration: if your code pattern-matched on Static.True/Static.False for threading dispatch, swap to FastBroadcast.Threaded/FastBroadcast.Serial.


Removed dependencies

All four removals are driven by TTFS.

PackageReplacementWhy
Static.jlFastBroadcast.Serial / FastBroadcast.ThreadedRemoving Static slashes load time and eliminates a wide surface of StaticInt/StaticBool specialization that the compiler was re-running for every user
StaticArrayInterface.jlArrayInterface.ismutableThe only thing OrdinaryDiffEq actually used from StaticArrayInterface was the mutability query, which ArrayInterface already provides
Polyester.jl (direct dep)Moved to weak dep OrdinaryDiffEqCorePolyesterExt; requires using Polyester to activatePolyester loads a nontrivial amount of threading infrastructure. Users who don't enable Polyester-threaded solvers no longer pay for it
StaticArrays.jl (direct dep)SVector/MVector in tableaus replaced with NTuple/Vector; SA moved to test-onlyLoading StaticArrays forces compilation of a large generated-function surface for every solver that mentions an SVector. Tableaus used them for constants, which NTuple expresses just as statically

DiffEqBase changes

DiffEqBase is now a sublibrary under lib/DiffEqBase (migrated from standalone package). Key changes:

  • has_destats re-export removed (function removed from SciMLBase v3 — use has_stats)
  • RECOMPILE_BY_DEFAULT re-export removed (unused)
  • fastpow deprecation removed — use FastPower.fastpower
  • DEStats deprecation removed — use SciMLBase.DEStats
  • concrete_solve deprecation removed — use solve directly
  • FunctionWrappersWrapper wrapping updated for new LinearSolve precs interface

Why migrate DiffEqBase into the monorepo: it's tightly coupled to OrdinaryDiffEq's internals and most features were only available through OrdinaryDiffEq; keeping them in lockstep releases eliminates a compatibility-bound class of bugs.


Callback changes

VectorContinuousCallback now fires every simultaneous event

Previously, when several conditions of a single VectorContinuousCallback crossed zero on the same step, only the first crossing's affect was applied. Other simultaneous events were silently dropped, even though their root was within the step's resolution. Bouncing-balls, multi-contact mechanics, and any callback used as a friction/threshold state machine were all affected.

In v7, VectorContinuousCallback resolves all simultaneous events on the same step and dispatches them in one call to the user's affect!. See SciML/OrdinaryDiffEq.jl#3230 (rolled into the v7 merge #3242) and the follow-up fix #3549.

Breaking: VectorContinuousCallbackaffect! signature changed

The affect! callback for VectorContinuousCallback used to be invoked once per triggering condition, with the index of that single event:

# v6
affect!(integrator, event_index::Int)

In v7 it is invoked once per step with a Vector{Int8} mask describing every condition's status:

# v7
affect!(integrator, simultaneous_events::Vector{Int8})

Each entry of simultaneous_events encodes both whether condition i triggered and which way it crossed:

valuemeaning
0condition did not trigger this step
-1upcrossing (condition went from negative to positive)
+1downcrossing (condition went from positive to negative)

The vector's length is the callback's len; the entries are stable across steps. affect_neg! is no longer called for VectorContinuousCallback — your single affect! handles both crossing directions by inspecting the sign of each nonzero entry.

Migration:

# v6: branch on the event index, optionally a separate affect_neg!
function affect!(integrator, event_index)
    if event_index == 1
        # ball 1 hit the ground
    elseif event_index == 2
        # ball 2 hit the ground
    end
end
cb = VectorContinuousCallback(condition, affect!, affect_neg!, 2)

# v7: branch on which entries fired, sign tells you the direction
function affect!(integrator, simultaneous_events)
    for i in eachindex(simultaneous_events)
        s = simultaneous_events[i]
        s == 0 && continue
        if i == 1
            # ball 1 hit the ground; s == -1 upcrossing, s == +1 downcrossing
        elseif i == 2
            # ball 2 hit the ground
        end
    end
end
cb = VectorContinuousCallback(condition, affect!, 2)

The previous "call affect! once per event index" behavior was load-bearing for very few users, since the pre-v7 implementation already only invoked it for the first simultaneous event anyway. The new shape makes the simultaneous case representable without additional API surface.

Why a Vector{Int8} rather than Vector{Bool}: the sign carries the crossing direction, which previously required a separate affect_neg! callback. Folding both into the mask removes the affect! / affect_neg! split for VectorContinuousCallback, so users no longer have to maintain two parallel functions to get up- and down-crossing handling. (The ContinuousCallbackaffect! / affect_neg! split is unchanged.)


OrdinaryDiffEqCore changes

  • DEOptions struct: removed gamma, qmax, qmin, qsteady_max, qsteady_min, qoldinit, controller fields and the Controller type parameter (moved to controller object; see Controller refactor above)
  • ODEIntegrator struct: removed EEst, qold, q11, erracc, dtacc, q fields and EEstT, QT type parameters; added controller_cache field. Migration:integrator.EEstOrdinaryDiffEqCore.get_EEst(integrator); the rest live on integrator.controller_cache
  • u_modified field → derivative_discontinuity
  • DEFAULT_PRECS removed (preconditioners now configured via linsolve)
  • ispredictive / isstandard controller traits removed — algorithms now override default_controller directly. Migration: if you defined a custom algorithm that set isstandard(::MyAlg) = true, instead define default_controller(::MyAlg, args...) = IController(…)
  • has_chunksize removed (dead code — chunksize is on the ADTypes object now)
  • Backwards-compat positional Alg(stage_limiter!, step_limiter!) constructors removed from every explicit RK sublibrary — 99 constructors total across LowStorageRK (44), SSPRK (18), LowOrderRK (22), Verner (5: Vern6/7/8/9, RKV76IIa), HighOrderRK (4: TanYam7, TsitPap8, DP8, PFRK87), SIMDRK (3: MER5v2, MER6v2, RK6v4), QPRK (QPRK98), TaylorSeries (ExplicitTaylor2), and Tsit5. Migration: use the keyword form Alg(; stage_limiter! = my_limiter, step_limiter! = my_limiter). The kwarg constructors (and no-arg Alg() defaulting both limiters to trivial_limiter!) are unchanged.

Tableau changes

Tableau functions in OrdinaryDiffEqExplicitTableaus / OrdinaryDiffEqImplicitTableaus dropped the construct prefix:

# v6
constructDormandPrince()

# v7
OrdinaryDiffEqExplicitTableaus.DormandPrince()

~80+ functions renamed. Functions are no longer exported — qualify explicitly with the sublibrary name. The new names also exist on v6 as deprecations, so you can rename first and bump later.

DiffEqDevTools tableau constructors removed (DiffEqDevTools v2 → v3, breaking)

Previously, DiffEqDevTools re-exported 105 construct* tableau constructors (constructEuler, constructKutta3, constructRK4, constructRK438Rule, constructSSPRK22, constructImplicitEuler, constructMidpointRule, constructTrapezoidalRule, constructLobattoIIIA4, constructGL2, …) defined in its own src/ode_tableaus.jl. These are removed in DiffEqDevTools v3 — the authoritative tableau definitions now live exclusively in OrdinaryDiffEqExplicitTableaus / OrdinaryDiffEqImplicitTableaus under their renamed bare forms.

Migration:

# v6 (DiffEqDevTools v2)
using DiffEqDevTools
tab = constructRK4()

# v7 (DiffEqDevTools v3)
using OrdinaryDiffEqExplicitTableaus
tab = OrdinaryDiffEqExplicitTableaus.RK4()

DiffEqDevTools.deduce_Butcher_tableau(alg) (which recovers A/b/c from a live solver) is kept unchanged, as is the ODERKTableau / ExplicitRKTableau / ImplicitRKTableau type surface.


StochasticDiffEq / DelayDiffEq

Same theme as the ODE side — all of the following break identically to the ODE versions, with identical migrations:

  • Same verbose, alias, autodiff Bool-to-typed-object changes as ODE solvers
  • alias_u0 / alias_jumps / alias_noise deprecated kwargs removed → use ODEAliasSpecifier (for SDE: SDEAliasSpecifier)
  • beta1 / beta2 PID parameter deprecation warnings removed → set on the controller object
  • initial_order deprecated kwarg removed from DelayDiffEq
  • ispredictive / isstandard trait definitions removed; algorithms now override default_controller directly
  • @static if isdefined version guards removed (always true on v7)

StochasticDelayDiffEq deprecated

StochasticDelayDiffEq.jl is deprecated. Use DelayDiffEq.jl directly — it has supported SDDE problems for some time, and the separate StochasticDelayDiffEq wrapper is no longer being maintained. It will not receive a v7-compatible release.

Migration: replace using StochasticDelayDiffEq with using DelayDiffEq (plus using StochasticDiffEq if you were relying on the SDE algorithm re-exports). MethodOfSteps(alg) and the SDDEProblem constructor continue to work from DelayDiffEq / SciMLBase respectively.