Skip to content

Commit 7baba67

Browse files
authored
rigorous computation of eigenvalues of matrices (#68)
* rigorous computation of eigenvalues of symmetric matrices * specialized algorithm for simple eigenvalues * added docstrings to docs * fix merging conflicts * updated algorithm to work with generic matrices * fix eigenvectors for general matrices
1 parent 3065a2f commit 7baba67

File tree

7 files changed

+257
-3
lines changed

7 files changed

+257
-3
lines changed

docs/src/api/eigenvalues.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1+
# Eigenvalues computations
2+
13
```@index
24
Pages = ["eigenvalues.md"]
35
```
46

7+
## Interval matrices eigenvalues
8+
59
```@autodocs
610
Modules=[IntervalLinearAlgebra]
711
Pages=["interval_eigenvalues.jl"]
812
Private=false
913
```
1014

15+
## Floating point eigenvalues verification
16+
17+
```@autodocs
18+
Modules=[IntervalLinearAlgebra]
19+
Pages=["verify_eigs.jl"]
20+
Private=false
21+
```
22+

docs/src/references.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,33 @@ S.M. Rump, [*Verification methods: Rigorous results using floating-point arithme
211211
```
212212
---
213213

214+
#### [RUM01]
215+
216+
```@raw html
217+
<ul><li>
218+
```
219+
Rump, Siegfried M. [*Computational error bounds for multiple or nearly multiple eigenvalues*](https://www.tuhh.de/ti3/paper/rump/Ru99c.pdf), Linear algebra and its applications 324.1-3 (2001): 209-226.
220+
```@raw html
221+
<li style="list-style: none"><details>
222+
<summary>bibtex</summary>
223+
```
224+
```
225+
@article{rump2001computational,
226+
title={Computational error bounds for multiple or nearly multiple eigenvalues},
227+
author={Rump, Siegfried M},
228+
journal={Linear algebra and its applications},
229+
volume={324},
230+
number={1-3},
231+
pages={209--226},
232+
year={2001},
233+
publisher={Elsevier}
234+
}
235+
```
236+
```@raw html
237+
</details></li></ul>
238+
```
239+
---
240+
214241
#### [RUM99]
215242

216243
```@raw html

src/IntervalLinearAlgebra.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ end
1616

1717
@reexport using LinearAlgebra, IntervalArithmetic
1818

19+
const IA = IntervalArithmetic
20+
1921
export
2022
set_multiplication_mode, get_multiplication_mode,
2123
LinearKrawczyk, Jacobi, GaussSeidel, GaussianElimination, HansenBliekRohn, NonLinearOettliPrager, LinearOettliPrager,
@@ -24,7 +26,7 @@ export
2426
comparison_matrix, interval_norm, interval_isapprox, Orthants,
2527
is_H_matrix, is_strongly_regular, is_strictly_diagonally_dominant, is_Z_matrix, is_M_matrix,
2628
rref,
27-
eigenbox
29+
eigenbox, verify_eigen, bound_perron_frobenius_eigenvalue
2830

2931

3032
include("linear_systems/enclosures.jl")
@@ -38,4 +40,5 @@ include("classify.jl")
3840
include("rref.jl")
3941

4042
include("eigenvalues/interval_eigenvalues.jl")
43+
include("eigenvalues/verify_eigs.jl")
4144
end

src/eigenvalues/verify_eigs.jl

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""
2+
verify_eigen(A[, λ, X0]; w=0.1, ϵ=1e-16, maxiter=10)
3+
4+
Finds a rigorous bound for the eigenvalues and eigenvectors of `A`. Eigenvalues are treated
5+
as simple.
6+
7+
### Input
8+
9+
- `A` -- matrix
10+
- `λ` -- (optional) approximate value for an eigenvalue of `A`
11+
- `X0` -- (optional) eigenvector associated to `λ`
12+
- `w` -- relative inflation parameter
13+
- `ϵ` -- absolute inflation parameter
14+
- `maxiter` -- maximum number of iterations
15+
16+
### Output
17+
18+
- Interval bounds on eigenvalues and eigenvectors.
19+
- A boolean certificate (or a vector of booleans if all eigenvalues are computed) `cert`.
20+
If `cert[i]==true`, then the bounds for the ith eigenvalue and eigenvectore are rigorous,
21+
otherwise not.
22+
23+
### Algorithm
24+
25+
The algorithm for this function is described in [[RUM01]](@ref).
26+
27+
### Example
28+
29+
```julia
30+
julia> A = Symmetric([1 2;2 3])
31+
2×2 Symmetric{Int64, Matrix{Int64}}:
32+
1 2
33+
2 3
34+
35+
julia> evals, evecs, cert = verify_eigen(A);
36+
37+
julia> evals
38+
2-element Vector{Interval{Float64}}:
39+
[-0.236068, -0.236067]
40+
[4.23606, 4.23607]
41+
42+
julia> evecs
43+
2×2 Matrix{Interval{Float64}}:
44+
[-0.850651, -0.85065] [0.525731, 0.525732]
45+
[0.525731, 0.525732] [0.85065, 0.850651]
46+
47+
julia> cert
48+
2-element Vector{Bool}:
49+
1
50+
1
51+
```
52+
"""
53+
function verify_eigen(A; kwargs...)
54+
evals, evecs = eigen(mid.(A))
55+
56+
T = interval_eigtype(A, evals[1])
57+
evalues = similar(evals, T)
58+
evectors = similar(evecs, T)
59+
60+
cert = Vector{Bool}(undef, length(evals))
61+
@inbounds for (i, λ₀) in enumerate(evals)
62+
λ, v, flag = verify_eigen(A, λ₀, view(evecs, :,i); kwargs...)
63+
evalues[i] = λ
64+
evectors[:, i] .= v
65+
cert[i] = flag
66+
67+
end
68+
return evalues, evectors, cert
69+
end
70+
71+
function verify_eigen(A, λ, X0; kwargs...)
72+
ρ, X, cert = _verify_eigen(A, λ, X0; kwargs...)
73+
return (real(λ) ± ρ) + (imag(λ) ± ρ) * im, X0 + X, cert
74+
end
75+
76+
function verify_eigen(A::Symmetric, λ, X0; kwargs...)
77+
ρ, X, cert = _verify_eigen(A, λ, X0; kwargs...)
78+
return λ ± ρ, X0 + real.(X), cert
79+
end
80+
81+
function _verify_eigen(A, λ::Number, X0::AbstractVector;
82+
w=0.1, ϵ=floatmin(), maxiter=10)
83+
84+
_, v = findmax(abs.(X0))
85+
86+
R = mid.(A) - λ * I
87+
R[:, v] .= -X0
88+
R = inv(R)
89+
C = IA.Interval.(A) - λ * I
90+
Z = -R * (C * X0)
91+
C[:, v] .= -X0
92+
C = I - R * C
93+
Zinfl = w * IA.Interval.(-mag.(Z), mag.(Z)) .+ IA.Interval(-ϵ, ϵ)
94+
95+
X = Complex.(Z)
96+
cert = false
97+
@inbounds for _ in 1:maxiter
98+
Y = (real.(X) + Zinfl) + (imag.(X) + Zinfl) * im
99+
100+
Ytmp = Y * Y[v]
101+
Ytmp[v] = 0
102+
103+
X = Z + C * Y + R * Ytmp
104+
cert = all(X .⊂ Y)
105+
cert && break
106+
end
107+
108+
ρ = mag(X[v])
109+
X[v] = 0
110+
111+
return ρ, X, cert
112+
end
113+
114+
115+
"""
116+
bound_perron_frobenius_eigenvalue(A, max_iter=10)
117+
118+
Finds an upper bound for the Perron-Frobenius eigenvalue of the **non-negative** matrix `A`.
119+
120+
### Input
121+
122+
- `A` -- square real non-negative matrix
123+
- `max_iter` -- maximum number of iterations of the power method used internally to compute
124+
an initial approximation of the Perron-Frobenius eigenvector
125+
126+
### Example
127+
128+
```julia-repl
129+
julia> A = [1 2;3 4]
130+
2×2 Matrix{Int64}:
131+
1 2
132+
3 4
133+
134+
julia> bound_perron_frobenius_eigenvalue(A)
135+
5.372281323275249
136+
```
137+
"""
138+
function bound_perron_frobenius_eigenvalue(A::AbstractMatrix{T}, max_iter=10) where {T<:Real}
139+
any(A .< 0) && throw(ArgumentError("Matrix contains negative entries"))
140+
return _bound_perron_frobenius_eigenvalue(A, max_iter)
141+
end
142+
143+
function _bound_perron_frobenius_eigenvalue(M, max_iter=10)
144+
145+
size(M, 1) == 1 && return M[1]
146+
xpf = IA.Interval.(_power_iteration(M, max_iter))
147+
Mxpf = M * xpf
148+
ρ = zero(eltype(M))
149+
@inbounds for (i, xi) in enumerate(xpf)
150+
iszero(xi) && continue
151+
tmp = Mxpf[i] / xi
152+
ρ = max(ρ, tmp.hi)
153+
end
154+
return ρ
155+
end
156+
157+
function _power_iteration(A, max_iter)
158+
n = size(A,1)
159+
xp = rand(n)
160+
@inbounds for _ in 1:max_iter
161+
xp .= A*xp
162+
xp ./= norm(xp)
163+
end
164+
return xp
165+
end
166+
167+
168+
interval_eigtype(::Symmetric, ::T) where {T<:Real} = Interval{T}
169+
interval_eigtype(::AbstractMatrix, ::T) where {T<:Real} = Complex{Interval{T}}
170+
interval_eigtype(::AbstractMatrix, ::Complex{T}) where {T<:Real} = Complex{Interval{T}}

src/utils.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mid(x::Real) = x
2+
mid(x::Complex) = x
23

34
"""
45
interval_isapprox(a::Interval, b::Interval; kwargs)
@@ -172,3 +173,7 @@ end
172173

173174
Base.firstindex(O::Orthants) = 1
174175
Base.lastindex(O::Orthants) = length(O)
176+
177+
178+
_unchecked_interval(x::Real) = Interval(x)
179+
_unchecked_interval(x::Complex) = Interval(real(x)) + Interval(imag(x)) * im

test/runtests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ include("test_utils.jl")
99

1010

1111
include("test_eigenvalues/test_interval_eigenvalues.jl")
12-
12+
include("test_eigenvalues/test_verify_eigs.jl")
1313
include("test_solvers/test_enclosures.jl")
1414
include("test_solvers/test_epsilon_inflation.jl")
15-
include("test_solvers/test_oettli_prager.jl")
1615
include("test_solvers/test_precondition.jl")
16+
include("test_solvers/test_oettli_prager.jl")
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@testset "verify perron frobenius " begin
2+
A = [1 2;3 4]
3+
ρ = bound_perron_frobenius_eigenvalue(A)
4+
@test (5+sqrt(big(5)))/2 ρ
5+
end
6+
7+
@testset "verified eigenvalues" begin
8+
n = 5 # matrix size
9+
10+
# symmetric case
11+
ev = sort(randn(n))
12+
D = Diagonal(ev)
13+
Q, _ = qr(rand(n, n))
14+
A = Symmetric(IA.Interval.(Q) * D * IA.Interval.(Q)')
15+
16+
evals, evecs, cert = verify_eigen(A)
17+
@test all(cert)
18+
@test all(ev .∈ evals)
19+
20+
21+
# real eigenvalues case
22+
P = rand(n, n)
23+
Pinv, _ = epsilon_inflation(P, Diagonal(ones(n)))
24+
A = IA.Interval.(P) * D * Pinv
25+
26+
evals, evecs, cert = verify_eigen(A)
27+
@test all(cert)
28+
@test all(ev .∈ evals)
29+
30+
# test complex eigenvalues
31+
ev = sort(rand(Complex{Float64}, n), by = x -> (real(x), imag(x)))
32+
A = IA.Interval.(P) * Matrix(Diagonal(ev)) * Pinv
33+
34+
evals, evecs, cert = verify_eigen(A)
35+
@test all(cert)
36+
@test all(ev .∈ evals)
37+
end

0 commit comments

Comments
 (0)