Skip to content

Bound the UMFPACK-backed sparse cachevals to the eltypes UMFPACK has - #1122

Merged
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:sparse-float32-cacheval
Jul 27, 2026
Merged

Bound the UMFPACK-backed sparse cachevals to the eltypes UMFPACK has#1122
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:sparse-float32-cacheval

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Please ignore until reviewed by @ChrisRackauckas.

Found while checking what the sparse default does per element type. Reproduces on a pristine checkout of main.

The bug

A = sparse(Float32(1)I, 50, 50) + spdiagm(1=>fill(0.1f0,49)); b = ones(Float32, 50)
LinearSolve.defaultalg(A, b, OperatorAssumptions(true))
# => DefaultLinearSolver(DefaultAlgorithmChoice.KLUFactorization, true, false)
init(LinearProblem(A,b), nothing)
# => MethodError: no method matching SparseArrays.UMFPACK.UmfpackLU(::SparseMatrixCSC{Float32, Int64})

The algorithm that throws is not the one selected. _init_default_cacheval (src/default.jl, @generated) emits an unconditional init_cacheval for every slot — only the three Krylov slots are guarded — so a too-permissive init_cacheval on an unselected algorithm still breaks init. KLU is chosen; the LUFactorization slot is constructed first and throws.

LUFactorization's and UMFPACKFactorization's sparse cachevals are bounded by BLASELTYPES = Union{Float32,Float64,ComplexF32,ComplexF64}, but UmfpackLU exists only for Float64/ComplexF64 (SuiteSparse's UMFVTypes) — too wide by exactly Float32/ComplexF32. KLU already gets this right with T <: KLU.KLUTypes.

4 of 10 eltype/index combinations fail on unmodified main:

Int64/Float32     INIT THREW    Int32/Float32     INIT THREW
Int64/ComplexF32  INIT THREW    Int32/ComplexF32  INIT THREW

The fix

Bound them to a local UMFPACKELTYPES = Union{Float64, ComplexF64}.

Two notes on the shape of it:

  • The extra BLASELTYPES method is not redundant. The old methods' is_cusparse(A) → cudss_loaded(A) ? lu_instance(A) : nothing branch is the only LUFactorization+CuSparse init_cacheval in the tree (LinearSolveCUDAExt defines none), so narrowing alone would have silently dropped Float32 cuDSS support.
  • UMFPACKELTYPES is defined locally rather than reusing SparseArrays.UMFPACK.UMFVTypes, which is a non-public internal.

Float32/ComplexF32 sparse now resolve to the KLUFactorization slot, which algchoice_to_alg maps to PureKLUFactorization() — pure Julia, a hard dependency, and the only thing in the tree that actually supports these eltypes (SuiteSparse KLU, UMFPACK, and Sparspak all reject them).

Bisect — two separate breakages

commit date effect
0ce03c28 2025-05-22 widened the cachevals to T <: BLASELTYPES while adding ComplexF64. Broke Float32 sparse for Sparspak users; everyone else still hit the "using Sparspak required" error in defaultalg first, so it stayed hidden.
52ebf258 (#1037) 2026-06-13 removed that early error, exposing the breakage to everyone.

Measured at 0ce03c28^ with Sparspak loaded: init OK. So this path did work once.

Testing

All 10 combinations after the fix — eltype preserved, no silent Float64 promotion:

Float32/Int64     OK resid=0.0  eltype=Float32
ComplexF32/Int32  OK resid=0.0  eltype=ComplexF32
BigFloat/Int64    OK resid=1.7e-77
  • test/Core/default_algs.jl gains a 4-combination testset (Float32/ComplexF32 × Int64/Int32). Confirmed it executes: Default Alg Tests goes 89 → 109 tests.
  • GROUP=Core passes on the fixed tree and on unmodified main — main is not independently red.
  • Test.detect_ambiguities on the extension: 12 before, 12 after.
  • Runic reports no formatting changes.

Out of scope

Explicitly-requested LUFactorization/UMFPACKFactorization on Float32 sparse remain unsupported, exactly as before this change — they now fail the way SuiteSparse KLU already does for an unsupported eltype instead of with a MethodError. Making them work would mean silent Float32→Float64 promotion (SparseArrays' lu does this), which is a behavior addition rather than a bug fix. Happy to do it separately if you want it.

Also cleared while looking: BigFloat sparse is not a second bug — the KLUFactorization enum slot maps to PureKLUFactorization, not SuiteSparse KLU, so it works today. And Symmetric Float32 sparse → CHOLMOD is fine, because CHOLMOD's init_cacheval returns nothing for the non-Float64 case.

🤖 Generated with Claude Code

https://claude.ai/code/session_017rr42T1vFRRT8D7DMAmJzf

`init(LinearProblem(A, b), nothing)` throws for a Float32 sparse matrix on
main:

    defaultalg(...)  = DefaultLinearSolver(DefaultAlgorithmChoice.KLUFactorization, ...)
    init(...)        = MethodError: no method matching
                       SparseArrays.UMFPACK.UmfpackLU(::SparseMatrixCSC{Float32, Int64})

Note the algorithm that throws is not the one selected. `_init_default_cacheval`
(src/default.jl, @generated) emits an unconditional `init_cacheval` for every
slot - only the three Krylov slots are guarded - so a too-permissive
`init_cacheval` on an *unselected* algorithm still breaks `init`. KLU is
chosen; the `LUFactorization` slot is built first and throws.

`LUFactorization`'s and `UMFPACKFactorization`'s sparse cachevals are bounded
by `BLASELTYPES = Union{Float32, Float64, ComplexF32, ComplexF64}`, but
UMFPACK's `UmfpackLU` exists only for Float64/ComplexF64 (SuiteSparse's
`UMFVTypes`), so the bound is too wide by exactly Float32/ComplexF32. KLU
already does this correctly with `T <: KLU.KLUTypes`.

Bound them to a local `UMFPACKELTYPES` instead. The additional `BLASELTYPES`
method is not redundant: the old methods' `is_cusparse(A)` branch is the only
`LUFactorization`+CuSparse `init_cacheval` in the tree, so narrowing alone
would have silently dropped Float32 cuDSS support. `UMFPACKELTYPES` is defined
locally rather than reusing `SparseArrays.UMFPACK.UMFVTypes`, which is a
non-public internal.

Float32/ComplexF32 sparse now resolve to the `KLUFactorization` slot, which
`algchoice_to_alg` maps to `PureKLUFactorization()` - pure Julia, and the only
thing in the tree that supports these eltypes. It is a hard dependency, so no
extra `using` is needed.

Two separate breakages, both bisected:

  0ce03c2  2025-05-22
    widened the cachevals to `T <: BLASELTYPES` while adding ComplexF64.
    Broke Float32 sparse for Sparspak users; everyone else still hit the
    "using Sparspak required" error in `defaultalg` first, so it stayed hidden.

  52ebf25  (SciML#1037)  2026-06-13
    removed that early error, exposing the breakage to everyone.

Measured before/after, all 10 eltype/index combinations:

  before: Float32 and ComplexF32 (Int64 and Int32) all `INIT THREW`
  after:  all 10 solve, residual 0, eltype preserved (no Float64 promotion)

Explicitly-requested `LUFactorization`/`UMFPACKFactorization` on Float32
sparse remain unsupported, as before this change - they now fail the way
SuiteSparse KLU already does for an unsupported eltype rather than with a
`MethodError`. Making them work would mean silent Float32->Float64 promotion,
which is a behavior addition, not a bug fix.

`Test.detect_ambiguities` on the extension: 12 before, 12 after.
GROUP=Core passes on the fixed tree and on unmodified main.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rr42T1vFRRT8D7DMAmJzf
@ChrisRackauckas
ChrisRackauckas marked this pull request as ready for review July 27, 2026 11:34
@ChrisRackauckas
ChrisRackauckas merged commit fd8271c into SciML:main Jul 27, 2026
56 of 62 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.

2 participants