Route duals-in-b PureKLU solves to a native LinearCache (uses PureKLU 1.1 mixed ldiv!)#1069
Merged
ChrisRackauckas merged 6 commits intoJun 30, 2026
Conversation
A factorization is a linear operator, so `K \ Dual(v, p₁…p_N) = Dual(K\v, K\p₁ … K\p_N)`.
PureKLU stores its L/U in the factor element type, so a `KLUFactorization{<:AbstractFloat}`
can backsolve a Dual right-hand side by solving the primal value and each partial column
in one multi-RHS `klu_solve!`, then repacking — without ever promoting `A` to a Dual.
This keeps the factorization in fast Float64 arithmetic and lets the duals ride only
through the back-substitution, which is the single-solve analogue of the split path's
per-partial back-solves. It makes `solve(LinearProblem(A_float64, b_dual), PureKLU)` work
natively (previously a MethodError on `ldiv!(::KLUFactorization{Float64}, ::Vector{<:Dual})`).
The factor eltype is constrained to `AbstractFloat` so it never collides with PureKLU's
own `ldiv!(::KLUFactorization{Tv}, ::VecOrMat{Tv})` on the dual-factor path — a `Dual` is
`<: Real` but not `<: AbstractFloat`, so the dual-factor case stays with PureKLU's method.
Adds a correctness test (value + every partial column matches the dense Dual solve, factor
stays Float64) for chunk sizes 1/2/3.
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
With the mixed-type `ldiv!` (primal KLU factor against a Dual RHS) in place, a `DualBLinearProblem` (duals only in b, A primal) no longer needs the split DualLinearCache machinery: it can be solved on a plain `LinearCache` whose factorization stays in Float64, with the duals carried through the back-solve. Add an `init(::DualBLinearProblem, ::PureKLUFactorization)` opt-out that returns `__init(prob, alg)` directly. This is type-stable — dispatch is purely on the problem subtype (b-dual / A-plain) and the alg type, so `init` always returns a `LinearCache` for this method (verified with `@inferred`), unlike the reverted value-based opt-out in SciML#1041-era code. It is also simpler than the split path and gets correct factorization reuse across b-only `reinit!`s for free, which the split path's `reinit!` does not (it unconditionally marks the inner cache fresh, forcing a refactorization even when only b changed). Scope: only PureKLU is routed, since it is the only sparse factorization with the mixed `ldiv!`. A-dual and mixed A/b problems are untouched and still take the split path. Adds routing + type-stability + correctness tests. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
The mixed-type `ldiv!` (primal KLU factor backsolving a Dual RHS) belongs in PureKLU, which owns `KLUFactorization` — it is the analogue of PureKLU's existing real-factor / Complex-RHS `ldiv!`, avoids type piracy here, and there it is non-allocating (the real `n × (N+1)` RHS reuses a buffer on the factorization). See SciML/PureKLU.jl#67. This drops the stop-gap `ldiv!` (and the `PureKLU` import) from this extension and bumps the PureKLU [compat] to 1.1, which provides it. The duals-in-b routing (`init(::DualBLinearProblem, ::PureKLUFactorization)` -> native `LinearCache`) is unchanged and now relies on PureKLU's `ldiv!`. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
…solves The duals-in-b routing needs the primal-factor/Dual-RHS `ldiv!` from PureKLU 1.1.0 (SciML/PureKLU.jl#67), which is not yet registered. Add a temporary `[sources]` entry pointing at the PR branch so `PureKLU = "1.1"` resolves in CI (Julia >= 1.11; ignored on older Julia). To be removed once PureKLU 1.1.0 is in the General registry. Verified locally: a fresh resolve with this `[sources]` pulls PureKLU v1.1.0. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
PureKLU 1.1.0 (with the primal-factor/Dual-RHS `ldiv!`, SciML/PureKLU.jl#67) is in the General registry, so the `PureKLU = "1.1"` compat resolves directly. Remove the temporary `[sources]` override that pointed at the PR branch. Verified: forwarddiff_overloads.jl passes end-to-end against the registered PureKLU 1.1.0. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
This was referenced Jul 3, 2026
ChrisRackauckas
added a commit
that referenced
this pull request
Jul 3, 2026
DualBLinearProblem's A slot is `Union{Number, AbstractArray}`, which a
Dual-eltype A also matches, so `DualLinearProblem <: DualBLinearProblem`.
The b-only PureKLU opt-out introduced in #1069 is more specific in the alg
argument than the general dual-path `init`, so both-Dual PureKLU problems
were routed to a plain `LinearCache` and `solve!` then failed with a
FieldError on `dual_linear_cache` (test/Core/forwarddiff_overloads.jl
"PureKLU direct dual path reuses inner LinearCache"). Add a more-specific
`DualLinearProblem` method restoring the direct dual path; the mixed-type
ldiv! only covers primal A.
Claude-Session: https://claude.ai/code/session_01EYp371jx6LurezUDhKcYRh
Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ChrisRackauckas-Claude
pushed a commit
to ChrisRackauckas-Claude/LinearSolve.jl
that referenced
this pull request
Jul 3, 2026
The repo-wide Runic Format Check fails on main because this file landed unformatted in aabf007 (SciML#1069); format it here alongside the dispatch fix for the same commit's fallout. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChrisRackauckas-Claude
pushed a commit
to ChrisRackauckas-Claude/LinearSolve.jl
that referenced
this pull request
Jul 3, 2026
The repo-wide Runic Format Check fails on main because this file landed unformatted in aabf007 (SciML#1069); format it here alongside the dispatch fix for the same commit's fallout. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds native handling of duals-only-in-
bPureKLU solves: aDualBLinearProblem(A primal, b dual) is routed to a plainLinearCacheand solved with PureKLU's primal-factor / Dual-RHSldiv!, instead of the splitDualLinearCache. Came out of the #1064 / #1065 discussion (cc @hersle).Depends on PureKLU 1.1.0 (SciML/PureKLU.jl#67), now merged and registered — the
PureKLU = "1.1"compat resolves directly.What's here
init(::DualBLinearProblem, ::PureKLUFactorization) = __init(prob, alg)— routes duals-in-bto a nativeLinearCache. Type-stable: dispatch is purely on the problem subtype (A primal / b dual) + alg type, soinitalways returns aLinearCachehere (verified with@inferred). This sidesteps theUnion{LinearCache, DualLinearCache}instability that caused the value-based opt-out to be reverted (commit25f159c), because the subtype already encodes "A primal, b dual." Scope is narrow: only PureKLU; A-dual and mixed-A/b problems still take the split path.Plus a
PureKLU[compat]bump to1.1.Scope note (this does NOT fix #1064 by itself)
#1064 is an A-dual problem (the Rosenbrock
Wcarries duals). This PR only covers the b-dual case, so it is additive, not a replacement for the A-dual fix (#1066). The two cover different cells:Why it's worth it
reinit!s for free. The split path'sreinit!unconditionally setslinear_cache.isfresh = true, so it refactorizes even when onlybchanged. For a fixed operator with a varying dual RHS (cache reused), the native path is 5–15× faster on higher-fill matrices for that reason. (WhenAchanges every step, both refactorize and it's a tie — a simplification + fixed-A win, not a claim of being fundamentally faster than split.)n×(N+1)real RHS buffer lives on the PureKLU factorization (PureKLU#67,@allocated == 0).Verification (Julia 1.12, Linux)
test/Core/forwarddiff_overloads.jlpasses end-to-end against registered PureKLU 1.1.0; integration testset 12/12 (correctness through the publicsolve, routing returnsLinearCache,@inferredtype-stability).Runic --checkclean.Please ignore until reviewed by @ChrisRackauckas.
🤖 Generated with Claude Code