Skip to content

Commit 49782ca

Browse files
exp: Padé denominator solve through GESVFactorization
Benchmarks of the four candidate solve paths on identical per-cell matrices (n = 5..250, scales 0.3/4.0, best-of-N with preallocated caches, Julia 1.12, OpenBLAS single-threaded) showed LinearSolve's GESVFactorization (v4.3) is the only variant at parity with the raw LAPACK.gesv! this code historically used, in every cell: -1.8% to +2.3% end-to-end, versus up to +5% for a pinned LUFactorization and up to +4.5% for the default algorithm choice (whose safety fallback adds an O(n^2) backup copy per refactorization). GESVFactorization reproduces the gesv! results bit-for-bit and keeps the same per-call allocation bytes; on a singular denominator it returns a Failure retcode, which this code turns back into the historical SingularException (the default algorithm's QR fallback resolved it silently instead). Requires LinearSolve 4.3 for GESVFactorization. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYp371jx6LurezUDhKcYRh
1 parent 7f486f5 commit 49782ca

2 files changed

Lines changed: 12 additions & 13 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ForwardDiff = "0.10.13, 1"
3030
GPUArraysCore = "0.1, 0.2"
3131
GenericSchur = "0.5.3"
3232
LinearAlgebra = "1.10"
33-
LinearSolve = "4.1"
33+
LinearSolve = "4.3"
3434
PrecompileTools = "1"
3535
Printf = "1.10"
3636
Random = "1"

src/exp_baseexp.jl

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using LinearSolve: LinearSolve, LinearProblem
1+
using LinearSolve: LinearSolve, LinearProblem, GESVFactorization
22

33
"""
44
ExpMethodHigham2005Base()
@@ -16,15 +16,16 @@ function alloc_mem(
1616
U = Matrix{T}(undef, n, n)
1717
V = Matrix{T}(undef, n, n)
1818
temp = Matrix{T}(undef, n, n)
19-
# Cached LinearSolve workspace for the Padé denominator solves, using
20-
# LinearSolve's default algorithm choice (size-dependent: e.g. generic LU
21-
# for tiny matrices, LAPACK/RecursiveFactorization above). The buffers are
22-
# workspace-owned, so aliasing lets the factorization refactorize in place
23-
# on every `solve!` instead of copying the n×n matrix.
19+
# Cached LinearSolve workspace for the Padé denominator solves.
20+
# GESVFactorization (LinearSolve 4.3) mirrors the raw LAPACK.gesv! this code historically
21+
# used (single combined factorize-and-solve call, bit-identical results)
22+
# and benchmarks at parity with it from n = 5 through n = 250. The buffers
23+
# are workspace-owned, so aliasing lets the factorization overwrite Abuf in
24+
# place on every `solve!` instead of copying the n×n matrix.
2425
Abuf = Matrix{T}(undef, n, n)
2526
Bbuf = Matrix{T}(undef, n, n)
2627
linsolve = LinearSolve.init(
27-
LinearProblem(Abuf, Bbuf);
28+
LinearProblem(Abuf, Bbuf), GESVFactorization();
2829
alias = LinearSolve.LinearAliasSpecifier(alias_A = true, alias_b = true)
2930
)
3031
return (A2, P, U, V, temp, linsolve)
@@ -37,13 +38,11 @@ function _pade_linsolve!(
3738
) where {T}
3839
Abuf = linsolve.A
3940
copyto!(Abuf, temp)
40-
linsolve.A = Abuf # flag the refactorization; lu! overwrites Abuf
41+
linsolve.A = Abuf # flag the refactorization; gesv! overwrites Abuf
4142
copyto!(linsolve.b, X)
4243
sol = LinearSolve.solve!(linsolve)
43-
# The Pade denominator is nonsingular by construction for the branch norm
44-
# bounds, so this only triggers if even the default algorithm's safety
45-
# fallback failed; surface it like LAPACK.gesv! did rather than silently
46-
# propagating garbage.
44+
# LAPACK.gesv! threw on a singular denominator; keep that contract instead
45+
# of silently propagating garbage from a failed factorization.
4746
if !LinearSolve.SciMLBase.successful_retcode(sol.retcode)
4847
throw(LinearAlgebra.SingularException(0))
4948
end

0 commit comments

Comments
 (0)