Skip to content

Commit eacea1a

Browse files
haampieandreasnoack
authored andcommitted
BiCGStab(ℓ) (#134)
* WIP on BiCGStabl(l) * Remove unused residual vector * Export Identity * Add the BiCGStab(l) test * Use the convex combination idea * Switch between convex update and standard behaviour * Fix for complex numbers & fix ordinary bicgstab(l) * More testing & benching * Translate to iterators & remove convex combination for now * Compatibility with 0.5, and recover the old API with ConvergenceHistry stuff
1 parent 0d8d216 commit eacea1a

File tree

6 files changed

+351
-0
lines changed

6 files changed

+351
-0
lines changed

benchmark/benchmark-linear-systems.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Base.A_ldiv_B!, Base.\
55
using BenchmarkTools
66
using IterativeSolvers
77

8+
include("../test/advection_diffusion.jl")
9+
810
# A DiagonalMatrix that doesn't check whether it is singular in the \ op.
911
immutable DiagonalPreconditioner{T}
1012
diag::Vector{T}
@@ -64,4 +66,15 @@ function gmres(; n = 100_000, tol = 1e-5, restart::Int = 15, maxiter::Int = 210)
6466
impr, old
6567
end
6668

69+
function bicgstabl()
70+
A, b = advection_dominated()
71+
72+
b1 = @benchmark IterativeSolvers.bicgstabl($A, $b, 2, max_mv_products = 1000, convex_combination = false) setup = (srand(1))
73+
b2 = @benchmark IterativeSolvers.bicgstabl($A, $b, 2, max_mv_products = 1000, convex_combination = true) setup = (srand(1))
74+
b3 = @benchmark IterativeSolvers.bicgstabl($A, $b, 4, max_mv_products = 1000, convex_combination = false) setup = (srand(1))
75+
b4 = @benchmark IterativeSolvers.bicgstabl($A, $b, 4, max_mv_products = 1000, convex_combination = true) setup = (srand(1))
76+
77+
b1, b2, b3, b4
78+
end
79+
6780
end

src/IterativeSolvers.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ include("hessenberg.jl")
2121
#Linear solvers
2222
include("stationary.jl")
2323
include("cg.jl")
24+
include("bicgstabl.jl")
2425
include("gmres.jl")
2526
include("chebyshev.jl")
2627
include("idrs.jl")

src/bicgstabl.jl

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
export bicgstabl, bicgstabl!, bicgstabl_iterator, bicgstabl_iterator!, BiCGStabIterable
2+
3+
import Base: start, next, done
4+
5+
type BiCGStabIterable{precT, matT, vecT <: AbstractVector, smallMatT <: AbstractMatrix, realT <: Real, scalarT <: Number}
6+
A::matT
7+
b::vecT
8+
l::Int
9+
10+
x::vecT
11+
r_shadow::vecT
12+
rs::smallMatT
13+
us::smallMatT
14+
15+
max_mv_products::Int
16+
mv_products::Int
17+
reltol::realT
18+
residual::realT
19+
20+
Pl::precT
21+
22+
γ::vecT
23+
ω::scalarT
24+
σ::scalarT
25+
M::smallMatT
26+
end
27+
28+
bicgstabl_iterator(A, b, l; kwargs...) = bicgstabl_iterator!(zerox(A, b), A, b, l; initial_zero = true, kwargs...)
29+
30+
function bicgstabl_iterator!(x, A, b, l::Int = 2;
31+
Pl = Identity(),
32+
max_mv_products = min(30, size(A, 1)),
33+
initial_zero = false,
34+
tol = sqrt(eps(real(eltype(b))))
35+
)
36+
T = eltype(b)
37+
n = size(A, 1)
38+
mv_products = 0
39+
40+
# Large vectors.
41+
r_shadow = rand(T, n)
42+
rs = Matrix{T}(n, l + 1)
43+
us = zeros(T, n, l + 1)
44+
45+
residual = view(rs, :, 1)
46+
47+
# Compute the initial residual rs[:, 1] = b - A * x
48+
# Avoid computing A * 0.
49+
if initial_zero
50+
copy!(residual, b)
51+
else
52+
A_mul_B!(residual, A, x)
53+
@blas! residual -= one(T) * b
54+
@blas! residual *= -one(T)
55+
mv_products += 1
56+
end
57+
58+
# Apply the left preconditioner
59+
A_ldiv_B!(Pl, residual)
60+
61+
γ = zeros(T, l)
62+
ω = σ = one(T)
63+
64+
nrm = norm(residual)
65+
66+
# For the least-squares problem
67+
M = zeros(T, l + 1, l + 1)
68+
69+
# Stopping condition based on relative tolerance.
70+
reltol = nrm * tol
71+
72+
BiCGStabIterable(A, b, l, x, r_shadow, rs, us,
73+
max_mv_products, mv_products, reltol, nrm,
74+
Pl,
75+
γ, ω, σ, M
76+
)
77+
end
78+
79+
@inline converged(it::BiCGStabIterable) = it.residual it.reltol
80+
@inline start(::BiCGStabIterable) = 0
81+
@inline done(it::BiCGStabIterable, iteration::Int) = it.mv_products it.max_mv_products || converged(it)
82+
83+
function next(it::BiCGStabIterable, iteration::Int)
84+
T = eltype(it.b)
85+
L = 2 : it.l + 1
86+
87+
it.σ = -it.ω * it.σ
88+
89+
## BiCG part
90+
for j = 1 : it.l
91+
ρ = dot(it.r_shadow, view(it.rs, :, j))
92+
β = ρ / it.σ
93+
94+
# us[:, 1 : j] .= rs[:, 1 : j] - β * us[:, 1 : j]
95+
for i = 1 : j
96+
@blas! view(it.us, :, i) *= -β
97+
@blas! view(it.us, :, i) += one(T) * view(it.rs, :, i)
98+
end
99+
100+
# us[:, j + 1] = Pl \ (A * us[:, j])
101+
next_u = view(it.us, :, j + 1)
102+
A_mul_B!(next_u, it.A, view(it.us, :, j))
103+
A_ldiv_B!(it.Pl, next_u)
104+
105+
it.σ = dot(it.r_shadow, next_u)
106+
α = ρ / it.σ
107+
108+
# rs[:, 1 : j] .= rs[:, 1 : j] - α * us[:, 2 : j + 1]
109+
for i = 1 : j
110+
@blas! view(it.rs, :, i) -= α * view(it.us, :, i + 1)
111+
end
112+
113+
# rs[:, j + 1] = Pl \ (A * rs[:, j])
114+
next_r = view(it.rs, :, j + 1)
115+
A_mul_B!(next_r, it.A , view(it.rs, :, j))
116+
A_ldiv_B!(it.Pl, next_r)
117+
118+
# x = x + α * us[:, 1]
119+
@blas! it.x += α * view(it.us, :, 1)
120+
end
121+
122+
# Bookkeeping
123+
it.mv_products += 2 * it.l
124+
125+
## MR part
126+
127+
# M = rs' * rs
128+
Ac_mul_B!(it.M, it.rs, it.rs)
129+
130+
# γ = M[L, L] \ M[L, 1]
131+
F = lufact!(view(it.M, L, L))
132+
A_ldiv_B!(it.γ, F, view(it.M, L, 1))
133+
134+
# This could even be BLAS 3 when combined.
135+
BLAS.gemv!('N', -one(T), view(it.us, :, L), it.γ, one(T), view(it.us, :, 1))
136+
BLAS.gemv!('N', one(T), view(it.rs, :, 1 : it.l), it.γ, one(T), it.x)
137+
BLAS.gemv!('N', -one(T), view(it.rs, :, L), it.γ, one(T), view(it.rs, :, 1))
138+
139+
it.ω = it.γ[it.l]
140+
it.residual = norm(view(it.rs, :, 1))
141+
142+
it.residual, iteration + 1
143+
end
144+
145+
# Classical API
146+
147+
bicgstabl(A, b, l = 2; kwargs...) = bicgstabl!(zerox(A, b), A, b, l; initial_zero = true, kwargs...)
148+
149+
function bicgstabl!(x, A, b, l = 2;
150+
tol = sqrt(eps(real(eltype(b)))),
151+
max_mv_products::Int = min(20, size(A, 1)),
152+
log::Bool = false,
153+
verbose::Bool = false,
154+
Pl = Identity(),
155+
kwargs...
156+
)
157+
history = ConvergenceHistory(partial = !log)
158+
history[:tol] = tol
159+
160+
# This doesn't yet make sense: the number of iters is smaller.
161+
log && reserve!(history, :resnorm, max_mv_products)
162+
163+
# Actually perform CG
164+
iterable = bicgstabl_iterator!(x, A, b, l; Pl = Pl, tol = tol, max_mv_products = max_mv_products, kwargs...)
165+
166+
if log
167+
history.mvps = iterable.mv_products
168+
end
169+
170+
for (iteration, item) = enumerate(iterable)
171+
if log
172+
nextiter!(history)
173+
history.mvps = iterable.mv_products
174+
push!(history, :resnorm, iterable.residual)
175+
end
176+
verbose && @printf("%3d\t%1.2e\n", iteration, iterable.residual)
177+
end
178+
179+
verbose && println()
180+
log && setconv(history, converged(iterable))
181+
log && shrink!(history)
182+
183+
log ? (iterable.x, history) : iterable.x
184+
end
185+
186+
#################
187+
# Documentation #
188+
#################
189+
190+
let
191+
doc_call = "bicgstab(A, b, l)"
192+
doc!_call = "bicgstab!(x, A, b, l)"
193+
194+
doc_msg = "Solve A*x = b with the BiCGStab(l)"
195+
doc!_msg = "Overwrite `x`.\n\n" * doc_msg
196+
197+
doc_arg = ""
198+
doc!_arg = """`x`: initial guess, overwrite final estimation."""
199+
200+
doc_version = (doc_call, doc_msg, doc_arg)
201+
doc!_version = (doc!_call, doc!_msg, doc!_arg)
202+
203+
docstring = String[]
204+
205+
#Build docs
206+
for (call, msg, arg) in (doc_version, doc!_version) #Start
207+
push!(docstring,
208+
"""
209+
$call
210+
211+
$msg
212+
213+
# Arguments
214+
215+
$arg
216+
217+
`A`: linear operator.
218+
219+
`b`: right hand side (vector).
220+
221+
`l::Int = 2`: Number of GMRES steps.
222+
223+
## Keywords
224+
225+
`Pl = Identity()`: left preconditioner of the method.
226+
227+
`tol::Real = sqrt(eps(real(eltype(b))))`: tolerance for stopping condition
228+
`|r_k| / |r_0| ≤ tol`. Note that:
229+
230+
1. The actual residual is never computed during the iterations; only an
231+
approximate residual is used.
232+
2. If a preconditioner is given, the stopping condition is based on the
233+
*preconditioned residual*.
234+
235+
`max_mv_products::Int = min(30, size(A, 1))`: maximum number of matrix
236+
vector products. For BiCGStab this is a less dubious criterion than maximum
237+
number of iterations.
238+
239+
# Output
240+
241+
`x`: approximated solution.
242+
`residual`: last approximate residual norm
243+
244+
# References
245+
[1] Sleijpen, Gerard LG, and Diederik R. Fokkema. "BiCGstab(l) for
246+
linear equations involving unsymmetric matrices with complex spectrum."
247+
Electronic Transactions on Numerical Analysis 1.11 (1993): 2000.
248+
"""
249+
)
250+
end
251+
252+
@doc docstring[1] -> bicgstabl
253+
@doc docstring[2] -> bicgstabl!
254+
end

test/advection_diffusion.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
f(x, y, z) = exp.(x .* y .* z) .* sin.(π .* x) .* sin.(π .* y) .* sin.(π .* z)
2+
3+
function advection_dominated(;N = 50, β = 1000.0)
4+
# Problem: Δu + βuₓ = f
5+
# u = 0 on the boundaries
6+
# f(x, y, z) = exp(xyz) sin(πx) sin(πy) sin(πz)
7+
# 2nd order central differences (shows serious wiggles)
8+
9+
# Total number of unknowns
10+
n = N^3
11+
12+
# Mesh width
13+
h = 1.0 / (N + 1)
14+
15+
# Interior points only
16+
xs = linspace(0, 1, N + 2)[2 : N + 1]
17+
18+
# The Laplacian
19+
Δ = laplace_matrix(Float64, N, 3) ./ -h^2
20+
21+
# And the dx bit.
22+
∂x_1d = spdiagm((fill(-β / 2h, N - 1), fill/ 2h, N - 1)), (-1, 1))
23+
∂x = kron(speye(N^2), ∂x_1d)
24+
25+
# Final matrix and rhs.
26+
A = Δ + ∂x
27+
b = reshape([f(x, y, z) for x xs, y xs, z xs], n)
28+
29+
A, b
30+
end
31+
32+
function laplace_matrix{T}(::Type{T}, n, dims)
33+
D = second_order_central_diff(T, n)
34+
A = copy(D)
35+
36+
for idx = 2 : dims
37+
A = kron(A, speye(n)) + kron(speye(size(A, 1)), D)
38+
end
39+
40+
A
41+
end
42+
43+
second_order_central_diff{T}(::Type{T}, dim) = convert(
44+
SparseMatrixCSC{T, Int},
45+
SymTridiagonal(fill(2 * one(T), dim), fill(-one(T), dim - 1))
46+
)

test/bicgstabl.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using IterativeSolvers
2+
using FactCheck
3+
using LinearMaps
4+
5+
srand(1234321)
6+
7+
include("advection_diffusion.jl")
8+
9+
facts("bicgstab(l)") do
10+
11+
for T in (Float32, Float64, Complex64, Complex128)
12+
context("Matrix{$T}") do
13+
14+
n = 20
15+
A = rand(T, n, n) + 15 * eye(T, n)
16+
x = ones(T, n)
17+
b = A * x
18+
19+
for l = (2, 4)
20+
context("BiCGStab($l)") do
21+
22+
# Solve without preconditioner
23+
x1, his1 = bicgstabl(A, b, l, max_mv_products = 100, log = true)
24+
@fact norm(A * x1 - b) / norm(b) --> less_than(eps(real(one(T))))
25+
26+
# Do an exact LU decomp of a nearby matrix
27+
F = lufact(A + rand(T, n, n))
28+
x2, his2 = bicgstabl(A, b, Pl = F, l, max_mv_products = 100, log = true)
29+
@fact norm(A * x2 - b) / norm(b) --> less_than(eps(real(one(T))))
30+
end
31+
end
32+
end
33+
end
34+
end

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ include("stationary.jl")
1313
#Conjugate gradients
1414
include("cg.jl")
1515

16+
#BiCGStab(l)
17+
include("bicgstabl.jl")
18+
1619
#GMRES
1720
include("gmres.jl")
1821

0 commit comments

Comments
 (0)