Skip to content
This repository was archived by the owner on Apr 26, 2021. It is now read-only.

Commit ad9be33

Browse files
committed
check for zeros on the diagonal, implement wide form
1 parent 5a57584 commit ad9be33

File tree

3 files changed

+191
-29
lines changed

3 files changed

+191
-29
lines changed

src/GenericSVD.jl

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import Base: SVD, svdvals!, svdfact!
55
include("utils.jl")
66
include("bidiagonalize.jl")
77

8-
9-
108
function svdfact!(X; sorted=true, thin=true)
119
m,n = size(X)
12-
m >= n || error("Generic SVD requires more rows than columns.")
10+
t =false
11+
if m < n
12+
m,n = n,m
13+
X = X'
14+
t = true
15+
end
1316
B,P = bidiagonalize_tall!(X)
1417
U,Vt = full(P,thin=thin)
1518
U,S,Vt = svd!(B,U,Vt)
@@ -27,11 +30,15 @@ function svdfact!(X; sorted=true, thin=true)
2730
U = U[:,I]
2831
Vt = Vt[I,:]
2932
end
30-
SVD(U,S,Vt)
33+
t ? SVD(Vt',S,U') : SVD(U,S,Vt)
3134
end
3235

3336
function svdvals!(X; sorted=true)
34-
B,P = bidiagonalize_tall!(copy(X))
37+
m,n = size(X)
38+
if m < n
39+
X = X'
40+
end
41+
B,P = bidiagonalize_tall!(X)
3542
S = svd!(B)[2]
3643
for i = eachindex(S)
3744
if signbit(S[i])
@@ -45,11 +52,16 @@ end
4552
"""
4653
Tests if the B[i-1,i] element is approximately zero, using the criteria
4754
```math
48-
|B_{i-1,i}| < ɛ*(|B_{i-1,i-1}| + |B_{i,i}|)
55+
|B_{i-1,i}| ɛ*(|B_{i-1,i-1}| + |B_{i,i}|)
4956
```
5057
"""
51-
offdiag_approx_zero(B::Bidiagonal,i,ɛ) =
52-
abs(B.ev[i-1]) < ɛ*(abs(B.dv[i-1]) + abs(B.dv[i]))
58+
function offdiag_approx_zero(B::Bidiagonal,i,ɛ)
59+
iszero = abs(B.ev[i-1]) ɛ*(abs(B.dv[i-1]) + abs(B.dv[i]))
60+
if iszero
61+
B.ev[i-1] = 0
62+
end
63+
iszero
64+
end
5365

5466

5567
"""
@@ -70,34 +82,112 @@ function svd!{T<:Real}(B::Bidiagonal{T}, U=nothing, Vt=nothing, ɛ::T = eps(T))
7082
n = size(B, 1)
7183
n₂ = n
7284

85+
maxB = max(maxabs(B.dv),maxabs(B.ev))
86+
7387
if istriu(B)
7488
while true
89+
@label mainloop
90+
7591
while offdiag_approx_zero(B,n₂,ɛ)
7692
n₂ -= 1
7793
if n₂ == 1
78-
return U,B.dv,Vt
94+
@goto done
7995
end
8096
end
97+
98+
99+
81100
n₁ = n₂ - 1
101+
# check for diagonal zeros
102+
if abs(B.dv[n₁]) ɛ*maxB
103+
svd_zerodiag_row!(U,B,n₁,n₂)
104+
@goto mainloop
105+
end
82106
while n₁ > 1 && !offdiag_approx_zero(B,n₁,ɛ)
83107
n₁ -= 1
108+
# check for diagonal zeros
109+
if abs(B.dv[n₁]) ɛ*maxB
110+
svd_zerodiag_row!(U,B,n₁,n₂)
111+
@goto mainloop
112+
end
113+
end
114+
115+
if abs(B.dv[n₂]) ɛ*maxB
116+
svd_zerodiag_col!(B,Vt,n₁,n₂)
117+
@goto mainloop
84118
end
85119

86-
# TODO: check for diagonal zeros
87120

88121
d₁ = B.dv[n₂-1]
89122
d₂ = B.dv[n₂]
90123
e = B.ev[n₂-1]
91-
124+
92125
s₁, s₂ = svdvals2x2(d₁, d₂, e)
93-
# use singular value closest to
126+
# use singular value closest to sqrt of final element of B'*B
94127
h = hypot(d₂,e)
95-
shift = abs(s₁-h) < abs(s₂-h) ? s₁ : s₂
128+
shift = abs(s₁-h) < abs(s₂-h) ? s₁ : s₂
96129
svd_gk!(B, U, Vt, n₁, n₂, shift)
97130
end
98131
else
99132
throw(ArgumentError("lower bidiagonal version not implemented yet"))
100133
end
134+
@label done
135+
U, B.dv, Vt
136+
end
137+
138+
139+
"""
140+
Sets B[n₁,n₁] to zero, then zeros out row n₁ by applying sequential row (left) Givens rotations up to n₂.
141+
"""
142+
function svd_zerodiag_row!(U,B,n₁,n₂)
143+
e = B.ev[n₁]
144+
B.dv[n₁] = 0 # set to zero
145+
B.ev[n₁] = 0
146+
147+
for i = n₁+1:n₂
148+
# n₁ [0 ,e ] = G * [e ,0 ]
149+
# [ ... ] [ ... ]
150+
# i [dᵢ,eᵢ] [dᵢ,eᵢ]
151+
dᵢ = B.dv[i]
152+
153+
G,r = givens(dᵢ,e,i,n₁)
154+
A_mul_Bc!(U,G)
155+
B.dv[i] = r # -G.s*e + G.c*dᵢ
156+
157+
if i < n₂
158+
eᵢ = B.ev[i]
159+
e = G.s*eᵢ
160+
B.ev[i] = G.c*eᵢ
161+
end
162+
end
163+
end
164+
165+
166+
"""
167+
Sets B[n₂,n₂] to zero, then zeros out column n₂ by applying sequential column (right) Givens rotations up to n₁.
168+
"""
169+
function svd_zerodiag_col!(B,Vt,n₁,n₂)
170+
e = B.ev[n₂-1]
171+
B.dv[n₂] = 0 # set to zero
172+
B.ev[n₂-1] = 0
173+
174+
for i = n₂-1:-1:n₁
175+
# i n₂ i n₂
176+
# [eᵢ,...,e ] = [eᵢ,...,0 ] * G'
177+
# [dᵢ,...,0 ] [dᵢ,...,e ]
178+
dᵢ = B.dv[i]
179+
180+
G,r = givens(dᵢ,e,i,n₂)
181+
A_mul_B!(G,Vt)
182+
183+
B.dv[i] = r # G.c*dᵢ + G.s*e
184+
185+
if n₁ < i
186+
eᵢ = B.ev[i-1]
187+
e = -G.s*eᵢ
188+
B.ev[i-1] = G.c*eᵢ
189+
end
190+
end
101191
end
102192

103193

@@ -110,15 +200,15 @@ A Givens rotation is applied to the top 2x2 matrix, and the resulting "bulge" is
110200
function svd_gk!{T<:Real}(B::Bidiagonal{T},U,Vt,n₁,n₂,shift)
111201

112202
if istriu(B)
113-
203+
114204
d₁′ = B.dv[n₁]
115205
e₁′ = B.ev[n₁]
116206
d₂′ = B.dv[n₁+1]
117207

118208
G, r = givens(d₁′ - abs2(shift)/d₁′, e₁′, n₁, n₁+1)
119209
A_mul_B!(G, Vt)
120210

121-
# [d₁,e₁] = [d₁′,e₁′] * G
211+
# [d₁,e₁] = [d₁′,e₁′] * G'
122212
# [b ,d₂] [0 ,d₂′]
123213

124214

@@ -129,48 +219,48 @@ function svd_gk!{T<:Real}(B::Bidiagonal{T},U,Vt,n₁,n₂,shift)
129219

130220
for i = n₁:n₂-2
131221

132-
# [. ,e₁′,b′ ] = G * [d₁,e₁,0 ]
222+
# [. ,e₁′,b′ ] = G * [d₁,e₁,0 ]
133223
# [0 ,d₂′,e₂′] [b ,d₂,e₂]
134224

135225
e₂ = B.ev[i+1]
136226

137227
G, r = givens(d₁, b, i, i+1)
138228
A_mul_Bc!(U, G)
139229

140-
B.dv[i] = G.c*d₁ + G.s*b
141-
230+
B.dv[i] = r # G.c*d₁ + G.s*b
231+
142232
e₁′ = G.c*e₁ + G.s*d₂
143233
d₂′ = -G.s*e₁ + G.c*d₂
144-
234+
145235
b′ = G.s*e₂
146236
e₂′ = G.c*e₂
147237

148-
# [. ,0 ] = [e₁′,b′ ] * G
238+
# [. ,0 ] = [e₁′,b′ ] * G'
149239
# [d₁,e₁] [d₂′,e₂′]
150240
# [b ,d₂] [0 ,d₃′]
151241

152242
d₃′ = B.dv[i+2]
153243

154244
G, r = givens(e₁′, b′, i+1, i+2)
155245
A_mul_B!(G, Vt)
156-
157-
B.ev[i] = e₁′*G.c + b′*G.s
158-
246+
247+
B.ev[i] = r # e₁′*G.c + b′*G.s
248+
159249
d₁ = d₂′*G.c + e₂′*G.s
160250
e₁ = -d₂′*G.s + e₂′*G.c
161251

162252
b = d₃′*G.s
163253
d₂ = d₃′*G.c
164254
end
165255

166-
# [. ,.] = G * [d₁,e₁]
256+
# [. ,.] = G * [d₁,e₁]
167257
# [0 ,.] [b ,d₂]
168258

169259
G, r = givens(d₁,b,n₂-1,n₂)
170260
A_mul_Bc!(U, G)
171-
172-
B.dv[n₂-1] = G.c*d₁ + G.s*b
173-
261+
262+
B.dv[n₂-1] = r # G.c*d₁ + G.s*b
263+
174264
B.ev[n₂-1] = G.c*e₁ + G.s*d₂
175265
B.dv[n₂] = -G.s*e₁ + G.c*d₂
176266
else
@@ -198,7 +288,7 @@ function svdvals2x2(f, h, g)
198288

199289
fhmin = min(fa,ha)
200290
fhmax = max(fa,ha)
201-
291+
202292
if fhmin == 0
203293
ssmin = zero(f)
204294
if fhmax == 0

src/utils.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,17 @@ function A_ldiv_B!{Ta,Tb}(A::SVD{Ta}, B::StridedVecOrMat{Tb})
3030
k = searchsortedlast(A.S, eps(real(Ta))*A.S[1], rev=true)
3131
sub(A.Vt,1:k,:)' * (sub(A.S,1:k) .\ (sub(A.U,:,1:k)' * B))
3232
end
33+
34+
35+
# we have to define our own givens function due to ordering restriction in Base (#14936)
36+
function givens{T}(f::T, g::T, i1::Integer, i2::Integer)
37+
if i1 == i2
38+
throw(ArgumentError("Indices must be distinct."))
39+
end
40+
c, s, r = Base.LinAlg.givensAlgorithm(f, g)
41+
if i1 > i2
42+
s = -s
43+
i1,i2 = i2,i1
44+
end
45+
Base.LinAlg.Givens(i1, i2, convert(T, c), convert(T, s)), r
46+
end

test/runtests.jl

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,79 @@ x,y = GenericSVD.svdvals2x2(a,b,c)
1212
@test sort(sqrt(eigvals(U'*U))) [x,y]
1313
@test sort(svdvals(U)) [x,y]
1414

15+
U = eye(3)
16+
B = Bidiagonal([0.0,1.0,2.0],[3.0,4.0],true)
17+
B1 = copy(B)
18+
19+
GenericSVD.svd_zerodiag_row!(U,B,1,3)
20+
@test B[1,1] == 0
21+
@test B[1,2] == 0
22+
@test U*full(B) B1
23+
24+
Vt = eye(3)
25+
B = Bidiagonal([1.0,2.0,0.0],[3.0,4.0],true)
26+
B1 = copy(B)
27+
28+
GenericSVD.svd_zerodiag_col!(B,Vt,1,3)
29+
@test B[3,3] == 0
30+
@test B[2,3] == 0
31+
@test full(B)*Vt B1
32+
33+
34+
1535
n,m = 100,20
1636

1737
X = randn(n,m)
1838
bX = big(X)
1939
bS = svdfact(bX)
2040
@test isapprox(full(bS), bX, rtol=1e3*eps(BigFloat))
2141
@test isapprox(svdvals(bX), svdvals(X), rtol=1e3*eps())
22-
42+
@test bX == X # check we didn't modify the input
2343

2444
bY = big(randn(n))
2545
@test isapprox(qrfact(bX,Val{false}) \ bY, bS \ bY, rtol=1e3*eps(BigFloat))
46+
@test bX == X # check we didn't modify the input
47+
48+
bXt = bX'
49+
bSt = svdfact(bXt)
50+
@test isapprox(full(bSt), bXt, rtol=1e3*eps(BigFloat))
51+
@test isapprox(svdvals(bXt), svdvals(X), rtol=1e3*eps())
52+
@test bXt == X' # check we didn't modify the input
53+
54+
X = Float64[1 2 0; 0 1 2; 0 0 0]
55+
bX = big(X)
56+
bS = svdfact(bX)
57+
@test isapprox(full(bS), bX, rtol=1e3*eps(BigFloat))
58+
@test isapprox(svdvals(bX), svdvals(X), rtol=1e3*eps())
59+
@test bX == X # check we didn't modify the input
60+
61+
X = Float64[0 2 0; 0 1 2; 0 0 1]
62+
bX = big(X)
63+
bS = svdfact(bX)
64+
@test isapprox(full(bS), bX, rtol=1e3*eps(BigFloat))
65+
@test isapprox(svdvals(bX), svdvals(X), rtol=1e3*eps())
66+
@test bX == X # check we didn't modify the input
67+
68+
69+
bD = big(randn(m))
70+
bX = diagm(bD)
71+
bS = svdfact(bX)
72+
73+
@test isapprox(full(bS), bX, rtol=1e3*eps(BigFloat))
74+
@test bS.S == sort(abs(bD),rev=true)
75+
76+
77+
2678

2779
X = randn(n,m)+im*randn(n,m)
2880
bX = big(X)
2981
bS = svdfact(bX)
3082
@test isapprox(full(bS), bX, rtol=1e3*eps(BigFloat))
3183
@test isapprox(svdvals(bX), svdvals(X), rtol=1e3*eps())
84+
@test bX == X # check we didn't modify the input
3285

86+
bXt = bX'
87+
bSt = svdfact(bXt)
88+
@test isapprox(full(bSt), bXt, rtol=1e3*eps(BigFloat))
89+
@test isapprox(svdvals(bXt), svdvals(X), rtol=1e3*eps())
90+
@test bXt == X' # check we didn't modify the input

0 commit comments

Comments
 (0)