BlockSparseMatrix
A BlockSparseMatrix
is a matrices that is (at least) sparse at the block level. This can, for example, occur when the matrix originates from a fast multipole method (FMM) or any other algorithm that naturally groups rows and columns into interacting blocks (e.g. near‑field interactions).
Below you will find a compact, step‑by‑step guide that shows how to
- Build a block‑sparse matrix from a list of dense blocks and their index maps.
- Multiply it with vectors (including transposes).
- Convert it to a standard
SparseMatrixCSC
for comparison. - Enable multi‑threaded construction with
OhMyThreads
.
Constructing a BlockSparseMatrix
using CompScienceMeshes, BEAST, H2Trees
using UnicodePlots
using BlockSparseMatrices
m = meshsphere(1.0, 0.1)
X = raviartthomas(m)
tree = TwoNTree(X, 0.2)
colvalues, rowvalues = H2Trees.nearinteractions(tree)
blocks = Matrix{Float64}[]
for i in eachindex(colvalues)
push!(blocks, randn(Float64, length(colvalues[i]), length(rowvalues[i])))
end
B = BlockSparseMatrix(blocks, colvalues, rowvalues, (numfunctions(X), numfunctions(X)))
4821×4821 BlockSparseMatrix{Float64} with 3050335 non-zero entries
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣿⣿⣿⣿⣿⡇⡇⠀⠀⡇⠃⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⢰⣦⢠⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⡄⣤⣶⣶⣦⠀
⠀⣿⣿⣿⣿⣿⣷⣷⠀⠀⣷⣦⠀⠀⠀⠀⠀⣤⣤⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠶⠗⠛⠻⠿⠿⠟⠀
⠀⠿⠿⢿⣿⣿⣿⣿⣤⣤⣿⡏⠀⠀⠀⠀⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢀⣀⡀⠀⢀⡀⠀⠀⠀
⠀⠉⠉⠙⠛⠛⣿⣿⣿⣾⣿⣆⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠿⠽⠿⠯⠍⠘⠋⠉⠁⠀
⠀⠤⠤⢤⣤⣤⣿⣾⣿⣿⣿⣿⣿⣿⠀⠀⠀⢤⣤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠤⠿⠇⠸⠿⠼⠿⠦⠄⠀⠀⠀⠀⠀
⠀⠉⠀⠈⠛⠋⠉⠈⢹⣿⣿⣿⣿⣿⣿⣿⡟⠛⠛⠛⠓⠂⠀⠀⠀⠀⢄⣤⡤⣤⡄⢠⣤⠤⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠛⣿⣿⣿⣿⣿⣧⢀⣠⠀⣤⣤⠀⢀⠀⣀⣴⣶⣦⣶⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠿⠿⣿⣿⣿⣿⣿⣿⣿⡏⠉⠈⠀⠉⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣿⣿⠀⣿⡇⠀⠀⠀⠀⣷⣿⠀⠀⣰⣿⣿⣿⣿⣿⣿⣁⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀
⠀⠉⠉⠀⠉⠁⠀⠀⠀⠀⠁⢿⠀⠀⣤⣿⣿⣿⣿⣿⣿⣿⣿⡿⠸⠿⢼⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⠁⠀
⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠛⡏⠉⠁⢸⣿⣿⣿⣿⣾⣾⣿⣿⠛⠀⠀⡗⠂⠀⠀⠀⠀⠀⡀⠀⣰⣀⠀
⠀⠈⣛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠂⠀⠀⠈⣛⡋⣺⣿⣻⣾⣿⣶⠒⠀⠀⡶⡶⠀⠀⠀⠀⢰⣶⠀⣻⣿⠀
⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢄⢀⣼⡇⠀⠀⠀⣛⣇⣿⣿⢻⣿⣿⣿⣿⣤⣤⣿⡇⠀⠀⠀⠀⠈⠉⠀⠙⠉⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⡿⠸⣿⡇⠀⠀⠀⠀⠛⠛⠀⠘⠀⠛⣿⣿⣿⣿⣿⣧⣤⣤⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⠇⠀⠿⠸⠿⠇⠀⠀⠀⠀⠀⢤⠤⢠⡤⣤⣿⣿⣿⣿⣿⣿⢿⣿⣀⣀⣠⣤⡀⠠⠤⠀
⠀⠀⠀⠐⠀⠀⠠⣶⡆⣶⡆⠀⣶⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠘⠋⠉⠉⠉⣿⣿⣟⣻⣾⣿⣿⣿⡟⠛⠋⠉⠉⠀
⠀⠀⠀⢠⡄⠀⢰⣷⡇⣶⡇⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠛⢻⣿⣿⣿⣿⣿⣧⣤⡄⠀⠀⠀
⠀⠀⠭⣽⠁⠀⠈⡏⠇⠈⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⣸⣿⠿⠿⣿⣿⣿⣿⣯⣭⣭⠀
⠀⢠⣿⣿⡆⠀⠰⡶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠘⠛⠃⠀⠀⠀⠀⠻⡿⠀⠀⠿⡿⣿⣿⣿⣿⣿⠀
⠀⠸⣿⣿⠇⠀⠀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠗⠒⠐⢺⣿⣾⡗⠀⠀⠀⠀⡆⡇⠀⠀⠀⡇⣿⣿⣿⣿⣿⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
B
now behaves like a regular matrix: you can query its size, inspect its blocks, etc.
Matrix–Vector Products
y = randn(Float64, size(B, 1))
@time B * y
@time B' * y
@time transpose(B) * y
0.335012 seconds (635.10 k allocations: 32.649 MiB, 3.41% gc time, 99.38% compilation time)
0.067908 seconds (171.44 k allocations: 8.632 MiB, 96.82% compilation time)
0.006797 seconds (30 allocations: 38.898 KiB, 69.53% compilation time)
All three operations are implemented in pure Julia and respect the block‑sparsity, so they are typically much faster than converting the matrix to a dense format first.
Converting to a Classical Sparse Matrix
Sometimes you need a SparseMatrixCSC
. The conversion is straightforward. You can compare the memory footprints:
using SparseArrays
Bsparse = sparse(B)
@show Base.format_bytes(Base.summarysize(B));
@show Base.format_bytes(Base.summarysize(Bsparse));
Base.format_bytes(Base.summarysize(B)) = "26.868 MiB"
Base.format_bytes(Base.summarysize(Bsparse)) = "46.581 MiB"
In specific examples BlockSparseMatrix
consumes less memory.
Multi‑Threaded Construction (Optional)
Depending on the example, the block-coloring step can be a bottleneck, thus multithreading is switched-off by default. To enable multithreading you can determine a scheduler from OhMyThreads.
using OhMyThreads
B = BlockSparseMatrix(
blocks,
colvalues,
rowvalues,
(numfunctions(X), numfunctions(X));
scheduler=DynamicScheduler(),
)