Skip to content

Commit

Permalink
RFC: treat small negative λ as 0 for sqrt(::Hermitian) (#35057)
Browse files Browse the repository at this point in the history
* treat small negative λ as 0 for sqrt(::Hermitian) and log(::Hermitian)

* typo

* added tests, docs; removed rtol argument for log

* don't ask for rtol so close to eps(Float64)
  • Loading branch information
stevengj authored May 7, 2020
1 parent f7de6d2 commit 0c388fc
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 8 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Standard library changes
* The BLAS submodule now supports the level-2 BLAS subroutine `hpmv!` ([#34211]).
* `normalize` now supports multidimensional arrays ([#34239]).
* `lq` factorizations can now be used to compute the minimum-norm solution to under-determined systems ([#34350]).
* `sqrt(::Hermitian)` now treats slightly negative eigenvalues as zero for nearly semidefinite matrices, and accepts a new `rtol` keyword argument for this tolerance ([#35057]).
* The BLAS submodule now supports the level-2 BLAS subroutine `spmv!` ([#34320]).
* The BLAS submodule now supports the level-1 BLAS subroutine `rot!` ([#35124]).
* New generic `rotate!(x, y, c, s)` and `reflect!(x, y, c, s)` functions ([#35124]).
Expand Down
11 changes: 9 additions & 2 deletions stdlib/LinearAlgebra/src/dense.jl
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,15 @@ If `A` has no negative real eigenvalues, compute the principal matrix square roo
that is the unique matrix ``X`` with eigenvalues having positive real part such that
``X^2 = A``. Otherwise, a nonprincipal square root is returned.
If `A` is symmetric or Hermitian, its eigendecomposition ([`eigen`](@ref)) is
used to compute the square root. Otherwise, the square root is determined by means of the
If `A` is real-symmetric or Hermitian, its eigendecomposition ([`eigen`](@ref)) is
used to compute the square root. For such matrices, eigenvalues λ that
appear to be slightly negative due to roundoff errors are treated as if they were zero
More precisely, matrices with all eigenvalues `≥ -rtol*(max |λ|)` are treated as semidefinite
(yielding a Hermitian square root), with negative eigenvalues taken to be zero.
`rtol` is a keyword argument to `sqrt` (in the Hermitian/real-symmetric case only) that
defaults to machine precision scaled by `size(A,1)`.
Otherwise, the square root is determined by means of the
Björck-Hammarling method [^BH83], which computes the complex Schur form ([`schur`](@ref))
and then the complex square root of the triangular factor.
Expand Down
17 changes: 11 additions & 6 deletions stdlib/LinearAlgebra/src/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1007,22 +1007,27 @@ end


for func in (:log, :sqrt)
# sqrt has rtol arg to handle matrices that are semidefinite up to roundoff errors
rtolarg = func === :sqrt ? Any[Expr(:kw, :(rtol::Real), :(eps(real(float(one(T))))*size(A,1)))] : Any[]
rtolval = func === :sqrt ? :(-maximum(abs, F.values) * rtol) : 0
@eval begin
function ($func)(A::HermOrSym{<:Real})
function ($func)(A::HermOrSym{T}; $(rtolarg...)) where {T<:Real}
F = eigen(A)
if all-> λ 0, F.values)
retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors'
λ₀ = $rtolval # treat λ ≥ λ₀ as "zero" eigenvalues up to roundoff
if all-> λ λ₀, F.values)
retmat = (F.vectors * Diagonal(($func).(max.(0, F.values)))) * F.vectors'
else
retmat = (F.vectors * Diagonal(($func).(complex.(F.values)))) * F.vectors'
end
return Symmetric(retmat)
end

function ($func)(A::Hermitian{<:Complex})
function ($func)(A::Hermitian{T}; $(rtolarg...)) where {T<:Complex}
n = checksquare(A)
F = eigen(A)
if all-> λ 0, F.values)
retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors'
λ₀ = $rtolval # treat λ ≥ λ₀ as "zero" eigenvalues up to roundoff
if all-> λ λ₀, F.values)
retmat = (F.vectors * Diagonal(($func).(max.(0, F.values)))) * F.vectors'
for i = 1:n
retmat[i,i] = real(retmat[i,i])
end
Expand Down
15 changes: 15 additions & 0 deletions stdlib/LinearAlgebra/test/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -663,4 +663,19 @@ end
@test LinearAlgebra.hermitian_type(Int) == Int
end

@testset "sqrt(nearly semidefinite)" begin
let A = [0.9999999999999998 4.649058915617843e-16 -1.3149405273715513e-16 9.9959579317056e-17; -8.326672684688674e-16 1.0000000000000004 2.9280733590254494e-16 -2.9993900031619594e-16; 9.43689570931383e-16 -1.339206523454095e-15 1.0000000000000007 -8.550505126287743e-16; -6.245004513516506e-16 -2.0122792321330962e-16 1.183061278035052e-16 1.0000000000000002],
B = [0.09648289218436859 0.023497875751503007 0.0 0.0; 0.023497875751503007 0.045787575150300804 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0],
C = Symmetric(A*B*A'), # semidefinite up to roundoff
Csqrt = sqrt(C)
@test Csqrt isa Symmetric{Float64}
@test Csqrt*Csqrt C rtol=1e-14
end
let D = Symmetric(Matrix(Diagonal([1 0; 0 -1e-14])))
@test sqrt(D) [1 0; 0 1e-7im] rtol=1e-14
@test sqrt(D, rtol=1e-13) [1 0; 0 0] rtol=1e-14
@test sqrt(D, rtol=1e-13)^2 D rtol=1e-13
end
end

end # module TestSymmetric

0 comments on commit 0c388fc

Please sign in to comment.