-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Conversation
1. Fix output permutation when A.P is not the identity matrix. 2. Fix the application of the elementary transformation (correct complex conjugation) to the solution as per LAPACK zlarzb https://netlib.org/lapack/explore-html/d5/ddd/zlarzb_8f_source.html 3. Align the final permutation with LAPACK's ZGELSY at https://netlib.org/lapack/explore-html/d8/d6e/zgelsy_8f_source.html 4. Fix a complex conjugate bug in the original solution for #48911, align with LAPACK zlarzb, https://netlib.org/lapack/explore-html/d5/ddd/zlarzb_8f_source.html 5. Improve permutation performance
…ues with symmetric and normal matrices
…n! functions for generalised Normal+Symmetric matrices and forward to the generic functions in eigen.jl
Regarding your questions, I would say that having more tests is certainly a good idea, and allowing different element types is perhaps unnecessary, since (from what I recall) the LAPACK functions require identical element types. Users may promote matrices to the same element type, if this is necessary. |
Done with tests for Hermitian symmetric matrices. NOTE: That I've retained Therefore, I had to add this |
This looks like a very rough approach, honestly. So the file was created in #39301, with certain conditions on Addendum: I only know read the original issue. Seems like I agree with @jishnub. @aravindh-krishnamoorthy You can see in the PDMats.jl (see also the PositiveDefiniteMatrices package) that there is no way to simply wrap a matrix and say it's spd, but the proof of spd is by performing a successful cholesky decomposition. As I think about it, another option could be to provide an We should reach out to @schneiderfelipe and @andreasnoack who were discussing these issues initially in #39301. |
@dkarrasch Thank you for your detailed response and your concerns. Below, I will try to address them point by point.
Indeed, the solution in the PR is a "basic" solution, where the computation is just forwarded to the generic equations in As noted in the issue, the present solution makes additional assumption about the inputs, which are not encoded in the type signature of the functions. Julia's Cholesky decomposition only works for positive definite (PD) matrices, so the present functions will fail for positive semi-definite (PSD, the most common case) and indefinite matrices. Making additional (failing) assumption on input types not a good thing for Furthermore, the suggestion to check for positive definiteness and move to the respective algorithms (Cholesky or LAPACK) does not seem elegant. This is because the switching will depend on a numerical algorithm (checking for positive definiteness), which not only incurs computation complexity, but may also be influenced by numerical errors. In my experience, avoiding such nondeterminism (in this case numerical algorithm chaining) in Moreover, as you're probably aware, the probability that an arbitrary matrix is PD is zero. Most PD matrices have a structure known to the user, or are computed as Regarding the performance, this was evaluated quickly in JuliaLang/LinearAlgebra.jl#1002. The preliminary outcome was that, indeed, for matrices of order around
Indeed, this is a good proposal for using Cholesky. Alternatively (or even alongside), we could implement I feel this might be a good long term solution. Furthermore, we could implement a switch based on the matrix order
Indeed. It will be interesting to understand why they went for Cholesky decomposition for this case. I look forward to yours and others opinion, given the above. |
The LDLt idea sounds interesting, could you check what they performance is like with that? I would imagine that it's comparable |
I think it's called |
@jishnub @dkarrasch Thank you very much for the inputs. I'll take a look at the function soon and update this PR with two additional definitions for Presently, I'm a bit caught up with other work, but I will get back to you at the earliest, at least in parts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments. Thanks for your persistent work!
Accept comment by @dkarrasch Co-authored-by: Daniel Karrasch <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last small comment. After this, I think this is ready to go. Are the timings still the same as they used to be?
I checked the discussion, and found that there was an issue with |
@dkarrasch Change StridedMatrix to AbstractMatrix in function eigen(A,B) Co-authored-by: Daniel Karrasch <[email protected]>
@dkarrasch Thank you for your suggestions and comments. Indeed, I was also planning to provide the benchmark figures after incorporating all review comments. Please note: The output below can be obtained by copy-pasting the contents of the attached file JT.txt into a fresh Julia prompt in the Summary
Benchmarkingjulia> # Packages
using Revise
julia> using LinearAlgebra
julia> Revise.track(LinearAlgebra)
julia> using BenchmarkTools
julia> # Test definitions
function mm(N)
A = complex.(randn(N,N), randn(N,N))
B = complex.(randn(N,N), randn(N,N))
return A, (B+B')/2
end
mm (generic function with 1 method)
julia> # Tests for size N=4
A,B = mm(4) ;
julia> @btime eigvals(A,B) ;
7.038 μs (12 allocations: 68.95 KiB)
julia> @btime eigvals(A,bunchkaufman(B)) ;
9.183 μs (36 allocations: 10.41 KiB)
julia> @btime eigen(A,B) ;
8.494 μs (15 allocations: 69.41 KiB)
julia> @btime eigen(A,bunchkaufman(B)) ;
14.998 μs (53 allocations: 18.48 KiB)
julia> BP = B*B' ;
julia> @btime eigvals(A,BP) ;
6.566 μs (12 allocations: 68.95 KiB)
julia> @btime eigvals(A,cholesky(BP)) ;
7.176 μs (15 allocations: 3.72 KiB)
julia> @btime eigen(A,BP) ;
8.118 μs (15 allocations: 69.41 KiB)
julia> @btime eigen(A,cholesky(BP)) ;
12.009 μs (28 allocations: 11.03 KiB)
julia> # Tests for size N=1000
A,B = mm(1000) ;
julia> @btime eigvals(A,B) ;
5.212 s (16 allocations: 33.62 MiB)
julia> @btime eigvals(A,bunchkaufman(B)) ;
1.106 s (41 allocations: 78.00 MiB)
julia> @btime eigen(A,B) ;
6.471 s (19 allocations: 48.87 MiB)
julia> @btime eigen(A,bunchkaufman(B)) ;
1.681 s (62 allocations: 140.53 MiB)
julia> BP = B*B' ;
julia> @btime eigvals(A,BP) ;
5.056 s (16 allocations: 33.62 MiB)
julia> @btime eigvals(A,cholesky(BP)) ;
1.040 s (17 allocations: 31.08 MiB)
julia> @btime eigen(A,BP) ;
6.503 s (19 allocations: 48.87 MiB)
julia> @btime eigen(A,cholesky(BP)) ;
1.721 s (32 allocations: 63.08 MiB)
julia> pwd()
"/home/ark/julia/julia/stdlib/LinearAlgebra/test"
julia> include("symmetriceigen.jl")
Test Summary: | Pass Total Time
bk-eigen-eigals | 12 12 3.8s
Test Summary: | Pass Total Time
chol-eigen-eigvals | 4 4 1.5s
Test Summary: | Pass Total Time
issue JuliaLang/LinearAlgebra.jl#1002 | 8 8 0.8s
Main.TestSymmetricEigen Inconsistency in the output for low-rank
|
As I understand, the issue is only with BK, right? Because Regarding the output of Given that we can't compute things in-place with the BK approach (and the consequence that we need massively more memory in the interesting case when the matrices are big) and the fake finite eigenvalues, should we remove the BK-related methods here and keep that for later? What are typical use cases for generalized eigencomputations with symmetric/Hermitian, but not necessarily positive-definite rhs's? |
@dkarrasch Thank you for raising these concerns. Kindly find my response and opinion below.
In fact, the problem affects both BK and (the earlier) Cholesky based implementations. That is, the (earlier) code based on Cholesky also returns a large value instead of
In
This was my initial thought as well. However, during implementation, I changed my mind and felt that BK-based routines are indeed quite elegant and superior. BK's inbuilt pivoting makes it superior to plain Cholesky for positive definite (PD) matrices. (Pivoted Cholesky based solution may be an option for this, but BK has more advantages.) Furthermore, BK can also handle PD matrices which turn out to be positive semi-definite (PSD) or indefinite due to numerical issues. Hence, BK solution may be a one-stop solution for all kinds of matrices: PD, PSD, and indefinite. This, coupled with the fact that BK routines have a runtime comparable with that of plain Cholesky makes them interesting. Note: Maybe the best solution is to implement BK-based method in LAPACK alongside GGEV and SYGVD :) As you rightly identified, the present implementation is memory intensive compared to the generic LAPACK-based methods. However, I feel that solving the Next, regarding the inconsistency in the outputs: I feel that it's better to investigate this further in the context of the yet-to-be-filed [ISSUE-3]. Personally, I feel this is a corner case (not a blocking important case). Furthermore, the output is not wrong, but only that the thresholding is inconsistent. Lastly, during my investigations, I found that the older Based on the above, and looking at the balance between memory and speed, my suggestion is to keep the BK routine in for now and evaluate it again once we have the fix to the |
For your |
eigen
/eigvals
@dkarrasch Thank you. I understand your concerns. I've removed the |
Hm, I overlooked that we have a method ambiguity, and realized that we should keep the |
Thank you. Please let me know if I can be of any help. |
You did a great job! Thank you. |
@dkarrasch There's just one more point that I forgot to add earlier. # Arbitrary Hermitian tridiagonal matrix
julia> d = [1+0*im, 2+0*im, 3+0*im] ;
julia> e = [1+im, 2+2im] ;
julia> A = Tridiagonal(e,d,conj(e))
3×3 Tridiagonal{Complex{Int64}, Vector{Complex{Int64}}}:
1+0im 1-1im ⋅
1+1im 2+0im 2-2im
⋅ 2+2im 3+0im
# Construct the diagonal unitary transformation
julia> D = Diagonal(exp.(im*cumsum([0;angle.(A.du)])))
3×3 Diagonal{ComplexF64, Vector{ComplexF64}}:
1.0+0.0im ⋅ ⋅
⋅ 0.707107-0.707107im ⋅
⋅ ⋅ 6.12323e-17-1.0im
# Apply transformation symmetrically -> results in SymTridiagonal, which allows ldlt(.)
julia> D*A*D'
3×3 Matrix{ComplexF64}:
1.0+0.0im 1.41421-1.11022e-16im 0.0+0.0im
1.41421+1.11022e-16im 2.0+0.0im 2.82843+4.88534e-17im
0.0+0.0im 2.82843+0.0im 3.0+0.0im |
This PR contains a fix for JuliaLang/LinearAlgebra.jl#1002.
Summary
eigenvals!(A,B)
when one ofA
orB
is not (Hermitian or) symmetric.eigen!(A,B)
when either ofA
orB
is not (Hermitian or) symmetric, computes the solution based on Cholesky decomposition, which is only valid for positive definite matrixB
. Hence, the function fails for Hermitian or symmetricB
.Changes
Include definitions for
function eigvals!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
andfunction eigvals!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
which utilise the generic (dense) matrix variants.Change the definitions of
function eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
andfunction eigen!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
to utilise the generic (dense) matrix variants.Define a new function
function eigvals(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
, which can take advantage of Cholesky decomposition to reduce the computational complexity ofB
is positive definite.Define a new function(To be moved to another PR)function eigvals(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
, which tan take advantage of Bunchkaufman (LDLT) decomposition to reduce the computational complexity ifB
is Hermitian or symmetric.Plan
function eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
is forwarded toeigen!(Matrix{T}(A), B; sortby)
function eigen!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
is forwarded toeigen!(A, Matrix{T}(B); sortby)
function eigvals!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
is forwarded toeigvals!(A, Matrix{T}(B); sortby)
function eigvals!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
is forwarded toeigvals!(Matrix{T}(A), B; sortby)
Define a new function
function eigvals(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
, which can take advantage of Cholesky decomposition to reduce the computational complexity ofB
is positive definite.Define a new function(To be moved to another PR)function eigvals(A::AbstractMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:Number}
, which tan take advantage of Bunchkaufman (LDLT) decomposition to reduce the computational complexity ifB
is Hermitian or symmetric.Add unit tests.
Notes
and Bunchkaufman decompostionbased versions are only efficient if the matrix dimensionTODO
tridiagonal.jl
to supportHermTridiagonal
andldlt
forHermTridiagonal
.bunchkaufman
to returnSymTridiagonal
orHermTridiagonal
instead ofTridiagonal
. Then,ldlt
can be exploited to speed up Bunchkaufman-based implementation.eig/en/valuss
for a low-rankeigen(A::HermOrSym,B::HermOrSym)
andeigvals(A::HermOrSym,B::HermOrSym)
fail forHermitian
/Symmetric
matrices ifB
is not additionally positive definite.eigen
andeigvals
in another PR.