Skip to content

Commit 27438ec

Browse files
jaemolihmlazarusA
authored andcommitted
Add interface to use symmetric eigendecomposition with different lapack method (JuliaLang#49355)
1 parent 5ec4a56 commit 27438ec

File tree

6 files changed

+94
-17
lines changed

6 files changed

+94
-17
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ Standard library changes
8888
#### LinearAlgebra
8989

9090
* `rank` can now take a `QRPivoted` matrix to allow rank estimation via QR factorization ([#54283]).
91+
* Added keyword argument `alg` to `eigen`, `eigen!`, `eigvals` and `eigvals!` for self-adjoint
92+
matrix types (i.e., the type union `RealHermSymComplexHerm`) that allows one to switch
93+
between different eigendecomposition algorithms ([#49355]).
9194

9295
#### Logging
9396

stdlib/LinearAlgebra/src/LinearAlgebra.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ end
190190
abstract type Algorithm end
191191
struct DivideAndConquer <: Algorithm end
192192
struct QRIteration <: Algorithm end
193+
struct RobustRepresentations <: Algorithm end
193194

194195
# Pivoting strategies for matrix factorization algorithms.
195196
abstract type PivotingStrategy end

stdlib/LinearAlgebra/src/lapack.jl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5762,11 +5762,6 @@ syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractM
57625762
Finds the eigenvalues (`jobz = N`) or eigenvalues and eigenvectors
57635763
(`jobz = V`) of a symmetric matrix `A`. If `uplo = U`, the upper triangle
57645764
of `A` is used. If `uplo = L`, the lower triangle of `A` is used.
5765-
5766-
Use the divide-and-conquer method, instead of the QR iteration used by
5767-
`syev!` or multiple relatively robust representations used by `syevr!`.
5768-
See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
5769-
a comparison of the accuracy and performatce of different methods.
57705765
"""
57715766
syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix)
57725767

stdlib/LinearAlgebra/src/svd.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ and `V` is ``N \\times N``, while in the thin factorization `U` is ``M
149149
\\times K`` and `V` is ``N \\times K``, where ``K = \\min(M,N)`` is the
150150
number of singular values.
151151
152-
If `alg = DivideAndConquer()` a divide-and-conquer algorithm is used to calculate the SVD.
153-
Another (typically slower but more accurate) option is `alg = QRIteration()`.
152+
`alg` specifies which algorithm and LAPACK method to use for SVD:
153+
- `alg = DivideAndConquer()` (default): Calls `LAPACK.gesdd!`.
154+
- `alg = QRIteration()`: Calls `LAPACK.gesvd!` (typically slower but more accurate) .
154155
155156
!!! compat "Julia 1.3"
156157
The `alg` keyword argument requires Julia 1.3 or later.

stdlib/LinearAlgebra/src/symmetriceigen.jl

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
eigencopy_oftype(A::Hermitian, S) = Hermitian(copytrito!(similar(parent(A), S, size(A)), A.data, A.uplo), sym_uplo(A.uplo))
66
eigencopy_oftype(A::Symmetric, S) = Symmetric(copytrito!(similar(parent(A), S, size(A)), A.data, A.uplo), sym_uplo(A.uplo))
77

8-
# Eigensolvers for symmetric and Hermitian matrices
9-
eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) =
10-
Eigen(sorteig!(LAPACK.syevr!('V', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)..., sortby)...)
8+
default_eigen_alg(A) = DivideAndConquer()
119

12-
function eigen(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing)
13-
S = eigtype(eltype(A))
14-
eigen!(eigencopy_oftype(A, S), sortby=sortby)
10+
# Eigensolvers for symmetric and Hermitian matrices
11+
function eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
12+
if alg === DivideAndConquer()
13+
Eigen(sorteig!(LAPACK.syevd!('V', A.uplo, A.data)..., sortby)...)
14+
elseif alg === QRIteration()
15+
Eigen(sorteig!(LAPACK.syev!('V', A.uplo, A.data)..., sortby)...)
16+
elseif alg === RobustRepresentations()
17+
Eigen(sorteig!(LAPACK.syevr!('V', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)..., sortby)...)
18+
else
19+
throw(ArgumentError("Unsupported value for `alg` keyword."))
20+
end
1521
end
1622
function eigen(A::RealHermSymComplexHerm{Float16}; sortby::Union{Function,Nothing}=nothing)
1723
S = eigtype(eltype(A))
@@ -21,6 +27,36 @@ function eigen(A::RealHermSymComplexHerm{Float16}; sortby::Union{Function,Nothin
2127
return Eigen(values, vectors)
2228
end
2329

30+
"""
31+
eigen(A::Union{Hermitian, Symmetric}, alg::Algorithm = default_eigen_alg(A)) -> Eigen
32+
33+
Compute the eigenvalue decomposition of `A`, returning an [`Eigen`](@ref) factorization object `F`
34+
which contains the eigenvalues in `F.values` and the eigenvectors in the columns of the
35+
matrix `F.vectors`. (The `k`th eigenvector can be obtained from the slice `F.vectors[:, k]`.)
36+
37+
Iterating the decomposition produces the components `F.values` and `F.vectors`.
38+
39+
`alg` specifies which algorithm and LAPACK method to use for eigenvalue decomposition:
40+
- `alg = DivideAndConquer()` (default): Calls `LAPACK.syevd!`.
41+
- `alg = QRIteration()`: Calls `LAPACK.syev!`.
42+
- `alg = RobustRepresentations()`: Multiple relatively robust representations method, Calls `LAPACK.syevr!`.
43+
44+
See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
45+
a comparison of the accuracy and performance of different algorithms.
46+
47+
The default `alg` used may change in the future.
48+
49+
!!! compat "Julia 1.12"
50+
The `alg` keyword argument requires Julia 1.12 or later.
51+
52+
The following functions are available for `Eigen` objects: [`inv`](@ref), [`det`](@ref), and [`isposdef`](@ref).
53+
"""
54+
function eigen(A::RealHermSymComplexHerm, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
55+
S = eigtype(eltype(A))
56+
eigen!(eigencopy_oftype(A, S), alg; sortby)
57+
end
58+
59+
2460
eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, irange::UnitRange) =
2561
Eigen(LAPACK.syevr!('V', 'I', A.uplo, A.data, 0.0, 0.0, irange.start, irange.stop, -1.0)...)
2662

@@ -71,17 +107,42 @@ function eigen(A::RealHermSymComplexHerm, vl::Real, vh::Real)
71107
eigen!(eigencopy_oftype(A, S), vl, vh)
72108
end
73109

74-
function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing)
75-
vals = LAPACK.syevr!('N', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)[1]
110+
111+
function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
112+
vals::Vector{real(eltype(A))} = if alg === DivideAndConquer()
113+
LAPACK.syevd!('N', A.uplo, A.data)
114+
elseif alg === QRIteration()
115+
LAPACK.syev!('N', A.uplo, A.data)
116+
elseif alg === RobustRepresentations()
117+
LAPACK.syevr!('N', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)[1]
118+
else
119+
throw(ArgumentError("Unsupported value for `alg` keyword."))
120+
end
76121
!isnothing(sortby) && sort!(vals, by=sortby)
77122
return vals
78123
end
79124

80-
function eigvals(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing)
125+
"""
126+
eigvals(A::Union{Hermitian, Symmetric}, alg::Algorithm = default_eigen_alg(A))) -> values
127+
128+
Return the eigenvalues of `A`.
129+
130+
`alg` specifies which algorithm and LAPACK method to use for eigenvalue decomposition:
131+
- `alg = DivideAndConquer()` (default): Calls `LAPACK.syevd!`.
132+
- `alg = QRIteration()`: Calls `LAPACK.syev!`.
133+
- `alg = RobustRepresentations()`: Multiple relatively robust representations method, Calls `LAPACK.syevr!`.
134+
135+
See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
136+
a comparison of the accuracy and performance of different methods.
137+
138+
The default `alg` used may change in the future.
139+
"""
140+
function eigvals(A::RealHermSymComplexHerm, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
81141
S = eigtype(eltype(A))
82-
eigvals!(eigencopy_oftype(A, S), sortby=sortby)
142+
eigvals!(eigencopy_oftype(A, S), alg; sortby)
83143
end
84144

145+
85146
"""
86147
eigvals!(A::Union{SymTridiagonal, Hermitian, Symmetric}, irange::UnitRange) -> values
87148

stdlib/LinearAlgebra/test/symmetric.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,22 @@ end
720720
end
721721
end
722722

723+
@testset "eigendecomposition Algorithms" begin
724+
using LinearAlgebra: DivideAndConquer, QRIteration, RobustRepresentations
725+
for T in (Float64, ComplexF64, Float32, ComplexF32)
726+
n = 4
727+
A = T <: Real ? Symmetric(randn(T, n, n)) : Hermitian(randn(T, n, n))
728+
d, v = eigen(A)
729+
for alg in (DivideAndConquer(), QRIteration(), RobustRepresentations())
730+
@test (@inferred eigvals(A, alg)) d
731+
d2, v2 = @inferred eigen(A, alg)
732+
@test d2 d
733+
@test A * v2 v2 * Diagonal(d2)
734+
end
735+
end
736+
end
737+
738+
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
723739
isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl"))
724740
using .Main.ImmutableArrays
725741

0 commit comments

Comments
 (0)