Skip to content

Commit a39171d

Browse files
ChrisRackauckas-ClaudeChrisRackauckasclaude
authored
SupernodalLU: route panel triangular solves through TriangularSolve (#1113)
The supernodal factorization applies two BLAS-3 trsms per supernode against its just-factored diagonal block: the L21 panel on the right by U11, and the U12 panel on the left by unit-L11. They now go through overridable hooks whose default is the stdlib triangular solve, with LinearSolveRecursiveFactorizationExt overriding them with TriangularSolve. TriangularSolve is a hard dependency of RecursiveFactorization and is what RecursiveFactorization uses for its own trsms, so this adds nothing to anyone's dependency graph: it is declared as a weakdep purely to trigger the extension, and since RecursiveFactorization pulls it in, loading RecursiveFactorization alone still activates everything. That is also exactly the condition under which RFLUFactorization becomes the dense default, so the sparse solver's panel work and the dense default run on the same kernels. Measured refactorization, BLAS pinned to 1 thread: poisson2d_512 0.4781 -> 0.3921 s (-18%) poisson3d_32 0.3466 -> 0.3027 s (-13%) Residuals unchanged (1.5e-14 / 8.8e-16). GROUP=Core passes. Co-authored-by: Chris Rackauckas <accounts@chrisrackauckas.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent e7742ab commit a39171d

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

ext/LinearSolveRecursiveFactorizationExt.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,31 @@ function LinearSolve._custom_adjoint_factorization_solve(
224224
return solution[1:n]
225225
end
226226

227+
# ---- SupernodalLU panel triangular solves ---------------------------------
228+
# The vendored supernodal sparse LU (src/SupernodalLU) applies two BLAS-3
229+
# trsms per supernode against its just-factored diagonal block: the L21 panel
230+
# on the right by U11, and the U12 panel on the left by unit-L11. Route them
231+
# through TriangularSolve, which RecursiveFactorization already depends on
232+
# and uses for its own trsms — so when RFLU is the dense default, the sparse
233+
# solver's panel work runs on the same kernels. Measured: recovers the
234+
# 2D-mesh refactorization gap left by the stdlib trsms.
235+
const SNLU = LinearSolve.SupernodalLU
236+
const SNLUTypes = Union{Float32, Float64}
237+
238+
function SNLU._panel_rdiv!(W::Matrix{Tv}, np::Int, len::Int) where {Tv <: SNLUTypes}
239+
len > np || return nothing
240+
TriangularSolve.rdiv!(
241+
view(W, (np + 1):len, 1:np), UpperTriangular(view(W, 1:np, 1:np)), Val(false)
242+
)
243+
return nothing
244+
end
245+
246+
function SNLU._panel_ldiv!(W::Matrix{Tv}, np::Int, Z::Matrix{Tv}) where {Tv <: SNLUTypes}
247+
isempty(Z) && return nothing
248+
TriangularSolve.ldiv!(
249+
UnitLowerTriangular(view(W, 1:np, 1:np)), Z, Val(false)
250+
)
251+
return nothing
252+
end
253+
227254
end

src/SupernodalLU/numeric.jl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ function _cache_lu!(
274274
return true
275275
end
276276

277+
# Panel triangular solves (L21 := L21·U11⁻¹ and U12 := L11⁻¹·U12). These are
278+
# BLAS-3 trsms against the just-factored diagonal block, not linear solves in
279+
# the LinearSolve sense, so they are plain overridable hooks: the defaults
280+
# below use the stdlib triangular solves, and
281+
# LinearSolveRecursiveFactorizationExt routes them through TriangularSolve —
282+
# the same library RecursiveFactorization (and hence the RFLU dense default)
283+
# already uses internally for its own trsms.
284+
function _panel_rdiv!(W::Matrix{Tv}, np::Int, len::Int) where {Tv}
285+
rdiv!(view(W, (np + 1):len, 1:np), UpperTriangular(view(W, 1:np, 1:np)))
286+
return nothing
287+
end
288+
289+
function _panel_ldiv!(W::Matrix{Tv}, np::Int, Z::Matrix{Tv}) where {Tv}
290+
ldiv!(UnitLowerTriangular(view(W, 1:np, 1:np)), Z)
291+
return nothing
292+
end
293+
277294
_lu_from_cacheval(cv::LinearAlgebra.LU) = cv
278295
_lu_from_cacheval(cv::Tuple) = _lu_from_cacheval(first(cv))
279296

@@ -497,8 +514,8 @@ function _process_supernode!(
497514
end
498515

499516
if nu > 0
500-
rdiv!(view(Ws, (np + 1):len, 1:np), UpperTriangular(view(Ws, 1:np, 1:np)))
501-
ldiv!(UnitLowerTriangular(view(Ws, 1:np, 1:np)), Zs)
517+
_panel_rdiv!(Ws, np, len)
518+
_panel_ldiv!(Ws, np, Zs)
502519
end
503520

504521
for a in 1:np

0 commit comments

Comments
 (0)