Fix get_du/get_du! reading the wrong derivative under CompositeAlgorithm - #4092
Merged
ChrisRackauckas merged 1 commit intoAug 2, 2026
Merged
Conversation
get_du and get_du! chose between reading integrator.fsallast and evaluating the interpolant using isfsal(integrator.alg) and has_stiff_interpolation(integrator.alg). Neither trait has a CompositeAlgorithm method, so both fell back to the generic defaults (true and false) and every Auto* algorithm took the fsallast branch regardless of which sub-algorithm was active. The buffer read there belongs to whichever sub-cache supplied it and is not written by a non-FSAL sub-algorithm, so the reported derivative was a never-written zero buffer for AutoVern9(Rodas4()) and, via get_fsalfirstlast(::DefaultCache, u) = (cache.u, cache.u), the state vector itself for the default algorithm. TerminateSteadyState consequently accepted a false steady state instead of erroring. Route both through the current sub-algorithm's traits, as the rest of the integrator already does via get_current_isfsal, adding a matching get_current_has_stiff_interpolation. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas
marked this pull request as ready for review
August 2, 2026 10:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Please ignore this PR until it has been reviewed by @ChrisRackauckas.
Fixes the underlying cause of SciML/DifferentialEquations.jl#1058, including the quieter variant reported in this comment.
The bug
get_duandget_du!choose between readingintegrator.fsallastand evaluating the interpolant using the traits ofintegrator.alg:CompositeAlgorithm <: OrdinaryDiffEqCompositeAlgorithm <: OrdinaryDiffEqAlgorithm, and neither trait has aCompositeAlgorithmmethod —isfsal(alg::CompositeAlgorithm)is commented out atalg_utils.jl:83. So both fall back to the generic defaults:Every
Auto*algorithm therefore takes thefsallastbranch regardless of which sub-algorithm is running. The buffer read there belongs to whichever sub-cache supplied it viaget_fsalfirstlast, and a non-FSAL sub-algorithm never writes it:AutoVern9(Rodas4())→Rodas4Cache.fsallast, allocated but never written → exact zerosAutoTsit5(Rodas5P())on the stiff branch → same → exact zerosget_fsalfirstlast(::DefaultCache, u) = (cache.u, cache.u)→uitself, notduEvery other consumer in the integrator already routes through
get_current_isfsal(integrator.alg, integrator.cache)(integrator_utils.jl:191,solve.jl:888,initdt.jl:71).get_du/get_du!were the exception.Why this is worse than the original report
Issue #1058 reported a loud
MethodError(fsallast === nothing→fill!(dest, nothing)). That crash is gone, but only because Rosenbrock caches gained realfsalfirst/fsallastbuffers — turningnothinginto an allocated buffer that Rosenbrock never writes. The failure became silent.On
OrdinaryDiffEq v7.2.1/DiffEqCallbacks v4.19.0, the MRE from #1058 no longer errors; it returnsretcode = Terminatedon a trajectory that has not converged:It terminates on the first step, because
get_du!reports zero at every step:The scope is wider than
TerminateSteadyState+AutoVern*: it is anyget_du/get_du!caller under any composite algorithm, including the plain default solver with no algorithm specified.The fix
Route both functions through the currently-active sub-algorithm's traits, reusing the existing
get_current_isfsaland adding a matchingget_current_has_stiff_interpolation.Verification
Ran locally against the issue's MRE, a stiff variant that forces the Rosenbrock branch to be active at termination, and the default-algorithm path:
AutoVern9(Rodas4()), RM modelt_end=0.11889, not convergedt_end=117.909, converged (matchesVern9()exactly)AutoTsit5(Rodas5P()), stifft_end=0.0267, not convergedt_end=10778.6, converged (Rodas5P()standalone:10975.2)get_du[0.02716, -0.01871](= truth)test/InterfaceII/get_du.jlcovered 12 standalone algorithms but no composite ones — that is the gap that let this through. Extended with five composites. On unpatchedOrdinaryDiffEqCorethe new cases fail:With the fix the whole file passes (exit 0) for all 17 algorithms.
Runic clean; no reformatting needed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TNSoDRgtXouPK8UgKz7avi