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

Rework symmetric generalized eigen/eigvals #49673

Merged
merged 54 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
53bf195
Fix for #48911
aravindh-krishnamoorthy Apr 25, 2023
40e7f38
Merge branch 'JuliaLang:master' into 48911
aravindh-krishnamoorthy Apr 29, 2023
b48c014
Integration fix for #48911: Permutation is taken care by ldiv!(QRPivo…
aravindh-krishnamoorthy May 2, 2023
df37bdc
Tests for #48911: LinearAlgebra/test/qr.jl
aravindh-krishnamoorthy May 2, 2023
e1c5207
Merge branch 'master' into 48911
dkarrasch May 4, 2023
f6f1720
Issue #48911: Include original test case mentioned in the issue.
aravindh-krishnamoorthy May 4, 2023
3b564f2
Fix for #49533: missing eigvals! definitions for generalized eigenval…
aravindh-krishnamoorthy May 5, 2023
4701fbc
Fix for JuliaLang#49533 (2): Remove Cholesky decomposition based eige…
aravindh-krishnamoorthy May 6, 2023
4c8a489
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy May 7, 2023
556627e
Additional tests for #49533: Hermitian symmetric matrices.
aravindh-krishnamoorthy May 7, 2023
eab6e76
Merge branch '49533' of https://github.com/aravindh-krishnamoorthy/ju…
aravindh-krishnamoorthy May 7, 2023
c473e57
Implementing Cholesky decomposition based eigenvalues and eigenvectors
aravindh-krishnamoorthy May 10, 2023
52fddda
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy May 12, 2023
a00bd41
Merge branch '49533' of https://github.com/aravindh-krishnamoorthy/ju…
aravindh-krishnamoorthy May 12, 2023
703c263
Implementing Cholesky decomposition based eigenvalues and eigenvectors
aravindh-krishnamoorthy May 13, 2023
23ed119
Cholesky decomposition based eigenvalues and eigenvectors + tests.
aravindh-krishnamoorthy May 15, 2023
82b26a7
End-of-file whitespace for symmetriceigen.jl
aravindh-krishnamoorthy May 15, 2023
aaa5a27
Symmetric/Hermitian Eigenvalues: Proof of concept using Bunch-Kaufman…
aravindh-krishnamoorthy May 15, 2023
d964eb5
BK-based eigenvalues considering uplo
aravindh-krishnamoorthy May 16, 2023
5e5302d
Avoid intermediate variables in call
aravindh-krishnamoorthy May 16, 2023
4dcbdf6
Sanitize calls with copy()
aravindh-krishnamoorthy May 16, 2023
144ae7d
First version of exclusive tests for symmetriceigen.jl
aravindh-krishnamoorthy May 20, 2023
45cb1fc
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy May 20, 2023
5de6d63
Align to standard style for BK eigvals
aravindh-krishnamoorthy May 21, 2023
850e2c0
BK eigen and eigvals: Unoptimised but functional code + tests
aravindh-krishnamoorthy May 21, 2023
edac9b7
Fix inadvertent uncommenting in test/symmetric.jl
aravindh-krishnamoorthy May 21, 2023
81d893e
Fix missing invperm for uplo=L in src/symmetriceigen.jl
aravindh-krishnamoorthy May 21, 2023
316ddfb
symmetriceigen.jl BK convert from views to slices.
aravindh-krishnamoorthy May 22, 2023
c0f9b70
Whitespace corrections to symmetriceigen.jl src and test.
aravindh-krishnamoorthy May 22, 2023
5e10e2b
src/symmetriceigen.jl add back support for B::Diagonal, which was ina…
aravindh-krishnamoorthy May 22, 2023
3e27189
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 1, 2023
78d41fc
Update stdlib/LinearAlgebra/src/symmetriceigen.jl based on the commen…
aravindh-krishnamoorthy Jun 13, 2023
a7d040b
Update stdlib/LinearAlgebra/src/symmetriceigen.jl based on the commen…
aravindh-krishnamoorthy Jun 13, 2023
9c24fbd
Fix two review comments from @dkarrasch on copy(C) and _choleigvals!
aravindh-krishnamoorthy Jun 14, 2023
0aee951
Superfluous function definitions: Fix review comments from @dkarrasch
aravindh-krishnamoorthy Jun 14, 2023
5c566b1
Additional test cases for real-valued matrices for BK and Cholesky ba…
aravindh-krishnamoorthy Jun 14, 2023
8361afa
eigen! and eigvals!: Create copy of A::Tridiagonal for u
aravindh-krishnamoorthy Jun 14, 2023
9c873af
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 14, 2023
a015b37
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 14, 2023
cc43748
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 15, 2023
90ead95
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 19, 2023
52194d5
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 20, 2023
d8561f1
Remove redundant copy(B)
aravindh-krishnamoorthy Jun 20, 2023
325a655
Merge branch 'JuliaLang:master' into 49533
aravindh-krishnamoorthy Jun 22, 2023
4c31578
Update NEWS.md for PR #49673
aravindh-krishnamoorthy Jun 22, 2023
b23659c
Rename eigen! and eigvals! that use Matrix{T}(.) to eigen and eigvals
aravindh-krishnamoorthy Jun 22, 2023
d186845
Delegate to in-place eigen! and eigvals! for functions with Matrix{T}(.)
aravindh-krishnamoorthy Jun 22, 2023
6a17c49
Update stdlib/LinearAlgebra/src/symmetriceigen.jl
aravindh-krishnamoorthy Jun 23, 2023
0347624
Remove redundant ! functions - per review comments.
aravindh-krishnamoorthy Jun 23, 2023
3696cfc
In-place versions for ::Cholesky
aravindh-krishnamoorthy Jun 23, 2023
0dcd83b
Remove UiAUti\!(A,B) and utilise UtiAUi\!(A,B')
aravindh-krishnamoorthy Jun 23, 2023
a01f750
Update stdlib/LinearAlgebra/src/symmetriceigen.jl based on a comment …
aravindh-krishnamoorthy Jun 26, 2023
638be55
Remove Bunchkaufman-based eigen and eigvals
aravindh-krishnamoorthy Jun 27, 2023
eae1999
synchronize with eigencomputations in diagonal.jl
dkarrasch Jun 27, 2023
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
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ Standard library changes
(real symmetric) part of a matrix ([#31836]).
* The `norm` of the adjoint or transpose of an `AbstractMatrix` now returns the norm of the
parent matrix by default, matching the current behaviour for `AbstractVector`s ([#49020]).
* `eigen(A, B)` and `eigvals(A, B)`, where one of `A` or `B` is symmetric or Hermitian,
are now fully supported ([#49533])
* `eigvals/eigen(A, cholesky(B))` and `eigvals/eigen(A, bunchkaufman(B))` now compute the generalized
eigenvalues (`eigen`: and eigenvectors) of `A` and `B` via Cholesky decomposition for positive definite `B`
and Bunchkaufmann (LDLT) decomposition for symmetric/Hermitian `B`, respectively. Note: The second argument is
the output of `cholesky` or `bunchkaufman`.

#### Printf
* Format specifiers now support dynamic width and precision, e.g. `%*s` and `%*.*g` ([#40105]).
Expand Down
73 changes: 63 additions & 10 deletions stdlib/LinearAlgebra/src/symmetriceigen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,26 +164,50 @@ function eigen!(A::Hermitian{T,S}, B::Hermitian{T,S}; sortby::Union{Function,Not
vals, vecs, _ = LAPACK.sygvd!(1, 'V', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
end
function eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
return _choleigen!(A, B, sortby)
function eigen(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
return eigen!(Matrix{T}(A), eigencopy_oftype(B, T); sortby)
end
function eigen!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
return _choleigen!(A, B, sortby)
function eigen(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
aravindh-krishnamoorthy marked this conversation as resolved.
Show resolved Hide resolved
return eigen!(eigencopy_oftype(A, T), Matrix{T}(B); sortby) ;
aravindh-krishnamoorthy marked this conversation as resolved.
Show resolved Hide resolved
end
function _choleigen!(A, B, sortby)
U = cholesky(B).U
vals, w = eigen!(UtiAUi!(A, U))
vecs = U \ w

function eigen(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
aravindh-krishnamoorthy marked this conversation as resolved.
Show resolved Hide resolved
eigen!(copy(A), C; sortby)
end
function eigen!(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
# Cholesky decomposition based eigenvalues and eigenvectors
vals, w = eigen!(UtiAUi!(A, C.U))
vecs = C.U \ w
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
end

function eigen(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
eigen!(copy(A), B; sortby)
end
function eigen!(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
# Bunchkaufman decomposition based eigenvalues and eigenvectors
if B.uplo == 'U'
vals, w = eigen!(ldiv(lu!(copy(B.D)), UiAUti!(A[B.p,B.p], B.U)); sortby)
vecs = (B.U' \ w)[invperm(B.p),:]
else # B.uplo == 'L'
vals, w = eigen!(ldiv(lu!(copy(B.D)), UiAUti!(A[B.p,B.p], B.L)); sortby)
vecs = (B.L' \ w)[invperm(B.p),:]
end
GeneralizedEigen(sorteig!(vals, vecs, sortby)...)
end

# Perform U' \ A / U in-place, where U::Union{UpperTriangular,Diagonal}
UtiAUi!(A::StridedMatrix, U) = _UtiAUi!(A, U)
UtiAUi!(A, U) = _UtiAUi!(A, U)
UtiAUi!(A::Symmetric, U) = Symmetric(_UtiAUi!(copytri!(parent(A), A.uplo), U), sym_uplo(A.uplo))
UtiAUi!(A::Hermitian, U) = Hermitian(_UtiAUi!(copytri!(parent(A), A.uplo, true), U), sym_uplo(A.uplo))

_UtiAUi!(A, U) = rdiv!(ldiv!(U', A), U)

# Perform U \ A / U' in-place, where U::Union{UpperTriangular,Diagonal}
UiAUti!(A, U) = _UiAUti!(A, U)
UiAUti!(A::Symmetric, U) = Symmetric(_UiAUti!(copytri!(parent(A), A.uplo), U), sym_uplo(A.uplo))
UiAUti!(A::Hermitian, U) = Hermitian(_UiAUti!(copytri!(parent(A), A.uplo, true), U), sym_uplo(A.uplo))
_UiAUti!(A, U) = rdiv!(ldiv!(U, A), U')

aravindh-krishnamoorthy marked this conversation as resolved.
Show resolved Hide resolved
function eigvals!(A::HermOrSym{T,S}, B::HermOrSym{T,S}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasReal,S<:StridedMatrix}
vals = LAPACK.sygvd!(1, 'N', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))[1]
isnothing(sortby) || sort!(vals, by=sortby)
Expand All @@ -195,3 +219,32 @@ function eigvals!(A::Hermitian{T,S}, B::Hermitian{T,S}; sortby::Union{Function,N
return vals
end
eigvecs(A::HermOrSym) = eigvecs(eigen(A))

# Note: No specilized LAPACK routines for Matrix+Symmetric and Symmetric+Matrix exist. Hence, the calls are forwarded to conventional Matrix eigvals!
function eigvals(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
return eigvals!(eigencopy_oftype(A, T), Matrix{T}(B); sortby)
end

function eigvals(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
return eigvals!(Matrix{T}(A), eigencopy_oftype(B, T); sortby)
end

function eigvals!(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
# Cholesky decomposition based eigenvalues
return eigvals!(UtiAUi!(A, C.U); sortby)
end
function eigvals(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
eigvals!(eigencopy_oftype(A, T), C; sortby)
end

# Bunch-Kaufmann (LDLT) based solution for generalized eigenvalues
function eigvals(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
eigvals!(copy(A), B; sortby)
end
function eigvals!(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
aravindh-krishnamoorthy marked this conversation as resolved.
Show resolved Hide resolved
if B.uplo == 'U'
return eigvals!(ldiv(lu!(copy(B.D)), UiAUti!(A[B.p,B.p], B.U)); sortby)
else # B.uplo == 'L'
return eigvals!(ldiv(lu!(copy(B.D)), UiAUti!(A[B.p,B.p], B.L)); sortby)
end
end
120 changes: 120 additions & 0 deletions stdlib/LinearAlgebra/test/symmetriceigen.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module TestSymmetricEigen

using Test, LinearAlgebra, Random

Random.seed!(555)

@testset "bk-eigen-eigals" begin
# Bunchkaufman decomposition based

# Real-valued random matrix
N = 10
A = randn(N,N)
B = randn(N,N)
BH = (B+B')/2
sf = x->(real(x),imag(x))
# eigen
e0 = eigvals(A,BH; sortby=sf)
e,v = eigen(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf)
@test e0 ≈ e
@test A*v ≈ BH*v*Diagonal(e)
e,v = eigen(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf)
@test e0 ≈ e
@test A*v ≈ BH*v*Diagonal(e)
# eigvals
e0 = eigvals(A,BH; sortby=sf)
el = eigvals(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf)
eu = eigvals(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf)
@test e0 ≈ el
@test e0 ≈ eu

# Complex-valued random matrix
N = 10
A = complex.(randn(N,N),randn(N,N))
B = complex.(randn(N,N),randn(N,N))
BH = (B+B')/2
sf = x->(real(x),imag(x))
# eigen
e0 = eigvals(A,BH; sortby=sf)
e,v = eigen(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf)
@test e0 ≈ e
@test A*v ≈ BH*v*Diagonal(e)
e,v = eigen(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf)
@test e0 ≈ e
@test A*v ≈ BH*v*Diagonal(e)
# eigvals
e0 = eigvals(A,BH; sortby=sf)
el = eigvals(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf)
eu = eigvals(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf)
@test e0 ≈ el
@test e0 ≈ eu
end

@testset "chol-eigen-eigvals" begin
## Cholesky decomposition based

# eigenvalue sorting
sf = x->(real(x),imag(x))

## Real valued
A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4]
H = (A+A')/2
B = Float64[2 1 4 3; 0 3 1 3; 3 1 0 0; 0 1 3 1]
BH = (B+B')/2
# PD matrix
BPD = B*B'
# eigen
C = cholesky(BPD)
e,v = eigen(A, C; sortby=sf)
@test A*v ≈ BPD*v*Diagonal(e)
# eigvals
@test eigvals(A, BPD; sortby=sf) ≈ eigvals(A, C; sortby=sf)

## Complex valued
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]
AH = (A+A')/2
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]
BH = (B+B')/2
# PD matrix
BPD = B*B'
# eigen
C = cholesky(BPD)
e,v = eigen(A, C; sortby=sf)
@test A*v ≈ BPD*v*Diagonal(e)
# eigvals
@test eigvals(A, BPD; sortby=sf) ≈ eigvals(A, C; sortby=sf)
end

@testset "issue #49533" begin
## Real valued
A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4]
B = Matrix(Diagonal(Float64[1:4;]))
# eigen
e0,v0 = eigen(A, B)
e1,v1 = eigen(A, Symmetric(B))
e2,v2 = eigen(Symmetric(A), B)
@test e0 ≈ e1 && v0 ≈ v1
@test e0 ≈ e2 && v0 ≈ v2
# eigvals
@test eigvals(A, B) ≈ eigvals(A, Symmetric(B))
@test eigvals(A, B) ≈ eigvals(Symmetric(A), B)

## Complex valued
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]
AH = (A+A')/2
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]
BH = (B+B')/2
# eigen
sf = x->(real(x),imag(x))
e1,v1 = eigen(A, Hermitian(BH))
e2,v2 = eigen(Hermitian(AH), B)
@test A*v1 ≈ Hermitian(BH)*v1*Diagonal(e1)
@test Hermitian(AH)*v2 ≈ B*v2*Diagonal(e2)
# eigvals
@test eigvals(A, BH; sortby=sf) ≈ eigvals(A, Hermitian(BH); sortby=sf)
@test eigvals(AH, B; sortby=sf) ≈ eigvals(Hermitian(AH), B; sortby=sf)
end

end # module TestSymmetricEigen