Skip to content

Comments

Add backward-difference stiff interpolation for BDF solvers#3046

Merged
ChrisRackauckas merged 12 commits intoSciML:masterfrom
ChrisRackauckas-Claude:bdf-stiff-interpolation
Feb 20, 2026
Merged

Add backward-difference stiff interpolation for BDF solvers#3046
ChrisRackauckas merged 12 commits intoSciML:masterfrom
ChrisRackauckas-Claude:bdf-stiff-interpolation

Conversation

@ChrisRackauckas-Claude
Copy link
Contributor

Summary

  • Replace the default Hermite (cubic) interpolation fallback with native Newton backward difference interpolation for FBDF, QNDF, and DFBDF solvers
  • Uses the formula p(Θ) = y₁ + Σ φⱼ(Θ-1) * k[j] where backward differences come from the solver's own history points, giving interpolation accuracy consistent with the solver order
  • Supports both function values (Val{0}) and first derivatives (Val{1})
  • Follows the same pattern established by the Rosenbrock stiff interpolation (has_stiff_interpolation trait, _ode_interpolant dispatch)

Files modified (6)

  • OrdinaryDiffEqBDF.jl — Added imports and includes for the 3 new files
  • alg_utils.jl — Added has_stiff_interpolation trait for QNDF, FBDF, DFBDF
  • bdf_caches.jl — Added dense field to QNDFCache and FBDFCache for aliasing integrator.k
  • dae_caches.jl — Added dense field to DFBDFCache
  • bdf_perform_step.jl — Modified initialize! (kshortsize = max_order) and perform_step! (store backward differences in integrator.k) for QNDF and FBDF
  • dae_perform_step.jl — Same for DFBDF

Files created (3)

  • interp_func.jlSciMLBase.interp_summary overrides for all BDF cache types
  • bdf_interpolants.jl — Newton backward difference interpolation (Val{0} and Val{1})
  • stiff_addsteps.jl — No-op _ode_addsteps! (backward diffs precomputed in perform_step!)

Test plan

  • BDF Regression Tests (8/8 pass + autodiff)
  • BDF Convergence Tests (72/72 pass)
  • DAE Convergence Tests (20/20 pass)
  • DAE AD Tests (16/16 pass)
  • DAE Event Tests (pass)
  • DAE Initialization Tests (pass)
  • Inference Tests (pass)
  • Dense interpolation accuracy ~1e-10 for QNDF and FBDF
  • First derivative accuracy ~1e-10
  • saveat with interpolation works correctly
  • Runic formatting applied

🤖 Generated with Claude Code

ChrisRackauckas and others added 2 commits February 12, 2026 07:59
Replace the default Hermite (cubic) interpolation fallback with native
Newton backward difference interpolation for BDF solvers. This uses the
formula p(Θ) = y₁ + Σ φⱼ(Θ-1) * k[j] where the backward differences
are computed from the solver's own history points, giving interpolation
accuracy consistent with the solver order rather than limited to cubic.

Follows the same pattern as the Rosenbrock stiff interpolation. Supports
both function values (Val{0}) and first derivatives (Val{1}).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- FBDF/DFBDF: Use direct Lagrange interpolation through past solution
  values stored in k (same polynomial as calc_Lagrange_interp). The
  formula avoids barycentric division singularities for ForwardDiff
  compatibility by using constant integer denominators (m-j).

- QNDF: Keep Newton backward difference interpolation through D columns.

- DFBDF calck blocks: Store solution values (uprev, u_corrector) in k
  instead of backward differences, matching FBDF pattern.

- Add QNDF/FBDF to ode_dense_tests.jl regression_test suite with
  empirically verified tolerances.

- BDF solvers not added to special_interps.jl because that test compares
  adaptive vs non-adaptive interpolation consistency, which is
  inapplicable to multi-step methods whose interpolation data (D matrix,
  u_history) depends on cumulative integration history.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ChrisRackauckas-Claude
Copy link
Contributor Author

Update: Lagrange interpolation + Dense tests

Changes in this commit:

FBDF/DFBDF interpolation switched to direct Lagrange formula:

  • bdf_interpolants.jl: Split into QNDF (Newton backward diff) and FBDF/DFBDF (Lagrange) paths
  • Direct Lagrange formula: L_j(Θ) = Π_{m≠j} (σ+m)/(m-j) where denominators are constant integers
  • This avoids barycentric division singularities that cause NaN with ForwardDiff dual numbers
  • Same polynomial as calc_Lagrange_interp in bdf_utils.jl

DFBDF calck blocks updated:

  • dae_perform_step.jl: Store past solution values (uprev, u_corrector columns) in k instead of backward differences, matching FBDF pattern

Dense output tests added:

  • ode_dense_tests.jl: Added QNDF and FBDF regression tests with empirically verified tolerances
    • QNDF: 1D ~0.009, 2D ~0.01, deriv ~1e-7
    • FBDF: 1D ~0.046, 2D ~0.076, deriv ~1e-7

BDF not added to special_interps.jl:

  • That test compares adaptive (MutableCache) vs non-adaptive (ConstantCache) interpolation consistency
  • For multi-step methods, interpolation data (D matrix, u_history) depends on cumulative integration history
  • With adaptive=false, BDF order stays at 1 (no adaptation), yielding fundamentally different interpolation
  • This is inherent to multi-step methods — all other methods in that test are single-step

All existing OrdinaryDiffEqBDF tests pass.

ChrisRackauckas and others added 5 commits February 14, 2026 22:56
Redesign FBDF/DFBDF interpolation so that the dense output interpolant
and the predictor/corrector use the same non-equidistant Lagrange
polynomial through u_history values. All interpolation data (solution
values and Θ positions) is stored in integrator.k with layout:
  k[1..half] = solution values, k[half+1..2*half] = Θ positions
where half = max_order + 1.

Key changes:
- Rewrite _ode_interpolant for FBDF_CACHES to use Lagrange basis
  functions at non-equidistant Θ positions stored in k, ignoring y₁
- Increase kshortsize from 2*max_order to 2*(max_order+1) to hold
  k+1 data points needed for order-k interpolation
- Update calck blocks to store u_new at Θ=1 plus u_history values
- Rebuild k with u_history data at step start for predictor/corrector
- Remove calc_Lagrange_interp, calc_Lagrange_interp!, and
  compute_weights! from bdf_utils.jl (no longer needed)
- QNDF unchanged (uses backward differences, not Lagrange)

All OrdinaryDiffEqBDF tests pass including DAE event handling.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rtions

Implement second and third derivative interpolation for FBDF/DFBDF
Lagrange polynomials:
- L''_i(Θ) = L_i(Θ) * (S1² - S2)
- L'''_i(Θ) = L_i(Θ) * (S1³ - 3·S1·S2 + 2·S3)
where Sk = Σ_{m≠i} 1/(Θ - θ_m)^k

Higher derivatives properly zero algebraic variables when
differential_vars is provided (mass matrix DAE problems).

Also fix Runic ternary indentation in calck blocks, and update
algebraic_interpolation.jl to expect stiffness-aware interpolation
for FBDF (no longer Hermite).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nterpolation

The logarithmic derivative formula L'_j(Θ) = L_j(Θ) * Σ 1/(Θ-θ_m) produces
NaN at node points (0*Inf) when Θ equals any θ_m. Replace with the product-rule
form that is numerically stable at all points including nodes:

  L'_j(Θ) = Σ_{m≠j} [1/(θ_j-θ_m)] * Π_{l≠j,l≠m} (Θ-θ_l)/(θ_j-θ_l)

Similarly for L''_j and L'''_j (used in Val{2} and Val{3}).

Also update get_du() (without !) to use the interpolation derivative for
stiff interpolation methods, matching the existing behavior in get_du!().

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
On Julia 1.10, [sources] in Project.toml is not supported, so the
registered OrdinaryDiffEqBDF (without the new interp_func.jl) is loaded
instead of the local one. The FBDF interp_summary therefore returns
"3rd order Hermite" rather than our custom stiffness-aware string.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ChrisRackauckas and others added 3 commits February 19, 2026 06:14
DiffEqBase v6.204.0 (PR SciML#1273, ModAB bracketing solver) broke
ForwardDiff AD through ContinuousCallbacks. The fix landed in v6.205.1.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace non-in-place _ode_interpolant() with _ode_interpolant!() using
terk_tmp as a temporary buffer for the corrector loop. This eliminates
allocations in perform_step! for both FBDF and DFBDF in-place caches.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that step! with save_everystep=false produces zero allocations
for both FBDF and DFBDF in-place caches after warmup.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Julia 1.10 reports 16 bytes of allocation in step! that don't appear
on Julia 1.12. Allow this small overhead to avoid false failures on lts.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants