Tutorial
Here we give a brief introduction to the contents of the package, see the API reference for more details.
using SparseMatrixColorings
using LinearAlgebra
using SparseArrays
using StableRNGsColoring problems and algorithms
SparseMatrixColorings.jl is based on the combination of a coloring problem and a coloring algorithm, which will be passed to the coloring function.
The problem defines what you want to solve. It is always a ColoringProblem, and you can select options such as
- the structure of the matrix (
:nonsymmetricor:symmetric) - the type of partition you want (
:column,:rowor:bidirectional).
problem = ColoringProblem()ColoringProblem{:nonsymmetric, :column}()The algorithm defines how you want to solve it. It can be either a GreedyColoringAlgorithm or a ConstantColoringAlgorithm. For GreedyColoringAlgorithm, you can select options such as
- the order in which vertices are processed (a subtype of
AbstractOrder, or a tuple of such objects) - the type of decompression you want (
:director:substitution)
algo = GreedyColoringAlgorithm()GreedyColoringAlgorithm{:direct, 1, Tuple{NaturalOrder}}((NaturalOrder(),), false)Coloring results
The coloring function takes a matrix, a problem and an algorithm, to return a result subtyping AbstractColoringResult.
S = sparse([
0 0 1 1 0 1
1 0 0 0 1 0
0 1 0 0 1 0
0 1 1 0 0 0
])
result = coloring(S, problem, algo)SparseMatrixColorings.ColumnColoringResult{SparseArrays.SparseMatrixCSC{Int64, Int64}, Int64, SparseMatrixColorings.BipartiteGraph{Int64}, Vector{Int64}, Vector{SubArray{Int64, 1, Vector{Int64}, Tuple{UnitRange{Int64}}, true}}, Vector{Int64}, Nothing}(sparse([2, 3, 4, 1, 4, 1, 2, 3, 1], [1, 2, 2, 3, 3, 4, 5, 5, 6], [1, 1, 1, 1, 1, 1, 1, 1, 1], 4, 6), SparseMatrixColorings.BipartiteGraph{Int64}([false true false false; false false true true; … ; false true true false; true false false false], [false false … false true; true false … true false; false true … true false; false true … false false]), [1, 1, 2, 1, 2, 3], SubArray{Int64, 1, Vector{Int64}, Tuple{UnitRange{Int64}}, true}[[1, 2, 4], [3, 5], [6]], [2, 3, 4, 5, 8, 1, 6, 7, 9], nothing)The detailed type and fields of that result are not part of the public API. To access its contents, you can use the following getters:
sparsity_patternfor the matrix initially provided tocoloringncolorsfor the total number of distinct colorsrow_colors,column_colorsfor vectors of integer colors (depending on the partition)row_groups,column_groupsfor vector of row or column indices grouped by color (depending on the partition)
Here, we have a column coloring, so we can try the following:
column_colors(result)6-element Vector{Int64}:
1
1
2
1
2
3column_groups(result)3-element Vector{SubArray{Int64, 1, Vector{Int64}, Tuple{UnitRange{Int64}}, true}}:
[1, 2, 4]
[3, 5]
[6]ncolors(result)3Compression and decompression
The functions compress and decompress efficiently store and retrieve compressed representations of sparse matrices, using the coloring result as a starting point.
Compression sums all columns or rows with the same color:
M = sparse([
0 0 4 6 0 9
1 0 0 0 7 0
0 2 0 0 8 0
0 3 5 0 0 0
])
B = compress(M, result)4×3 Matrix{Int64}:
6 4 9
1 7 0
2 8 0
3 5 0Decompression recovers the original matrix from its compressed version:
C = decompress(B, result)4×6 SparseArrays.SparseMatrixCSC{Int64, Int64} with 9 stored entries:
⋅ ⋅ 4 6 ⋅ 9
1 ⋅ ⋅ ⋅ 7 ⋅
⋅ 2 ⋅ ⋅ 8 ⋅
⋅ 3 5 ⋅ ⋅ ⋅The functions decompress! and decompress_single_color! are in-place variants of decompress.
D = [
10 14 18
11 15 0
12 16 0
13 17 0
]
decompress!(C, D, result)4×6 SparseArrays.SparseMatrixCSC{Int64, Int64} with 9 stored entries:
⋅ ⋅ 14 10 ⋅ 18
11 ⋅ ⋅ ⋅ 15 ⋅
⋅ 12 ⋅ ⋅ 16 ⋅
⋅ 13 17 ⋅ ⋅ ⋅nonzeros(C) .= -1
decompress_single_color!(C, D[:, 2], 2, result)4×6 SparseArrays.SparseMatrixCSC{Int64, Int64} with 9 stored entries:
⋅ ⋅ 14 -1 ⋅ -1
-1 ⋅ ⋅ ⋅ 15 ⋅
⋅ -1 ⋅ ⋅ 16 ⋅
⋅ -1 17 ⋅ ⋅ ⋅Unidirectional variants
We now illustrate the variants of colorings available, on the following matrix:
S = sparse(Symmetric(sprand(StableRNG(0), Bool, 10, 10, 0.4)))10×10 SparseArrays.SparseMatrixCSC{Bool, Int64} with 31 stored entries:
1 1 ⋅ 1 1 ⋅ ⋅ ⋅ ⋅ ⋅
1 1 1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ 1 ⋅ 1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ 1 1 ⋅ ⋅ 1 1
⋅ 1 1 1 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ ⋅ 1 ⋅ 1 1 ⋅ 1
⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ 1 1We start with unidirectional colorings, where only rows or columns are colored and the matrix is not assumed to be symmetric.
Column coloring
problem = ColoringProblem(; structure=:nonsymmetric, partition=:column)
algo = GreedyColoringAlgorithm(; decompression=:direct)
result = coloring(S, problem, algo)ncolors(result), column_colors(result)(6, [1, 2, 3, 4, 5, 6, 1, 2, 2, 3])Here is the colored matrix
and its columnwise compression
Row coloring
problem = ColoringProblem(; structure=:nonsymmetric, partition=:row)
algo = GreedyColoringAlgorithm(; decompression=:direct)
result = coloring(S, problem, algo)ncolors(result), row_colors(result)(6, [1, 2, 3, 4, 5, 6, 1, 2, 2, 3])Here is the colored matrix
and its rowwise compression
Symmetric variants
We continue with unidirectional symmetric colorings, where coloring rows is equivalent to coloring columns. Symmetry is leveraged to possibly reduce the number of necessary colors.
Star coloring
Star coloring is the algorithm used for symmetric matrices with direct decompression.
problem = ColoringProblem(; structure=:symmetric, partition=:column)
algo = GreedyColoringAlgorithm(; decompression=:direct)
result = coloring(S, problem, algo)ncolors(result), column_colors(result)(5, [1, 2, 1, 3, 3, 4, 1, 1, 2, 5])Here is the colored matrix
and its columnwise compression
Acyclic coloring
Acyclic coloring is the algorithm used for symmetric matrices with decompression by substitution.
problem = ColoringProblem(; structure=:symmetric, partition=:column)
algo = GreedyColoringAlgorithm(; decompression=:substitution)
result = coloring(S, problem, algo)ncolors(result), column_colors(result)(3, [1, 2, 1, 2, 2, 3, 1, 1, 3, 1])Here is the colored matrix
and its columnwise compression
Bidirectional variants
We finish with bidirectional colorings, where both rows and columns are colored and the matrix is not assumed to be symmetric.
Bicoloring is most relevant for matrices with dense rows and columns, which is why we consider the following test case:
S_bi = copy(S)
S_bi[:, 1] .= true
S_bi[1, :] .= true
S_bi10×10 SparseArrays.SparseMatrixCSC{Bool, Int64} with 43 stored entries:
1 1 1 1 1 1 1 1 1 1
1 1 1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
1 1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ 1 ⋅ 1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ 1 1 ⋅ ⋅ 1 1
1 1 1 1 1 ⋅ ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅
1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅
1 ⋅ ⋅ ⋅ 1 ⋅ 1 1 ⋅ 1
1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ 1 1With our implementations, bidirectional coloring works better using a RandomOrder.
Star bicoloring
problem = ColoringProblem(; structure=:nonsymmetric, partition=:bidirectional)
algo = GreedyColoringAlgorithm(RandomOrder(StableRNG(0), 0); decompression=:direct)
result = coloring(S_bi, problem, algo)ncolors(result), column_colors(result)(9, [1, 2, 2, 2, 3, 2, 2, 2, 4, 2])Here is the colored matrix
its columnwise compression
and rowwise compression
Both are necessary to reconstruct the original.
Acyclic bicoloring
problem = ColoringProblem(; structure=:nonsymmetric, partition=:bidirectional)
algo = GreedyColoringAlgorithm(RandomOrder(StableRNG(0), 0); decompression=:substitution)
result = coloring(S_bi, problem, algo)ncolors(result), column_colors(result)(8, [1, 2, 2, 3, 4, 2, 2, 2, 3, 5])Here is the colored matrix
its columnwise compression
and rowwise compression
Both are necessary to reconstruct the original.