|
| 1 | +using LinearAlgebra: |
| 2 | + LinearAlgebra, Factorization, Algorithm, default_svd_alg, Adjoint, Transpose |
| 3 | +using BlockArrays: AbstractBlockMatrix, BlockedArray, BlockedMatrix, BlockedVector |
| 4 | +using BlockArrays: BlockLayout |
| 5 | + |
| 6 | +# Singular Value Decomposition: |
| 7 | +# need new type to deal with U and V having possible different types |
| 8 | +# this is basically a carbon copy of the LinearAlgebra implementation. |
| 9 | +# additionally, by default we implement a fallback to the LinearAlgebra implementation |
| 10 | +# in hope to support as many foreign types as possible that chose to extend those methods. |
| 11 | + |
| 12 | +# TODO: add this to MatrixFactorizations |
| 13 | +# TODO: decide where this goes |
| 14 | +# TODO: decide whether or not to restrict types to be blocked. |
| 15 | +""" |
| 16 | + SVD <: Factorization |
| 17 | +
|
| 18 | +Matrix factorization type of the singular value decomposition (SVD) of a matrix `A`. |
| 19 | +This is the return type of [`svd(_)`](@ref), the corresponding matrix factorization function. |
| 20 | +
|
| 21 | +If `F::SVD` is the factorization object, `U`, `S`, `V` and `Vt` can be obtained |
| 22 | +via `F.U`, `F.S`, `F.V` and `F.Vt`, such that `A = U * Diagonal(S) * Vt`. |
| 23 | +The singular values in `S` are sorted in descending order. |
| 24 | +
|
| 25 | +Iterating the decomposition produces the components `U`, `S`, and `V`. |
| 26 | +
|
| 27 | +# Examples |
| 28 | +```jldoctest |
| 29 | +julia> A = [1. 0. 0. 0. 2.; 0. 0. 3. 0. 0.; 0. 0. 0. 0. 0.; 0. 2. 0. 0. 0.] |
| 30 | +4×5 Matrix{Float64}: |
| 31 | + 1.0 0.0 0.0 0.0 2.0 |
| 32 | + 0.0 0.0 3.0 0.0 0.0 |
| 33 | + 0.0 0.0 0.0 0.0 0.0 |
| 34 | + 0.0 2.0 0.0 0.0 0.0 |
| 35 | +
|
| 36 | +julia> F = svd(A) |
| 37 | +SVD{Float64, Float64, Matrix{Float64}, Vector{Float64}} |
| 38 | +U factor: |
| 39 | +4×4 Matrix{Float64}: |
| 40 | + 0.0 1.0 0.0 0.0 |
| 41 | + 1.0 0.0 0.0 0.0 |
| 42 | + 0.0 0.0 0.0 1.0 |
| 43 | + 0.0 0.0 -1.0 0.0 |
| 44 | +singular values: |
| 45 | +4-element Vector{Float64}: |
| 46 | + 3.0 |
| 47 | + 2.23606797749979 |
| 48 | + 2.0 |
| 49 | + 0.0 |
| 50 | +Vt factor: |
| 51 | +4×5 Matrix{Float64}: |
| 52 | + -0.0 0.0 1.0 -0.0 0.0 |
| 53 | + 0.447214 0.0 0.0 0.0 0.894427 |
| 54 | + 0.0 -1.0 0.0 0.0 0.0 |
| 55 | + 0.0 0.0 0.0 1.0 0.0 |
| 56 | +
|
| 57 | +julia> F.U * Diagonal(F.S) * F.Vt |
| 58 | +4×5 Matrix{Float64}: |
| 59 | + 1.0 0.0 0.0 0.0 2.0 |
| 60 | + 0.0 0.0 3.0 0.0 0.0 |
| 61 | + 0.0 0.0 0.0 0.0 0.0 |
| 62 | + 0.0 2.0 0.0 0.0 0.0 |
| 63 | +
|
| 64 | +julia> u, s, v = F; # destructuring via iteration |
| 65 | +
|
| 66 | +julia> u == F.U && s == F.S && v == F.V |
| 67 | +true |
| 68 | +``` |
| 69 | +""" |
| 70 | +struct SVD{T,Tr,M<:AbstractArray{T},C<:AbstractVector{Tr},N<:AbstractArray{T}} <: |
| 71 | + Factorization{T} |
| 72 | + U::M |
| 73 | + S::C |
| 74 | + Vt::N |
| 75 | + function SVD{T,Tr,M,C,N}( |
| 76 | + U, S, Vt |
| 77 | + ) where {T,Tr,M<:AbstractArray{T},C<:AbstractVector{Tr},N<:AbstractArray{T}} |
| 78 | + Base.require_one_based_indexing(U, S, Vt) |
| 79 | + return new{T,Tr,M,C,N}(U, S, Vt) |
| 80 | + end |
| 81 | +end |
| 82 | +function SVD(U::AbstractArray{T}, S::AbstractVector{Tr}, Vt::AbstractArray{T}) where {T,Tr} |
| 83 | + return SVD{T,Tr,typeof(U),typeof(S),typeof(Vt)}(U, S, Vt) |
| 84 | +end |
| 85 | +function SVD{T}(U::AbstractArray, S::AbstractVector{Tr}, Vt::AbstractArray) where {T,Tr} |
| 86 | + return SVD( |
| 87 | + convert(AbstractArray{T}, U), |
| 88 | + convert(AbstractVector{Tr}, S), |
| 89 | + convert(AbstractArray{T}, Vt), |
| 90 | + ) |
| 91 | +end |
| 92 | + |
| 93 | +function SVD{T}(F::SVD) where {T} |
| 94 | + return SVD( |
| 95 | + convert(AbstractMatrix{T}, F.U), |
| 96 | + convert(AbstractVector{real(T)}, F.S), |
| 97 | + convert(AbstractMatrix{T}, F.Vt), |
| 98 | + ) |
| 99 | +end |
| 100 | +LinearAlgebra.Factorization{T}(F::SVD) where {T} = SVD{T}(F) |
| 101 | + |
| 102 | +# iteration for destructuring into components |
| 103 | +Base.iterate(S::SVD) = (S.U, Val(:S)) |
| 104 | +Base.iterate(S::SVD, ::Val{:S}) = (S.S, Val(:V)) |
| 105 | +Base.iterate(S::SVD, ::Val{:V}) = (S.V, Val(:done)) |
| 106 | +Base.iterate(::SVD, ::Val{:done}) = nothing |
| 107 | + |
| 108 | +function Base.getproperty(F::SVD, d::Symbol) |
| 109 | + if d === :V |
| 110 | + return getfield(F, :Vt)' |
| 111 | + else |
| 112 | + return getfield(F, d) |
| 113 | + end |
| 114 | +end |
| 115 | + |
| 116 | +function Base.propertynames(F::SVD, private::Bool=false) |
| 117 | + return private ? (:V, fieldnames(typeof(F))...) : (:U, :S, :V, :Vt) |
| 118 | +end |
| 119 | + |
| 120 | +Base.size(A::SVD, dim::Integer) = dim == 1 ? size(A.U, dim) : size(A.Vt, dim) |
| 121 | +Base.size(A::SVD) = (size(A, 1), size(A, 2)) |
| 122 | + |
| 123 | +function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, F::SVD) |
| 124 | + summary(io, F) |
| 125 | + println(io) |
| 126 | + println(io, "U factor:") |
| 127 | + show(io, mime, F.U) |
| 128 | + println(io, "\nsingular values:") |
| 129 | + show(io, mime, F.S) |
| 130 | + println(io, "\nVt factor:") |
| 131 | + return show(io, mime, F.Vt) |
| 132 | +end |
| 133 | + |
| 134 | +Base.adjoint(usv::SVD) = SVD(adjoint(usv.Vt), usv.S, adjoint(usv.U)) |
| 135 | +Base.transpose(usv::SVD) = SVD(transpose(usv.Vt), usv.S, transpose(usv.U)) |
| 136 | + |
| 137 | +# Conversion |
| 138 | +Base.AbstractMatrix(F::SVD) = (F.U * Diagonal(F.S)) * F.Vt |
| 139 | +Base.AbstractArray(F::SVD) = AbstractMatrix(F) |
| 140 | +Base.Matrix(F::SVD) = Array(AbstractArray(F)) |
| 141 | +Base.Array(F::SVD) = Matrix(F) |
| 142 | +SVD(usv::SVD) = usv |
| 143 | +SVD(usv::LinearAlgebra.SVD) = SVD(usv.U, usv.S, usv.Vt) |
| 144 | + |
| 145 | +# functions default to LinearAlgebra |
| 146 | +# ---------------------------------- |
| 147 | +""" |
| 148 | + svd!(A; full::Bool = false, alg::Algorithm = default_svd_alg(A)) -> SVD |
| 149 | +
|
| 150 | +`svd!` is the same as [`svd`](@ref), but saves space by |
| 151 | +overwriting the input `A`, instead of creating a copy. See documentation of [`svd`](@ref) for details. |
| 152 | +""" |
| 153 | +svd!(A; kwargs...) = SVD(LinearAlgebra.svd!(A; kwargs...)) |
| 154 | + |
| 155 | +""" |
| 156 | + svd(A; full::Bool = false, alg::Algorithm = default_svd_alg(A)) -> SVD |
| 157 | +
|
| 158 | +Compute the singular value decomposition (SVD) of `A` and return an `SVD` object. |
| 159 | +
|
| 160 | +`U`, `S`, `V` and `Vt` can be obtained from the factorization `F` with `F.U`, |
| 161 | +`F.S`, `F.V` and `F.Vt`, such that `A = U * Diagonal(S) * Vt`. |
| 162 | +The algorithm produces `Vt` and hence `Vt` is more efficient to extract than `V`. |
| 163 | +The singular values in `S` are sorted in descending order. |
| 164 | +
|
| 165 | +Iterating the decomposition produces the components `U`, `S`, and `V`. |
| 166 | +
|
| 167 | +If `full = false` (default), a "thin" SVD is returned. For an ``M |
| 168 | +\\times N`` matrix `A`, in the full factorization `U` is ``M \\times M`` |
| 169 | +and `V` is ``N \\times N``, while in the thin factorization `U` is ``M |
| 170 | +\\times K`` and `V` is ``N \\times K``, where ``K = \\min(M,N)`` is the |
| 171 | +number of singular values. |
| 172 | +
|
| 173 | +`alg` specifies which algorithm and LAPACK method to use for SVD: |
| 174 | +- `alg = DivideAndConquer()` (default): Calls `LAPACK.gesdd!`. |
| 175 | +- `alg = QRIteration()`: Calls `LAPACK.gesvd!` (typically slower but more accurate) . |
| 176 | +
|
| 177 | +!!! compat "Julia 1.3" |
| 178 | + The `alg` keyword argument requires Julia 1.3 or later. |
| 179 | +
|
| 180 | +# Examples |
| 181 | +```jldoctest |
| 182 | +julia> A = rand(4,3); |
| 183 | +
|
| 184 | +julia> F = svd(A); # Store the Factorization Object |
| 185 | +
|
| 186 | +julia> A ≈ F.U * Diagonal(F.S) * F.Vt |
| 187 | +true |
| 188 | +
|
| 189 | +julia> U, S, V = F; # destructuring via iteration |
| 190 | +
|
| 191 | +julia> A ≈ U * Diagonal(S) * V' |
| 192 | +true |
| 193 | +
|
| 194 | +julia> Uonly, = svd(A); # Store U only |
| 195 | +
|
| 196 | +julia> Uonly == U |
| 197 | +true |
| 198 | +``` |
| 199 | +""" |
| 200 | +svd(A; kwargs...) = |
| 201 | + SVD(svd!(eigencopy_oftype(A, LinearAlgebra.eigtype(eltype(A))); kwargs...)) |
| 202 | + |
| 203 | +LinearAlgebra.svdvals(usv::SVD{<:Any,T}) where {T} = (usv.S)::Vector{T} |
| 204 | + |
| 205 | +# Added here to avoid type-piracy |
| 206 | +eigencopy_oftype(A, S) = LinearAlgebra.eigencopy_oftype(A, S) |
0 commit comments