Skip to content

Commit 004403d

Browse files
authored
[Enhancement] Add singular value decomposition (#16)
1 parent 7570c2c commit 004403d

File tree

10 files changed

+448
-4
lines changed

10 files changed

+448
-4
lines changed

Project.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name = "BlockSparseArrays"
22
uuid = "2c9a651f-6452-4ace-a6ac-809f4280fbb4"
33
authors = ["ITensor developers <[email protected]> and contributors"]
4-
version = "0.2.6"
4+
version = "0.2.7"
55

66
[deps]
77
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
88
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
99
BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e"
1010
DerivableInterfaces = "6c5e35bf-e59e-4898-b73c-732dcc4ba65f"
11+
DiagonalArrays = "74fd4be6-21e2-4f6f-823a-4360d37c7a77"
1112
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
1213
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1314
GradedUnitRanges = "e2de450a-8a67-46c7-b59c-01d5a3d041c5"
@@ -31,7 +32,8 @@ Adapt = "4.1.1"
3132
Aqua = "0.8.9"
3233
ArrayLayouts = "1.10.4"
3334
BlockArrays = "1.2.0"
34-
DerivableInterfaces = "0.3.7"
35+
DerivableInterfaces = "0.3.8"
36+
DiagonalArrays = "0.2.2"
3537
Dictionaries = "0.4.3"
3638
FillArrays = "1.13.0"
3739
GPUArraysCore = "0.1.0, 0.2"

docs/make.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ using BlockSparseArrays: BlockSparseArrays
22
using Documenter: Documenter, DocMeta, deploydocs, makedocs
33

44
DocMeta.setdocmeta!(
5-
BlockSparseArrays, :DocTestSetup, :(using BlockSparseArrays); recursive=true
5+
BlockSparseArrays,
6+
:DocTestSetup,
7+
quote
8+
using BlockSparseArrays
9+
using LinearAlgebra: Diagonal
10+
end;
11+
recursive=true,
612
)
713

814
include("make_index.jl")
@@ -16,7 +22,7 @@ makedocs(;
1622
edit_link="main",
1723
assets=String[],
1824
),
19-
pages=["Home" => "index.md"],
25+
pages=["Home" => "index.md", "Library" => "library.md"],
2026
)
2127

2228
deploydocs(;

docs/src/library.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Library
2+
3+
```@autodocs
4+
Modules = [BlockSparseArrays]
5+
```

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ export BlockSparseArray,
77
eachblockstoredindex,
88
eachstoredblock
99

10+
# factorizations
11+
include("factorizations/svd.jl")
12+
13+
# possible upstream contributions
1014
include("BlockArraysExtensions/BlockArraysExtensions.jl")
15+
16+
# interface functions that don't have to specialize
1117
include("blocksparsearrayinterface/blocksparsearrayinterface.jl")
1218
include("blocksparsearrayinterface/linearalgebra.jl")
1319
include("blocksparsearrayinterface/getunstoredblock.jl")
@@ -16,6 +22,8 @@ include("blocksparsearrayinterface/map.jl")
1622
include("blocksparsearrayinterface/arraylayouts.jl")
1723
include("blocksparsearrayinterface/views.jl")
1824
include("blocksparsearrayinterface/cat.jl")
25+
26+
# functions defined for any abstractblocksparsearray
1927
include("abstractblocksparsearray/abstractblocksparsearray.jl")
2028
include("abstractblocksparsearray/wrappedabstractblocksparsearray.jl")
2129
include("abstractblocksparsearray/abstractblocksparsematrix.jl")
@@ -27,8 +35,12 @@ include("abstractblocksparsearray/broadcast.jl")
2735
include("abstractblocksparsearray/map.jl")
2836
include("abstractblocksparsearray/linearalgebra.jl")
2937
include("abstractblocksparsearray/cat.jl")
38+
39+
# functions specifically for BlockSparseArray
3040
include("blocksparsearray/defaults.jl")
3141
include("blocksparsearray/blocksparsearray.jl")
42+
include("blocksparsearray/blockdiagonalarray.jl")
43+
3244
include("BlockArraysSparseArraysBaseExt/BlockArraysSparseArraysBaseExt.jl")
3345

3446
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,84 @@
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+
7+
function eigencopy_oftype(A::AbstractBlockSparseMatrix, T)
8+
if is_block_permutation_matrix(A)
9+
Acopy = similar(A, T)
10+
for bI in eachblockstoredindex(A)
11+
Acopy[bI] = eigencopy_oftype(@view!(A[bI]), T)
12+
end
13+
return Acopy
14+
else
15+
return BlockedMatrix{T}(A)
16+
end
17+
end
18+
19+
function is_block_permutation_matrix(a::AbstractBlockSparseMatrix)
20+
return allunique(first Tuple, eachblockstoredindex(a)) &&
21+
allunique(last Tuple, eachblockstoredindex(a))
22+
end
23+
24+
function _allocate_svd_output(A::AbstractBlockSparseMatrix, full::Bool, ::Algorithm)
25+
@assert !full "TODO"
26+
bm, bn = blocksize(A)
27+
bmn = min(bm, bn)
28+
29+
brows = blocklengths(axes(A, 1))
30+
bcols = blocklengths(axes(A, 2))
31+
slengths = Vector{Int}(undef, bmn)
32+
33+
# fill in values for blocks that are present
34+
bIs = collect(eachblockstoredindex(A))
35+
browIs = Int.(first.(Tuple.(bIs)))
36+
bcolIs = Int.(last.(Tuple.(bIs)))
37+
for bI in eachblockstoredindex(A)
38+
row, col = Int.(Tuple(bI))
39+
nrows = brows[row]
40+
ncols = bcols[col]
41+
slengths[col] = min(nrows, ncols)
42+
end
43+
44+
# fill in values for blocks that aren't present, pairing them in order of occurence
45+
# this is a convention, which at least gives the expected results for blockdiagonal
46+
emptyrows = setdiff(1:bm, browIs)
47+
emptycols = setdiff(1:bn, bcolIs)
48+
for (row, col) in zip(emptyrows, emptycols)
49+
slengths[col] = min(brows[row], bcols[col])
50+
end
51+
52+
s_axis = blockedrange(slengths)
53+
U = similar(A, axes(A, 1), s_axis)
54+
S = similar(A, real(eltype(A)), s_axis)
55+
Vt = similar(A, s_axis, axes(A, 2))
56+
57+
# also fill in identities for blocks that aren't present
58+
for (row, col) in zip(emptyrows, emptycols)
59+
copyto!(@view!(U[Block(row, col)]), LinearAlgebra.I)
60+
copyto!(@view!(Vt[Block(col, col)]), LinearAlgebra.I)
61+
end
62+
63+
return U, S, Vt
64+
end
65+
66+
function svd(A::AbstractBlockSparseMatrix; kwargs...)
67+
return svd!(eigencopy_oftype(A, LinearAlgebra.eigtype(eltype(A))); kwargs...)
68+
end
69+
70+
function svd!(
71+
A::AbstractBlockSparseMatrix; full::Bool=false, alg::Algorithm=default_svd_alg(A)
72+
)
73+
@assert is_block_permutation_matrix(A) "Cannot keep sparsity: use `svd` to convert to `BlockedMatrix"
74+
U, S, Vt = _allocate_svd_output(A, full, alg)
75+
for bI in eachblockstoredindex(A)
76+
bUSV = svd!(@view!(A[bI]); full, alg)
77+
brow, bcol = Tuple(bI)
78+
U[brow, bcol] = bUSV.U
79+
S[bcol] = bUSV.S
80+
Vt[bcol, bcol] = bUSV.Vt
81+
end
82+
83+
return SVD(U, S, Vt)
84+
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# type alias for block-diagonal
2+
using LinearAlgebra: Diagonal
3+
using DiagonalArrays: DiagonalArrays, diagonal
4+
5+
const BlockDiagonal{T,A,Axes,V<:AbstractVector{A}} = BlockSparseMatrix{
6+
T,A,Diagonal{A,V},Axes
7+
}
8+
const BlockSparseDiagonal{T,A<:AbstractBlockSparseVector{T}} = Diagonal{T,A}
9+
10+
@interface interface::BlockSparseArrayInterface function blocks(a::BlockSparseDiagonal)
11+
return Diagonal(Diagonal.(blocks(a.diag)))
12+
end
13+
14+
function BlockDiagonal(blocks::AbstractVector{<:AbstractMatrix})
15+
return BlockSparseArray(
16+
Diagonal(blocks), (blockedrange(size.(blocks, 1)), blockedrange(size.(blocks, 2)))
17+
)
18+
end
19+
20+
function DiagonalArrays.diagonal(S::BlockSparseVector)
21+
D = similar(S, (axes(S, 1), axes(S, 1)))
22+
for bI in eachblockstoredindex(S)
23+
D[bI, bI] = diagonal(@view!(S[bI]))
24+
end
25+
return D
26+
end

0 commit comments

Comments
 (0)