Skip to content

Commit c5e2ecd

Browse files
Change default solver to SPQR (#131)
1 parent 57e1a50 commit c5e2ecd

9 files changed

Lines changed: 118 additions & 71 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ smooth!(x, smoother::S, b)
8686
Where `S` denotes the smoothers cache which also must hold the matrix A.
8787
`smooth!` performs relaxation steps updating the current iterate x in-place: x ← x + S⁻¹(b − A·x).
8888

89+
## Coarse solver
90+
91+
Users can choose from different coarse solvers.
92+
* AlgebraicMultigrid.LinearSolveWrapper(alg) where alg is any LinearSolve.jl solver (https://docs.sciml.ai/LinearSolve/stable/basics/algorithm_selection/#Algorithm-Categories)
93+
* AlgebraicMultigrid.QRSolver
94+
* AlgebraicMultigrid.Pinv
95+
8996
## Features and Roadmap
9097

9198
This package currently supports:

src/AlgebraicMultigrid.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ include("smoother.jl")
2828
export GaussSeidel, SymmetricSweep, ForwardSweep, BackwardSweep,
2929
JacobiProlongation, SOR
3030

31+
include("coarse_solver.jl")
32+
3133
include("multilevel.jl")
3234
export RugeStubenAMG, SmoothedAggregationAMG
3335

src/aggregation.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function smoothed_aggregation(A::TA,
7878
diagonal_dominance = false,
7979
keep = false,
8080
verbose = false,
81-
coarse_solver = Pinv, kwargs...) where {T,V,bs,TA<:SparseMatrixCSC{T,V}}
81+
coarse_solver = _default_coarse_solver(A), kwargs...) where {T,V,bs,TA<:SparseMatrixCSC{T,V}}
8282

8383
@timeit_debug "prologue" begin
8484

@@ -94,12 +94,10 @@ function smoothed_aggregation(A::TA,
9494
end
9595

9696
while length(levels) + 1 < max_levels && size(A, 1) > max_coarse
97-
prev_n_levels = length(levels)
98-
@timeit_debug "extend_hierarchy!" A, B, bsr_flag = extend_hierarchy_sa!(levels, strength, aggregate, smooth,
97+
@timeit_debug "extend_hierarchy!" A, B, bsr_flag, stop_coarsening = extend_hierarchy_sa!(levels, strength, aggregate, smooth,
9998
improve_candidates, diagonal_dominance,
10099
keep, A, B, presmoother, postsmoother, symmetry, bsr_flag, verbose)
101-
# Break if no coarsening happened (all nodes isolated) or degenerate coarse matrix
102-
length(levels) == prev_n_levels && break
100+
stop_coarsening && break
103101
coarse_x!(w, size(A, 1))
104102
coarse_b!(w, size(A, 1))
105103
residual!(w, size(A, 1))
@@ -131,7 +129,7 @@ function extend_hierarchy_sa!(levels, strength, aggregate, smooth,
131129
@timeit_debug "aggregation" AggOp = aggregate(S)
132130

133131
# Cannot coarsen further if no aggregates were formed (e.g. all nodes isolated)
134-
size(AggOp, 1) == 0 && return A, B, bsr_flag
132+
size(AggOp, 1) == 0 && return A, B, bsr_flag, true
135133

136134
# Improve candidates
137135
b = zeros(eltype(A), size(A,1),size(B,2))
@@ -140,6 +138,7 @@ function extend_hierarchy_sa!(levels, strength, aggregate, smooth,
140138

141139
@timeit_debug "restriction setup" begin
142140
P = smooth(A, T, S, B)
141+
size(P, 2) == 0 && return A, B, true, true
143142
R = construct_R(symmetry, P)
144143
end
145144

@@ -154,7 +153,7 @@ function extend_hierarchy_sa!(levels, strength, aggregate, smooth,
154153
bsr_flag = true
155154

156155
# RAP is the coarse matrix and B is the coarse null space
157-
RAP, B, bsr_flag
156+
RAP, B, bsr_flag, false
158157
end
159158
construct_R(::HermitianSymmetry, P) = P'
160159
construct_R(::NoSymmetry, P) = P'

src/classical.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function ruge_stuben(A::TA,
1212
postsmoother = GaussSeidel(),
1313
max_levels = 10,
1414
max_coarse = 10,
15-
coarse_solver = Pinv, kwargs...) where {Ti,Tv,bs,TA<:SparseMatrixCSC{Ti,Tv}}
15+
coarse_solver = _default_coarse_solver(A), kwargs...) where {Ti,Tv,bs,TA<:SparseMatrixCSC{Ti,Tv}}
1616

1717
# fails if near null space `B` is provided
1818
haskey(kwargs, :B) && kwargs[:B] !== nothing && error("near null space `B` is only supported for smoothed aggregation AMG, not Ruge-Stüben AMG.")
@@ -22,7 +22,8 @@ function ruge_stuben(A::TA,
2222
residual!(w, size(A, 1))
2323

2424
while length(levels) + 1 < max_levels && size(A, 1) > max_coarse
25-
@timeit_debug "extend_hierarchy!" A = extend_hierarchy_rs!(levels, strength, CF, A, presmoother, postsmoother, symmetry)
25+
@timeit_debug "extend_hierarchy!" A, stop_coarsening = extend_hierarchy_rs!(levels, strength, CF, A, presmoother, postsmoother, symmetry)
26+
stop_coarsening && break
2627
coarse_x!(w, size(A, 1))
2728
coarse_b!(w, size(A, 1))
2829
residual!(w, size(A, 1))
@@ -41,6 +42,7 @@ function extend_hierarchy_rs!(levels, strength, CF, A::SparseMatrixCSC{Ti,Tv}, p
4142
@timeit_debug "strength" S, T = strength(At)
4243
@timeit_debug "splitting" splitting = CF(S)
4344
@timeit_debug "interpolation" P, R = direct_interpolation(At, T, splitting)
45+
size(P, 2) == 0 && return A, true
4446
@timeit_debug "RAP" RAP = R * A * P
4547

4648
@timeit_debug "smoother setup" begin
@@ -49,7 +51,7 @@ function extend_hierarchy_rs!(levels, strength, CF, A::SparseMatrixCSC{Ti,Tv}, p
4951
push!(levels, Level(A, P, R, pre, post))
5052
end
5153

52-
return RAP
54+
return RAP, false
5355
end
5456

5557
function direct_interpolation(At, T, splitting)

src/coarse_solver.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
2+
abstract type CoarseSolver end
3+
4+
"""
5+
Pinv{T} <: CoarseSolver
6+
7+
Moore-Penrose pseudo inverse coarse solver. Calls `pinv`
8+
"""
9+
struct Pinv{T} <: CoarseSolver
10+
pinvA::Matrix{T}
11+
Pinv{T}(A) where T = new{T}(pinv(Matrix(A)))
12+
end
13+
Pinv(A) = Pinv{eltype(A)}(A)
14+
Base.show(io::IO, p::Pinv) = print(io, "Pinv")
15+
16+
(p::Pinv)(x, b) = mul!(x, p.pinvA, b)
17+
18+
# This one is used internally.
19+
"""
20+
LinearSolveWrapperInternal <: CoarseSolver
21+
22+
Helper to allow the usage of LinearSolve.jl solvers for the coarse-level solve. Constructed via `LinearSolveWrapper`.
23+
"""
24+
struct LinearSolveWrapperInternal{LC <: LinearSolve.LinearCache} <: CoarseSolver
25+
linsolve::LC
26+
function LinearSolveWrapperInternal(A, alg::LinearSolve.SciMLLinearSolveAlgorithm)
27+
rhs_tmp = zeros(eltype(A), size(A,1))
28+
u_tmp = zeros(eltype(A), size(A,2))
29+
linprob = LinearProblem(A, rhs_tmp; u0 = u_tmp, alias_A = false, alias_b = false)
30+
linsolve = init(linprob, alg)
31+
new{typeof(linsolve)}(linsolve)
32+
end
33+
end
34+
35+
function (p::LinearSolveWrapperInternal{LC})(x, b) where {LC <: LinearSolve.LinearCache}
36+
for i 1:size(b, 2)
37+
# Update right hand side
38+
p.linsolve.b = b[:, i]
39+
# Solve for x and update
40+
x[:, i] = solve!(p.linsolve).u
41+
end
42+
end
43+
44+
function Base.show(io::IO, ml::LinearSolveWrapperInternal)
45+
print(io, ml.linsolve.alg)
46+
end
47+
48+
49+
# This one simplifies passing of LinearSolve.jl algorithms into AlgebraicMultigrid.jl as coarse solvers.
50+
"""
51+
LinearSolveWrapper <: CoarseSolver
52+
53+
Helper to allow the usage of LinearSolve.jl solvers for the coarse-level solve.
54+
"""
55+
struct LinearSolveWrapper{A <: LinearSolve.SciMLLinearSolveAlgorithm} <: CoarseSolver
56+
alg::A
57+
end
58+
(p::LinearSolveWrapper)(A::AbstractMatrix) = LinearSolveWrapperInternal(A, p.alg)
59+
60+
61+
"""
62+
QRSolver{F} <: CoarseSolver
63+
64+
Coarse solver using Julia's built-in factorizations via `qr()`.
65+
"""
66+
struct QRSolver{F} <: CoarseSolver
67+
factorization::F
68+
function QRSolver(A)
69+
fact = qr(A)
70+
new{typeof(fact)}(fact)
71+
end
72+
end
73+
Base.show(io::IO, p::QRSolver) = print(io, "QRSolver")
74+
75+
function (solver::QRSolver)(x, b)
76+
# Handle multiple RHS efficiently
77+
for i 1:size(b, 2)
78+
# Use backslash - Julia's factorizations are optimized for this
79+
x[:, i] = solver.factorization \ b[:, i]
80+
end
81+
end
82+
83+
# Guess the best coarse solver based on the matrix type
84+
_default_coarse_solver(A) = QRSolver

src/multilevel.jl

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -58,59 +58,6 @@ function coarse_b!(m::MultiLevelWorkspace{TX, bs}, n) where {TX, bs}
5858
end
5959
end
6060

61-
abstract type CoarseSolver end
62-
63-
"""
64-
Pinv{T} <: CoarseSolver
65-
66-
Moore-Penrose pseudo inverse coarse solver. Calls `pinv`
67-
"""
68-
struct Pinv{T} <: CoarseSolver
69-
pinvA::Matrix{T}
70-
Pinv{T}(A) where T = new{T}(pinv(Matrix(A)))
71-
end
72-
Pinv(A) = Pinv{eltype(A)}(A)
73-
Base.show(io::IO, p::Pinv) = print(io, "Pinv")
74-
75-
(p::Pinv)(x, b) = mul!(x, p.pinvA, b)
76-
77-
# This one is used internally.
78-
"""
79-
LinearSolveWrapperInternal <: CoarseSolver
80-
81-
Helper to allow the usage of LinearSolve.jl solvers for the coarse-level solve. Constructed via `LinearSolveWrapper`.
82-
"""
83-
struct LinearSolveWrapperInternal{LC <: LinearSolve.LinearCache} <: CoarseSolver
84-
linsolve::LC
85-
function LinearSolveWrapperInternal(A, alg::LinearSolve.SciMLLinearSolveAlgorithm)
86-
rhs_tmp = zeros(eltype(A), size(A,1))
87-
u_tmp = zeros(eltype(A), size(A,2))
88-
linprob = LinearProblem(A, rhs_tmp; u0 = u_tmp, alias_A = false, alias_b = false)
89-
linsolve = init(linprob, alg)
90-
new{typeof(linsolve)}(linsolve)
91-
end
92-
end
93-
94-
function (p::LinearSolveWrapperInternal{LC})(x, b) where {LC <: LinearSolve.LinearCache}
95-
for i 1:size(b, 2)
96-
# Update right hand side
97-
p.linsolve.b = b[:, i]
98-
# Solve for x and update
99-
x[:, i] = solve!(p.linsolve).u
100-
end
101-
end
102-
103-
# This one simplifies passing of LinearSolve.jl algorithms into AlgebraicMultigrid.jl as coarse solvers.
104-
"""
105-
LinearSolveWrapper <: CoarseSolver
106-
107-
Helper to allow the usage of LinearSolve.jl solvers for the coarse-level solve.
108-
"""
109-
struct LinearSolveWrapper <: CoarseSolver
110-
alg::LinearSolve.SciMLLinearSolveAlgorithm
111-
end
112-
(p::LinearSolveWrapper)(A::AbstractMatrix) = LinearSolveWrapperInternal(A, p.alg)
113-
11461
Base.length(ml::MultiLevel) = length(ml.levels) + 1
11562

11663
function Base.show(io::IO, ml::MultiLevel)

test/nns_test.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ end
218218
x_nns, residuals_nns = solve(A, b, SmoothedAggregationAMG(), log=true, reltol=1e-10,B=B)
219219
println("With NNS: final residual at iteration ", length(residuals_nns), ": ", residuals_nns[end])
220220
@test A * x_nns b
221-
x_wonns, residuals_wonns = solve(A, b, SmoothedAggregationAMG(), log=true, reltol=1e-10)
221+
x_wonns, residuals_wonns = solve(A, b, SmoothedAggregationAMG(), coarse_solver=AMG.Pinv, log=true, reltol=1e-10)
222222
println("No NNS: final residual at iteration ", length(residuals_wonns), ": ", residuals_wonns[end])
223223
@test !(A * x_wonns b) && first(residuals_wonns) > last(residuals_wonns)
224224

@@ -249,11 +249,12 @@ end
249249
u = A \ b
250250
@test u[end-1] (P * L^3) / (3 * E * I) # vertical disp. at the end of the beam
251251

252-
x_nns, residuals_nns = solve(A, b, SmoothedAggregationAMG(); log=true, reltol=1e-10, B=B, max_levels=2)
252+
# FIXME revisit after https://github.com/JuliaLinearAlgebra/AlgebraicMultigrid.jl/pull/129
253+
x_nns, residuals_nns = solve(A, b, SmoothedAggregationAMG(); coarse_solver=AMG.Pinv, log=true, reltol=1e-10, B=B, max_levels=2)
253254
println("With NNS: final residual at iteration ", length(residuals_nns), ": ", residuals_nns[end])
254255
@test A * x_nns b
255256

256-
x_wonns, residuals_wonns = solve(A, b, SmoothedAggregationAMG(); log=true, reltol=1e-10, max_levels=2)
257+
x_wonns, residuals_wonns = solve(A, b, SmoothedAggregationAMG(); coarse_solver=AMG.Pinv, log=true, reltol=1e-10, max_levels=2)
257258
println("No NNS: final residual at iteration ", length(residuals_wonns), ": ", residuals_wonns[end])
258259
@test !(A * x_wonns b)
259260

test/runtests.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ x = AlgebraicMultigrid._solve(ml, A * ones(100))
140140

141141
end
142142

143-
@testset "Preconditioning" begin
143+
@testset "Preconditioning non-SPD problem" begin
144144
A = include("thing.jl")
145145
n = size(A, 1)
146146
smoother = GaussSeidel(ForwardSweep())
147147
ml = ruge_stuben(A, presmoother = smoother,
148-
postsmoother = smoother)
148+
postsmoother = smoother, coarse_solver = AMG.Pinv)
149149
p = aspreconditioner(ml)
150150
b = zeros(n)
151151
b[1] = 1
@@ -170,7 +170,8 @@ diff = x - [ 1.88664780e-16, 2.34982727e-16, 2.33917697e-16,
170170
@test sum(abs2, diff) < 1e-8
171171
x = solve(A, b, RugeStubenAMG(); presmoother = smoother,
172172
postsmoother = smoother,
173-
maxiter = 1, abstol = 1e-12)
173+
maxiter = 1, abstol = 1e-12,
174+
coarse_solver = AMG.Pinv)
174175
diff = x - [ 0.76347046, -0.5498286 , -0.2705487 , -0.15047352, -0.10248021,
175176
0.60292674, -0.11497073, -0.08460548, -0.06931461, 0.38230708,
176177
-0.055664 , -0.04854558, -0.04577031, 0.09964325, 0.01825624,
@@ -197,7 +198,7 @@ diff = x - [ 0.82365077, -0.537589 , -0.30632349, -0.19370186, -0.14773294,
197198

198199
# Symmetric GaussSeidel Smoothing
199200

200-
ml = ruge_stuben(A)
201+
ml = ruge_stuben(A, coarse_solver = AMG.Pinv)
201202
p = aspreconditioner(ml)
202203

203204
x = cg(A, b, Pl = p, maxiter = 100_000, reltol = 1e-6)

test/test_regression.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ end
5959
@testset "Regression for issue #56" begin
6060
# Issue #56
6161
X = poisson(27_000)+24.0*I
62-
ml = ruge_stuben(X)
6362
b = rand(27_000)
63+
64+
ml = ruge_stuben(X)
65+
@test AlgebraicMultigrid._solve(ml, b, reltol = 1e-10) X \ b rtol = 1e-10
66+
67+
ml = smoothed_aggregation(X, strength = SymmetricStrength(0.05))
6468
@test AlgebraicMultigrid._solve(ml, b, reltol = 1e-10) X \ b rtol = 1e-10
6569
end
6670

0 commit comments

Comments
 (0)