Skip to content

Commit 70b6b93

Browse files
exp: Pade denominator workspace uses LinearSolve's default algorithm
Drop the pinned LUFactorization in alloc_mem and let LinearSolve.init size-select the algorithm (GenericLUFactorization for n <= 10, tuned LAPACK/RecursiveFactorization choices above). The tiny-matrix generic LU refactorizes in place with a cached pivot vector, making the warm solve! allocation-free and at parity with (n = 5) or faster than (n = 10) the raw LAPACK.gesv! this path originally used. Best-of-N end-to-end exponential! times against the old gesv! code (Julia 1.12, OpenBLAS single-threaded, this machine) are within ~3% at all of n = 5, 10, 30, 60, 120, 250 for scales 0.3 and 4.0, with fewer per-call allocation bytes at n <= 10. One behavioral note: an exactly singular Pade denominator is now resolved by the default algorithm's QR safety fallback instead of throwing, matching generic \ semantics. The denominator is nonsingular by construction for the branch norm bounds, so the retcode guard only fires if the fallback itself fails. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 849d306 commit 70b6b93

1 file changed

Lines changed: 11 additions & 7 deletions

File tree

src/exp_baseexp.jl

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

33
"""
44
ExpMethodHigham2005Base()
@@ -16,13 +16,15 @@ 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. The buffers
20-
# are workspace-owned, so aliasing lets LUFactorization refactorize in
21-
# place (`lu!`) on every `solve!` instead of copying the n×n matrix.
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.
2224
Abuf = Matrix{T}(undef, n, n)
2325
Bbuf = Matrix{T}(undef, n, n)
2426
linsolve = LinearSolve.init(
25-
LinearProblem(Abuf, Bbuf), LUFactorization();
27+
LinearProblem(Abuf, Bbuf);
2628
alias = LinearSolve.LinearAliasSpecifier(alias_A = true, alias_b = true)
2729
)
2830
return (A2, P, U, V, temp, linsolve)
@@ -38,8 +40,10 @@ function _pade_linsolve!(
3840
linsolve.A = Abuf # flag the refactorization; lu! overwrites Abuf
3941
copyto!(linsolve.b, X)
4042
sol = LinearSolve.solve!(linsolve)
41-
# LAPACK.gesv! threw on a singular denominator; keep that contract instead
42-
# of silently propagating garbage from a failed factorization.
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.
4347
if !LinearSolve.SciMLBase.successful_retcode(sol.retcode)
4448
throw(LinearAlgebra.SingularException(0))
4549
end

0 commit comments

Comments
 (0)