Skip to content

Commit 0c388fc

Browse files
authored
RFC: treat small negative λ as 0 for sqrt(::Hermitian) (#35057)
* 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)
1 parent f7de6d2 commit 0c388fc

File tree

4 files changed

+36
-8
lines changed

4 files changed

+36
-8
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ Standard library changes
163163
* The BLAS submodule now supports the level-2 BLAS subroutine `hpmv!` ([#34211]).
164164
* `normalize` now supports multidimensional arrays ([#34239]).
165165
* `lq` factorizations can now be used to compute the minimum-norm solution to under-determined systems ([#34350]).
166+
* `sqrt(::Hermitian)` now treats slightly negative eigenvalues as zero for nearly semidefinite matrices, and accepts a new `rtol` keyword argument for this tolerance ([#35057]).
166167
* The BLAS submodule now supports the level-2 BLAS subroutine `spmv!` ([#34320]).
167168
* The BLAS submodule now supports the level-1 BLAS subroutine `rot!` ([#35124]).
168169
* New generic `rotate!(x, y, c, s)` and `reflect!(x, y, c, s)` functions ([#35124]).

stdlib/LinearAlgebra/src/dense.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,15 @@ If `A` has no negative real eigenvalues, compute the principal matrix square roo
710710
that is the unique matrix ``X`` with eigenvalues having positive real part such that
711711
``X^2 = A``. Otherwise, a nonprincipal square root is returned.
712712
713-
If `A` is symmetric or Hermitian, its eigendecomposition ([`eigen`](@ref)) is
714-
used to compute the square root. Otherwise, the square root is determined by means of the
713+
If `A` is real-symmetric or Hermitian, its eigendecomposition ([`eigen`](@ref)) is
714+
used to compute the square root. For such matrices, eigenvalues λ that
715+
appear to be slightly negative due to roundoff errors are treated as if they were zero
716+
More precisely, matrices with all eigenvalues `≥ -rtol*(max |λ|)` are treated as semidefinite
717+
(yielding a Hermitian square root), with negative eigenvalues taken to be zero.
718+
`rtol` is a keyword argument to `sqrt` (in the Hermitian/real-symmetric case only) that
719+
defaults to machine precision scaled by `size(A,1)`.
720+
721+
Otherwise, the square root is determined by means of the
715722
Björck-Hammarling method [^BH83], which computes the complex Schur form ([`schur`](@ref))
716723
and then the complex square root of the triangular factor.
717724

stdlib/LinearAlgebra/src/symmetric.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,22 +1007,27 @@ end
10071007

10081008

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

1021-
function ($func)(A::Hermitian{<:Complex})
1025+
function ($func)(A::Hermitian{T}; $(rtolarg...)) where {T<:Complex}
10221026
n = checksquare(A)
10231027
F = eigen(A)
1024-
if all-> λ 0, F.values)
1025-
retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors'
1028+
λ₀ = $rtolval # treat λ ≥ λ₀ as "zero" eigenvalues up to roundoff
1029+
if all-> λ λ₀, F.values)
1030+
retmat = (F.vectors * Diagonal(($func).(max.(0, F.values)))) * F.vectors'
10261031
for i = 1:n
10271032
retmat[i,i] = real(retmat[i,i])
10281033
end

stdlib/LinearAlgebra/test/symmetric.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,4 +663,19 @@ end
663663
@test LinearAlgebra.hermitian_type(Int) == Int
664664
end
665665

666+
@testset "sqrt(nearly semidefinite)" begin
667+
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],
668+
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],
669+
C = Symmetric(A*B*A'), # semidefinite up to roundoff
670+
Csqrt = sqrt(C)
671+
@test Csqrt isa Symmetric{Float64}
672+
@test Csqrt*Csqrt C rtol=1e-14
673+
end
674+
let D = Symmetric(Matrix(Diagonal([1 0; 0 -1e-14])))
675+
@test sqrt(D) [1 0; 0 1e-7im] rtol=1e-14
676+
@test sqrt(D, rtol=1e-13) [1 0; 0 0] rtol=1e-14
677+
@test sqrt(D, rtol=1e-13)^2 D rtol=1e-13
678+
end
679+
end
680+
666681
end # module TestSymmetric

0 commit comments

Comments
 (0)