Expand description
BDF / NDF: variable-order, variable-step backward differentiation formulas.
This is a Rust port of SciPy’s scipy.integrate.BDF (the canonical
implementation of Shampine–Reichelt’s NDF method from MATLAB’s ode15s).
The previous revision of this file was algorithmically incorrect — see
“Why a rewrite” below.
§Mathematical formulation
The k-th order BDF method approximates M · ∑ⱼ αⱼ · y_{n+1-j} = h · β · f(t_{n+1}, y_{n+1}) where M is an optional (possibly singular) mass matrix. For ODEs M = I.
Rather than storing past y values and applying fixed-coefficient BDF
formulas (which is not order-k accurate when the step size varies — this
was the previous file’s fundamental error), we work with modified
divided differences D, defined by:
D⁰yₙ = yₙ
Dʲyₙ = Dʲ⁻¹yₙ − Dʲ⁻¹y_{n-1} (j ≥ 1)
The differences are stored in an array D[0..k+2] whose rows are scaled
so that the BDF formula holds as if the step size were constant. When h
changes, the array is rescaled in-place via a small transformation matrix
(change_d); this preserves order accuracy. This is the
“fixed-leading-coefficient” / “quasi-constant step size” trick from
Shampine & Reichelt (1997).
§NDF coefficients (Klopfenstein–Shampine)
For each order k we store a KAPPA[k] “boost” parameter; setting κ = 0
recovers pure BDF, while small negative κ gives the NDF method, which
reduces the error constant by ~25–30% at the cost of slight stability
tightening. Defaults follow Shampine–Reichelt.
§DAE support (index-1)
For singular M, the iteration matrix becomes M − c·J (instead of I − c·J), and the Newton residual carries M·ψ and M·d terms. Algebraic constraint rows (zero rows of M) reduce to f_alg(t, y) = 0, enforced at every step.
§Why a rewrite
The previous bdf.rs had three independent algorithmic bugs that manifested catastrophically (silent wrong answers, runaway step counts):
- Newton convergence test on the residual norm — when h·f was small
in absolute terms, Newton declared convergence at iteration 0 with
y_new = predictor = y_old, leaving the solution unchanged while time advanced. This is the source of the reported “y_final = y_initial at success=true” bug. - Error estimator identically ≈ 0 — the formula referenced
history[0], which was always equal to the currenty(just pushed), so the estimate’s leading term cancelled. - Fixed BDF coefficients applied to variable-h history — without the modified-divided-differences rescaling, the method silently loses order whenever h changes. The textbook coefficients [1, -4/3, 1/3, 2/3·h] only define BDF2 when y_n, y_{n-1} are spaced equally.
§References
- L. F. Shampine and M. W. Reichelt, “The MATLAB ODE Suite”, SIAM J. Sci. Comput. 18(1), 1–22 (1997). [NDF coefficients, fixed-leading-coefficient BDF, error estimator.]
- G. D. Byrne and A. C. Hindmarsh, “A Polyalgorithm for the Numerical Solution of ODEs”, ACM TOMS 1(1), 71–96 (1975).
- SciPy
scipy/integrate/_ivp/bdf.py(BSD-3 / Apache-2.0). Reference implementation that this file ports.
Author: Moussa Leblouba Date: 5 March 2026 Modified: 6 May 2026 (full rewrite based on SciPy’s NDF/MDD algorithm)
Structs§
- Bdf
- BDF / NDF solver for stiff ODEs and index-1 DAEs (orders 1-5).