Skip to content

Commit 0246517

Browse files
authored
Bicoloring (#152)
* Implement efficient adjacency graph from bipartite (#145) * Custom adjacency graph from bipartite * Fix nb edges * More typing * Working prototype for bicoloring (coloring + decompression) (#146) * Start bicoloring result * Working bidirectional decompression * Min diff * Slight perf optim * Perf * Gettin bi * Less code * Fix tests * Remove remaining compat * Fix group typing * Avoid accessing ag.S * Add bidirectional coloring visualization (#153) * Allocation-free bidirectional decompression (#154) * Minimize diff * Bump version to 0.4.9 * Check scale for bicoloring visualization * Fix visualization tests * Improve docs * Undo view change * Typo * Add views
1 parent 42622b5 commit 0246517

14 files changed

Lines changed: 452 additions & 104 deletions

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseMatrixColorings"
22
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
33
authors = ["Guillaume Dalle", "Alexis Montoison"]
4-
version = "0.4.8"
4+
version = "0.4.9"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ConstantColoringAlgorithm
2626
AbstractColoringResult
2727
column_colors
2828
row_colors
29+
ncolors
2930
column_groups
3031
row_groups
3132
sparsity_pattern

docs/src/dev.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ SparseMatrixColorings.RowColoringResult
3838
SparseMatrixColorings.StarSetColoringResult
3939
SparseMatrixColorings.TreeSetColoringResult
4040
SparseMatrixColorings.LinearSystemColoringResult
41+
SparseMatrixColorings.BicoloringResult
42+
SparseMatrixColorings.remap_colors
4143
```
4244

4345
## Testing

ext/SparseMatrixColoringsColorsExt.jl

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ using SparseMatrixColorings:
2121
AbstractColoringResult,
2222
sparsity_pattern,
2323
column_colors,
24-
row_colors
24+
row_colors,
25+
ncolors
2526
using Colors: Colorant, RGB, RGBA, distinguishable_colors
2627

2728
const DEFAULT_BACKGROUND = RGBA(0, 0, 0, 0)
@@ -31,9 +32,6 @@ const DEFAULT_PAD = 0 # update docstring in src/images.jl when changing this def
3132
# Sample n distinguishable colors, excluding the background color
3233
default_colorscheme(n, background) = distinguishable_colors(n, background; dropseed=true)
3334

34-
ncolors(res::AbstractColoringResult{s,:column}) where {s} = maximum(column_colors(res))
35-
ncolors(res::AbstractColoringResult{s,:row}) where {s} = maximum(row_colors(res))
36-
3735
## Top-level function that handles argument errors, eagerly promotes types and allocates output buffer
3836

3937
function SparseMatrixColorings.show_colors(
@@ -119,4 +117,30 @@ function show_colors!(
119117
return out
120118
end
121119

120+
function show_colors!(
121+
out, res::AbstractColoringResult{s,:bidirectional}, colorscheme, scale, pad
122+
) where {s}
123+
scale < 3 && throw(ArgumentError("`scale` has to be ≥ 3 to visualize bicoloring"))
124+
ccolor_indices = mod1.(column_colors(res), length(colorscheme)) # cycle color indices if necessary
125+
row_shift = maximum(column_colors(res))
126+
rcolor_indices = mod1.(row_shift .+ row_colors(res), length(colorscheme)) # cycle color indices if necessary
127+
ccolors = colorscheme[ccolor_indices]
128+
rcolors = colorscheme[rcolor_indices]
129+
pattern = sparsity_pattern(res)
130+
for I in CartesianIndices(pattern)
131+
if !iszero(pattern[I])
132+
r, c = Tuple(I)
133+
area = matrix_entry_area(I, scale, pad)
134+
for i in axes(area, 1), j in axes(area, 2)
135+
if j > i
136+
out[area[i, j]] = ccolors[c]
137+
elseif i > j
138+
out[area[i, j]] = rcolors[r]
139+
end
140+
end
141+
end
142+
end
143+
return out
144+
end
145+
122146
end # module

src/SparseMatrixColorings.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export DynamicDegreeBasedOrder, SmallestLast, IncidenceDegree, DynamicLargestFir
6060
export ColoringProblem, GreedyColoringAlgorithm, AbstractColoringResult
6161
export ConstantColoringAlgorithm
6262
export coloring
63-
export column_colors, row_colors
63+
export column_colors, row_colors, ncolors
6464
export column_groups, row_groups
6565
export sparsity_pattern
6666
export compress, decompress, decompress!, decompress_single_color!

src/decompression.jl

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ Compress `A` given a coloring `result` of the sparsity pattern of `A`.
99
Compression means summing either the columns or the rows of `A` which share the same color.
1010
It is undone by calling [`decompress`](@ref) or [`decompress!`](@ref).
1111
12-
!!! warning
13-
At the moment, `:bidirectional` partitions are not implemented.
14-
1512
# Example
1613
1714
```jldoctest
@@ -63,10 +60,25 @@ function compress(A, result::AbstractColoringResult{structure,:row}) where {stru
6360
return B
6461
end
6562

63+
function compress(
64+
A, result::AbstractColoringResult{structure,:bidirectional}
65+
) where {structure}
66+
row_group = row_groups(result)
67+
column_group = column_groups(result)
68+
Br = stack(row_group; dims=1) do g
69+
dropdims(sum(A[g, :]; dims=1); dims=1)
70+
end
71+
Bc = stack(column_group; dims=2) do g
72+
dropdims(sum(A[:, g]; dims=2); dims=2)
73+
end
74+
return Br, Bc
75+
end
76+
6677
"""
67-
decompress(B::AbstractMatrix, result::AbstractColoringResult)
78+
decompress(B::AbstractMatrix, result::AbstractColoringResult{_,:column/:row})
79+
decompress(Br::AbstractMatrix, Bc::AbstractMatrix, result::AbstractColoringResult{_,:bidirectional})
6880
69-
Decompress `B` into a new matrix `A`, given a coloring `result` of the sparsity pattern of `A`.
81+
Decompress `B` (or the tuple `(Br,Bc)`) into a new matrix `A`, given a coloring `result` of the sparsity pattern of `A`.
7082
The in-place alternative is [`decompress!`](@ref).
7183
7284
Compression means summing either the columns or the rows of `A` which share the same color.
@@ -120,13 +132,27 @@ function decompress(B::AbstractMatrix, result::AbstractColoringResult)
120132
return decompress!(A, B, result)
121133
end
122134

135+
function decompress(
136+
Br::AbstractMatrix,
137+
Bc::AbstractMatrix,
138+
result::AbstractColoringResult{structure,:bidirectional},
139+
) where {structure}
140+
A = respectful_similar(result.A, Base.promote_eltype(Br, Bc))
141+
return decompress!(A, Br, Bc, result)
142+
end
143+
123144
"""
124145
decompress!(
125146
A::AbstractMatrix, B::AbstractMatrix,
126-
result::AbstractColoringResult, [uplo=:F]
147+
result::AbstractColoringResult{_,:column/:row}, [uplo=:F]
148+
)
149+
150+
decompress!(
151+
A::AbstractMatrix, Br::AbstractMatrix, Bc::AbstractMatrix
152+
result::AbstractColoringResult{_,:bidirectional}
127153
)
128154
129-
Decompress `B` in-place into `A`, given a coloring `result` of the sparsity pattern of `A`.
155+
Decompress `B` (or the tuple `(Br,Bc)`) in-place into `A`, given a coloring `result` of the sparsity pattern of `A`.
130156
The out-of-place alternative is [`decompress`](@ref).
131157
132158
!!! note
@@ -632,3 +658,60 @@ function decompress!(
632658
end
633659
return A
634660
end
661+
662+
## BicoloringResult
663+
664+
function _join_compressed!(result::BicoloringResult, Br::AbstractMatrix, Bc::AbstractMatrix)
665+
#=
666+
Say we have an original matrix `A` of size `(n, m)` and we build an augmented matrix `A_and_Aᵀ = [zeros(n, n) Aᵀ; A zeros(m, m)]`.
667+
Its first `1:n` columns have the form `[zeros(n); A[:, j]]` and its following `n+1:n+m` columns have the form `[A[i, :]; zeros(m)]`.
668+
The symmetric column coloring is performed on `A_and_Aᵀ` and the column-wise compression of `A_and_Aᵀ` should return a matrix `Br_and_Bc`.
669+
But in reality, `Br_and_Bc` is computed as two partial compressions: the row-wise compression `Br` (corresponding to `Aᵀ`) and the columnwise compression `Bc` (corresponding to `A`).
670+
Before symmetric decompression, we must reconstruct `Br_and_Bc` from `Br` and `Bc`, knowing that the symmetric colors (those making up `Br_and_Bc`) are present in either a row of `Br`, a column of `Bc`, or both.
671+
Therefore, the column indices in `Br_and_Bc` don't necessarily match with the row indices in `Br` or the column indices in `Bc` since some colors may be missing in the partial compressions.
672+
The columns of the top part of `Br_and_Bc` (rows `1:n`) are the rows of `Br`, interlaced with zero columns whenever the current color hasn't been used to color any row.
673+
The columns of the bottom part of `Br_and_Bc` (rows `n+1:n+m`) are the columns of `Bc`, interlaced with zero columns whenever the current color hasn't been used to color any column.
674+
We use the dictionaries `col_color_ind` and `row_color_ind` to map from symmetric colors to row/column colors.
675+
=#
676+
(; A, col_color_ind, row_color_ind) = result
677+
m, n = size(A)
678+
R = Base.promote_eltype(Br, Bc)
679+
if eltype(result.Br_and_Bc) == R
680+
Br_and_Bc = result.Br_and_Bc
681+
else
682+
Br_and_Bc = similar(result.Br_and_Bc, R)
683+
end
684+
fill!(Br_and_Bc, zero(R))
685+
for c in axes(Br_and_Bc, 2)
686+
if haskey(row_color_ind, c) # some rows were colored with symmetric color c
687+
copyto!(view(Br_and_Bc, 1:n, c), view(Br, row_color_ind[c], :))
688+
end
689+
if haskey(col_color_ind, c) # some columns were colored with symmetric c
690+
copyto!(view(Br_and_Bc, (n + 1):(n + m), c), view(Bc, :, col_color_ind[c]))
691+
end
692+
end
693+
return Br_and_Bc
694+
end
695+
696+
function decompress!(
697+
A::AbstractMatrix, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult
698+
)
699+
m, n = size(A)
700+
Br_and_Bc = _join_compressed!(result, Br, Bc)
701+
A_and_Aᵀ = decompress(Br_and_Bc, result.symmetric_result)
702+
copyto!(A, A_and_Aᵀ[(n + 1):(n + m), 1:n]) # original matrix in bottom left corner
703+
return A
704+
end
705+
706+
function decompress!(
707+
A::SparseMatrixCSC, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult
708+
)
709+
(; large_colptr, large_rowval, symmetric_result) = result
710+
m, n = size(A)
711+
Br_and_Bc = _join_compressed!(result, Br, Bc)
712+
# pretend A is larger
713+
A_and_noAᵀ = SparseMatrixCSC(m + n, m + n, large_colptr, large_rowval, A.nzval)
714+
# decompress lower triangle only
715+
decompress!(A_and_noAᵀ, Br_and_Bc, symmetric_result, :L)
716+
return A
717+
end

src/graph.jl

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,13 @@ function degree(g::AdjacencyGraph, v::Integer)
145145
end
146146

147147
function nb_edges(g::AdjacencyGraph)
148-
S = pattern(g)
149148
ne = 0
150-
for j in vertices(g)
151-
for k in nzrange(S, j)
152-
i = rowvals(S)[k]
153-
if i > j
154-
ne += 1
155-
end
149+
for v in vertices(g)
150+
for u in neighbors(g, v)
151+
ne += 1
156152
end
157153
end
158-
return ne
154+
return ne ÷ 2
159155
end
160156

161157
maximum_degree(g::AdjacencyGraph) = maximum(Base.Fix1(degree, g), vertices(g))

src/interface.jl

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function check_valid_problem(structure::Symbol, partition::Symbol)
22
valid = (
3-
(structure == :nonsymmetric && partition in (:column, :row)) ||
3+
(structure == :nonsymmetric && partition in (:column, :row, :bidirectional)) ||
44
(structure == :symmetric && partition == :column)
55
)
66
if !valid
@@ -49,7 +49,7 @@ Matrix coloring is often used in automatic differentiation, and here is the tran
4949
| -------- | ------- | --------------- | ---------------- | ----------- |
5050
| Jacobian | forward | `:nonsymmetric` | `:column` | yes |
5151
| Jacobian | reverse | `:nonsymmetric` | `:row` | yes |
52-
| Jacobian | mixed | `:nonsymmetric` | `:bidirectional` | no |
52+
| Jacobian | mixed | `:nonsymmetric` | `:bidirectional` | yes |
5353
| Hessian | - | `:symmetric` | `:column` | yes |
5454
| Hessian | - | `:symmetric` | `:row` | no |
5555
"""
@@ -223,6 +223,37 @@ function coloring(
223223
return TreeSetColoringResult(A, ag, color, tree_set, decompression_eltype)
224224
end
225225

226+
function coloring(
227+
A::AbstractMatrix,
228+
::ColoringProblem{:nonsymmetric,:bidirectional},
229+
algo::GreedyColoringAlgorithm{decompression};
230+
decompression_eltype::Type{R}=Float64,
231+
symmetric_pattern::Bool=false,
232+
) where {decompression,R}
233+
m, n = size(A)
234+
T = eltype(A)
235+
Aᵀ = if symmetric_pattern || A isa Union{Symmetric,Hermitian}
236+
A
237+
else
238+
transpose(A)
239+
end # TODO: fuse with next step?
240+
A_and_Aᵀ = [
241+
spzeros(T, n, n) SparseMatrixCSC(Aᵀ)
242+
SparseMatrixCSC(A) spzeros(T, m, m)
243+
] # TODO: slow
244+
ag = AdjacencyGraph(A_and_Aᵀ)
245+
if decompression == :direct
246+
color, star_set = star_coloring(ag, algo.order)
247+
symmetric_result = StarSetColoringResult(A_and_Aᵀ, ag, color, star_set)
248+
else
249+
color, tree_set = acyclic_coloring(ag, algo.order)
250+
symmetric_result = TreeSetColoringResult(
251+
A_and_Aᵀ, ag, color, tree_set, decompression_eltype
252+
)
253+
end
254+
return BicoloringResult(A, ag, symmetric_result, decompression_eltype)
255+
end
256+
226257
## ADTypes interface
227258

228259
function ADTypes.column_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)

0 commit comments

Comments
 (0)