Skip to content

Commit 6df384d

Browse files
ChrisRackauckas-ClaudeChrisRackauckasclaude
authored
Add SupernodalLUFactorization (pure-Julia Schenk–Gärtner supernodal LU via PurePardiso.jl) (#1102)
* Add SupernodalLUFactorization (pure-Julia Schenk-Gartner supernodal LU) New sparse LU algorithm backed by PurePardiso.jl (hard dependency, following the PureKLU precedent): the supernodal left-right-looking LU method of O. Schenk and K. Gartner (FGCS 20(3) 2004; ETNA 23 2006) -- supernodal BLAS-3 LU on the symmetrized pattern with restricted-block static pivoting, iterative-refinement recovery, and MC64-style matching + scaling preprocessing. Pure Julia, no binary dependencies. This is the strongest solver for 'more structured' (PDE-mesh-like) sparse systems: in PurePardiso's benchmark suite it is the fastest single-threaded full factorization on every mesh-like problem, ahead of UMFPACK (0.5-0.8x) and KLU (up to 50x on 3D), with allocation-free numeric refactorization on an unchanged pattern (the cache-reuse path here). Numerically singular systems surface as ReturnCode.Infeasible via a residual check on the (rare) perturbed-pivot path, since static pivoting never aborts. Loading RecursiveFactorization (e.g. for RFLUFactorization) also activates PurePardiso's speed extension automatically, routing the dense panel kernels through RecursiveFactorization/TriangularSolve. Not wired into the default polyalgorithm in this PR; the natural follow-up is routing the 'more structure' branch (currently UMFPACKFactorization, gated on Base.USE_GPL_LIBS) to this algorithm, which would make the entire default sparse LU stack pure Julia. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add SupernodalLUFactorization to resolve.jl sparse-input lists The re-solve sweep iterates all AbstractSparseFactorization subtypes and converts the test matrix to sparse only for algorithms in its explicit lists; without this the new algorithm received a dense Matrix and its sparse-only solve! errored (the 4 Core-group failures). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Route the structured-sparse default LU to SupernodalLUFactorization The default polyalgorithm's :UMFPACKFactorization slot now resolves to the pure-Julia supernodal left-right-looking LU (same pattern as the :KLUFactorization slot resolving to PureKLU); explicit UMFPACKFactorization() requests are unchanged. Since both sparse LU slots now resolve to pure-Julia solvers, the LU side of the sparse default no longer depends on Base.USE_GPL_LIBS (previously the non-GPL build sent structured systems to the scalar-kernel KLU path); the QR side keeps its SPQR gating. Occupying the UMFPACK slot gives the new algorithm the established sparse-LU -> column-pivoted-QR fallback chain: verified locally that a consistent singular structured system falls back and solves to residual 0.0, an inconsistent one returns the least-squares solution with the chain's existing retcode policy, and explicit SupernodalLUFactorization() requests still report Infeasible. Rationale for replacing UMFPACK in the default: benchmarks (PurePardiso results/SUMMARY.md) show it faster on every mesh-like structured system (0.5-0.8x UMFPACK full factorization, 0.2-0.7x refactorization) with equal-or-better residuals; the trade-off is circuit-like large-n systems (~1.6x slower than UMFPACK on circuit_100k), which sit near the klulike/structured boundary anyway. test/Core/default_algs.jl passes unchanged: the DefaultAlgorithmChoice enum and slot names are untouched, only the slot's resolved algorithm changed. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Vendor SupernodalLU self-contained in src/SupernodalLU; drop external dep Per review direction: the solver now lives entirely inside LinearSolve as src/SupernodalLU (module LinearSolve.SupernodalLU) instead of depending on an external registered package. The folder is self-contained (needs only SparseArrays and LinearAlgebra, both hard deps): AMD port (BSD-3, retained SPDX headers + NOTICE.md with the full per-component lineage), orderings, symbolic analysis, MC64-style matching, the supernodal numeric kernel, and solves. Internal names are de-branded (snlu/snlu!/snlu_symbolic, SupernodalLUFactor, SymbolicAnalysis); the method is cited as Schenk & Gartner (FGCS 20(3) 2004; ETNA 23 2006). Dense-default composition is now wired directly: LinearSolveRecursiveFactorizationExt gains TriangularSolve as a second trigger (loads transitively with RecursiveFactorization, so activation conditions are unchanged) and overrides the supernodal dense panel hooks with RecursiveFactorization/TriangularSolve kernels - the same components RFLUFactorization uses - with static-pivoting semantics preserved via the optimistic fast path. The sparse structured default and the dense default share one engine whenever RF is loaded. The default-slot routing (:UMFPACKFactorization -> supernodal) is unchanged from the previous commit. New test/Core/supernodal_lu.jl ports the solver's internal invariant tests (factor identity, zero-alloc refactorization, matching incl. the mass-perturbation retry, banded fast path, threaded-vs-serial, BigFloat/Complex, multi-RHS). GROUP=Core passes locally in full. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Factor supernode diagonal blocks through LinearSolve's dense init/solve! Replaces the bespoke RecursiveFactorization/TriangularSolve kernel overrides in LinearSolveRecursiveFactorizationExt (reverted, along with the TriangularSolve weakdep) with LinearSolve's own dense machinery: each supernode whose pivot block is at least dense_threshold (64) wide owns a dense LinearCache over an np x np buffer, built with SciMLBase.init at analysis time, and the block factorization runs through SciMLBase.solve! on it. The dense algorithm therefore follows whatever LinearSolve's dense default resolves to for that block size and element type - RFLUFactorization when RecursiveFactorization is loaded, MKL/Apple/ LAPACK/generic otherwise, including autotune preferences - with no backend-specific code in the sparse solver. The new "dense_alg" keyword on SupernodalLUFactorization overrides the choice explicitly. (defaultalg is queried with "A === nothing", its documented dense stand-in, because a panel-block view is not a DenseMatrix.) Static pivoting is preserved without a backup copy: the block is copied into the cache buffer, factored there, and copied back only if every pivot clears eps*||A||; on rejection the panel is still pristine, so the built-in static-perturbation kernel reruns on it directly. Measured (poisson2d_512/poisson3d_32, thresholds 32-256, with and without RecursiveFactorization): performance-neutral vs the built-in kernel, +/-2%. Honest note: the removed direct-kernel wiring measured 8-18% faster refactorization, but most of that came from routing the panel triangular solves through TriangularSolve, which is a separate dependency and not part of the dense init/solve! API; the block-getrf routing itself is a wash. One documented property changed: refactorization is no longer strictly zero-allocation - each dense block cache's solve! returns a LinearSolution (~64 B per cached supernode, independent of problem size). Solves remain allocation-free, and dense_threshold=typemax(Int) restores fully allocation-free refactorization. Test and docstrings updated to state this precisely. GROUP=Core passes in full. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix QA and adjoint-policy violations after merging main - Declare the adjoint reuse policy for SupernodalLUFactorization (_NoAdjointFactorizationReuse, matching PureUMFPACKFactorization): the factorization object is not a LinearAlgebra.Factorization and exposes no adjoint solve, so the generic reverse path applies. Required by the new adjoint.jl policy test from main. - Drop the stale explicit import of spzeros in the SupernodalLU module and access LinearAlgebra.BlasFloat from its owner rather than through LinearAlgebra.BLAS (ExplicitImports QA). - Restore Project.toml to main verbatim: the vendored solver needs no manifest change at all (SparseArrays/LinearAlgebra/SciMLBase are existing deps), and a stray RecursiveFactorization [deps] entry from local testing was tripping Aqua stale-deps. GROUP=Core and GROUP=QA both pass; the vendored solver's internal tests also pass on Julia 1.10 LTS. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Allow-list SuiteSparse AMD port identifiers in the typos config pn, hashi (variable names carried over from the SuiteSparse AMD source in src/SupernodalLU/amd.jl) and Tpos (the transpose-position map in the supernodal factorization) are identifiers, not misspellings. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Revert accidental absolute dev path in test/qa/Project.toml Running the QA group locally made Pkg rewrite the [sources] entry from the relative ../.. to my absolute checkout path, which was committed and broke every CI job that instantiates that environment (Dev path does not exist). Restored from main. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Split out the default-algorithm change into a follow-up PR This PR is now purely additive: it adds SupernodalLUFactorization (and the vendored src/SupernodalLU solver behind it) as an opt-in algorithm, without touching the default polyalgorithm. Reverted here and moved to the follow-up: - src/default.jl: the :UMFPACKFactorization slot resolving to SupernodalLUFactorization (file is now identical to main) - the sparse defaultalg in LinearSolveSparseArraysExt (restored to main's USE_GPL_LIBS-gated version) - the docs wording claiming it is the structured-sparse default The algorithm now reads its own :SupernodalLUFactorization cacheval slot (the symbol only matters for DefaultLinearSolver caches, matching the PureUMFPACKFactorization convention), and the docs note says explicitly that it is not yet wired into the default polyalgorithm. GROUP=Core passes; default routing verified unchanged (structured sparse still selects UMFPACKFactorization). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Chris Rackauckas <accounts@chrisrackauckas.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 4f2af4a commit 6df384d

19 files changed

Lines changed: 3568 additions & 1 deletion

.typos.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,8 @@ MTK = "MTK"
7676
ODE = "ODE"
7777
PDE = "PDE"
7878
SDE = "SDE"
79+
80+
# SupernodalLU (src/SupernodalLU) identifiers
81+
pn = "pn" # SuiteSparse AMD port variable (amd.jl)
82+
Tpos = "Tpos" # transpose-position map in the supernodal factorization
83+
hashi = "hashi" # SuiteSparse AMD port variable (amd.jl)

docs/src/solvers/solvers.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ LinearSolveSparseArraysExt.KLU.klu
230230
LinearSolveSparseArraysExt.KLU.klu!
231231
PureKLUFactorization
232232
PureUMFPACKFactorization
233+
SupernodalLUFactorization
233234
UMFPACKFactorization
234235
SparseColumnPivotedQRFactorization
235236
```
@@ -241,6 +242,15 @@ SparseColumnPivotedQRFactorization
241242
patterns, replacing the SuiteSparse-backed `KLUFactorization` in the default
242243
polyalgorithm.
243244

245+
!!! note
246+
247+
`SupernodalLUFactorization` is a pure-Julia implementation of the supernodal
248+
left–right-looking sparse LU method of Schenk & Gärtner (vendored
249+
self-contained in `src/SupernodalLU`, no binary dependency). It is the
250+
strongest choice for "more structured" (PDE-mesh-like) sparse systems,
251+
where it outperforms both `UMFPACKFactorization` and `KLUFactorization`.
252+
It is not yet wired into the default polyalgorithm; request it explicitly.
253+
244254
!!! note
245255

246256
`SparseColumnPivotedQRFactorization` is a pure-Julia, rank-revealing

ext/LinearSolveSparseArraysExt.jl

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ using LinearSolve: LinearSolve, BLASELTYPES, pattern_changed, ArrayInterface,
77
NormalCholeskyFactorization,
88
OperatorAssumptions, LinearVerbosity,
99
QRFactorization, RFLUFactorization, UMFPACKFactorization,
10-
SparseColumnPivotedQRFactorization, solve
10+
SparseColumnPivotedQRFactorization, SupernodalLUFactorization, solve
1111
using SciMLOperators: AbstractSciMLOperator, has_concretization
1212
using ArrayInterface: ArrayInterface
1313
using LinearAlgebra: LinearAlgebra, I, Hermitian, Symmetric, cholesky, ldiv!, lu, lu!
@@ -31,6 +31,10 @@ include("../src/KLU/klu.jl")
3131
# PureKLU (pure-Julia, no SuiteSparse) is a hard dependency and the default
3232
# sparse LU; the SuiteSparse `KLUFactorization` above is unchanged.
3333
import PureKLU
34+
# SupernodalLU (pure-Julia supernodal left-right-looking LU, Schenk-Gärtner
35+
# method) is vendored in src/SupernodalLU: the BLAS-3 sparse LU for
36+
# structured systems.
37+
const SNLU = LinearSolve.SupernodalLU
3438
# SparseColumnPivotedQR (pure-Julia, rank-revealing column-pivoted sparse QR) is a
3539
# hard dependency: the default sparse QR and the singular-LU fallback.
3640
import SparseColumnPivotedQR
@@ -680,6 +684,128 @@ function SciMLBase.solve!(
680684
end
681685
end
682686

687+
# --- SupernodalLU: pure-Julia supernodal left-right-looking LU (Schenk-Gärtner) ---
688+
# The BLAS-3 sparse LU for structured (PDE-mesh-like) systems (vendored in src/SupernodalLU).
689+
690+
function LinearSolve.init_cacheval(
691+
alg::SupernodalLUFactorization, A::AbstractArray, b, u, Pl, Pr,
692+
maxiters::Int, abstol, reltol,
693+
verbose::Union{LinearVerbosity, Bool}, assumptions::OperatorAssumptions
694+
)
695+
return nothing
696+
end
697+
698+
function LinearSolve.init_cacheval(
699+
alg::SupernodalLUFactorization, A::LinearSolve.GPUArraysCore.AnyGPUArray, b, u,
700+
Pl, Pr,
701+
maxiters::Int, abstol, reltol,
702+
verbose::Union{LinearVerbosity, Bool}, assumptions::OperatorAssumptions
703+
)
704+
return nothing
705+
end
706+
707+
const PREALLOCATED_SUPERNODAL = SNLU.snlu(
708+
SparseMatrixCSC{Float64, Int64}(0, 0, [Int64(1)], Int64[], Float64[])
709+
)
710+
711+
function LinearSolve.init_cacheval(
712+
alg::SupernodalLUFactorization, A::AbstractSparseArray{Float64, Int64}, b, u,
713+
Pl, Pr,
714+
maxiters::Int, abstol, reltol,
715+
verbose::Union{LinearVerbosity, Bool}, assumptions::OperatorAssumptions
716+
)
717+
return PREALLOCATED_SUPERNODAL
718+
end
719+
720+
# SupernodalLU is pure Julia and factors any `Number` element type; the empty
721+
# cacheval carries the correct types so `pplu!`/`pplu` dispatch is concrete.
722+
function LinearSolve.init_cacheval(
723+
alg::SupernodalLUFactorization, A::AbstractSparseArray{T, Ti}, b, u, Pl, Pr,
724+
maxiters::Int, abstol, reltol,
725+
verbose::Union{LinearVerbosity, Bool}, assumptions::OperatorAssumptions
726+
) where {T <: Number, Ti <: Integer}
727+
return SNLU.snlu(
728+
SparseMatrixCSC{T, Ti}(0, 0, [one(Ti)], Ti[], T[])
729+
)
730+
end
731+
732+
function LinearSolve.init_cacheval(
733+
alg::SupernodalLUFactorization, A::AbstractSciMLOperator, b, u, Pl, Pr,
734+
maxiters::Int, abstol, reltol,
735+
verbose::Union{LinearVerbosity, Bool}, assumptions::OperatorAssumptions
736+
)
737+
if has_concretization(A)
738+
return LinearSolve.init_cacheval(
739+
alg, convert(AbstractMatrix, A), b, u, Pl, Pr,
740+
maxiters, abstol, reltol, verbose, assumptions
741+
)
742+
else
743+
nothing
744+
end
745+
end
746+
747+
function LinearSolve.pattern_changed(
748+
F::SNLU.SupernodalLUFactor, A::SparseArrays.AbstractSparseMatrixCSC
749+
)
750+
Aold = F.A
751+
return getcolptr(Aold) != getcolptr(A) || rowvals(Aold) != rowvals(A)
752+
end
753+
754+
function SciMLBase.solve!(
755+
cache::LinearSolve.LinearCache, alg::SupernodalLUFactorization; kwargs...
756+
)
757+
A = cache.A
758+
A = LinearSolve.reduce_operand!(cache.sparse_reduction, A)
759+
A = convert(AbstractMatrix, A)
760+
if cache.isfresh
761+
cacheval = LinearSolve.@get_cacheval(cache, :SupernodalLUFactorization)
762+
As = SparseMatrixCSC(size(A)..., getcolptr(A), rowvals(A), nonzeros(A))
763+
if alg.reuse_symbolic && size(cacheval) == size(As) &&
764+
nnz(cacheval.A) == nnz(As) &&
765+
!(alg.check_pattern && pattern_changed(cacheval, As))
766+
# numeric-only refactorization: reuses the analysis, matching, and
767+
# all numeric storage (allocation-free)
768+
fact = SNLU.snlu!(cacheval, As)
769+
else
770+
# `check = false`: static pivoting never aborts — numerically
771+
# singular systems surface through the finiteness check below.
772+
fact = SNLU.snlu(
773+
As; ordering = alg.ordering, matching = alg.matching,
774+
eps_pivot = alg.eps_pivot, threaded = alg.threaded,
775+
dense_alg = alg.dense_alg, check = false
776+
)
777+
end
778+
cache.cacheval = fact
779+
cache.isfresh = false
780+
end
781+
F = LinearSolve.@get_cacheval(cache, :SupernodalLUFactorization)
782+
y = SNLU.solve!(cache.u, F, cache.b)
783+
# Static pivoting never aborts: a numerically singular system factors with
784+
# perturbed pivots and produces a finite but meaningless solution. When
785+
# pivots were perturbed (rare), verify the residual (one sparse mat-vec)
786+
# so singularity surfaces as `Infeasible` instead of a silent `Success`.
787+
ok = all(isfinite, y)
788+
if ok && SNLU.nperturbed(F) > 0
789+
r = F.A * y
790+
r .-= cache.b
791+
bn = LinearAlgebra.norm(cache.b)
792+
ok = LinearAlgebra.norm(r) <= 1.0e-6 * max(bn, floatmin(real(eltype(r))))
793+
end
794+
return if ok
795+
SciMLBase.build_linear_solution(
796+
alg, y, nothing, nothing; retcode = ReturnCode.Success
797+
)
798+
else
799+
@SciMLMessage(
800+
"Solver produced a non-finite or inaccurate solution; matrix is likely singular",
801+
cache.verbose, :solver_failure
802+
)
803+
SciMLBase.build_linear_solution(
804+
alg, cache.u, nothing, nothing; retcode = ReturnCode.Infeasible
805+
)
806+
end
807+
end
808+
683809
# --- SparseColumnPivotedQR: pure-Julia rank-revealing column-pivoted sparse QR ---
684810
# The default sparse QR (non-square sparse systems) and the singular-LU fallback.
685811

src/LinearSolve.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ function defaultalg_symbol end
409409

410410
include("verbosity.jl")
411411
include("blas_logging.jl")
412+
include("SupernodalLU/SupernodalLU.jl")
412413
include("generic_lufact.jl")
413414
include("eigenvalue.jl")
414415
include("common.jl")
@@ -524,6 +525,7 @@ export LUFactorization, SVDFactorization, QRFactorization, GenericFactorization,
524525
RFLUFactorization, ButterflyFactorization,
525526
NormalCholeskyFactorization, NormalBunchKaufmanFactorization,
526527
UMFPACKFactorization, KLUFactorization, PureKLUFactorization,
528+
SupernodalLUFactorization,
527529
PureUMFPACKFactorization, SparseColumnPivotedQRFactorization, FastLUFactorization,
528530
FastQRFactorization,
529531
SparspakFactorization, DiagonalFactorization, CholeskyFactorization,

src/SupernodalLU/NOTICE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
SupernodalLU (LinearSolve src/SupernodalLU) — code lineage and licensing notice
2+
==================================================
3+
4+
Summary: MIT for this folder, BSD-3-Clause for the vendored `amd.jl`.
5+
No GPL/LGPL-licensed code and no proprietary code is included. The full
6+
license texts are in LICENSE. Per-component provenance:
7+
8+
1. The PARDISO method itself (supernodal left-looking LU on the symmetrized
9+
pattern, restricted-block pivoting with static perturbation, weighted
10+
matching preprocessing) is implemented from the published literature:
11+
12+
- O. Schenk, K. Gärtner: "Solving unsymmetric sparse systems of linear
13+
equations with PARDISO", FGCS 20(3), 2004.
14+
- O. Schenk, K. Gärtner: "On fast factorization pivoting methods for
15+
sparse symmetric indefinite systems", ETNA 23, 2006.
16+
17+
PARDISO (pardiso-project.org) and Intel MKL PARDISO are closed-source;
18+
no source code of either exists publicly and none was used.
19+
20+
2. `amd.jl` is a pure-Julia port of SuiteSparse AMD, vendored from
21+
PureKLU.jl. SuiteSparse AMD is BSD-3-Clause, (c) 1996-2025 Timothy A.
22+
Davis, Patrick R. Amestoy, and Iain S. Duff; the file remains under
23+
BSD-3-Clause (text in LICENSE).
24+
25+
3. `src/matching.jl` implements maximum-weight (max-product) bipartite
26+
matching with dual-variable scalings from the published algorithm
27+
descriptions (I. S. Duff, J. Koster, SIMAX 22(4), 2001; M. Olschowka,
28+
A. Neumaier, Linear Algebra Appl. 240, 1996), as shortest augmenting
29+
paths with potentials (Jonker–Volgenant style). HSL MC64 is proprietary
30+
and its source was not used.
31+
32+
4. `src/symbolic.jl` implements standard published symbolic-analysis
33+
algorithms — the elimination tree via union-find with path halving
34+
(J. W. H. Liu, "The role of elimination trees in sparse factorization",
35+
SIMAX 11(1), 1990), L-structure prediction by row subtrees (J. R.
36+
Gilbert, E. Ng, B. W. Peyton, SIMAX 15(1), 1994), and relaxed supernode
37+
amalgamation in the style of Ashcraft–Grimes with the relaxation
38+
constants published for CHOLMOD (Y. Chen, T. A. Davis, W. W. Hager,
39+
S. Rajamanickam, ACM TOMS 35(3), 2008). The implementations were
40+
written from the algorithm descriptions and deliberately use different
41+
formulations from the CSparse/SuiteSparse (LGPL/GPL) implementations of
42+
the same algorithms: union-find etree instead of ancestor stamping,
43+
cursor-stack DFS over counting-sorted child arrays instead of
44+
destructive sibling lists, row-subtree column construction instead of
45+
child-set merging, and single bucket-pass pattern permutation instead
46+
of per-column sorting.
47+
48+
5. The banded-detection acceptance thresholds in `symbolic.jl` follow the
49+
heuristic introduced in PureKLU.jl by the same copyright holder.
50+
51+
6. Everything else (`numeric.jl`, `solve.jl`, `interface.jl`,
52+
`ordering.jl` apart from the AMD calls, and the
53+
RecursiveFactorization/TriangularSolve kernel overrides in
54+
LinearSolveRecursiveFactorizationExt) is original work of the same
55+
authors, MIT-licensed.
56+
57+
For comparison, the related packages carry different licenses because they
58+
ARE ports: PureKLU.jl is LGPL-2.1+ (KLU/BTF translation) and PureUMFPACK.jl
59+
is GPL-2.0+ (UMFPACK/CSparse translation). This folder is not a
60+
translation of any existing codebase, which is why MIT (+ BSD-3 for the
61+
vendored `amd.jl`) is the appropriate license, matching LinearSolve's.

src/SupernodalLU/SupernodalLU.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# SPDX-FileCopyrightText: 2026 Chris Rackauckas <accounts@chrisrackauckas.com> and contributors
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# SupernodalLU — a pure-Julia implementation of the supernodal
5+
# left-right-looking sparse LU method of O. Schenk and K. Gärtner
6+
# (FGCS 20(3), 2004; ETNA 23, 2006): supernodal BLAS-3 LU on the symmetrized
7+
# nonzero pattern of A + Aᵀ, pivoting restricted to the supernode diagonal
8+
# block with static pivot perturbation (ε‖A‖) compensated by iterative
9+
# refinement, and maximum-weight matching + scaling preprocessing.
10+
# Implemented from the papers; see NOTICE.md for the full per-component
11+
# lineage (no code from the proprietary PARDISO library, which popularized
12+
# the method, nor from Intel MKL PARDISO or HSL; `amd.jl` is a BSD-3-Clause
13+
# SuiteSparse AMD port).
14+
#
15+
# Internal to LinearSolve: the public surface is `SupernodalLUFactorization`.
16+
# Entry points here are `snlu` (analyze + factor), `snlu!` (refactorize, same
17+
# pattern), `solve!`, and the `snlu_symbolic` analysis.
18+
# Each supernode's dense diagonal block is factored through LinearSolve's own
19+
# dense `init`/`solve!` machinery (a per-supernode dense `LinearCache` over a
20+
# view of the panel), so the sparse solver automatically uses whatever
21+
# LinearSolve's dense default resolves to — RFLUFactorization when
22+
# RecursiveFactorization is loaded, MKL/LAPACK/generic otherwise — with no
23+
# backend-specific wiring here. Static pivoting is preserved by an
24+
# optimistic fast path: accept the dense factorization iff every pivot
25+
# clears the ε‖A‖ threshold, else restore the block and rerun the built-in
26+
# static-perturbation kernel.
27+
28+
module SupernodalLU
29+
30+
using SparseArrays: SparseMatrixCSC, sparse, getcolptr, rowvals, nonzeros, nnz
31+
using LinearAlgebra: LinearAlgebra, SingularException, UpperTriangular,
32+
UnitLowerTriangular, ldiv!, rdiv!, mul!, norm, BLAS
33+
using ..LinearSolve: LinearSolve
34+
using SciMLBase: SciMLBase
35+
36+
include("amd.jl") # vendored BSD-3 SuiteSparse AMD port (module AMD)
37+
include("ordering.jl")
38+
include("symbolic.jl")
39+
include("matching.jl")
40+
include("numeric.jl")
41+
include("solve.jl")
42+
include("interface.jl")
43+
44+
end # module

0 commit comments

Comments
 (0)