-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
An implementation of DIOM #63
Changes from 7 commits
d7fa15a
776082f
ddffd4e
e2e4f8e
1e5569b
3ab0c65
20ca5f2
147e280
dfa69c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
# An implementation of DIOM for the solution of the square linear system Ax = b. | ||
# | ||
# This method is described in | ||
# | ||
# Y. Saad, Iterative methods for sparse linear systems. | ||
# PWS Publishing Company, Boston, USA, 1996. | ||
# | ||
# Y. Saad, Practical use of some krylov subspace methods for solving indefinite and nonsymmetric linear systems. | ||
# SIAM journal on scientific and statistical computing, 5(1), pp. 203--228, 1984. | ||
# | ||
# Alexis Montoison, <[email protected]> | ||
# Montreal, September 2018. | ||
|
||
export diom | ||
|
||
"""Solve the consistent linear system Ax = b using direct incomplete orthogonalization method. | ||
|
||
DIOM is similar to SYMMLQ for nonsymmetric problems. | ||
It's a more economical algorithm based upon the LU factorization with partial pivoting. | ||
|
||
In the particular case where A is symmetric indefinite, DIOM is theorecally equivalent | ||
to SYMMLQ but slightly more economical. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is true either; DIOM and SYMMLQ do not minimize the same quantities. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's not norm(x) that is minimized. I think we could delete this sentence? |
||
|
||
An advantage of DIOM is that nonsymmetric or symmetric indefinite or both nonsymmetric | ||
and indefinite systems of linear equations can be handled by this single algorithm. | ||
|
||
This implementation allows a right preconditioning with M. | ||
""" | ||
function diom{T <: Number}(A :: AbstractLinearOperator, b :: AbstractVector{T}; | ||
M :: AbstractLinearOperator=opEye(size(A,1)), | ||
atol :: Float64=1.0e-8, rtol :: Float64=1.0e-6, | ||
itmax :: Int=0, memory :: Int=20, verbose :: Bool=false) | ||
|
||
m, n = size(A) | ||
m == n || error("System must be square") | ||
size(b, 1) == m || error("Inconsistent problem size") | ||
verbose && @printf("DIOM: system of size %d\n", n) | ||
|
||
# Initial solution x₀ and residual r₀. | ||
x = zeros(T, n) | ||
x_old = copy(x) | ||
r = copy(b) | ||
# Compute β. | ||
rNorm = @knrm2(n, r) # rNorm = ‖r₀‖ | ||
rNorm ≈ 0 && return x, SimpleStats(true, false, [rNorm], [], "x = 0 is a zero-residual solution") | ||
|
||
iter = 0 | ||
itmax == 0 && (itmax = 2*n) | ||
|
||
rNorms = [rNorm;] | ||
ε = atol + rtol * rNorm | ||
verbose && @printf("%5d %7.1e\n", iter, rNorm) | ||
|
||
# Set up workspace. | ||
mem = min(memory, itmax) # Memory. | ||
V = zeros(n, mem) # Preconditioned Krylov vectors, orthogonal basis for {r₀, AM⁻¹r₀, (AM⁻¹)²r₀, ..., (AM⁻¹)ᵐ⁻¹r₀}. | ||
P = zeros(n, mem) # Directions for x : Pₘ = Vₘ(Uₘ)⁻¹. | ||
H = zeros(mem+2) # Last column of the band hessenberg matrix Hₘ = LₘUₘ. | ||
# Each column has at most mem + 1 nonzero elements. hᵢ.ₘ is stored as H[m-i+2]. | ||
# m-i+2 represents the indice of the diagonal where hᵢ.ₘ is located. | ||
# In addition of that, the last column of Uₘ is stored in H. | ||
L = zeros(mem) # Last mem Pivots of Lₘ. | ||
p = zeros(Bool, mem) # Last mem permutations. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I didn't know that a |
||
|
||
# Initial ξ₁ and V₁. | ||
ξ = rNorm | ||
V[:,1] = r / rNorm | ||
|
||
# Stopping criterion. | ||
solved = rNorm ≤ ε | ||
tired = iter ≥ itmax | ||
status = "unknown" | ||
|
||
while !(solved || tired) | ||
|
||
# Update iteration index. | ||
iter = iter + 1 | ||
|
||
# Set position in circulars stacks. | ||
pos = mod(iter-1, mem) + 1 # Position corresponding to pₘ and vₘ in circular stacks P and V. | ||
next_pos = mod(iter, mem) + 1 # Position corresponding to vₘ₊₁ in the circular stack V. | ||
|
||
# Incomplete Arnoldi procedure. | ||
z = M * V[:,pos] # Forms pₘ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder why Github isn't rendering UTF-8 characters correctly. Is your editor set to use UTF-8 encoding? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both Safari and Chrome seem to be missing characters... |
||
w = A * z # Forms vₘ₊₁ | ||
for i = max(1, iter-mem+1) : iter | ||
ipos = mod(i-1, mem) + 1 # Position corresponding to vᵢ in the circular stack V. | ||
diag = iter - i + 2 | ||
H[diag] = @kdot(n, w, V[:,ipos]) # hᵢ.ₘ = < A * vₘ , vᵢ > | ||
@kaxpy!(n, -H[diag], V[:,ipos], w) # w ← w - hᵢ.ₘ * vᵢ | ||
end | ||
# Compute hₘ₊₁.ₘ and vₘ₊₁. | ||
H[1] = @knrm2(n, w) # hₘ₊₁.ₘ = ‖vₘ₊₁‖ | ||
if H[1] ≉ 0 # hₘ₊₁.ₘ ≈ 0 ⇒ "lucky breakdown" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't there a tolerance involved in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there is a tolerance involved in But when we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. |
||
V[:,next_pos] = w / H[1] # vₘ₊₁ = w / hₘ₊₁.ₘ | ||
end | ||
# It's possible that uₘ₋ₘₑₘ.ₘ ≠ 0 when m ≥ mem + 1 | ||
if iter ≥ mem + 2 | ||
H[mem+2] = 0 # hₘ₋ₘₑₘ.ₘ = 0 | ||
end | ||
|
||
# Update the LU factorization with partial pivoting of H. | ||
# Compute the last column of Uₘ. | ||
if iter ≥ 2 | ||
for i = max(2,iter-mem+1) : iter | ||
lpos = mod(i-1, mem) + 1 # Position corresponding to lᵢ.ᵢ₋₁ in the circular stack L. | ||
diag = iter - i + 2 | ||
next_diag = diag + 1 | ||
if p[lpos] | ||
# The rows i-1 and i are permuted. | ||
H[diag], H[next_diag] = H[next_diag], H[diag] | ||
end | ||
# uᵢ.ₘ ← hᵢ.ₘ - lᵢ.ᵢ₋₁ * uᵢ₋₁.ₘ | ||
H[diag] = H[diag] - L[lpos] * H[next_diag] | ||
end | ||
# Compute ξₘ the last component of zₘ = β(Lₘ)⁻¹e₁. | ||
if !p[pos] # p[pos] ⇒ ξₘ = ξₘ₋₁ | ||
# ξₘ = -lₘ.ₘ₋₁ * ξₘ₋₁ | ||
ξ = - L[pos] * ξ | ||
end | ||
end | ||
|
||
# Compute the direction pₘ, the last column of Pₘ = Vₘ(Uₘ)⁻¹. | ||
for i = max(1,iter-mem) : iter - 1 | ||
ipos = mod(i-1, mem) + 1 # Position corresponding to pᵢ in the circular stack P. | ||
diag = iter - i + 2 | ||
# z ← z - uᵢ.ₘ * pᵢ | ||
@kaxpy!(n, -H[diag], P[:,ipos], z) | ||
end | ||
|
||
# Determine if interchange between hₘ₊₁.ₘ and uₘ.ₘ is needed and compute next pivot lₘ₊₁.ₘ. | ||
if abs(H[2]) < H[1] | ||
p[next_pos] = true | ||
# pₘ = z / hₘ₊₁.ₘ | ||
P[:,pos] = z / H[1] | ||
# lₘ₊₁.ₘ = uₘ.ₘ / hₘ₊₁.ₘ | ||
L[next_pos] = H[2] / H[1] | ||
else | ||
p[next_pos] = false | ||
# pₘ = z / uₘ.ₘ | ||
P[:,pos] = z / H[2] | ||
# lₘ₊₁.ₘ = hₘ₊₁.ₘ / uₘ.ₘ | ||
L[next_pos] = H[1] / H[2] | ||
end | ||
|
||
# Compute solution xₘ. | ||
if p[pos] | ||
# xₘ = xₘ₋ₙ + ξₘ₋ₙ * pₘ | ||
# x_old = xₘ₋ₙ, with m-n is the last iteration without permutation at the next step | ||
x = x_old + ξ * P[:,pos] | ||
else | ||
# xₘ = xₘ₋₁ + ξₘ * pₘ | ||
@kaxpy!(n, ξ, P[:,pos], x) | ||
end | ||
|
||
# Update x_old and residual norm. | ||
if !p[next_pos] | ||
copy!(x_old, x) | ||
# ‖ b - Axₘ ‖ = hₘ₊₁.ₘ * |ξₘ / uₘ.ₘ| without pivoting | ||
rNorm = H[1] * abs(ξ / H[2]) | ||
else | ||
# ‖ b - Axₘ ‖ = hₘ₊₁.ₘ * |ξₘ / hₘ₊₁.ₘ| = |ξₘ| with pivoting | ||
rNorm = abs(ξ) | ||
end | ||
push!(rNorms, rNorm) | ||
|
||
# Update stopping criterion. | ||
solved = rNorm ≤ ε | ||
tired = iter ≥ itmax | ||
verbose && @printf("%5d %7.1e\n", iter, rNorm) | ||
end | ||
verbose && @printf("\n") | ||
|
||
status = tired ? "maximum number of iterations exceeded" : "solution good enough given atol and rtol" | ||
stats = SimpleStats(solved, false, rNorms, T[], status) | ||
return (x, stats) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
include("get_div_grad.jl") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DIOM and DQGMRES should really be tested on unsymmetric systems as well. |
||
|
||
diom_tol = 1.0e-6 | ||
|
||
# Symmetric and positive definite systems (cubic spline matrix). | ||
n = 10 | ||
A = spdiagm((ones(n-1), 4*ones(n), ones(n-1)), (-1, 0, 1)) | ||
b = A * [1:n;] | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Symmetric indefinite variant. | ||
A = A - 3 * speye(n) | ||
b = A * [1:n;] | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Nonsymmetric and positive definite systems. | ||
A = [i == j ? 10.0 : i < j ? 1.0 : -1.0 for i=1:n, j=1:n] | ||
b = A * [1:n;] | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Nonsymmetric indefinite variant. | ||
A = [i == j ? 10*(-1.0)^(i*j) : i < j ? 1.0 : -1.0 for i=1:n, j=1:n] | ||
b = A * [1:n;] | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Code coverage. | ||
(x, stats) = diom(full(A), b) | ||
show(stats) | ||
|
||
# Sparse Laplacian. | ||
A = get_div_grad(16, 16, 16) | ||
b = ones(size(A, 1)) | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Symmetric indefinite variant, almost singular. | ||
A = A - 5 * speye(size(A, 1)) | ||
(x, stats) = diom(A, b) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) | ||
|
||
# Test b == 0 | ||
(x, stats) = diom(A, zeros(size(A,1))) | ||
@test x == zeros(size(A,1)) | ||
@test stats.status == "x = 0 is a zero-residual solution" | ||
|
||
# Test integer values | ||
A = spdiagm((ones(Int, n-1), 4*ones(Int, n), ones(Int, n-1)), (-1, 0, 1)); b = A * [1:n;] | ||
(x, stats) = diom(A, b) | ||
@test stats.solved | ||
|
||
# Test with Jacobi (or diagonal) preconditioner | ||
A = ones(10,10) + 9 * eye(10) | ||
b = 10 * [1:10;] | ||
M = 1/10 * opEye(10) | ||
(x, stats) = diom(A, b, M=M) | ||
show(stats) | ||
r = b - A * x | ||
resid = norm(r) / norm(b) | ||
@printf("DIOM: Relative residual: %8.1e\n", resid) | ||
@test(resid ≤ diom_tol) | ||
@test(stats.solved) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is accurate. FOM is to the Arnoldi process as CG is to the Lanczos process. I think it would be more accurate to say that DIOM is similar to CG with partial reorthogonalization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will rewrite the docString.