Skip to content

Commit 6b86f16

Browse files
committed
abstol/reltol for GMRES
1 parent 43a49ae commit 6b86f16

File tree

2 files changed

+83
-35
lines changed

2 files changed

+83
-35
lines changed

src/gmres.jl

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,21 @@ mutable struct GMRESIterable{preclT, precrT, solT, rhsT, vecT, arnoldiT <: Arnol
4242
restart::Int
4343
k::Int
4444
maxiter::Int
45-
reltol::resT
45+
tol::resT
4646
β::resT
4747
end
4848

49-
converged(g::GMRESIterable) = g.residual.current g.reltol
49+
converged(g::GMRESIterable) = g.residual.current g.tol
5050

5151
start(::GMRESIterable) = 0
5252

5353
done(g::GMRESIterable, iteration::Int) = iteration g.maxiter || converged(g)
5454

5555
function iterate(g::GMRESIterable, iteration::Int=start(g))
56-
if done(g, iteration) return nothing end
56+
# Check for termination first
57+
if done(g, iteration)
58+
return nothing
59+
end
5760

5861
# Arnoldi step: expand
5962
expand!(g.arnoldi, g.Pl, g.Pr, g.k, g.Ax)
@@ -100,15 +103,22 @@ function iterate(g::GMRESIterable, iteration::Int=start(g))
100103
end
101104

102105
function gmres_iterable!(x, A, b;
103-
Pl = Identity(),
104-
Pr = Identity(),
105-
tol = sqrt(eps(real(eltype(b)))),
106-
restart::Int = min(20, size(A, 2)),
107-
maxiter::Int = size(A, 2),
108-
initially_zero::Bool = false
109-
)
106+
Pl = Identity(),
107+
Pr = Identity(),
108+
abstol::Real = zero(real(eltype(b))),
109+
reltol::Real = sqrt(eps(real(eltype(b)))),
110+
tol = nothing, # TODO: Deprecations introduced in v0.8
111+
restart::Int = min(20, size(A, 2)),
112+
maxiter::Int = size(A, 2),
113+
initially_zero::Bool = false)
110114
T = eltype(x)
111115

116+
# TODO: Deprecations introduced in v0.8
117+
if tol !== nothing
118+
Base.depwarn("The keyword argument `tol` is deprecated, use `reltol` instead.", :gmres_iterable!)
119+
reltol = tol
120+
end
121+
112122
# Approximate solution
113123
arnoldi = ArnoldiDecomp(A, restart, T)
114124
residual = Residual(restart, T)
@@ -119,11 +129,11 @@ function gmres_iterable!(x, A, b;
119129
residual.current = init!(arnoldi, x, b, Pl, Ax, initially_zero = initially_zero)
120130
init_residual!(residual, residual.current)
121131

122-
reltol = tol * residual.current
132+
tolerance = max(reltol * residual.current, abstol)
123133

124134
GMRESIterable(Pl, Pr, x, b, Ax,
125135
arnoldi, residual,
126-
mv_products, restart, 1, maxiter, reltol, residual.current
136+
mv_products, restart, 1, maxiter, tolerance, residual.current
127137
)
128138
end
129139

@@ -150,7 +160,10 @@ Solves the problem ``Ax = b`` with restarted GMRES.
150160
- `initially_zero::Bool`: If `true` assumes that `iszero(x)` so that one
151161
matrix-vector product can be saved when computing the initial
152162
residual vector;
153-
- `tol`: relative tolerance;
163+
- `abstol::Real = zero(real(eltype(b)))`,
164+
`reltol::Real = sqrt(eps(real(eltype(b))))`: absolute and relative
165+
tolerance for the stopping condition
166+
`|r_k| / |r_0| ≤ max(reltol * resnorm, abstol)`, where `r_k = A * x_k - b`
154167
- `restart::Int = min(20, size(A, 2))`: restarts GMRES after specified number of iterations;
155168
- `maxiter::Int = size(A, 2)`: maximum number of inner iterations of GMRES;
156169
- `Pl`: left preconditioner;
@@ -170,20 +183,29 @@ Solves the problem ``Ax = b`` with restarted GMRES.
170183
- `history`: convergence history.
171184
"""
172185
function gmres!(x, A, b;
173-
Pl = Identity(),
174-
Pr = Identity(),
175-
tol = sqrt(eps(real(eltype(b)))),
176-
restart::Int = min(20, size(A, 2)),
177-
maxiter::Int = size(A, 2),
178-
log::Bool = false,
179-
initially_zero::Bool = false,
180-
verbose::Bool = false
181-
)
186+
Pl = Identity(),
187+
Pr = Identity(),
188+
abstol::Real = zero(real(eltype(b))),
189+
reltol::Real = sqrt(eps(real(eltype(b)))),
190+
tol = nothing, # TODO: Deprecations introduced in v0.8
191+
restart::Int = min(20, size(A, 2)),
192+
maxiter::Int = size(A, 2),
193+
log::Bool = false,
194+
initially_zero::Bool = false,
195+
verbose::Bool = false)
182196
history = ConvergenceHistory(partial = !log, restart = restart)
183197
history[:tol] = tol
184198
log && reserve!(history, :resnorm, maxiter)
185199

186-
iterable = gmres_iterable!(x, A, b; Pl = Pl, Pr = Pr, tol = tol, maxiter = maxiter, restart = restart, initially_zero = initially_zero)
200+
# TODO: Deprecations introduced in v0.8
201+
if tol !== nothing
202+
Base.depwarn("The keyword argument `tol` is deprecated, use `reltol` instead.", :cg!)
203+
reltol = tol
204+
end
205+
206+
iterable = gmres_iterable!(x, A, b; Pl = Pl, Pr = Pr,
207+
abstol = abstol, reltol = reltol, maxiter = maxiter,
208+
restart = restart, initially_zero = initially_zero)
187209

188210
verbose && @printf("=== gmres ===\n%4s\t%4s\t%7s\n","rest","iter","resnorm")
189211

test/gmres.jl

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,29 @@ n = 10
1515
A = rand(T, n, n)
1616
b = rand(T, n)
1717
F = lu(A)
18-
tol = eps(real(T))
18+
reltol = eps(real(T))
1919

2020
# Test optimality condition: residual should be non-increasing
21-
x, history = gmres(A, b, log = true, restart = 3, maxiter = 10, tol = tol);
21+
x, history = gmres(A, b, log=true, restart=3, maxiter=10, reltol=reltol);
2222
@test isa(history, ConvergenceHistory)
2323
@test all(diff(history[:resnorm]) .<= 0.0)
2424

2525
# Left exact preconditioner
26-
x, history = gmres(A, b, Pl=F, maxiter=1, restart=1, tol=tol, log=true)
26+
x, history = gmres(A, b, Pl=F, maxiter=1, restart=1, reltol=reltol, log=true)
2727
@test history.isconverged
28-
@test norm(F \ (A * x - b)) / norm(b) tol
28+
@test norm(F \ (A * x - b)) / norm(b) reltol
2929

3030
# Right exact preconditioner
31-
x, history = gmres(A, b, Pl=Identity(), Pr=F, maxiter=1, restart=1, tol=tol, log=true)
31+
x, history = gmres(A, b, Pl=Identity(), Pr=F, maxiter=1, restart=1, reltol=reltol, log=true)
3232
@test history.isconverged
33-
@test norm(A * x - b) / norm(b) tol
33+
@test norm(A * x - b) / norm(b) reltol
3434
end
3535

3636
@testset "SparseMatrixCSC{$T, $Ti}" for T in (Float64, ComplexF64), Ti in (Int64, Int32)
3737
A = sprand(T, n, n, 0.5) + I
3838
b = rand(T, n)
3939
F = lu(A)
40-
tol = eps(real(T))
40+
reltol = eps(real(T))
4141

4242
# Test optimality condition: residual should be non-increasing
4343
x, history = gmres(A, b, log = true, restart = 3, maxiter = 10);
@@ -46,21 +46,21 @@ end
4646
# Left exact preconditioner
4747
x, history = gmres(A, b, Pl=F, maxiter=1, restart=1, log=true)
4848
@test history.isconverged
49-
@test norm(F \ (A * x - b)) / norm(b) tol
49+
@test norm(F \ (A * x - b)) / norm(b) reltol
5050

5151
# Right exact preconditioner
5252
x, history = gmres(A, b, Pl = Identity(), Pr=F, maxiter=1, restart=1, log=true)
5353
@test history.isconverged
54-
@test norm(A * x - b) / norm(b) tol
54+
@test norm(A * x - b) / norm(b) reltol
5555
end
5656

5757
@testset "Linear operator defined as a function" begin
5858
A = LinearMap(cumsum!, 100; ismutating=true)
5959
b = rand(100)
60-
tol = 1e-5
60+
reltol = 1e-5
6161

62-
x = gmres(A, b; tol=tol, maxiter=2000)
63-
@test norm(A * x - b) / norm(b) tol
62+
x = gmres(A, b; reltol=reltol, maxiter=2000)
63+
@test norm(A * x - b) / norm(b) reltol
6464
end
6565

6666
@testset "Off-diagonal in hessenberg matrix exactly zero" begin
@@ -69,4 +69,30 @@ end
6969
x = gmres(A, b)
7070
@test all(x .== b)
7171
end
72+
73+
@testset "Termination criterion" begin
74+
for T in (Float32, Float64, ComplexF32, ComplexF64)
75+
A = T[ 2 -1 0
76+
-1 2 -1
77+
0 -1 2]
78+
n = size(A, 2)
79+
b = ones(T, n)
80+
x0 = A \ b
81+
perturbation = T[(-1)^i for i in 1:n]
82+
83+
# If the initial residual is small and a small relative tolerance is used,
84+
# many iterations are necessary
85+
x = x0 + sqrt(eps(real(T))) * perturbation
86+
initial_residual = norm(A * x - b)
87+
x, ch = gmres!(x, A, b, log=true)
88+
@test 2 niters(ch) n
89+
90+
# If the initial residual is small and a large absolute tolerance is used,
91+
# no iterations are necessary
92+
x = x0 + 10*sqrt(eps(real(T))) * perturbation
93+
initial_residual = norm(A * x - b)
94+
x, ch = gmres!(x, A, b, abstol=2*initial_residual, reltol=zero(real(T)), log=true)
95+
@test niters(ch) == 0
96+
end
97+
end
7298
end

0 commit comments

Comments
 (0)