Skip to content

Fix QuadraticSpline edge-case crashes: n=2 BoundsError, Rational t starting at 0, duplicate knots#543

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:quadraticspline-edge-cases
Jun 13, 2026
Merged

Fix QuadraticSpline edge-case crashes: n=2 BoundsError, Rational t starting at 0, duplicate knots#543
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:quadraticspline-edge-cases

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Note

This PR should be ignored until reviewed by @ChrisRackauckas.

Fixes #542.

Changes

  1. n = 2 no longer throws BoundsError. For two data points the knot vector [t1, t1, t2, t2, t2] has boundary multiplicity 2 < degree + 1, so the deficient B-spline basis cannot be evaluated at the interval midpoint in quadratic_spline_parameters (it under-indexed into N[0]). Since a quadratic spline through two points is underdetermined and the natural choice is the linear interpolant, the parameter computation now special-cases length(t) == 2 with uᵢ₊ = (u[1] + u[2]) / 2, which yields α = 0, β = u₂ - u₁ — i.e. exactly the linear interpolant. All downstream code paths (evaluation, derivative, integral, cached parameters, vector-valued u) share these parameters, so they are all fixed by this one change.

  2. Rational knots with t[1] == 0 no longer throw DivideError. The coefficient eltype was computed as typeof(t[1] / t[1]), which evaluates 0 // 0. Replaced with typeof(one(eltype(t)) / one(eltype(t))) in all three occurrences (interpolation_utils.jl and both QuadraticSpline constructors).

  3. Duplicate time points now throw an informative ArgumentError instead of a raw SingularException from the tridiagonal collocation solve. Both interior duplicates (t = [0, 1, 1, 2, 3]) and boundary duplicates (t[2] == t[1]) are caught at construction in quadratic_spline_params.

Verification

All three failures were reproduced on unmodified master before the fix (BoundsError, DivideError, SingularException respectively). After the fix, ran locally:

  • n = 2: evaluation gives A(0.25) == 1.5 for u = [1, 3], derivative 2.0, integral over the domain 2.0 — exact linear interpolant; cached-parameters and Vector{Vector} constructors verified too.
  • Rational: QuadraticSpline([1//1, 4//1, 9//1], [0//1, 1//1, 2//1]) (i.e. u = (t+1)^2) reproduces the quadratic exactly: A(1//2) == 9//4, A(3//2) == 25//4.
  • Duplicates: both reproducers from the issue now throw ArgumentError with a clear message.
  • Full local test suite passes: Core 2550 pass (5 pre-existing broken), Methods 41385 pass, Extensions 13178 pass, Misc 10 pass.
  • Runic formatting check is clean on all changed files.

Tests for all three cases added to the QuadraticSpline testset in test/interpolation_tests.jl.

🤖 Generated with Claude Code

Fixes three construction crashes reported in SciML#542:

- n = 2 threw a BoundsError: the knot vector for two data points has
  boundary multiplicity 2 < degree + 1, so the midpoint B-spline basis
  evaluation in quadratic_spline_parameters under-indexed. The spline
  now degenerates to the linear interpolant (alpha = 0, beta = u2 - u1),
  which evaluation, derivative, and integral all share.

- Rational t with t[1] == 0 threw a DivideError: the coefficient eltype
  was computed as typeof(t[1] / t[1]), which evaluates 0//0. Use
  typeof(one(eltype(t)) / one(eltype(t))) instead.

- Duplicate time points threw a raw SingularException from the
  tridiagonal collocation solve. Construction now throws an informative
  ArgumentError.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review June 13, 2026 07:49
@ChrisRackauckas ChrisRackauckas merged commit fd693b5 into SciML:master Jun 13, 2026
18 of 20 checks passed
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.

QuadraticSpline edge-case crashes: n=2 BoundsError, Rational t starting at 0, duplicate knots

2 participants