@@ -92,21 +92,22 @@ default_svd_alg(A) = DivideAndConquer()
9292
9393
9494"""
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
9696
9797`svd!` is the same as [`svd`](@ref), but saves space by
9898overwriting the input `A`, instead of creating a copy. See documentation of [`svd`](@ref) for details.
9999"""
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 }
101101 m, n = size (A)
102102 if m == 0 || n == 0
103103 u, s, vt = (Matrix {T} (I, m, full ? m : n), real (zeros (T,0 )), Matrix {T} (I, n, n))
104104 else
105105 u, s, vt = _svd! (A, full, alg)
106106 end
107- SVD (u, s, vt)
107+ s[_count_svdvals (s, atol, rtol)+ 1 : end ] .= 0
108+ return SVD (u, s, vt)
108109end
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 }
110111 m = length (A)
111112 normA = norm (A)
112113 if iszero (normA)
@@ -116,6 +117,7 @@ function svd!(A::StridedVector{T}; full::Bool = false, alg::Algorithm = default_
116117 return SVD (reshape (A, (m, 1 )), [normA], ones (T, 1 , 1 ))
117118 else
118119 u, s, vt = _svd! (reshape (A, (m, 1 )), full, alg)
120+ s[_count_svdvals (s, atol, rtol)+ 1 : end ] .= 0
119121 return SVD (u, s, vt)
120122 end
121123end
@@ -129,10 +131,15 @@ function _svd!(A::StridedMatrix{T}, full::Bool, alg::QRIteration) where {T<:Blas
129131 u, s, vt = LAPACK. gesvd! (c, c, A)
130132end
131133
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
133140
134141"""
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
136143
137144Compute the singular value decomposition (SVD) of `A` and return an `SVD` object.
138145
@@ -151,11 +158,18 @@ number of singular values.
151158
152159`alg` specifies which algorithm and LAPACK method to use for SVD:
153160- `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`.
155166
156167!!! compat "Julia 1.3"
157168 The `alg` keyword argument requires Julia 1.3 or later.
158169
170+ !!! compat "Julia 1.13"
171+ The `atol` and `rtol` arguments require Julia 1.13 or later.
172+
159173# Examples
160174```jldoctest
161175julia> A = rand(4,3);
@@ -176,25 +190,25 @@ julia> Uonly == U
176190true
177191```
178192"""
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 )
181195end
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 )
184198 return SVD {T} (A)
185199end
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 ))
188202end
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 )
191205end
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 )
194208 return SVD (s. Vt' , s. S, s. U' )
195209end
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 )
198212 return SVD (transpose (s. Vt), s. S, transpose (s. U))
199213end
200214
@@ -248,7 +262,7 @@ svdvals(S::SVD{<:Any,T}) where {T} = (S.S)::Vector{T}
248262"""
249263 rank(S::SVD{<:Any, T}; atol::Real=0, rtol::Real=min(n,m)*ϵ) where {T}
250264
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
252266than `max(atol, rtol*σ₁)` where `σ₁` is the largest calculated singular value.
253267This is equivalent to the default [`rank(::AbstractMatrix)`](@ref) method except that it re-uses an existing SVD factorization.
254268`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`.
258272!!! compat "Julia 1.12"
259273 The `rank(::SVD)` method requires at least Julia 1.12.
260274"""
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))))))
262276 tol = max (atol, rtol* S. S[1 ])
263277 count (> (tol), S. S)
264278end
265279
266280# ## 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
271306 return B
272307end
273308
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)
275316 @inbounds for i in eachindex (F. S)
276317 iszero (F. S[i]) && throw (SingularException (i))
277318 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))))
280320end
281321
282322size (A:: SVD , dim:: Integer ) = dim == 1 ? size (A. U, dim) : size (A. Vt, dim)
0 commit comments