Skip to content

Commit 5c070f4

Browse files
Rework symmetric generalized eigen/eigvals (#49673)
1 parent 014f8de commit 5c070f4

File tree

5 files changed

+116
-17
lines changed

5 files changed

+116
-17
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ Standard library changes
9292
(real symmetric) part of a matrix ([#31836]).
9393
* The `norm` of the adjoint or transpose of an `AbstractMatrix` now returns the norm of the
9494
parent matrix by default, matching the current behaviour for `AbstractVector`s ([#49020]).
95+
* `eigen(A, B)` and `eigvals(A, B)`, where one of `A` or `B` is symmetric or Hermitian,
96+
are now fully supported ([#49533])
97+
* `eigvals/eigen(A, cholesky(B))` now computes the generalized eigenvalues (`eigen`: and eigenvectors)
98+
of `A` and `B` via Cholesky decomposition for positive definite `B`. Note: The second argument is
99+
the output of `cholesky`.
95100

96101
#### Printf
97102
* Format specifiers now support dynamic width and precision, e.g. `%*s` and `%*.*g` ([#40105]).

stdlib/LinearAlgebra/src/diagonal.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -796,12 +796,11 @@ function eigen(A::AbstractMatrix, D::Diagonal; sortby::Union{Function,Nothing}=n
796796
end
797797
if size(A, 1) == size(A, 2) && isdiag(A)
798798
return eigen(Diagonal(A), D; sortby)
799-
elseif ishermitian(A)
799+
elseif all(isposdef, D.diag)
800800
S = promote_type(eigtype(eltype(A)), eltype(D))
801-
return eigen!(eigencopy_oftype(Hermitian(A), S), Diagonal{S}(D); sortby)
801+
return eigen(A, cholesky(Diagonal{S}(D)); sortby)
802802
else
803-
S = promote_type(eigtype(eltype(A)), eltype(D))
804-
return eigen!(eigencopy_oftype(A, S), Diagonal{S}(D); sortby)
803+
return eigen!(D \ A; sortby)
805804
end
806805
end
807806

stdlib/LinearAlgebra/src/eigen.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ true
524524
"""
525525
function eigen(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}; kws...) where {TA,TB}
526526
S = promote_type(eigtype(TA), TB)
527-
eigen!(eigencopy_oftype(A, S), eigencopy_oftype(B, S); kws...)
527+
eigen!(copy_similar(A, S), copy_similar(B, S); kws...)
528528
end
529529
eigen(A::Number, B::Number) = eigen(fill(A,1,1), fill(B,1,1))
530530

@@ -619,7 +619,7 @@ julia> eigvals(A,B)
619619
"""
620620
function eigvals(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}; kws...) where {TA,TB}
621621
S = promote_type(eigtype(TA), TB)
622-
return eigvals!(eigencopy_oftype(A, S), eigencopy_oftype(B, S); kws...)
622+
return eigvals!(copy_similar(A, S), copy_similar(B, S); kws...)
623623
end
624624

625625
"""

stdlib/LinearAlgebra/src/symmetriceigen.jl

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ end
156156
eigmax(A::RealHermSymComplexHerm{<:Real}) = eigvals(A, size(A, 1):size(A, 1))[1]
157157
eigmin(A::RealHermSymComplexHerm{<:Real}) = eigvals(A, 1:1)[1]
158158

159+
function eigen(A::HermOrSym{TA}, B::HermOrSym{TB}; kws...) where {TA,TB}
160+
S = promote_type(eigtype(TA), TB)
161+
return eigen!(eigencopy_oftype{S}(A), eigencopy_oftype(B, S); kws...)
162+
end
163+
159164
function eigen!(A::HermOrSym{T,S}, B::HermOrSym{T,S}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasReal,S<:StridedMatrix}
160165
vals, vecs, _ = LAPACK.sygvd!(1, 'V', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))
161166
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
@@ -164,26 +169,32 @@ function eigen!(A::Hermitian{T,S}, B::Hermitian{T,S}; sortby::Union{Function,Not
164169
vals, vecs, _ = LAPACK.sygvd!(1, 'V', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))
165170
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
166171
end
167-
function eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
168-
return _choleigen!(A, B, sortby)
169-
end
170-
function eigen!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
171-
return _choleigen!(A, B, sortby)
172+
173+
function eigen(A::AbstractMatrix, C::Cholesky; sortby::Union{Function,Nothing}=nothing)
174+
if ishermitian(A)
175+
eigen!(eigencopy_oftype(Hermitian(A), eigtype(eltype(A))), C; sortby)
176+
else
177+
eigen!(copy_similar(A, eigtype(eltype(A))), C; sortby)
178+
end
172179
end
173-
function _choleigen!(A, B, sortby)
174-
U = cholesky(B).U
175-
vals, w = eigen!(UtiAUi!(A, U))
176-
vecs = U \ w
180+
function eigen!(A::AbstractMatrix, C::Cholesky; sortby::Union{Function,Nothing}=nothing)
181+
# Cholesky decomposition based eigenvalues and eigenvectors
182+
vals, w = eigen!(UtiAUi!(A, C.U))
183+
vecs = C.U \ w
177184
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
178185
end
179186

180187
# Perform U' \ A / U in-place, where U::Union{UpperTriangular,Diagonal}
181-
UtiAUi!(A::StridedMatrix, U) = _UtiAUi!(A, U)
188+
UtiAUi!(A, U) = _UtiAUi!(A, U)
182189
UtiAUi!(A::Symmetric, U) = Symmetric(_UtiAUi!(copytri!(parent(A), A.uplo), U), sym_uplo(A.uplo))
183190
UtiAUi!(A::Hermitian, U) = Hermitian(_UtiAUi!(copytri!(parent(A), A.uplo, true), U), sym_uplo(A.uplo))
184-
185191
_UtiAUi!(A, U) = rdiv!(ldiv!(U', A), U)
186192

193+
function eigvals(A::HermOrSym{TA}, B::HermOrSym{TB}; kws...) where {TA,TB}
194+
S = promote_type(eigtype(TA), TB)
195+
return eigen!(eigencopy_oftype{S}(A), eigencopy_oftype(B, S); kws...)
196+
end
197+
187198
function eigvals!(A::HermOrSym{T,S}, B::HermOrSym{T,S}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasReal,S<:StridedMatrix}
188199
vals = LAPACK.sygvd!(1, 'N', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))[1]
189200
isnothing(sortby) || sort!(vals, by=sortby)
@@ -195,3 +206,15 @@ function eigvals!(A::Hermitian{T,S}, B::Hermitian{T,S}; sortby::Union{Function,N
195206
return vals
196207
end
197208
eigvecs(A::HermOrSym) = eigvecs(eigen(A))
209+
210+
function eigvals(A::AbstractMatrix, C::Cholesky; sortby::Union{Function,Nothing}=nothing)
211+
if ishermitian(A)
212+
eigvals!(eigencopy_oftype(Hermitian(A), eigtype(eltype(A))), C; sortby)
213+
else
214+
eigvals!(copy_similar(A, eigtype(eltype(A))), C; sortby)
215+
end
216+
end
217+
function eigvals!(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
218+
# Cholesky decomposition based eigenvalues
219+
return eigvals!(UtiAUi!(A, C.U); sortby)
220+
end
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module TestSymmetricEigen
4+
5+
using Test, LinearAlgebra
6+
7+
@testset "chol-eigen-eigvals" begin
8+
## Cholesky decomposition based
9+
10+
# eigenvalue sorting
11+
sf = x->(real(x),imag(x))
12+
13+
## Real valued
14+
A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4]
15+
H = (A+A')/2
16+
B = Float64[2 1 4 3; 0 3 1 3; 3 1 0 0; 0 1 3 1]
17+
BH = (B+B')/2
18+
# PD matrix
19+
BPD = B*B'
20+
# eigen
21+
C = cholesky(BPD)
22+
e,v = eigen(A, C; sortby=sf)
23+
@test A*v BPD*v*Diagonal(e)
24+
# eigvals
25+
@test eigvals(A, BPD; sortby=sf) eigvals(A, C; sortby=sf)
26+
27+
## Complex valued
28+
A = [1.0+im 1.0+1.0im 0 0; 1.0+1.0im 2.0+3.0im 1.0+1.0im 0; 0 1.0+2.0im 3.0+4.0im 1.0+5.0im; 0 0 1.0+1.0im 4.0+4.0im]
29+
AH = (A+A')/2
30+
B = [2.0+2.0im 1.0+1.0im 4.0+4.0im 3.0+3.0im; 0 3.0+2.0im 1.0+1.0im 3.0+4.0im; 3.0+3.0im 1.0+4.0im 0 0; 0 1.0+2.0im 3.0+1.0im 1.0+1.0im]
31+
BH = (B+B')/2
32+
# PD matrix
33+
BPD = B*B'
34+
# eigen
35+
C = cholesky(BPD)
36+
e,v = eigen(A, C; sortby=sf)
37+
@test A*v BPD*v*Diagonal(e)
38+
# eigvals
39+
@test eigvals(A, BPD; sortby=sf) eigvals(A, C; sortby=sf)
40+
end
41+
42+
@testset "issue #49533" begin
43+
## Real valued
44+
A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4]
45+
B = Matrix(Diagonal(Float64[1:4;]))
46+
# eigen
47+
e0,v0 = eigen(A, B)
48+
e1,v1 = eigen(A, Symmetric(B))
49+
e2,v2 = eigen(Symmetric(A), B)
50+
@test e0 e1 && v0 v1
51+
@test e0 e2 && v0 v2
52+
# eigvals
53+
@test eigvals(A, B) eigvals(A, Symmetric(B))
54+
@test eigvals(A, B) eigvals(Symmetric(A), B)
55+
56+
## Complex valued
57+
A = [1.0+im 1.0+1.0im 0 0; 1.0+1.0im 2.0+3.0im 1.0+1.0im 0; 0 1.0+2.0im 3.0+4.0im 1.0+5.0im; 0 0 1.0+1.0im 4.0+4.0im]
58+
AH = (A+A')/2
59+
B = [2.0+2.0im 1.0+1.0im 4.0+4.0im 3.0+3.0im; 0 3.0+2.0im 1.0+1.0im 3.0+4.0im; 3.0+3.0im 1.0+4.0im 0 0; 0 1.0+2.0im 3.0+1.0im 1.0+1.0im]
60+
BH = (B+B')/2
61+
# eigen
62+
sf = x->(real(x),imag(x))
63+
e1,v1 = eigen(A, Hermitian(BH))
64+
e2,v2 = eigen(Hermitian(AH), B)
65+
@test A*v1 Hermitian(BH)*v1*Diagonal(e1)
66+
@test Hermitian(AH)*v2 B*v2*Diagonal(e2)
67+
# eigvals
68+
@test eigvals(A, BH; sortby=sf) eigvals(A, Hermitian(BH); sortby=sf)
69+
@test eigvals(AH, B; sortby=sf) eigvals(Hermitian(AH), B; sortby=sf)
70+
end
71+
72+
end # module TestSymmetricEigen

0 commit comments

Comments
 (0)