OrdinaryDiffEq.jl v7 / DifferentialEquations.jl v8 Breaking Changes
June 16, 2026 · View on GitHub
This release bumps to SciMLBase v3, RecursiveArrayTools v4, and includes breaking changes across DiffEqBase, OrdinaryDiffEqCore, and all solver sublibraries. It also coincides with the DifferentialEquations.jl v8 umbrella release, which is itself a breaking change to the user-facing meta-package.
DifferentialEquations.jl v8: scope reduction
DifferentialEquations.jl v8 no longer re-exports the full SciML solver suite. Previously, using DifferentialEquations pulled in OrdinaryDiffEq, StochasticDiffEq, DelayDiffEq, BoundaryValueDiffEq, Sundials, JumpProcesses, SteadyStateDiffEq, LinearSolve, NonlinearSolve, Optimization, etc. — a large default surface that drove up using time and made it unclear which package any given 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, or any non-ODE solver, you will need to add the topic-specific package to your project explicitly.
Migration
Find the topic you need a solver for and add the corresponding sublib(s) directly. The DiffEqDocs tutorials and solver pages now specify, per algorithm, which package it ships from. Common cases:
| Topic | Old (DiffEq v7 umbrella) | New (DiffEq v8) |
|---|---|---|
| ODEs | using DifferentialEquations | using OrdinaryDiffEq (or using OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, … for individual solver families) |
| Stochastic ODEs | using DifferentialEquations | using StochasticDiffEq |
| Delay ODEs | using DifferentialEquations | using DelayDiffEq |
| Boundary value problems | using DifferentialEquations | using BoundaryValueDiffEq (or one of BoundaryValueDiffEqMIRK, BoundaryValueDiffEqFIRK, BoundaryValueDiffEqShooting, …) |
| Jump processes | using DifferentialEquations | using JumpProcesses |
| Steady state | using DifferentialEquations | using SteadyStateDiffEq |
| DAEs (mass matrix or implicit) | using DifferentialEquations | using OrdinaryDiffEq (mass matrix), using Sundials (IDA), or topic sublib |
| Sundials wrappers (CVODE, IDA, ARKODE) | using DifferentialEquations | using Sundials |
| Linear / nonlinear / optimization | using DifferentialEquations | using LinearSolve / using NonlinearSolve / using Optimization |
For ODE work specifically, prefer importing only the sublib you need (e.g. using OrdinaryDiffEqTsit5: Tsit5) rather than the umbrella using OrdinaryDiffEq — the v7 ecosystem split lets you trim using time substantially. The DiffEqDocs tutorials and solver index annotate every algorithm with its host sublib.
Why
Removing the meta-package's broad re-exports lets each topic's package version cycle independently, eliminates the long using DifferentialEquations precompile chain for users who only need ODEs, and makes the dependency graph for any given script honest about what's actually being loaded.
This change is independent of the OrdinaryDiffEq v7 changes below — OrdinaryDiffEq v7 ships with DifferentialEquations v8, but you can also use OrdinaryDiffEq v7 directly without the umbrella package at all.
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, andStaticArrays.jlwere dropped;using OrdinaryDiffEqnow loads only the default solver set;ODEFunctionswitched toAutoSpecialize. All of this means less code loaded and more precompilation caching on firstsolve. -
Type stability everywhere. All
Boolsolver/solvekeyword arguments (autodiff,verbose,alias,lazy, …) were replaced by typed objects. Passing aBoolno 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. For example:# v6 — a Bool Rosenbrock23(autodiff = true) solve(prob, alg; verbose = false, alias = true) # v7 — a typed object Rosenbrock23(autodiff = AutoForwardDiff()) solve(prob, alg; verbose = DEVerbosity(SciMLLogging.None()), alias = ODEAliasSpecifier(alias_u0 = true))The
Boolform chose between code paths (e.g. ForwardDiff vs. FiniteDiff forautodiff) at runtime, so the choice was invisible to the compiler; the typed object carries that choice in its type, so the solver specializes on it at compile time. The same swap applies tolazy(nowVal{true}()/Val{false}()). See the per-keyword before/after sections below (autodiff,verbose,alias,lazy) for the full migration of each. -
Generality beyond ForwardDiff.
chunk_size,diff_type,standardtag, etc. encoded ForwardDiff-specific or FiniteDiff-specific knobs on every solver. They are replaced by theADTypesinterface (AutoForwardDiff,AutoFiniteDiff,AutoEnzyme,AutoZygote, …) so every solver automatically generalizes to any AD backend. -
Controller is now an object, not a pile of
solvekwargs.gamma,beta1,beta2,qmin,qmax,qsteady_min,qsteady_max,qoldinitwere moved onto concretePIController/PIDController/IController/PredictiveControllerstructs, andEEstmoved 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(nowhas_stats),sol.destats(nowsol.stats),DEAlgorithm/DEProblem/DESolutionabstract types,tuples()/intervals(),QuadratureProblem,fastpow,concrete_solve, etc. were on a deprecation path for one or more minor releases. v7 removes them.
Recommended upgrade path
The cleanest path is not to jump straight from an old environment onto v7. Most renamed APIs (e.g. DEAlgorithm → AbstractDEAlgorithm, u_modified! → derivative_discontinuity!, has_destats → has_stats, sol.destats → sol.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:
- Stay on SciMLBase v2 / OrdinaryDiffEq v6. Update your code to the new names (
has_stats,sol.stats,AbstractDEAlgorithm,derivative_discontinuity!,ODEAliasSpecifier,DEVerbosity,ADTypes-basedautodiff, explicitcontroller = …objects, new tableau names) while the deprecation shims still exist. - Verify your tests pass on v6 with no deprecation warnings.
- 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_funcsignature, struct type parameter removals, kwargs that truly no longer exist, default changes likeCheckInitandwilliamson_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:
| Operation | v3 (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 timesteps | prod(size(sol)) (total elements) | Use length(sol.t) or length(sol.u) |
eachindex(sol) | 1:nsteps | CartesianIndices(size(sol)) | Use eachindex(sol.u) |
iterate(sol) | Iterates over timesteps | Iterates over scalar elements | Use for u in sol.u |
first(sol) / last(sol) | First/last timestep | First/last scalar element | Use first(sol.u) / last(sol.u) |
map(f, sol) | Maps over timesteps | Maps over elements | Use map(f, sol.u) |
maximum(sol) | Maximum over timesteps | Maximum over all elements | Use 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:
| Operation | v3 (old) | v4 (new) | Migration |
|---|---|---|---|
sim[j] | j-th trajectory (ODESolution) | j-th scalar element of the flattened container | Use 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 timestep | Scalar at (i, j, k) | Use sim.u[j].u[i][k] |
length(sim) | Number of trajectories | prod(size(sim)) (total scalar count) | Use length(sim.u) |
for sol in sim | Iterate trajectories | Iterate 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) viarewrap- Ragged arrays:
size(sol)reports maximum dimensions; out-of-bounds elements returnzero(T) - Removed: custom
length,eachindex,iterate,first,last,eltype,ndims,axes,any,all,sum,prod,mapreduce,mapoverrides (all inherited fromAbstractArray) - 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_modified | integrator.derivative_discontinuity | Field access rename. |
DEAlgorithm | AbstractDEAlgorithm | Abstract type name aligned with SciML-wide Abstract… convention. |
DEProblem | AbstractSciMLProblem | Same. |
DESolution | AbstractSciMLSolution | Same. |
sol.destats | sol.stats | Drop 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_destatsfunction → usehas_statssymbol_to_ReturnCodeand Symbol-to-ReturnCode conversion → useReturnCode.*directly (see next section)syms/paramsyms/indepsymkwargs on allSciMLFunctionconstructors → use the problem'ssys/ MTK symbolic interfacesol.xonAbstractOptimizationSolution→ usesol.uprob.lb/prob.ubonIntegralProblem→ useprob.domainsol.minimizer/sol.minimum→ usesol.u/sol.objectivetuples(),intervals()iterator functions → iterate oversol.u/sol.tdirectlyIntegratorTuples,IntegratorIntervals,TimeChoiceIteratortypesQuadratureProblemalias → useIntegralProblemEnsembleProblemvector-of-problems constructor → use aprob_funcIntegralProblemnout/batchkwargs → set on the integrand function directlySciMLBaseMLStyleExtextension (MLStyledependency removed)- Moshi-backed clock ADT support removed from SciMLBase's common runtime surface. Solver interpolation only needs the
AbstractClock/IndexedClockAPI; richer clock ADTs now belong in ModelingToolkit.
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.
| Old | New (v3) |
|---|---|
sol.retcode == :Success | SciMLBase.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 == :MaxIters | sol.retcode == ReturnCode.MaxIters |
sol.retcode == :Default | sol.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 usesAutoSpecializeby default (wasFullSpecialize). Why: drastically more precompilation caching now "works" out of the box —AutoSpecializestores 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 off), writeODEFunction{iip, FullSpecialize}(f)explicitly.is_discrete_time_domain(nothing)now returnsfalse(wastrue). Affects only code that called this trait withnothing— replace with an explicit domain.
Ensemble RNG redesign
prob_func(prob, i, repeat)→prob_func(prob, ctx)wherectx::EnsembleContextoutput_func(sol, i)→output_func(sol, ctx)- New
seed/rng/rng_funckwargs onsolve()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,Vern6–Vern9,AutoVern6–AutoVern9,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*/TRBDF2 → OrdinaryDiffEqSDIRK, Rosenbrock*/Rodas* except Rosenbrock23/Rodas5P → OrdinaryDiffEqRosenbrock, 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.
Rosenbrock tableau data split
Non-essential Rosenbrock/Rodas tableau coefficient data moved into the new OrdinaryDiffEqRosenbrockTableaus sublibrary (#3361). OrdinaryDiffEqRosenbrock still owns the solver algorithms, caches, and perform-step code, and keeps the essential tableaus for Rosenbrock23/Rosenbrock32, Rodas5P/Rodas5Pe, Rodas6P, Rodas23W, and Tsit5DA directly available.
The extracted package contains the shared RodasTableau definition plus research and less commonly used tableau data such as the Rodas4*, ROS2*, ROS3*, ROS34*, GRK4*, RosShamp4, Veldd4, Velds4, Ros4LStab, ROK4a, and RosenbrockW6S4OS families. Normal solver use through OrdinaryDiffEqRosenbrock is unchanged. Code that imported internal tableau constructors directly should import them from OrdinaryDiffEqRosenbrockTableaus.
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 kwarg | Migration |
|---|---|
chunk_size | Set via autodiff = AutoForwardDiff(chunksize=N) |
diff_type | Set via autodiff = AutoFiniteDiff(fdtype=Val(:central)) |
standardtag | Always true; remove the kwarg |
precs | Preconditioners 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 DEVerbosity
solve(prob, alg, verbose=DEVerbosity(SciMLLogging.None()))
Why: same type-stability reason, plus DEVerbosity 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 kwarg | Migration |
|---|---|
gamma | Pass controller = PIController(gamma=…) or PIDController(...) |
beta1, beta2 | Constructor args on PIController / PIDController |
qmin, qmax | Same — on the controller object |
qsteady_min, qsteady_max | Same |
qoldinit | Same |
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, Vern6–Vern9: 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.
d_discontinuities shifted past the discontinuity
d_discontinuities now has an explicit right-discontinuity convention (#3394). When the integrator lands on a discontinuity time t_d, it advances t by one ULP in the integration direction and re-evaluates FSAL data on the post-discontinuity side. This makes user code written as if t > t_d; new_regime; else; old_regime; end behave as intended instead of taking the first post-discontinuity step with stale pre-discontinuity derivative data.
Starting-time derivative discontinuities are now handled too: a d_discontinuities entry equal to tspan[1] is no longer silently skipped by the strict tstops bounds. For non-AbstractFloat time types the one-ULP shift is a no-op, preserving the old behavior. This clarification also distinguishes d_discontinuities from plain tstops: tstops only forces the solver to step at a time, while d_discontinuities marks a vector-field derivative discontinuity and refreshes FSAL state on the correct side of that discontinuity.
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.
| Package | Replacement | Why |
|---|---|---|
| Static.jl | FastBroadcast.Serial / FastBroadcast.Threaded | Removing Static slashes load time and eliminates a wide surface of StaticInt/StaticBool specialization that the compiler was re-running for every user |
| StaticArrayInterface.jl | ArrayInterface.ismutable | The 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 activate | Polyester 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-only | Loading 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_destatsre-export removed (function removed from SciMLBase v3 — usehas_stats)RECOMPILE_BY_DEFAULTre-export removed (unused)fastpowdeprecation removed — useFastPower.fastpowerDEStatsdeprecation removed — useSciMLBase.DEStatsconcrete_solvedeprecation removed — usesolvedirectlyFunctionWrappersWrapperwrapping 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: VectorContinuousCallback affect! 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:
| value | meaning |
|---|---|
0 | condition did not trigger this step |
+1 | upcrossing (condition went from negative to positive) |
-1 | downcrossing (condition went from positive to negative) |
(Note: the first releases of the v7 incorrectly flipped this sign and it was immediately corrected)
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 ContinuousCallback affect! / affect_neg! split is unchanged.)
OrdinaryDiffEqCore changes
DEOptionsstruct: removedgamma,qmax,qmin,qsteady_max,qsteady_min,qoldinit,controllerfields and theControllertype parameter (moved to controller object; see Controller refactor above)ODEIntegratorstruct: removedEEst,qold,q11,erracc,dtacc,qfields andEEstT,QTtype parameters; addedcontroller_cachefield. Migration:integrator.EEst→OrdinaryDiffEqCore.get_EEst(integrator); the rest live onintegrator.controller_cacheu_modifiedfield →derivative_discontinuityDEFAULT_PRECSremoved (preconditioners now configured vialinsolve)ispredictive/isstandardcontroller traits removed — algorithms now overridedefault_controllerdirectly. Migration: if you defined a custom algorithm that setisstandard(::MyAlg) = true, instead definedefault_controller(::MyAlg, args...) = IController(…)has_chunksizeremoved (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 formAlg(; stage_limiter! = my_limiter, step_limiter! = my_limiter). The kwarg constructors (and no-argAlg()defaulting both limiters totrivial_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,autodiffBool-to-typed-object changes as ODE solvers alias_u0/alias_jumps/alias_noisedeprecated kwargs removed → useODEAliasSpecifier(for SDE:SDEAliasSpecifier)beta1/beta2PID parameter deprecation warnings removed → set on the controller objectinitial_orderdeprecated kwarg removed from DelayDiffEqispredictive/isstandardtrait definitions removed; algorithms now overridedefault_controllerdirectly@static if isdefinedversion 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.