Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SVD on BlockArray #426

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/BlockArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ include("blockreduce.jl")
include("blockdeque.jl")
include("blockarrayinterface.jl")
include("blockbanded.jl")
include("blocksvd.jl")

@deprecate getblock(A::AbstractBlockArray{T,N}, I::Vararg{Integer, N}) where {T,N} view(A, Block(I))
@deprecate getblock!(X, A::AbstractBlockArray{T,N}, I::Vararg{Integer, N}) where {T,N} copyto!(X, view(A, Block(I)))
Expand Down
35 changes: 35 additions & 0 deletions src/blocksvd.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#=
SVD on blockmatrices:
Interpret the matrix as a linear map acting on vector spaces with a direct sum structure, which is reflected in the structure of U and V.
In the generic case, the SVD does not preserve this structure, and can mix up the blocks, so S becomes a single block.
(Implementation-wise, also most efficiently done by first mapping to a `BlockedArray`)
In the case of `BlockDiagonal` however, the structure is preserved and carried over to the structure of `S`.
=#

LinearAlgebra.eigencopy_oftype(A::AbstractBlockMatrix, S) = BlockedArray{S}(A)

Check warning on line 9 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L9

Added line #L9 was not covered by tests

function LinearAlgebra.eigencopy_oftype(A::BlockDiagonal, S)
diag = map(Base.Fix2(LinearAlgebra.eigencopy_oftype, S), A.blocks.diag)
return BlockDiagonal(diag)

Check warning on line 13 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L11-L13

Added lines #L11 - L13 were not covered by tests
end

function LinearAlgebra.svd!(A::BlockedMatrix; full::Bool=false, alg::LinearAlgebra.Algorithm=default_svd_alg(A))
USV = svd!(parent(A); full, alg)

Check warning on line 17 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L16-L17

Added lines #L16 - L17 were not covered by tests

# restore block pattern
m = length(USV.S)
bsz1, bsz2, bsz3 = blocksizes(A, 1), [m], blocksizes(A, 2)

Check warning on line 21 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L20-L21

Added lines #L20 - L21 were not covered by tests

u = BlockedArray(USV.U, bsz1, bsz2)
s = BlockedVector(USV.S, bsz2)
vt = BlockedArray(USV.Vt, bsz2, bsz3)
return SVD(u, s, vt)

Check warning on line 26 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L23-L26

Added lines #L23 - L26 were not covered by tests
end

function LinearAlgebra.svd!(A::BlockDiagonal; full::Bool=false, alg::LinearAlgebra.Algorithm=default_svd_alg(A))
USVs = map(a -> svd!(a; full, alg), A.blocks.diag)
Us = map(Base.Fix2(getproperty, :U), USVs)
Ss = map(Base.Fix2(getproperty, :S), USVs)
Vts = map(Base.Fix2(getproperty, :Vt), USVs)
return SVD(BlockDiagonal(Us), mortar(Ss), BlockDiagonal(Vts))

Check warning on line 34 in src/blocksvd.jl

View check run for this annotation

Codecov / codecov/patch

src/blocksvd.jl#L29-L34

Added lines #L29 - L34 were not covered by tests
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ include("test_blockrange.jl")
include("test_blockarrayinterface.jl")
include("test_blockbroadcast.jl")
include("test_blocklinalg.jl")
include("test_blocksvd.jl")
include("test_blockproduct.jl")
include("test_blockreduce.jl")
include("test_blockdeque.jl")
Expand Down
107 changes: 107 additions & 0 deletions test/test_blocksvd.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
module TestBlockSVD

using BlockArrays, Test, LinearAlgebra, Random
using BlockArrays: BlockDiagonal

Random.seed!(0)

eltypes = (Float32, Float64, ComplexF32, ComplexF64, Int)

@testset "Block SVD ($T)" for T in eltypes
x = rand(T, 4, 4)
USV = svd(x)
U, S, Vt = USV.U, USV.S, USV.Vt

y = BlockArray(x, [2, 2], [2, 2])
# https://github.com/JuliaArrays/BlockArrays.jl/issues/425
# USV_blocked = @inferred svd(y)
USV_block = svd(y)
U_block, S_block, Vt_block = USV_block.U, USV_block.S, USV_block.Vt

# test types
@test U_block isa BlockedMatrix
@test eltype(U_block) == float(T)
@test S_block isa BlockedVector
@test eltype(S_block) == real(float(T))
@test Vt_block isa BlockedMatrix
@test eltype(Vt_block) == float(T)

# test structure
@test blocksizes(U_block, 1) == blocksizes(y, 1)
@test length(blocksizes(U_block, 2)) == 1
@test blocksizes(Vt_block, 2) == blocksizes(y, 2)
@test length(blocksizes(Vt_block, 1)) == 1

# test correctness
@test U_block ≈ U
@test S_block ≈ S
@test Vt_block ≈ Vt
@test U_block * Diagonal(S_block) * Vt_block ≈ y
end

@testset "Blocked SVD ($T)" for T in eltypes
x = rand(T, 4, 4)
USV = svd(x)
U, S, Vt = USV.U, USV.S, USV.Vt

y = BlockedArray(x, [2, 2], [2, 2])
# https://github.com/JuliaArrays/BlockArrays.jl/issues/425
# USV_blocked = @inferred svd(y)
USV_blocked = svd(y)
U_blocked, S_blocked, Vt_blocked = USV_blocked.U, USV_blocked.S, USV_blocked.Vt

# test types
@test U_blocked isa BlockedMatrix
@test eltype(U_blocked) == float(T)
@test S_blocked isa BlockedVector
@test eltype(S_blocked) == real(float(T))
@test Vt_blocked isa BlockedMatrix
@test eltype(Vt_blocked) == float(T)

# test structure
@test blocksizes(U_blocked, 1) == blocksizes(y, 1)
@test length(blocksizes(U_blocked, 2)) == 1
@test blocksizes(Vt_blocked, 2) == blocksizes(y, 2)
@test length(blocksizes(Vt_blocked, 1)) == 1

# test correctness
@test U_blocked ≈ U
@test S_blocked ≈ S
@test Vt_blocked ≈ Vt
@test U_blocked * Diagonal(S_blocked) * Vt_blocked ≈ y
end

@testset "BlockDiagonal SVD ($T)" for T in eltypes
blocksz = (2, 3, 1)
y = BlockDiagonal([rand(T, d, d) for d in blocksz])
x = Array(y)

USV = svd(x)
U, S, Vt = USV.U, USV.S, USV.Vt

# https://github.com/JuliaArrays/BlockArrays.jl/issues/425
# USV_blocked = @inferred svd(y)
USV_block = svd(y)
U_block, S_block, Vt_block = USV_block.U, USV_block.S, USV_block.Vt

# test types
@test U_block isa BlockDiagonal
@test eltype(U_block) == float(T)
@test S_block isa BlockVector
@test eltype(S_block) == real(float(T))
@test Vt_block isa BlockDiagonal
@test eltype(Vt_block) == float(T)

# test structure
@test blocksizes(U_block, 1) == blocksizes(y, 1)
@test length(blocksizes(U_block, 2)) == length(blocksz)
@test blocksizes(Vt_block, 2) == blocksizes(y, 2)
@test length(blocksizes(Vt_block, 1)) == length(blocksz)

# test correctness: SVD is not unique, so cannot compare to dense
@test U_block * BlockDiagonal(Diagonal.(S_block.blocks)) * Vt_block ≈ y
@test U_block' * U_block ≈ LinearAlgebra.I
@test Vt_block * Vt_block' ≈ LinearAlgebra.I
end

end # module
Loading