Skip to content

GESVFactorization + dense LU pivot-buffer reuse (v4.2.0)#1078

Merged
ChrisRackauckas merged 6 commits into
SciML:mainfrom
ChrisRackauckas-Claude:gesv-factorization
Jul 10, 2026
Merged

GESVFactorization + dense LU pivot-buffer reuse (v4.2.0)#1078
ChrisRackauckas merged 6 commits into
SciML:mainfrom
ChrisRackauckas-Claude:gesv-factorization

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Please ignore until reviewed by @ChrisRackauckas. Draft; opened by an agent.

Two commits that were pushed to the #1074 branch after it merged, so they never appeared in that PR — rebased onto main:

  1. GESVFactorization (new exported alg, docs + release notes): fresh A → one combined LAPACK.gesv! into cache.u (honors alias_A; non-aliased users keep A pristine via a cacheval buffer); warm b-only re-solves → zero-alloc getrs! on the stored factors; batched matrix RHS native; singular → ReturnCode.Failure, no throw. 24 tests in test/Core/gesv.jl. Profiled: warm b-only re-solve 0.60 µs vs 0.93 µs for a raw gesv! at n=5; in the ExponentialUtilities exp! harness it restores raw-LAPACK parity at small n (−1.8% at n=5, ties at n=10–250).
  2. Dense LUFactorization pivot/factors-buffer reuse on refactorization via preallocated-ipiv getrf! — 0 B warm refactorization on Julia ≥ 1.11 (inert on 1.10, which lacks the method; old path kept).

Version → 4.2.0 (new public API). First consumer: SciML/ExponentialUtilities.jl#240 (compat "4.2").

🤖 Generated with Claude Code

ChrisRackauckas and others added 6 commits July 9, 2026 17:38
…fers

Warm refactorizations (cache.A = X then solve!) previously allocated a
fresh ipiv vector and LU wrapper via lu!/lu on every A update — and,
without alias_A, a fresh O(n^2) factors copy as well. When the cached LU
holds size-matched buffers and the matrix is a strided BLAS type with
RowMaximum pivoting, factorize through LAPACK.getrf! with the cached
ipiv (copying A into the cached factors first when the user's matrix
must stay intact). The preallocated-ipiv getrf! method exists on
Julia >= 1.11 only; older releases keep the previous allocating path.

Best-of-N warm-loop measurements (n = 5/10, Julia 1.12, OpenBLAS
single-threaded): alias_A=true refactorization solve drops from
96/144 B to 0 B per call (1.10 -> 1.08 us / 2.72 -> 2.71 us), and
alias_A=false from 368/1088 B to 0 B (1.15 -> 1.10 us / 2.89 -> 2.74 us).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
….2.0)

New exported dense algorithm mirroring the classic dgesv workflow: on a
fresh matrix the right-hand side is copied into u and LAPACK.gesv!
factorizes and solves in a single call; the (factors, ipiv) pair is
cached so b-only re-solves are one allocation-free LAPACK.getrs!
triangular solve. Batched (matrix) right-hand sides work natively
through both calls. With alias_A = true the factorization overwrites
cache.A directly; otherwise a cacheval-owned buffer is refilled by
copyto! and the user's matrix stays pristine. Singular matrices return
ReturnCode.Failure instead of leaking the LAPACK throw, matching the
existing BandedMatrix-LU catch pattern. Non-strided or non-BlasFloat
operators get an informative ArgumentError at init.

Warm-cache measurements (n = 5/10, Julia 1.12, OpenBLAS
single-threaded) against a raw LAPACK.gesv! call including the matrix
copy: refactorize-every-call solves land at parity (1.00 vs 0.93 us,
2.55 vs 2.57 us) and b-only re-solves at 0.60/1.47 us with zero
allocations. In the ExponentialUtilities exp! Pade benchmark it is
bit-identical to the raw gesv! path and within -1.8% to +2.3% of its
runtime for n = 5..250.

Tests cover vector/matrix RHS, cache reuse with new A and new b, both
alias_A semantics, singular Failure retcodes, allocation bounds, and
the unsupported-type error.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
solve! combined getrf!/getrs! instead of gesv! + try/catch: getrf!
returns info > 0 for a singular U without raising (gesv! calls
chklapackerror and throws), so a singular denominator becomes
ReturnCode.Failure directly with no exception on the path. Warm b-only
re-solves and the alias_A in-place contract are unchanged.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow @ChrisRackauckas's review: instead of catching (or manually
info-checking) a throwing LAPACK call, factorize with the idiomatic
`lu!(A; check = false)` and branch on `issuccess`, solving with `ldiv!` —
the same no-throw pattern the other dense factorizations use. A singular
factor becomes ReturnCode.Failure with no exception on the path (and,
matching LUFactorization, leaves isfresh set so a re-solve refactorizes).
The cached LU makes warm b-only re-solves allocation-free; fresh-A solves
stay at raw-gesv! speed (0.62 vs 0.61 µs at n=5, 9.8 vs 10.2 at n=30).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
StableRNGs is not in the test target, so the Downgrade Core job errored
at load ("Package StableRNGs not found"). Seed with MersenneTwister from
the already-available Random stdlib; the rng-threaded rand(rng, ...) calls
and determinism are unchanged.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Revert the previous stopgap (MersenneTwister) and keep StableRNGs — its
cross-version-stable stream is the point — by adding it properly to the
[extras], the test target, and [compat = "1"]. Verified the full test
target resolves and test/Core/gesv.jl passes (24/24).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ChrisRackauckas
ChrisRackauckas marked this pull request as ready for review July 10, 2026 06:45
@ChrisRackauckas
ChrisRackauckas merged commit c75997b into SciML:main Jul 10, 2026
55 of 59 checks passed
ChrisRackauckas added a commit that referenced this pull request Jul 11, 2026
…1082)

The alias_A = true dense LUFactorization refactorization path reuses the
cached ipiv through LAPACK.getrf! since #1078, but only for strided BLAS
matrices with RowMaximum pivoting; NoPivot/RowNonZero pivoting and
non-BLAS element types still allocated a fresh pivot vector and LU
wrapper through lu! on every cache.A = X refactorization. Route those
through the vendored generic_lufact! with the cached pivot vector, which
accepts a preallocated ipiv on every supported Julia version (unlike the
getrf! method, which needs >= 1.11).

51x51 Matrix{Float64} warm refactorization (cache.A = X; solve!) with
alias_A = true, Julia 1.12.6: NoPivot drops 480 B -> 0 B per
refactorization (RowMaximum and the default algorithm were already 0 B
via #1078; pre-#1078 all paths allocated 480 B). On Julia 1.10 the
NoPivot path drops to 0 B as well, while the BLAS paths keep the
previous allocating lu! there by design.

Adds test/Core/lu_refactorization.jl covering: zero-allocation warm
refactorization cycles (RowMaximum, NoPivot, and the default algorithm),
pivot-vector identity across refactorizations, correctness against A \ b
across cycles, singular refactorization returning ReturnCode.Failure
without throwing and recovering on the next nonsingular matrix, the
default algorithm's QR safety fallback surviving a warm singular
refactorization, BigFloat (generic eltype) correctness, and pivot
reallocation after resize!.

Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ChrisRackauckas added a commit that referenced this pull request Jul 11, 2026
…ve! (#1081)

Fixes the QA group's ExplicitImports no_implicit_imports failure
introduced in c75997b (#1078), which added an unqualified
issuccess call. Matches the qualified use elsewhere in the file.

Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Co-authored-by: Claude Fable 5 <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.

2 participants