Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
1 change: 1 addition & 0 deletions stdlib/LinearAlgebra/src/LinearAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ end
abstract type Algorithm end
struct DivideAndConquer <: Algorithm end
struct QRIteration <: Algorithm end
struct RobustRepresentations <: Algorithm end

abstract type PivotingStrategy end
struct NoPivot <: PivotingStrategy end
Expand Down
5 changes: 0 additions & 5 deletions stdlib/LinearAlgebra/src/lapack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5651,11 +5651,6 @@ syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractM
Finds the eigenvalues (`jobz = N`) or eigenvalues and eigenvectors
(`jobz = V`) of a symmetric matrix `A`. If `uplo = U`, the upper triangle
of `A` is used. If `uplo = L`, the lower triangle of `A` is used.

Use the divide-and-conquer method, instead of the QR iteration used by
`syev!` or multiple relatively robust representations used by `syevr!`.
See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
a comparison of the accuracy and performatce of different methods.
"""
syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix)

Expand Down
5 changes: 3 additions & 2 deletions stdlib/LinearAlgebra/src/svd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ and `V` is ``N \\times N``, while in the thin factorization `U` is ``M
\\times K`` and `V` is ``N \\times K``, where ``K = \\min(M,N)`` is the
number of singular values.

If `alg = DivideAndConquer()` a divide-and-conquer algorithm is used to calculate the SVD.
Another (typically slower but more accurate) option is `alg = QRIteration()`.
`alg` specifies which algorithm and LAPACK method to use for SVD:
- `alg = DivideAndConquer()` (default): Calls `LAPACK.gesdd!`.
- `alg = QRIteration()`: Calls `LAPACK.gesvd!` (typically slower but more accurate) .

!!! compat "Julia 1.3"
The `alg` keyword argument requires Julia 1.3 or later.
Expand Down
77 changes: 69 additions & 8 deletions stdlib/LinearAlgebra/src/symmetriceigen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,51 @@
eigencopy_oftype(A::Hermitian, S) = Hermitian(copy_similar(A, S), sym_uplo(A.uplo))
eigencopy_oftype(A::Symmetric, S) = Symmetric(copy_similar(A, S), sym_uplo(A.uplo))

default_eigen_alg(A) = DivideAndConquer()

# Eigensolvers for symmetric and Hermitian matrices
eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) =
Eigen(sorteig!(LAPACK.syevr!('V', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)..., sortby)...)
function eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
if alg === DivideAndConquer()
Eigen(sorteig!(LAPACK.syevd!('V', A.uplo, A.data)..., sortby)...)
elseif alg === QRIteration()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't better to check for type?

elseif alg isa QRIteration

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. One could perhaps check with @code_typed eigen!(A, QRIteration()) and see if that constant is propagated and the other branches are eliminated. If not, then the type check should help.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine:

julia> @code_typed eigen!(A, LinearAlgebra.QRIteration())
CodeInfo(
1%1 = Base.getfield(A, :uplo)::Char%2 = Base.getfield(A, :data)::Matrix{Float64}%3 = invoke LinearAlgebra.LAPACK.syev!('V'::Char, %1::Char, %2::Matrix{Float64})::Union{Tuple{Vector{Float64}, Matrix{Float64}}, Vector{Float64}}%4 = Core._apply_iterate(Base.iterate, LinearAlgebra.sorteig!, %3, (nothing,))::Tuple{Vector{Float64}, Matrix{Float64}}%5 = Core.getfield(%4, 1)::Vector{Float64}%6 = Core.getfield(%4, 2)::Matrix{Float64}%7 = %new(Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}, %5, %6)::Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}
└──      return %7
) => Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}

julia> @code_typed eigen!(A, LinearAlgebra.RobustRepresentations())
CodeInfo(
1%1 = Base.getfield(A, :uplo)::Char%2 = Base.getfield(A, :data)::Matrix{Float64}%3 = invoke LinearAlgebra.LAPACK.syevr!('V'::Char, 'A'::Char, %1::Char, %2::Matrix{Float64}, 0.0::Float64, 0.0::Float64, 0::Int64, 0::Int64, -1.0::Float64)::Tuple{Vector{Float64}, Matrix{Float64}}%4 = Core.getfield(%3, 1)::Vector{Float64}%5 = Core.getfield(%3, 2)::Matrix{Float64}%6 = %new(Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}, %4, %5)::Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}
└──      return %6
) => Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}

Even the sorteig! is compiled away.

Eigen(sorteig!(LAPACK.syev!('V', A.uplo, A.data)..., sortby)...)
elseif alg === RobustRepresentations()
Eigen(sorteig!(LAPACK.syevr!('V', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)..., sortby)...)
else
throw(ArgumentError("Unsupported value for `alg` keyword."))
end
end

"""
eigen(A::Union{Hermitian, Symmetric}, alg::Algorithm = default_eigen_alg(A)) -> Eigen

Compute the eigenvalue decomposition of `A`, returning an [`Eigen`](@ref) factorization object `F`
which contains the eigenvalues in `F.values` and the eigenvectors in the columns of the
matrix `F.vectors`. (The `k`th eigenvector can be obtained from the slice `F.vectors[:, k]`.)

Iterating the decomposition produces the components `F.values` and `F.vectors`.

`alg` specifies which algorithm and LAPACK method to use for eigenvalue decomposition:
- `alg = DivideAndConquer()` (default): Calls `LAPACK.syevd!`.
- `alg = QRIteration()`: Calls `LAPACK.syev!`.
- `alg = RobustRepresentations()`: Multiple relatively robust representations method, Calls `LAPACK.syevr!`.

See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
a comparison of the accuracy and performance of different algorithms.

The default `alg` used may change in the future.

function eigen(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing)
!!! compat "Julia 1.10"
The `alg` keyword argument requires Julia 1.10 or later.

The following functions are available for `Eigen` objects: [`inv`](@ref), [`det`](@ref), and [`isposdef`](@ref).
"""
function eigen(A::RealHermSymComplexHerm, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
S = eigtype(eltype(A))
eigen!(eigencopy_oftype(A, S), sortby=sortby)
eigen!(eigencopy_oftype(A, S), alg; sortby)
end


eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, irange::UnitRange) =
Eigen(LAPACK.syevr!('V', 'I', A.uplo, A.data, 0.0, 0.0, irange.start, irange.stop, -1.0)...)

Expand Down Expand Up @@ -63,17 +99,42 @@ function eigen(A::RealHermSymComplexHerm, vl::Real, vh::Real)
eigen!(eigencopy_oftype(A, S), vl, vh)
end

function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing)
vals = LAPACK.syevr!('N', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)[1]

function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
vals::Vector{real(eltype(A))} = if alg === DivideAndConquer()
LAPACK.syevd!('N', A.uplo, A.data)
elseif alg === QRIteration()
LAPACK.syev!('N', A.uplo, A.data)
elseif alg === RobustRepresentations()
LAPACK.syevr!('N', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)[1]
else
throw(ArgumentError("Unsupported value for `alg` keyword."))
end
!isnothing(sortby) && sort!(vals, by=sortby)
return vals
end

function eigvals(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing)
"""
eigvals(A::Union{Hermitian, Symmetric}, alg::Algorithm = default_eigen_alg(A))) -> values

Return the eigenvalues of `A`.

`alg` specifies which algorithm and LAPACK method to use for eigenvalue decomposition:
- `alg = DivideAndConquer()` (default): Calls `LAPACK.syevd!`.
- `alg = QRIteration()`: Calls `LAPACK.syev!`.
- `alg = RobustRepresentations()`: Multiple relatively robust representations method, Calls `LAPACK.syevr!`.

See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for
a comparison of the accuracy and performance of different methods.

The default `alg` used may change in the future.
"""
function eigvals(A::RealHermSymComplexHerm, alg::Algorithm = default_eigen_alg(A); sortby::Union{Function,Nothing}=nothing)
S = eigtype(eltype(A))
eigvals!(eigencopy_oftype(A, S), sortby=sortby)
eigvals!(eigencopy_oftype(A, S), alg; sortby)
end


"""
eigvals!(A::Union{SymTridiagonal, Hermitian, Symmetric}, irange::UnitRange) -> values

Expand Down
16 changes: 16 additions & 0 deletions stdlib/LinearAlgebra/test/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,22 @@ end
end
end

@testset "eigendecomposition Algorithms" begin
using LinearAlgebra: DivideAndConquer, QRIteration, RobustRepresentations
for T in (Float64, ComplexF64, Float32, ComplexF32)
n = 4
A = T <: Real ? Symmetric(randn(T, n, n)) : Hermitian(randn(T, n, n))
d, v = eigen(A)
for alg in (DivideAndConquer(), QRIteration(), RobustRepresentations())
@test (@inferred eigvals(A, alg)) ≈ d
d2, v2 = @inferred eigen(A, alg)
@test d2 ≈ d
@test A * v2 ≈ v2 * Diagonal(d2)
end
end
end

const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl"))
using .Main.ImmutableArrays

Expand Down