Skip to content

Commit 2d20279

Browse files
committed
Migrate changes from ITensor/ITensors.jl#1572
1 parent 1d4f398 commit 2d20279

File tree

6 files changed

+489
-0
lines changed

6 files changed

+489
-0
lines changed

src/BlockArraysExtensions/BlockArraysExtensions.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,28 @@ macro view!(expr)
598598
@capture(expr, array_[indices__])
599599
return :(view!($(esc(array)), $(esc.(indices)...)))
600600
end
601+
602+
# SVD additions
603+
# -------------
604+
using LinearAlgebra: Algorithm
605+
using BlockArrays: BlockedMatrix
606+
607+
# svd first calls `eigencopy_oftype` to create something that can be in-place SVD'd
608+
# Here, we hijack this system to determine if there is any structure we can exploit
609+
# default: SVD is most efficient with BlockedArray
610+
function eigencopy_oftype(A::AbstractBlockArray, S)
611+
return BlockedMatrix{S}(A)
612+
end
613+
614+
function svd!(A::BlockedMatrix; full::Bool=false, alg::Algorithm=default_svd_alg(A))
615+
F = svd!(parent(A); full, alg)
616+
617+
# restore block pattern
618+
m = length(F.S)
619+
bax1, bax2, bax3 = axes(A, 1), blockedrange([m]), axes(A, 2)
620+
621+
u = BlockedArray(F.U, (bax1, bax2))
622+
s = BlockedVector(F.S, (bax2,))
623+
vt = BlockedArray(F.Vt, (bax2, bax3))
624+
return SVD(u, s, vt)
625+
end

src/BlockSparseArrays.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
module BlockSparseArrays
2+
3+
# factorizations
4+
include("factorizations/svd.jl")
5+
6+
# possible upstream contributions
27
include("BlockArraysExtensions/BlockArraysExtensions.jl")
8+
9+
# interface functions that don't have to specialize
310
include("blocksparsearrayinterface/blocksparsearrayinterface.jl")
411
include("blocksparsearrayinterface/linearalgebra.jl")
512
include("blocksparsearrayinterface/getunstoredblock.jl")
@@ -8,6 +15,8 @@ include("blocksparsearrayinterface/map.jl")
815
include("blocksparsearrayinterface/arraylayouts.jl")
916
include("blocksparsearrayinterface/views.jl")
1017
include("blocksparsearrayinterface/cat.jl")
18+
19+
# functions defined for any abstractblocksparsearray
1120
include("abstractblocksparsearray/abstractblocksparsearray.jl")
1221
include("abstractblocksparsearray/wrappedabstractblocksparsearray.jl")
1322
include("abstractblocksparsearray/abstractblocksparsematrix.jl")
@@ -19,7 +28,12 @@ include("abstractblocksparsearray/broadcast.jl")
1928
include("abstractblocksparsearray/map.jl")
2029
include("abstractblocksparsearray/linearalgebra.jl")
2130
include("abstractblocksparsearray/cat.jl")
31+
32+
# functions specifically for BlockSparseArray
2233
include("blocksparsearray/defaults.jl")
2334
include("blocksparsearray/blocksparsearray.jl")
35+
include("blocksparsearray/blockdiagonalarray.jl")
36+
2437
include("BlockArraysSparseArraysBaseExt/BlockArraysSparseArraysBaseExt.jl")
38+
2539
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,22 @@
11
const AbstractBlockSparseMatrix{T} = AbstractBlockSparseArray{T,2}
2+
3+
# SVD is implemented by trying to
4+
# 1. Attempt to find a block-diagonal implementation by permuting
5+
# 2. Fallback to AbstractBlockArray implementation via BlockedArray
6+
function svd(
7+
A::AbstractBlockSparseMatrix; full::Bool=false, alg::Algorithm=default_svd_alg(A)
8+
)
9+
T = LinearAlgebra.eigtype(eltype(A))
10+
A′ = try_to_blockdiagonal(A)
11+
12+
if isnothing(A′)
13+
# not block-diagonal, fall back to dense case
14+
Adense = eigencopy_oftype(A, T)
15+
return svd!(Adense; full, alg)
16+
end
17+
18+
# compute block-by-block and permute back
19+
A″, (I, J) = A′
20+
F = svd!(eigencopy_oftype(A″, T); full, alg)
21+
return SVD(F.U[Block.(I), Block.(J)], F.S, F.Vt)
22+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# type alias for block-diagonal
2+
using LinearAlgebra: Diagonal
3+
4+
const BlockDiagonal{T,A,Axes,V<:AbstractVector{A}} = BlockSparseMatrix{
5+
T,A,Diagonal{A,V},Axes
6+
}
7+
8+
function BlockDiagonal(blocks::AbstractVector{<:AbstractMatrix})
9+
return BlockSparseArray(
10+
Diagonal(blocks), (blockedrange(size.(blocks, 1)), blockedrange(size.(blocks, 2)))
11+
)
12+
end
13+
14+
# Cast to block-diagonal implementation if permuted-blockdiagonal
15+
function try_to_blockdiagonal_perm(A)
16+
inds = map(x -> Int.(Tuple(x)), vec(collect(block_stored_indices(A))))
17+
I = first.(inds)
18+
allunique(I) || return nothing
19+
J = last.(inds)
20+
p = sortperm(J)
21+
Jsorted = J[p]
22+
allunique(Jsorted) || return nothing
23+
return Block.(I[p], Jsorted)
24+
end
25+
26+
"""
27+
try_to_blockdiagonal(A)
28+
29+
Attempt to find a permutation of blocks that makes `A` blockdiagonal. If unsuccesful,
30+
returns nothing, otherwise returns both the blockdiagonal `B` as well as the permutation `I, J`.
31+
"""
32+
function try_to_blockdiagonal(A::AbstractBlockSparseMatrix)
33+
perm = try_to_blockdiagonal_perm(A)
34+
isnothing(perm) && return perm
35+
I = first.(Tuple.(perm))
36+
J = last.(Tuple.(perm))
37+
diagblocks = map(invperm(I), J) do i, j
38+
return A[Block(i, j)]
39+
end
40+
return BlockDiagonal(diagblocks), perm
41+
end
42+
43+
# SVD implementation
44+
function eigencopy_oftype(A::BlockDiagonal, S)
45+
diag = map(Base.Fix2(eigencopy_oftype, S), A.blocks.diag)
46+
return BlockDiagonal(diag)
47+
end
48+
49+
function svd(A::BlockDiagonal; kwargs...)
50+
return svd!(eigencopy_oftype(A, LinearAlgebra.eigtype(eltype(A))); kwargs...)
51+
end
52+
function svd!(A::BlockDiagonal; full::Bool=false, alg::Algorithm=default_svd_alg(A))
53+
# TODO: handle full
54+
F = map(a -> svd!(a; full, alg), blocks(A).diag)
55+
Us = map(Base.Fix2(getproperty, :U), F)
56+
Ss = map(Base.Fix2(getproperty, :S), F)
57+
Vts = map(Base.Fix2(getproperty, :Vt), F)
58+
return SVD(BlockDiagonal(Us), mortar(Ss), BlockDiagonal(Vts))
59+
end

src/factorizations/svd.jl

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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

Comments
 (0)