Skip to content

Commit 8da9184

Browse files
haampieandreasnoack
authored andcommitted
Power method iterators (#139)
* Power method as iterator * Some doc fixes and getting around the lufact bug in base * Improve presentation & docs
1 parent 053ca1d commit 8da9184

File tree

2 files changed

+142
-101
lines changed

2 files changed

+142
-101
lines changed

src/simple.jl

Lines changed: 114 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,115 @@
1+
import Base: start, next, done
2+
13
#Simple methods
24
export powm, invpowm
35

4-
####################
5-
# API method calls #
6-
####################
6+
type PowerMethodIterable{matT, vecT <: AbstractVector, numT <: Number, eigvalT <: Number}
7+
A::matT
8+
x::vecT
9+
tol::numT
10+
maxiter::Int
11+
θ::eigvalT
12+
r::vecT
13+
Ax::vecT
14+
residual::numT
15+
end
716

8-
function powm(A;
9-
x=nothing, tol::Real=eps(real(eltype(A)))*size(A,2)^3, maxiter::Int=size(A,2),
10-
log::Bool=false, kwargs...
11-
)
12-
K = KrylovSubspace(A, 1)
13-
x==nothing ? initrand!(K) : init!(K, x/norm(x))
1417

15-
history = ConvergenceHistory(partial=!log)
16-
history[:tol] = tol
17-
reserve!(history,:resnorm, maxiter)
18-
eig, v = powm_method!(history, K; tol=tol, maxiter=maxiter, kwargs...)
19-
log && shrink!(history)
20-
log ? (eig, v, history) : (eig, v)
18+
##
19+
## Iterators
20+
##
21+
22+
@inline converged(p::PowerMethodIterable) = p.residual p.tol
23+
24+
@inline start(p::PowerMethodIterable) = 0
25+
26+
@inline done(p::PowerMethodIterable, iteration::Int) = iteration > p.maxiter || converged(p)
27+
28+
function next(p::PowerMethodIterable, iteration::Int)
29+
30+
A_mul_B!(p.Ax, p.A, p.x)
31+
32+
# Rayleigh quotient θ = x'Ax
33+
p.θ = dot(p.x, p.Ax)
34+
35+
# (Previous) residual vector r = Ax - λx
36+
copy!(p.r, p.Ax)
37+
BLAS.axpy!(-p.θ, p.x, p.r)
38+
39+
# Normed residual
40+
p.residual = norm(p.r)
41+
42+
# Normalize the next approximation
43+
copy!(p.x, p.Ax)
44+
scale!(p.x, one(eltype(p.x)) / norm(p.x))
45+
46+
p.residual, iteration + 1
2147
end
2248

23-
#########################
24-
# Method Implementation #
25-
#########################
26-
27-
function powm_method!{T}(log::ConvergenceHistory, K::KrylovSubspace{T};
28-
tol::Real=eps(real(T))*K.n^3, maxiter::Int=K.n, verbose::Bool=false
29-
)
30-
verbose && @printf("=== powm ===\n%4s\t%7s\n","iter","resnorm")
31-
θ = zero(T)
32-
v = Array(T, K.n)
33-
for iter=1:maxiter
34-
nextiter!(log,mvps=1)
35-
v = lastvec(K)
36-
y = nextvec(K)
37-
θ = dot(v, y)
38-
resnorm = real(norm(y - θ*v))
39-
push!(log, :resnorm, resnorm)
40-
verbose && @printf("%3d\t%1.2e\n",iter,resnorm)
41-
resnorm <= tol*abs(θ) && (setconv(log, resnorm >= 0); break)
42-
appendunit!(K, y)
43-
end
44-
verbose && @printf("\n")
45-
θ, v
49+
# Transforms the eigenvalue back whether shifted or inversed
50+
@inline transform_eigenvalue(θ, inverse::Bool, σ) = σ + (inverse ? inv(θ) : θ)
51+
52+
function powm_iterable!(A, x; tol = eps(real(eltype(A))) * size(A, 2) ^ 3, maxiter = size(A, 1))
53+
T = eltype(x)
54+
PowerMethodIterable(A, x, tol, maxiter, zero(T), similar(x), similar(x), realmax(real(T)))
55+
end
56+
57+
function powm_iterable(A; kwargs...)
58+
x0 = rand(Complex{real(eltype(A))}, size(A, 1))
59+
scale!(x0, one(eltype(A)) / norm(x0))
60+
powm_iterable!(A, x0; kwargs...)
4661
end
4762

4863
####################
4964
# API method calls #
5065
####################
5166

52-
function invpowm(A;
53-
x=nothing, shift::Number=0, tol::Real=eps(real(eltype(A)))*size(A,2)^3,
54-
maxiter::Int=size(A,2), log::Bool=false, kwargs...
55-
)
56-
K = KrylovSubspace(A, 1)
57-
x==nothing ? initrand!(K) : init!(K, x/norm(x))
67+
function powm(A; kwargs...)
68+
x0 = rand(Complex{real(eltype(A))}, size(A, 1))
69+
scale!(x0, one(eltype(A)) / norm(x0))
70+
powm!(A, x0; kwargs...)
71+
end
5872

59-
history = ConvergenceHistory(partial=!log)
73+
function powm!(A, x;
74+
tol = eps(real(eltype(A))) * size(A, 2) ^ 3,
75+
maxiter = size(A, 1),
76+
shift = zero(eltype(A)),
77+
inverse::Bool = false,
78+
log::Bool = false,
79+
verbose::Bool = false
80+
)
81+
history = ConvergenceHistory(partial = !log)
6082
history[:tol] = tol
61-
reserve!(history,:resnorm, maxiter)
62-
eig, v = invpowm_method!(history, K, shift; tol=tol, maxiter=maxiter, kwargs...)
83+
reserve!(history, :resnorm, maxiter)
84+
verbose && @printf("=== powm ===\n%4s\t%7s\n", "iter", "resnorm")
85+
86+
iterable = powm_iterable!(A, x, tol = tol, maxiter = maxiter)
87+
88+
for (iteration, residual) = enumerate(iterable)
89+
nextiter!(history, mvps = 1)
90+
verbose && @printf("%3d\t%1.2e\n", iteration, residual)
91+
end
92+
93+
setconv(history, converged(iterable))
94+
95+
verbose && println()
96+
6397
log && shrink!(history)
64-
log ? (eig, v, history) : (eig, v)
98+
99+
λ = transform_eigenvalue(iterable.θ, inverse, shift)
100+
x = iterable.x
101+
102+
log ? (λ, x, history) : (λ, x)
65103
end
66104

67-
#########################
68-
# Method Implementation #
69-
#########################
70-
71-
function invpowm_method!{T}(log::ConvergenceHistory, K::KrylovSubspace{T}, σ::Number=zero(T);
72-
tol::Real=eps(real(T))*K.n^3, maxiter::Int=K.n, verbose::Bool=false
73-
)
74-
verbose && @printf("=== invpowm ===\n%4s\t%7s\n","iter","resnorm")
75-
θ = zero(T)
76-
v = Array(T, K.n)
77-
y = Array(T, K.n)
78-
σ = convert(T, σ)
79-
for iter=1:maxiter
80-
nextiter!(log,mvps=1)
81-
v = lastvec(K)
82-
y = (K.A-σ*eye(K))\v
83-
θ = dot(v, y)
84-
resnorm = norm(y - θ*v)
85-
push!(log, :resnorm, resnorm)
86-
verbose && @printf("%3d\t%1.2e\n",iter,resnorm)
87-
resnorm <= tol*abs(θ) && (setconv(log, resnorm >= 0); break)
88-
appendunit!(K, y)
89-
end
90-
verbose && @printf("\n")
91-
σ+1/θ, y/θ
105+
function invpowm(B; kwargs...)
106+
x0 = rand(Complex{real(eltype(B))}, size(B, 1))
107+
scale!(x0, one(eltype(B)) / norm(x0))
108+
invpowm!(B, x0; kwargs...)
92109
end
93110

111+
invpowm!(B, x0; kwargs...) = powm!(B, x0; inverse = true, kwargs...)
112+
94113
#################
95114
# Documentation #
96115
#################
@@ -99,13 +118,25 @@ let
99118
#Initialize parameters
100119
doc1_call = """ powm(A)
101120
"""
102-
doc2_call = """ invpowm(A)
121+
doc2_call = """ invpowm(B)
103122
"""
104-
doc1_msg = """Find biggest eigenvalue of `A` and its associated eigenvector
123+
doc1_msg = """Find the largest eigenvalue `λ` (in absolute value) of `A` and its associated eigenvector `x`
105124
using the power method.
106125
"""
107-
doc2_msg = """Find closest eigenvalue of `A` to `shift` and its associated eigenvector
108-
using the inverse power iteration method.
126+
doc2_msg = """For an eigenvalue problem Ax = λx, find the closest eigenvalue in the complex plane to `shift`
127+
together with its associated eigenvector `x`. The first argument `B` should be a mapping that has
128+
the effect of B * x = inv(A - shift * I) * x and should support the `A_mul_B!` operation.
129+
130+
# Examples
131+
132+
```julia
133+
using LinearMaps
134+
σ = 1.0 + 1.3im
135+
A = rand(Complex128, 50, 50)
136+
F = lufact(A - UniformScaling(σ))
137+
Fmap = LinearMap((y, x) -> A_ldiv_B!(y, F, x), 50, Complex128, ismutating = true)
138+
λ, x = invpowm(Fmap, shift = σ, tol = 1e-4, maxiter = 200)
139+
```
109140
"""
110141
doc1_karg = ""
111142
doc2_karg = "`shift::Number=0`: shift to be applied to matrix A."
@@ -124,13 +155,11 @@ $call
124155
125156
$msg
126157
127-
If `log` is set to `true` is given, method will output a tuple `eig, v, ch`. Where
128-
`ch` is a `ConvergenceHistory` object. Otherwise it will only return `eig, v`.
158+
If `log` is set to `true` is given, method will output a tuple `λ, x, ch`. Where
159+
`ch` is a `ConvergenceHistory` object. Otherwise it will only return `λ, x`.
129160
130161
# Arguments
131162
132-
`K::KrylovSubspace`: krylov subspace.
133-
134163
`A`: linear operator.
135164
136165
## Keywords
@@ -152,15 +181,15 @@ containing extra information of the method execution.
152181
153182
**if `log` is `false`**
154183
155-
`eig::Real`: eigen value
184+
`λ::Number`: eigenvalue
156185
157-
`v::Vector`: eigen vector
186+
`x::Vector`: eigenvector
158187
159188
**if `log` is `true`**
160189
161-
`eig::Real`: eigen value
190+
`eig::Real`: eigenvalue
162191
163-
`v::Vector`: eigen vector
192+
`x::Vector`: eigenvector
164193
165194
`ch`: convergence history.
166195

test/simple_eigensolvers.jl

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,44 @@ srand(1234321)
99
# Eigensystem solvers #
1010
#######################
1111

12-
facts("simple eigensolvers") do
12+
facts("Simple eigensolvers") do
13+
14+
n = 10
15+
1316
for T in (Float32, Float64, Complex64, Complex128)
17+
1418
context("Matrix{$T}") do
15-
A=convert(Matrix{T}, randn(n,n))
16-
T<:Complex && (A+=convert(Matrix{T}, im*randn(n,n)))
17-
A=A+A' #Symmetric/Hermitian
1819

19-
tol = (eltype(T) <: Complex ?2:1)*n^2*cond(A)*eps(real(one(T)))
20-
v = eigvals(A)
20+
A = rand(T, n, n)
21+
A = A' * A
22+
λs = eigvals(A)
23+
24+
tol = (eltype(T) <: Complex ? 2 : 1) * n^2 * cond(A) * eps(real(one(T)))
2125

2226
## Simple methods
2327

2428
context("Power iteration") do
25-
eval_big = maximum(v) > abs(minimum(v)) ? maximum(v) : minimum(v)
26-
eval_pow = powm(A; tol=sqrt(eps(real(one(T)))), maxiter=2000)[1]
27-
@fact norm(eval_big-eval_pow) --> less_than(tol)
29+
λ, x = powm(A; tol = tol, maxiter = 10n)
30+
@fact λs[end] --> roughly(λ)
31+
@fact norm(A * x - λ * x) --> less_than(tol)
2832
end
2933

3034
context("Inverse iteration") do
31-
irnd = ceil(Int, rand()*(n-2))
32-
eval_rand = v[1+irnd] #Pick random eigenvalue
33-
# Perturb the eigenvalue by < 1/4 of the distance to the nearest eigenvalue
34-
eval_diff = min(abs(v[irnd]-eval_rand), abs(v[irnd+2]-eval_rand))
35-
σ = eval_rand + eval_diff/2*(rand()-.5)
36-
eval_ii = invpowm(A; shift=σ, tol=sqrt(eps(real(one(T)))), maxiter=2000)[1]
37-
@fact norm(eval_rand-eval_ii) --> less_than(tol)
35+
# Set a target near the middle eigenvalue
36+
idx = div(n, 2)
37+
σ = T(0.75 * λs[idx] + 0.25 * λs[idx + 1])
38+
39+
# Construct F = inv(A - σI) "matrix free"
40+
# Make sure we use complex arithmetic everywhere,
41+
# because of the follow bug in base: https://github.com/JuliaLang/julia/issues/22683
42+
F = lufact(complex(A) - UniformScaling(σ))
43+
Fmap = LinearMap((y, x) -> A_ldiv_B!(y, F, x), size(A, 1), complex(T), ismutating = true)
44+
45+
λ, x = invpowm(Fmap; shift = σ, tol = tol, maxiter = 10n)
46+
47+
@fact norm(A * x - λ * x) --> less_than(tol)
48+
@fact λ --> roughly(λs[idx])
49+
3850
end
3951

4052
end

0 commit comments

Comments
 (0)