Skip to content

Commit 7db67ee

Browse files
Fix ExplicitTaylor fsallast aliasing to state buffer
get_fsalfirstlast for ExplicitTaylorCache and ExplicitTaylorAdaptiveOrderCache returned (cache.u, cache.u), aliasing the FSAL buffer to the state itself. The generic auto-dt heuristic writes an extra f evaluation into fsallast, so this made that call f(u, u, p, t), corrupting u mid-evaluation for RHS functions that read from u after writing to du. On the Pleiades N-body problem several bodies share identical initial velocity components, so the corruption produces coincident positions and a 0/0 division in the pairwise force term - default-adaptive ExplicitTaylor(order=Val(6)) returns retcode=DtNaN after one step. Give both caches an independent scratch buffer instead, matching ExplicitTaylor2Cache's existing pattern.
1 parent 2111422 commit 7db67ee

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

lib/OrdinaryDiffEqTaylorSeries/src/TaylorSeries_caches.jl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@ function alg_cache(
8181
)
8282
end
8383

84-
get_fsalfirstlast(cache::ExplicitTaylorCache, u) = (cache.u, cache.u)
84+
# FSAL currently not used. `cache.u` must NOT be reused here: the generic
85+
# auto-dt heuristic (`ode_determine_initdt`) writes an extra `f` evaluation
86+
# into whichever buffer `fsallast` points to, so aliasing it to the state
87+
# itself makes that call `f(u, u, p, t)` and can corrupt `u` mid-evaluation
88+
# for RHS functions that read from `u` after writing to `du` (e.g. the
89+
# Pleiades N-body problem), producing a spurious NaN. Use an independent
90+
# dummy buffer instead, matching `ExplicitTaylor2Cache` above.
91+
get_fsalfirstlast(cache::ExplicitTaylorCache, u) = (zero(cache.u), zero(cache.u))
8592

8693
struct ExplicitTaylorConstantCache{P, taylorType, uType, tType} <:
8794
OrdinaryDiffEqConstantCache
@@ -151,7 +158,9 @@ function alg_cache(
151158
)
152159
end
153160

154-
get_fsalfirstlast(cache::ExplicitTaylorAdaptiveOrderCache, u) = (cache.u, cache.u)
161+
# See the note on `ExplicitTaylorCache`'s `get_fsalfirstlast` above: `cache.u`
162+
# must not be reused as the FSAL buffer here either.
163+
get_fsalfirstlast(cache::ExplicitTaylorAdaptiveOrderCache, u) = (zero(cache.u), zero(cache.u))
155164

156165
struct ExplicitTaylorAdaptiveOrderConstantCache{P, Q, taylorType, uType, tType} <:
157166
OrdinaryDiffEqConstantCache

lib/OrdinaryDiffEqTaylorSeries/test/runtests.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,42 @@ if TEST_GROUP == "Core" || TEST_GROUP == "ALL"
4141
@test SciMLBase.successful_retcode(sol)
4242
end
4343

44+
# Regression test: `get_fsalfirstlast` used to alias `fsallast` to `cache.u`
45+
# for ExplicitTaylor. The generic auto-dt heuristic (`ode_determine_initdt`)
46+
# writes an extra `f` evaluation into `fsallast`, so with that aliasing it
47+
# effectively called `f(u, u, p, t)`, corrupting `u` mid-evaluation for RHS
48+
# functions that read from `u` after writing to `du`. On the Pleiades
49+
# N-body problem several bodies share identical initial velocity
50+
# components, so the corruption produced coincident positions and a 0/0
51+
# division in the pairwise force term, yielding `retcode == DtNaN` on the
52+
# very first step under fully default (adaptive, no starting `dt`)
53+
# settings.
54+
@testset "Default auto-dt on Pleiades (fsallast aliasing regression)" begin
55+
sol = solve(
56+
prob_ode_pleiades, ExplicitTaylor(order = Val(6)),
57+
abstol = 1.0e-10, reltol = 1.0e-10
58+
)
59+
@test SciMLBase.successful_retcode(sol)
60+
# A real integration of this problem at this tolerance takes hundreds
61+
# of accepted steps; a handful of steps would indicate the state got
62+
# silently corrupted and the step-size controller was fooled into
63+
# accepting one enormous, inaccurate step instead of erroring loudly.
64+
@test length(sol.t) > 100
65+
@test all(isfinite, sol.u[end])
66+
67+
# ExplicitTaylorAdaptiveOrder had the same fsallast aliasing bug and no
68+
# longer crashes with DtNaN, but its per-step order/error controller
69+
# separately accepts an oversized step on this problem (3 total steps)
70+
# independent of this fix. Tracked as a known follow-up rather than
71+
# silently passing or failing here.
72+
sol_adaptive = solve(
73+
prob_ode_pleiades, ExplicitTaylorAdaptiveOrder(),
74+
abstol = 1.0e-10, reltol = 1.0e-10
75+
)
76+
@test sol_adaptive.retcode != SciMLBase.ReturnCode.DtNaN
77+
@test_broken length(sol_adaptive.t) > 100
78+
end
79+
4480
# Test AutoSpecialize (default ODEProblem wraps in FunctionWrappers)
4581
# and FullSpecialize paths for IIP problems
4682
@testset "AutoSpecialize / FullSpecialize IIP" begin

0 commit comments

Comments
 (0)