Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ ForwardDiff = "0.10.13, 1"
GPUArraysCore = "0.1, 0.2"
GenericSchur = "0.5.3"
LinearAlgebra = "1.10"
LinearSolve = "4"
LinearSolve = "4.1"
PrecompileTools = "1"
Printf = "1.10"
Random = "1"
Expand Down
42 changes: 38 additions & 4 deletions src/exp_baseexp.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using LinearSolve: LinearSolve, LinearProblem

"""
ExpMethodHigham2005Base()

Expand All @@ -14,7 +16,39 @@ function alloc_mem(
U = Matrix{T}(undef, n, n)
V = Matrix{T}(undef, n, n)
temp = Matrix{T}(undef, n, n)
return (A2, P, U, V, temp)
# Cached LinearSolve workspace for the Padé denominator solves, using
# LinearSolve's default algorithm choice (size-dependent: e.g. generic LU
# for tiny matrices, LAPACK/RecursiveFactorization above). The buffers are
# workspace-owned, so aliasing lets the factorization refactorize in place
# on every `solve!` instead of copying the n×n matrix.
Abuf = Matrix{T}(undef, n, n)
Bbuf = Matrix{T}(undef, n, n)
linsolve = LinearSolve.init(
LinearProblem(Abuf, Bbuf);
alias = LinearSolve.LinearAliasSpecifier(alias_A = true, alias_b = true)
)
return (A2, P, U, V, temp, linsolve)
end

# X .= temp \ X through the cached LinearSolve workspace: refill the
# workspace-owned buffers, mark A as replaced, and solve in place.
function _pade_linsolve!(
X::StridedMatrix{T}, temp::StridedMatrix{T}, linsolve
) where {T}
Abuf = linsolve.A
copyto!(Abuf, temp)
linsolve.A = Abuf # flag the refactorization; lu! overwrites Abuf
copyto!(linsolve.b, X)
sol = LinearSolve.solve!(linsolve)
# The Pade denominator is nonsingular by construction for the branch norm
# bounds, so this only triggers if even the default algorithm's safety
# fallback failed; surface it like LAPACK.gesv! did rather than silently
# propagating garbage.
if !LinearSolve.SciMLBase.successful_retcode(sol.retcode)
throw(LinearAlgebra.SingularException(0))
end
copyto!(X, sol.u)
return X
end

## Destructive matrix exponential using algorithm from Higham, 2008,
Expand All @@ -32,7 +66,7 @@ function exponential!(
# return copytri!(parent(exp(Hermitian(A))), 'U', true)
# end

A2, P, U, V, temp = cache
A2, P, U, V, temp, linsolve = cache

fill!(P, zero(T))
fill!(@diagview(P), one(T)) # P = Inn
Expand Down Expand Up @@ -80,7 +114,7 @@ function exponential!(
U, temp = temp, U # equivalent to U = A * U
@. X = V + U
@. temp = V - U
LAPACK.gesv!(temp, X)
_pade_linsolve!(X, temp, linsolve)
else
s = log2(nA / 5.4) # power of 2 later reversed by squaring
si = 0 # always defined so the s > 0 squaring loop is type-stable
Expand Down Expand Up @@ -109,7 +143,7 @@ function exponential!(
U, temp = temp, U # equivalent to U = A * U
@. X = V + U
@. temp = V - U
LAPACK.gesv!(temp, X)
_pade_linsolve!(X, temp, linsolve)

if s > 0 # squaring to reverse dividing by power of 2
for t in 1:si
Expand Down
Loading