Skip to content
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

Add COCG method for complex symmetric linear systems #289

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/IterativeSolvers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include("hessenberg.jl")
include("stationary.jl")
include("stationary_sparse.jl")
include("cg.jl")
include("cocg.jl")
include("minres.jl")
include("bicgstabl.jl")
include("gmres.jl")
Expand Down
241 changes: 241 additions & 0 deletions src/cocg.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import Base: iterate
using Printf
export cocg, cocg!, COCGIterable, PCOCGIterable, cocg_iterator!, COCGStateVariables

mutable struct COCGIterable{matT, solT, vecT, numT <: Real, paramT <: Number}
A::matT
x::solT
r::vecT
c::vecT
u::vecT
tol::numT
residual::numT
rr_prev::paramT
maxiter::Int
mv_products::Int
end

mutable struct PCOCGIterable{precT, matT, solT, vecT, numT <: Real, paramT <: Number}
Pl::precT
A::matT
x::solT
r::vecT
c::vecT
u::vecT
tol::numT
residual::numT
rc_prev::paramT
maxiter::Int
mv_products::Int
end

@inline converged(it::Union{COCGIterable, PCOCGIterable}) = it.residual ≤ it.tol

@inline start(it::Union{COCGIterable, PCOCGIterable}) = 0

@inline done(it::Union{COCGIterable, PCOCGIterable}, iteration::Int) = iteration ≥ it.maxiter || converged(it)


#################
# Ordinary COCG #
#################

function iterate(it::COCGIterable, iteration::Int=start(it))
# Check for termination first
if done(it, iteration)
return nothing
end

# u := r + βu (almost an axpy)
rr = sum(rₖ^2 for rₖ in it.r) # rᵀ * r
β = rr / it.rr_prev
it.u .= it.r .+ β .* it.u

# c = A * u
mul!(it.c, it.A, it.u)
uc = sum(uₖ*cₖ for (uₖ,cₖ) in zip(it.u,it.c)) # uᵀ * c
α = rr / uc

# Improve solution and residual
it.rr_prev = rr
it.x .+= α .* it.u
it.r .-= α .* it.c

it.residual = norm(it.r)

# Return the residual at item and iteration number as state
it.residual, iteration + 1
end

#######################
# Preconditioned COCG #
#######################

function iterate(it::PCOCGIterable, iteration::Int=start(it))
# Check for termination first
if done(it, iteration)
return nothing
end

# Apply left preconditioner: c = Pl⁻¹ r
ldiv!(it.c, it.Pl, it.r)

# u := c + βu (almost an axpy)
rc = sum(rₖ*cₖ for (rₖ,cₖ) in zip(it.r,it.c)) # rᵀ * c
β = rc / it.rc_prev
it.u .= it.c .+ β .* it.u

# c = A * u
mul!(it.c, it.A, it.u)
uc = sum(uₖ*cₖ for (uₖ,cₖ) in zip(it.u,it.c)) # uᵀ * c
α = rc / uc

# Improve solution and residual
it.rc_prev = rc
it.x .+= α .* it.u
it.r .-= α .* it.c

it.residual = norm(it.r)

# Return the residual at item and iteration number as state
it.residual, iteration + 1
end

# Utility functions

"""
Intermediate COCG state variables to be used inside cocg and cocg!. `u`, `r` and `c` should be of the same type as the solution of `cocg` or `cocg!`.
```
struct COCGStateVariables{T,Tx<:AbstractArray{T}}
u::Tx
r::Tx
c::Tx
end
```
"""
struct COCGStateVariables{T,Tx<:AbstractArray{T}}
u::Tx
r::Tx
c::Tx
end

function cocg_iterator!(x, A, b, Pl = Identity();
abstol::Real = zero(real(eltype(b))),
reltol::Real = sqrt(eps(real(eltype(b)))),
maxiter::Int = size(A, 2),
statevars::COCGStateVariables = COCGStateVariables(zero(x), similar(x), similar(x)),
initially_zero::Bool = false)
u = statevars.u
r = statevars.r
c = statevars.c
u .= zero(eltype(x))
copyto!(r, b)

# Compute r with an MV-product or not.
if initially_zero
mv_products = 0
else
mv_products = 1
mul!(c, A, x)
r .-= c
end
residual = norm(r)
tolerance = max(reltol * residual, abstol)

# Return the iterable
if isa(Pl, Identity)
return COCGIterable(A, x, r, c, u,
tolerance, residual, one(eltype(r)),
maxiter, mv_products
)
else
return PCOCGIterable(Pl, A, x, r, c, u,
tolerance, residual, one(eltype(r)),
maxiter, mv_products
)
end
end

"""
cocg(A, b; kwargs...) -> x, [history]

Same as [`cocg!`](@ref), but allocates a solution vector `x` initialized with zeros.
"""
cocg(A, b; kwargs...) = cocg!(zerox(A, b), A, b; initially_zero = true, kwargs...)

"""
cocg!(x, A, b; kwargs...) -> x, [history]

# Arguments

- `x`: Initial guess, will be updated in-place;
- `A`: linear operator;
- `b`: right-hand side.

## Keywords

- `statevars::COCGStateVariables`: Has 3 arrays similar to `x` to hold intermediate results;
- `initially_zero::Bool`: If `true` assumes that `iszero(x)` so that one
matrix-vector product can be saved when computing the initial
residual vector;
- `Pl = Identity()`: left preconditioner of the method. Should be symmetric,
positive-definite like `A`;
- `abstol::Real = zero(real(eltype(b)))`,
`reltol::Real = sqrt(eps(real(eltype(b))))`: absolute and relative
tolerance for the stopping condition
`|r_k| / |r_0| ≤ max(reltol * resnorm, abstol)`, where `r_k = A * x_k - b`
is the residual in the `k`th iteration;
- `maxiter::Int = size(A,2)`: maximum number of iterations;
- `verbose::Bool = false`: print method information;
- `log::Bool = false`: keep track of the residual norm in each iteration.

# Output

**if `log` is `false`**

- `x`: approximated solution.

**if `log` is `true`**

- `x`: approximated solution.
- `ch`: convergence history.

**ConvergenceHistory keys**

- `:tol` => `::Real`: stopping tolerance.
- `:resnom` => `::Vector`: residual norm at each iteration.
"""
function cocg!(x, A, b;
abstol::Real = zero(real(eltype(b))),
reltol::Real = sqrt(eps(real(eltype(b)))),
maxiter::Int = size(A, 2),
log::Bool = false,
statevars::COCGStateVariables = COCGStateVariables(zero(x), similar(x), similar(x)),
verbose::Bool = false,
Pl = Identity(),
kwargs...)
history = ConvergenceHistory(partial = !log)
history[:abstol] = abstol
history[:reltol] = reltol
log && reserve!(history, :resnorm, maxiter + 1)

# Actually perform COCG
iterable = cocg_iterator!(x, A, b, Pl; abstol = abstol, reltol = reltol, maxiter = maxiter,
statevars = statevars, kwargs...)
if log
history.mvps = iterable.mv_products
end
for (iteration, item) = enumerate(iterable)
if log
nextiter!(history, mvps = 1)
push!(history, :resnorm, iterable.residual)
end
verbose && @printf("%3d\t%1.2e\n", iteration, iterable.residual)
end

verbose && println()
log && setconv(history, converged(iterable))
log && shrink!(history)

log ? (iterable.x, history) : iterable.x
end
71 changes: 71 additions & 0 deletions test/cocg.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module TestCOCG

using IterativeSolvers
using Test
using Random
using LinearAlgebra

@testset ("Conjugate Orthogonal Conjugate Gradient") begin

Random.seed!(1234321)
n = 20

@testset "Matrix{$T}" for T in (Float32, Float64, ComplexF32, ComplexF64)
A = rand(T, n, n)
A = A + transpose(A) + 15I
x = ones(T, n)
b = A * x

reltol = √eps(real(T))

# Solve without preconditioner
x1, his1 = cocg(A, b, reltol = reltol, maxiter = 100, log = true)
@test isa(his1, ConvergenceHistory)
@test norm(A * x1 - b) / norm(b) ≤ reltol

# With an initial guess
x_guess = rand(T, n)
x2, his2 = cocg!(x_guess, A, b, reltol = reltol, maxiter = 100, log = true)
@test isa(his2, ConvergenceHistory)
@test x2 == x_guess
@test norm(A * x2 - b) / norm(b) ≤ reltol

# The following tests fails CI on Windows and Ubuntu due to a
# `SingularException(4)`
if T == Float32 && (Sys.iswindows() || Sys.islinux())
continue
end
# Do an exact LU decomp of a nearby matrix
F = lu(A + rand(T, n, n))
x3, his3 = cocg(A, b, Pl = F, maxiter = 100, reltol = reltol, log = true)
@test norm(A * x3 - b) / norm(b) ≤ reltol
end

@testset "Termination criterion" begin
for T in (Float32, Float64, ComplexF32, ComplexF64)
A = T[ 2 -1 0
-1 2 -1
0 -1 2]
n = size(A, 2)
b = ones(T, n)
x0 = A \ b
perturbation = 10 * sqrt(eps(real(T))) * T[(-1)^i for i in 1:n]

# If the initial residual is small and a small relative tolerance is used,
# many iterations are necessary
x = x0 + perturbation
initial_residual = norm(A * x - b)
x, ch = cocg!(x, A, b, log=true)
@test 2 ≤ niters(ch) ≤ n

# If the initial residual is small and a large absolute tolerance is used,
# no iterations are necessary
x = x0 + perturbation
initial_residual = norm(A * x - b)
x, ch = cocg!(x, A, b, abstol=2*initial_residual, reltol=zero(real(T)), log=true)
@test niters(ch) == 0
end
end
end

end # module
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ include("stationary.jl")
#Conjugate gradients
include("cg.jl")

#Conjugate Orthogonal Conjugate gradient
include("cocg.jl")

#BiCGStab(l)
include("bicgstabl.jl")

Expand Down