@@ -92,21 +92,22 @@ default_svd_alg(A) = DivideAndConquer()
92
92
93
93
94
94
"""
95
- svd!(A; full::Bool = false, alg::Algorithm = default_svd_alg(A)) -> SVD
95
+ svd!(A; full::Bool = false, alg::Algorithm = default_svd_alg(A), atol::Real=0, rtol::Real=0 ) -> SVD
96
96
97
97
`svd!` is the same as [`svd`](@ref), but saves space by
98
98
overwriting the input `A`, instead of creating a copy. See documentation of [`svd`](@ref) for details.
99
99
"""
100
- function svd! (A:: StridedMatrix{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A)) where {T<: BlasFloat }
100
+ function svd! (A:: StridedMatrix{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 ) where {T<: BlasFloat }
101
101
m, n = size (A)
102
102
if m == 0 || n == 0
103
103
u, s, vt = (Matrix {T} (I, m, full ? m : n), real (zeros (T,0 )), Matrix {T} (I, n, n))
104
104
else
105
105
u, s, vt = _svd! (A, full, alg)
106
106
end
107
- SVD (u, s, vt)
107
+ s[_count_svdvals (s, atol, rtol)+ 1 : end ] .= 0
108
+ return SVD (u, s, vt)
108
109
end
109
- function svd! (A:: StridedVector{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A)) where {T<: BlasFloat }
110
+ function svd! (A:: StridedVector{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 ) where {T<: BlasFloat }
110
111
m = length (A)
111
112
normA = norm (A)
112
113
if iszero (normA)
@@ -116,6 +117,7 @@ function svd!(A::StridedVector{T}; full::Bool = false, alg::Algorithm = default_
116
117
return SVD (reshape (A, (m, 1 )), [normA], ones (T, 1 , 1 ))
117
118
else
118
119
u, s, vt = _svd! (reshape (A, (m, 1 )), full, alg)
120
+ s[_count_svdvals (s, atol, rtol)+ 1 : end ] .= 0
119
121
return SVD (u, s, vt)
120
122
end
121
123
end
@@ -129,10 +131,15 @@ function _svd!(A::StridedMatrix{T}, full::Bool, alg::QRIteration) where {T<:Blas
129
131
u, s, vt = LAPACK. gesvd! (c, c, A)
130
132
end
131
133
132
-
134
+ # count positive singular values S ≥ given tolerances, S assumed sorted
135
+ function _count_svdvals (S, atol:: Real , rtol:: Real )
136
+ isempty (S) && return 0
137
+ tol = max (rtol * S[1 ], atol)
138
+ return iszero (S[1 ]) ? 0 : searchsortedlast (S, tol, rev= true )
139
+ end
133
140
134
141
"""
135
- svd(A; full::Bool = false, alg::Algorithm = default_svd_alg(A)) -> SVD
142
+ svd(A; full::Bool = false, alg::Algorithm = default_svd_alg(A), atol::Real=0, rtol::Real=0 ) -> SVD
136
143
137
144
Compute the singular value decomposition (SVD) of `A` and return an `SVD` object.
138
145
@@ -151,11 +158,18 @@ number of singular values.
151
158
152
159
`alg` specifies which algorithm and LAPACK method to use for SVD:
153
160
- `alg = LinearAlgebra.DivideAndConquer()` (default): Calls `LAPACK.gesdd!`.
154
- - `alg = LinearAlgebra.QRIteration()`: Calls `LAPACK.gesvd!` (typically slower but more accurate) .
161
+ - `alg = LinearAlgebra.QRIteration()`: Calls `LAPACK.gesvd!` (typically slower but more accurate).
162
+
163
+ The `atol` and `rtol` parameters specify optional tolerances to truncate the SVD,
164
+ dropping (setting to zero) singular values less than `max(atol, rtol*σ₁)` where
165
+ `σ₁` is the largest singular value of `A`.
155
166
156
167
!!! compat "Julia 1.3"
157
168
The `alg` keyword argument requires Julia 1.3 or later.
158
169
170
+ !!! compat "Julia 1.13"
171
+ The `atol` and `rtol` arguments require Julia 1.13 or later.
172
+
159
173
# Examples
160
174
```jldoctest
161
175
julia> A = rand(4,3);
@@ -176,25 +190,25 @@ julia> Uonly == U
176
190
true
177
191
```
178
192
"""
179
- function svd (A:: AbstractVecOrMat{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A)) where {T}
180
- svd! (eigencopy_oftype (A, eigtype (T)), full = full , alg = alg )
193
+ function svd (A:: AbstractVecOrMat{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 ) where {T}
194
+ svd! (eigencopy_oftype (A, eigtype (T)); full, alg, atol, rtol )
181
195
end
182
- function svd (A:: AbstractVecOrMat{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A)) where {T <: Union{Float16,Complex{Float16}} }
183
- A = svd! (eigencopy_oftype (A, eigtype (T)), full = full , alg = alg )
196
+ function svd (A:: AbstractVecOrMat{T} ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 ) where {T <: Union{Float16,Complex{Float16}} }
197
+ A = svd! (eigencopy_oftype (A, eigtype (T)); full, alg, atol, rtol )
184
198
return SVD {T} (A)
185
199
end
186
- function svd (x:: Number ; full:: Bool = false , alg:: Algorithm = default_svd_alg (x))
187
- SVD (x == 0 ? fill (one (x), 1 , 1 ) : fill (x/ abs (x), 1 , 1 ), [abs (x)], fill (one (x), 1 , 1 ))
200
+ function svd (x:: Number ; full:: Bool = false , alg:: Algorithm = default_svd_alg (x), atol :: Real = 0 , rtol :: Real = 0 )
201
+ SVD (iszero (x) || abs (x) < atol ? fill (one (x), 1 , 1 ) : fill (x/ abs (x), 1 , 1 ), [abs (x)], fill (one (x), 1 , 1 ))
188
202
end
189
- function svd (x:: Integer ; full:: Bool = false , alg:: Algorithm = default_svd_alg (x))
190
- svd (float (x), full = full , alg = alg )
203
+ function svd (x:: Integer ; full:: Bool = false , alg:: Algorithm = default_svd_alg (x), atol :: Real = 0 , rtol :: Real = 0 )
204
+ svd (float (x); full, alg, atol, rtol )
191
205
end
192
- function svd (A:: Adjoint ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A))
193
- s = svd (A. parent, full = full , alg = alg )
206
+ function svd (A:: Adjoint ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 )
207
+ s = svd (A. parent; full, alg, atol, rtol )
194
208
return SVD (s. Vt' , s. S, s. U' )
195
209
end
196
- function svd (A:: Transpose ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A))
197
- s = svd (A. parent, full = full , alg = alg )
210
+ function svd (A:: Transpose ; full:: Bool = false , alg:: Algorithm = default_svd_alg (A), atol :: Real = 0 , rtol :: Real = 0 )
211
+ s = svd (A. parent; full, alg, atol, rtol )
198
212
return SVD (transpose (s. Vt), s. S, transpose (s. U))
199
213
end
200
214
@@ -248,7 +262,7 @@ svdvals(S::SVD{<:Any,T}) where {T} = (S.S)::Vector{T}
248
262
"""
249
263
rank(S::SVD{<:Any, T}; atol::Real=0, rtol::Real=min(n,m)*ϵ) where {T}
250
264
251
- Compute the numerical rank of the SVD object `S` by counting how many singular values are greater
265
+ Compute the numerical rank of the SVD object `S` by counting how many singular values are greater
252
266
than `max(atol, rtol*σ₁)` where `σ₁` is the largest calculated singular value.
253
267
This is equivalent to the default [`rank(::AbstractMatrix)`](@ref) method except that it re-uses an existing SVD factorization.
254
268
`atol` and `rtol` are the absolute and relative tolerances, respectively.
@@ -258,25 +272,51 @@ and `ϵ` is the [`eps`](@ref) of the element type of `S`.
258
272
!!! compat "Julia 1.12"
259
273
The `rank(::SVD)` method requires at least Julia 1.12.
260
274
"""
261
- function rank (S:: SVD ; atol:: Real = 0. 0 , rtol:: Real = (min (size (S)... )* eps (real (float (eltype (S))))))
275
+ function rank (S:: SVD ; atol:: Real = 0 , rtol:: Real = (min (size (S)... )* eps (real (float (eltype (S))))))
262
276
tol = max (atol, rtol* S. S[1 ])
263
277
count (> (tol), S. S)
264
278
end
265
279
266
280
# ## SVD least squares ###
267
- function ldiv! (A:: SVD{T} , B:: AbstractVecOrMat ) where T
268
- m, n = size (A)
269
- k = searchsortedlast (A. S, eps (real (T))* A. S[1 ], rev= true )
270
- mul! (view (B, 1 : n, :), view (A. Vt, 1 : k, :)' , view (A. S, 1 : k) .\ (view (A. U, :, 1 : k)' * _cut_B (B, 1 : m)))
281
+ """
282
+ ldiv!(F::SVD, B; atol::Real=0, rtol::Real=atol>0 ? 0 : n*ϵ)
283
+
284
+ Given the SVD `F` of an ``m \\ times n`` matrix, multiply the first ``m`` rows of `B` in-place
285
+ by the Moore-Penrose pseudoinverse, storing the result in the first ``n`` rows of `B`, returning `B`.
286
+ This is equivalent to a least-squares solution (for ``m \\ ge n``) or a minimum-norm solution (for ``m \\ le n``).
287
+
288
+ Similar to the [`pinv`](@ref) function, the solution can be regularized by truncating the SVD,
289
+ dropping any singular values less than `max(atol, rtol*σ₁)` where `σ₁` is the largest singular value.
290
+ The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of `M`, and
291
+ `ϵ` is the [`eps`](@ref) of the element type of `M`.
292
+
293
+ !!! compat "Julia 1.13"
294
+ The `atol` and `rtol` arguments require Julia 1.13 or later.
295
+ """
296
+ function ldiv! (F:: SVD{T} , B:: AbstractVecOrMat ; atol:: Real = 0 , rtol:: Real = (eps (real (float (oneunit (T))))* min (size (F)... ))* iszero (atol)) where T
297
+ m, n = size (F)
298
+ k = _count_svdvals (F. S, atol, rtol)
299
+ if k == 0
300
+ B[1 : n, :] .= 0
301
+ else
302
+ temp = view (F. U, :, 1 : k)' * _cut_B (B, 1 : m)
303
+ ldiv! (Diagonal (view (F. S, 1 : k)), temp)
304
+ mul! (view (B, 1 : n, :), view (F. Vt, 1 : k, :)' , temp)
305
+ end
271
306
return B
272
307
end
273
308
274
- function inv (F:: SVD{T} ) where T
309
+ function pinv (F:: SVD{T} ; atol:: Real = 0 , rtol:: Real = (eps (real (float (oneunit (T))))* min (size (F)... ))* iszero (atol)) where T
310
+ k = _count_svdvals (F. S, atol, rtol)
311
+ @views (F. S[1 : k] .\ F. Vt[1 : k, :])' * F. U[:,1 : k]'
312
+ end
313
+
314
+ function inv (F:: SVD )
315
+ # TODO : checksquare(F)
275
316
@inbounds for i in eachindex (F. S)
276
317
iszero (F. S[i]) && throw (SingularException (i))
277
318
end
278
- k = searchsortedlast (F. S, eps (real (T))* F. S[1 ], rev= true )
279
- @views (F. S[1 : k] .\ F. Vt[1 : k, :])' * F. U[:,1 : k]'
319
+ pinv (F; rtol= eps (real (eltype (F))))
280
320
end
281
321
282
322
size (A:: SVD , dim:: Integer ) = dim == 1 ? size (A. U, dim) : size (A. Vt, dim)
0 commit comments