Skip to content

Commit c92ad9e

Browse files
authored
Fix SVD convergence criteria and criteria for switching between the (#65)
* Fix SVD convergence criteria and criteria for switching between the zero-shift algorithm and the algorithm with shifts. The algorhtm now follows the criterion of LAWN for switching between the two algorithms. Fixes #54 * Include some unrelated cleanup in the general eigensolver * Test on latest release
1 parent 83280b3 commit c92ad9e

File tree

4 files changed

+59
-34
lines changed

4 files changed

+59
-34
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ os:
55

66
julia:
77
- 1.0
8-
- 1.2
8+
- 1
99
- nightly
1010

1111
matrix:

src/eigenGeneral.jl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,6 @@ function _schur!(
116116
# Set the subdiagonal element to zero to signal that a split has taken place
117117
HH[istart, istart - 1] = 0
118118
break
119-
# elseif _istart > 1 && abs(HH[_istart, _istart - 1]) <= tol*(abs(HH[_istart - 1, _istart - 1]) + abs(HH[_istart, _istart]))
120-
121-
# debug && @printf("Split! Next subdiagonal element is: %10.3e and istart now %6d\n", HH[_istart, _istart - 1], _istart)
122-
# HH[_istart, _istart - 1]=0
123-
# istart = _istart
124-
# break
125119
end
126120

127121
# If no splits have taken place then we start from the top

src/svd.jl

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,28 @@ function svdDemmelKahan!(B::Bidiagonal{T}, n1, n2, U = nothing, Vᴴ = nothing)
102102
return B
103103
end
104104

105+
# Recurrence to estimate smallest singular value from LAWN3 Lemma 1
106+
function estimate_σ⁻(dv::AbstractVector, ev::AbstractVector, n1::Integer, n2::Integer)
107+
λ = abs(dv[n2])
108+
B∞ = λ
109+
for j in (n2 - 1):-1:n1
110+
λ = abs(dv[j])*/+ abs(ev[j])))
111+
B∞ = min(B∞, λ)
112+
end
113+
114+
μ = abs(dv[n1])
115+
B1 = μ
116+
for j in n1:(n2 - 1)
117+
μ = abs(dv[j + 1])**+ abs(ev[j])))
118+
B1 = min(B1, μ)
119+
end
120+
121+
return min(B∞, B1)
122+
end
123+
105124
# The actual SVD solver routine
106125
# Notice that this routine doesn't adjust the sign and sorts the values
107-
function __svd!(B::Bidiagonal{T}, U = nothing, Vᴴ = nothing; tol = eps(T), debug = false) where T<:Real
126+
function __svd!(B::Bidiagonal{T}, U = nothing, Vᴴ = nothing; tol = 100eps(T), debug = false) where T<:Real
108127

109128
n = size(B, 1)
110129
n1 = 1
@@ -113,22 +132,21 @@ function __svd!(B::Bidiagonal{T}, U = nothing, Vᴴ = nothing; tol = eps(T), deb
113132
e = B.ev
114133
count = 0
115134

135+
# See LAWN3 page 6 and 22
136+
σ⁻ = estimate_σ⁻(d, e, n1, n2)
137+
fudge = n
138+
thresh = tol*σ⁻
139+
116140
if istriu(B)
117141
while true
118142

119143
# Search for biggest index for non-zero off diagonal value in e
120144
for n2i = n2:-1:1
121145
if n2i == 1
122146
# We are done!
123-
124147
return nothing
125148
else
126-
tolcritTop = tol * abs(d[n2i - 1] * d[n2i])
127-
128-
# debug && println("n2i=", n2i, ", d[n2i-1]=", d[n2i-1], ", d[n2i]=", d[n2i],
129-
# ", e[n2i-1]=", e[n2i-1], ", tolcritTop=", tolcritTop)
130-
131-
if abs(e[n2i - 1]) > tolcritTop
149+
if abs(e[n2i - 1]) > thresh
132150
n2 = n2i
133151
break
134152
end
@@ -137,40 +155,45 @@ function __svd!(B::Bidiagonal{T}, U = nothing, Vᴴ = nothing; tol = eps(T), deb
137155

138156
# Search for largest sub-bidiagonal matrix ending at n2
139157
for _n1 = (n2 - 1):-1:1
140-
141-
if _n1 == 1
158+
if abs(e[_n1]) < thresh
159+
n1 = _n1
160+
break
161+
elseif _n1 == 1
142162
n1 = _n1
143163
break
144-
else
145-
tolcritBottom = tol * abs(d[_n1 - 1] * d[_n1])
146-
147-
# debug && println("n1=", n1, ", d[n1]=", d[n1], ", d[n1-1]=", d[n1-1], ", e[n1-1]", e[n1-1],
148-
# ", tolcritBottom=", tolcritBottom)
149-
150-
if abs(e[_n1 - 1]) < tolcritBottom
151-
n1 = _n1
152-
break
153-
end
154164
end
155165
end
156166

157-
debug && println("n1=", n1, ", n2=", n2, ", d[n1]=", d[n1], ", d[n2]=", d[n2], ", e[n1]=", e[n1])
167+
debug && println("n1=", n1, ", n2=", n2, ", d[n1]=", d[n1], ", d[n2]=", d[n2], ", e[n1]=", e[n1], " e[n2]=", e[n2-1], " thresh=", thresh)
158168

159-
# FixMe! Calling the zero shifted version only the first time is adhoc but seems
160-
# to work well. Reexamine analysis in LAWN 3 to improve this later.
161-
if count == 0
169+
170+
# See LAWN 3 p 18 and
171+
# We approximate the smallest and largest singular values of the
172+
# current block to determine if it's safe to use shift or if
173+
# the zero shift algorithm is required to maintain high relative
174+
# accuracy
175+
σ⁻ = estimate_σ⁻(d, e, n1, n2)
176+
σ⁺ = max(maximum(view(d, n1:n2)), maximum(view(e, n1:(n2 - 1))))
177+
178+
if fudge*tol*σ⁻ <= eps(σ⁺)
162179
svdDemmelKahan!(B, n1, n2, U, Vᴴ)
163180
else
164181
shift = svdvals2x2(d[n2 - 1], d[n2], e[n2 - 1])[1]
165-
svdIter!(B, n1, n2, shift, U, Vᴴ)
182+
if shift^2 < eps(B.dv[n1]^2)
183+
# Shift is too small to affect the iteration so just use
184+
# zero-shift algorithm anyway
185+
svdDemmelKahan!(B, n1, n2, U, Vᴴ)
186+
else
187+
svdIter!(B, n1, n2, shift, U, Vᴴ)
188+
end
166189
end
190+
167191
count += 1
168192
debug && println("count=", count)
169193
end
170194
else
171195
# Just transpose the matrix
172-
error("")
173-
# return _svd!(Bidiagonal(d, e, :U), Vᴴ, U; tol = tol, debug = debug)
196+
error("SVD for lower triangular bidiagonal matrices isn't implemented yet.")
174197
end
175198
end
176199

test/svd.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,12 @@ using Test, GenericLinearAlgebra, LinearAlgebra, Quaternions
4545
@test abs.(F.U'Float64.(Fbig.U)) I
4646
@test abs.(F.V'Float64.(Fbig.V)) I
4747
end
48+
49+
@testset "Issue 54" begin
50+
U0, _, V0 = svd(big.(reshape(0:15, 4, 4)))
51+
A = U0[:, 1:3] * V0[:, 1:3]'
52+
53+
U, S, V = svd(A)
54+
@test A U*Diagonal(S)*V'
55+
end
4856
end

0 commit comments

Comments
 (0)