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

[BlockSparseArrays] Sub-slices of multiple blocks #1489

Merged
merged 6 commits into from
Jun 10, 2024

Conversation

mtfishman
Copy link
Member

@mtfishman mtfishman commented Jun 9, 2024

This adds support for taking slices of multiple blocks at once (as proposed in ITensor/BlockSparseArrays.jl#2 and JuliaArrays/BlockArrays.jl#358), for example for a block sparse array:

using BlockArrays: Block
using NDTensors.BlockSparseArrays: BlockSparseArray
a = BlockSparseArray{Float64}([3, 3], [3, 3])
a[Block(1, 1)] = randn(3, 3)
a[Block(2, 2)] = randn(3, 3)

we can now do this:

julia> a
typeof(axes) = Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 6×6 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}}:
 -0.945395   -0.116158  -0.4700180.0        0.0        0.0     
  0.0648115   1.33954   -0.531280.0        0.0        0.0     
  2.21892     1.07313   -1.396570.0        0.0        0.0     
 ──────────────────────────────────┼─────────────────────────────────
  0.0         0.0        0.00.961448   0.667434   0.490545
  0.0         0.0        0.0-0.282894  -0.614402  -1.1862  
  0.0         0.0        0.01.12401    0.924081   0.397953

julia> I = [Block(1)[2:3], Block(2)[1:2]];

julia> a[I, I]
typeof(axes) = Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 4×4 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockArrays.BlockedUnitRange{Vector{Int64}}, BlockArrays.BlockedUnitRange{Vector{Int64}}}}:
 1.33954  -0.531280.0        0.0     
 1.07313  -1.396570.0        0.0     
 ───────────────────┼──────────────────────
 0.0       0.00.961448   0.667434
 0.0       0.0-0.282894  -0.614402

One thing you can see is that the implementation is pretty minimal, because it can make use of the infrastructure built for other slicing operations in BlockSparseArrays and the BlockArrays.jl package.

Additionally, I think this kind of slicing operation will be very useful for implementing low-rank block-wise matrix factorizations. For example, we could reduce the rank of an SVD like this:

# Contrived non-truncated block-wise
# SVD of a block diagonal matrix.
using LinearAlgebra: qr

u = BlockSparseArray{Float64}([3, 3], [3, 3])
u[Block(1, 1)] = Matrix(qr(randn(3, 3)).Q)
u[Block(2, 2)] = Matrix(qr(randn(3, 3)).Q)

s = BlockSparseArray{Float64}([3, 3], [3, 3])
s[Block(1, 1)] = Diagonal(normalize([0.9, 0.3, 0.005]))
s[Block(2, 2)] = Diagonal(normalize([0.8, 0.6, 0.001]))

v = BlockSparseArray{Float64}([3, 3], [3, 3])
v[Block(1, 1)] = Matrix(qr(randn(3, 3)).Q)
v[Block(2, 2)] = Matrix(qr(randn(3, 3)).Q)

block_ranks = [2, 2]
I = [Block(1)[1:block_ranks[1]], Block(2)[1:block_ranks[2]]]
u_truncated = u[:, I]
s_truncated = s[I, I]
v_truncated = v[I, :]

which truncates the singular values and associated singular vectors of each block down to the specified block ranks:

julia> u
typeof(axes) = Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 6×6 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}:
 -0.201584   0.975938  0.08312020.0        0.0       0.0      
 -0.137013  -0.112125  0.9842030.0        0.0       0.0      
  0.969841   0.187011  0.1563190.0        0.0       0.0      
 ─────────────────────────────────┼─────────────────────────────────
  0.0        0.0       0.0-0.79183    0.607681  0.0610696
  0.0        0.0       0.0-0.155642  -0.29747   0.941959 
  0.0        0.0       0.00.590577   0.736366  0.330126 

julia> u_truncated
typeof(axes) = Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 6×4 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}:
 -0.201584   0.9759380.0        0.0     
 -0.137013  -0.1121250.0        0.0     
  0.969841   0.1870110.0        0.0     
 ──────────────────────┼──────────────────────
  0.0        0.0-0.79183    0.607681
  0.0        0.0-0.155642  -0.29747 
  0.0        0.00.590577   0.736366

julia> s
typeof(axes) = Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 6×6 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}:
 0.94867  0.0       0.00.0  0.0  0.0  
 0.0      0.316223  0.00.0  0.0  0.0  
 0.0      0.0       0.005270390.0  0.0  0.0  
 ───────────────────────────────┼─────────────────
 0.0      0.0       0.00.8  0.0  0.0  
 0.0      0.0       0.00.0  0.6  0.0  
 0.0      0.0       0.00.0  0.0  0.001

julia> s_truncated
typeof(axes) = Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}

Warning: To temporarily circumvent a bug in printing BlockSparseArrays with mixtures of dual and non-dual axes, the types of the dual axes printed below might not be accurate. The types printed above this message are the correct ones.

2×2-blocked 4×4 BlockSparseArray{Float64, 2, Matrix{Float64}, NDTensors.SparseArrayDOKs.SparseArrayDOK{Matrix{Float64}, 2, NDTensors.BlockSparseArrays.BlockZero{Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}}, Tuple{BlockedUnitRange{Vector{Int64}}, BlockedUnitRange{Vector{Int64}}}}:
 0.94867  0.00.0  0.0
 0.0      0.3162230.0  0.0
 ───────────────────┼──────────
 0.0      0.00.8  0.0
 0.0      0.00.0  0.6

julia> norm(u_truncated * s_truncated * v_truncated - u * s * v)
0.005364420303655687

This kind of slicing can be performed either with the syntax I = [Block(1)[1:2], Block(2)[1:2]] to define the slice of the blocks in each dimension as shown above or using the syntax I = mortar([Block(1)[1:2], Block(2)[1:2]]). mortar is one of the BlockArrays.jl interfaces for making a BlockVector, so it acts like a vector over [Block(1)[1], Block(1)[2], Block(2)[1], Block(2)[2]] but keeps track of the block structure as well.

To-do:

  • Add support for slicing with I = [Block(1)[1:2], Block(2)[1:2]], in addition to the current implementation which is based around I = mortar([Block(1)[1:2], Block(2)[1:2]]).

@ogauthe, @emstoudenmire

@mtfishman mtfishman changed the title [WIP] [BlockSparseArrays] Sub-slices of multiple blocks [BlockSparseArrays] Sub-slices of multiple blocks Jun 10, 2024
@mtfishman mtfishman marked this pull request as ready for review June 10, 2024 00:17
@mtfishman mtfishman merged commit 1bc091a into main Jun 10, 2024
16 checks passed
@mtfishman mtfishman deleted the BlockSparseArrays_slice_multiple_blocks branch June 10, 2024 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant