Skip to content

Commit d5420a1

Browse files
authored
Color by decreasing vertex degree (#262)
* Color vertices in descending degree order * More docstrings * Fix color grouping * Uselesss tests
1 parent 8aa5bb4 commit d5420a1

File tree

7 files changed

+81
-44
lines changed

7 files changed

+81
-44
lines changed

DifferentiationInterface/src/sparse/coloring.jl

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@
22
Everything in this file is taken from "What color is your Jacobian?"
33
=#
44

5-
function get_groups(colors::AbstractVector{<:Integer})
6-
return map(unique(colors)) do c
7-
filter(j -> colors[j] == c, eachindex(colors))
5+
"""
6+
color_groups(colors)
7+
8+
Return `groups::Vector{Vector{Int}}` such that `i ∈ groups[c]` iff `colors[i] == c`.
9+
10+
Assumes the colors are contiguously numbered from `1` to some `cmax`.
11+
"""
12+
function color_groups(colors::AbstractVector{<:Integer})
13+
cmin, cmax = extrema(colors)
14+
@assert cmin == 1
15+
groups = [Int[] for c in 1:cmax]
16+
for (k, c) in enumerate(colors)
17+
push!(groups[c], k)
818
end
19+
return groups
920
end
1021

1122
abstract type AbstractMatrixGraph end
@@ -49,15 +60,15 @@ function distance2_column_coloring(g::BipartiteGraph)
4960
n = length(columns(g))
5061
colors = zeros(Int, n)
5162
forbidden_colors = zeros(Int, n)
52-
for v in columns(g) # default ordering
63+
for v in sort(columns(g); by=j -> length(neighbors_of_column(g, j)), rev=true)
5364
for w in neighbors_of_column(g, v)
5465
for x in neighbors_of_row(g, w)
5566
if !iszero(colors[x])
5667
forbidden_colors[colors[x]] = v
5768
end
5869
end
5970
end
60-
for c in columns(g)
71+
for c in eachindex(forbidden_colors)
6172
if forbidden_colors[c] != v
6273
colors[v] = c
6374
break
@@ -71,15 +82,15 @@ function distance2_row_coloring(g::BipartiteGraph)
7182
m = length(rows(g))
7283
colors = zeros(Int, m)
7384
forbidden_colors = zeros(Int, m)
74-
for v in 1:m # default ordering
85+
for v in sort(rows(g); by=i -> length(neighbors_of_row(g, i)), rev=true)
7586
for w in neighbors_of_row(g, v)
7687
for x in neighbors_of_column(g, w)
7788
if !iszero(colors[x])
7889
forbidden_colors[colors[x]] = v
7990
end
8091
end
8192
end
82-
for c in rows(g)
93+
for c in eachindex(forbidden_colors)
8394
if forbidden_colors[c] != v
8495
colors[v] = c
8596
break
@@ -129,7 +140,7 @@ function star_coloring(g::AdjacencyGraph)
129140
n = length(columns(g))
130141
colors = zeros(Int, n)
131142
forbidden_colors = zeros(Int, n)
132-
for v in columns(g) # default ordering
143+
for v in sort(columns(g); by=j -> length(neighbors(g, j)), rev=true)
133144
for w in neighbors(g, v)
134145
if !iszero(colors[w]) # w is colored
135146
forbidden_colors[colors[w]] = v
@@ -149,7 +160,7 @@ function star_coloring(g::AdjacencyGraph)
149160
end
150161
end
151162
end
152-
for c in columns(g)
163+
for c in eachindex(forbidden_colors)
153164
if forbidden_colors[c] != v
154165
colors[v] = c
155166
break
@@ -164,33 +175,36 @@ end
164175
"""
165176
GreedyColoringAlgorithm <: ADTypes.AbstractColoringAlgorithm
166177
167-
Matrix coloring algorithm for sparse Jacobians and Hessians.
178+
Matrix coloring algorithm for sparse Jacobians and Hessians, in which vertices are colored sequentially by order of decreasing degree.
168179
169180
Compatible with the [ADTypes.jl coloring framework](https://sciml.github.io/ADTypes.jl/stable/#Coloring-algorithm).
170181
171-
# See also
182+
# Implements
183+
184+
- [`ADTypes.column_coloring`](@extref ADTypes) with a partial distance-2 coloring of the bipartite graph
185+
- [`ADTypes.row_coloring`](@extref ADTypes) with a partial distance-2 coloring of the bipartite graph
186+
- [`ADTypes.symmetric_coloring`](@extref ADTypes) with a star coloring of the adjacency graph
172187
173-
- `ADTypes.column_coloring`
174-
- `ADTypes.row_coloring`
175-
- `ADTypes.symmetric_coloring`
188+
!!! warning
189+
Symmetric coloring is not used by DifferentiationInterface.jl at the moment: Hessians are colored by columns just like Jacobians.
176190
177191
# Reference
178192
179193
> [What Color Is Your Jacobian? Graph Coloring for Computing Derivatives](https://epubs.siam.org/doi/abs/10.1137/S0036144504444711), Gebremedhin et al. (2005)
180194
"""
181195
struct GreedyColoringAlgorithm <: ADTypes.AbstractColoringAlgorithm end
182196

183-
function ADTypes.column_coloring(A, ::GreedyColoringAlgorithm)
197+
function ADTypes.column_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
184198
g = BipartiteGraph(A)
185199
return distance2_column_coloring(g)
186200
end
187201

188-
function ADTypes.row_coloring(A, ::GreedyColoringAlgorithm)
202+
function ADTypes.row_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
189203
g = BipartiteGraph(A)
190204
return distance2_row_coloring(g)
191205
end
192206

193-
function ADTypes.symmetric_coloring(A, ::GreedyColoringAlgorithm)
207+
function ADTypes.symmetric_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
194208
g = AdjacencyGraph(A)
195209
return star_coloring(g)
196210
end

DifferentiationInterface/src/sparse/detector.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ Sparsity detection algorithm based on the [Symbolics.jl tracing system](https://
66
Compatible with the [ADTypes.jl sparsity detection framework](https://sciml.github.io/ADTypes.jl/stable/#Sparsity-detector).
77
88
!!! danger
9-
This functionality is implemented in an extension, and requires [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) to be loaded.
9+
This functionality is in a package extension, and requires [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) to be loaded.
1010
11-
# See also
11+
# Implements
1212
13-
- `ADTypes.jacobian_sparsity`
14-
- `ADTypes.hessian_sparsity`
13+
- [`ADTypes.jacobian_sparsity`](@extref ADTypes)
14+
- [`ADTypes.hessian_sparsity`](@extref ADTypes)
1515
1616
# Reference
1717

DifferentiationInterface/src/sparse/hessian.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ end
1212
function prepare_hessian(f::F, backend::AutoSparse, x) where {F}
1313
initial_sparsity = hessian_sparsity(f, x, sparsity_detector(backend))
1414
sparsity = col_major(initial_sparsity)
15-
colors = column_coloring(sparsity, coloring_algorithm(backend))
16-
groups = get_groups(colors)
15+
colors = column_coloring(sparsity, coloring_algorithm(backend)) # no star coloring
16+
groups = color_groups(colors)
1717
seeds = map(groups) do group
1818
seed = zero(x)
1919
seed[group] .= one(eltype(x))

DifferentiationInterface/src/sparse/jacobian.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function prepare_jacobian(f::F, backend::AutoSparse, x) where {F}
2727
if Bool(pushforward_performance(backend))
2828
sparsity = col_major(initial_sparsity)
2929
colors = column_coloring(sparsity, coloring_algorithm(backend))
30-
groups = get_groups(colors)
30+
groups = color_groups(colors)
3131
seeds = map(groups) do group
3232
seed = zero(x)
3333
seed[group] .= one(eltype(x))
@@ -42,7 +42,7 @@ function prepare_jacobian(f::F, backend::AutoSparse, x) where {F}
4242
else
4343
sparsity = row_major(initial_sparsity)
4444
colors = row_coloring(sparsity, coloring_algorithm(backend))
45-
groups = get_groups(colors)
45+
groups = color_groups(colors)
4646
seeds = map(groups) do group
4747
seed = zero(y)
4848
seed[group] .= one(eltype(y))
@@ -110,7 +110,7 @@ function prepare_jacobian(f!::F, y, backend::AutoSparse, x) where {F}
110110
if Bool(pushforward_performance(backend))
111111
sparsity = col_major(initial_sparsity)
112112
colors = column_coloring(sparsity, coloring_algorithm(backend))
113-
groups = get_groups(colors)
113+
groups = color_groups(colors)
114114
seeds = map(groups) do group
115115
seed = zero(x)
116116
seed[group] .= one(eltype(x))
@@ -125,7 +125,7 @@ function prepare_jacobian(f!::F, y, backend::AutoSparse, x) where {F}
125125
else
126126
sparsity = row_major(initial_sparsity)
127127
colors = row_coloring(sparsity, coloring_algorithm(backend))
128-
groups = get_groups(colors)
128+
groups = color_groups(colors)
129129
seeds = map(groups) do group
130130
seed = zero(y)
131131
seed[group] .= one(eltype(y))

DifferentiationInterfaceTest/test/coloring.jl

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,41 @@ import DifferentiationInterface as DI
33
import DifferentiationInterfaceTest as DIT
44
using LinearAlgebra: I, Symmetric
55
using SparseArrays: sprand
6+
using Test
67

78
alg = DI.GreedyColoringAlgorithm()
89

9-
A = sprand(Bool, 100, 200, 0.05)
10+
@testset "Grouping" begin
11+
colors = [1, 3, 1, 3, 1, 2]
12+
@test DI.color_groups(colors) == [[1, 3, 5], [6], [2, 4]]
13+
colors = [2, 3, 2, 3, 2, 1]
14+
@test DI.color_groups(colors) == [[6], [1, 3, 5], [2, 4]]
15+
colors = [2, 3, 2, 3, 2]
16+
@test_throws AssertionError DI.color_groups(colors)
17+
end
1018

11-
column_colors = ADTypes.column_coloring(A, alg)
12-
@test DIT.check_structurally_orthogonal_columns(A, column_colors)
13-
@test maximum(column_colors) < size(A, 2) ÷ 2
19+
@testset "Column coloring" begin
20+
for A in (sprand(Bool, 100, 200, 0.05), sprand(Bool, 200, 100, 0.05))
21+
column_colors = ADTypes.column_coloring(A, alg)
22+
@test DIT.check_structurally_orthogonal_columns(A, column_colors)
23+
@test minimum(column_colors) == 1
24+
@test maximum(column_colors) < size(A, 2) ÷ 2
25+
end
26+
end
1427

15-
row_colors = ADTypes.row_coloring(A, alg)
16-
@test DIT.check_structurally_orthogonal_rows(A, row_colors)
17-
@test maximum(row_colors) < size(A, 1) ÷ 2
28+
@testset "Row coloring" begin
29+
for A in (sprand(Bool, 100, 200, 0.05), sprand(Bool, 200, 100, 0.05))
30+
row_colors = ADTypes.row_coloring(A, alg)
31+
@test DIT.check_structurally_orthogonal_rows(A, row_colors)
32+
@test minimum(row_colors) == 1
33+
@test maximum(row_colors) < size(A, 1) ÷ 2
34+
end
35+
end
1836

19-
S = Symmetric(sprand(Bool, 100, 100, 0.05)) + I
20-
symmetric_colors = ADTypes.symmetric_coloring(S, alg)
21-
@test DIT.check_symmetrically_structurally_orthogonal(S, symmetric_colors)
22-
@test maximum(symmetric_colors) < size(A, 2) ÷ 2
37+
@testset "Symmetric coloring" begin
38+
S = Symmetric(sprand(Bool, 100, 100, 0.05)) + I
39+
symmetric_colors = ADTypes.symmetric_coloring(S, alg)
40+
@test DIT.check_symmetrically_structurally_orthogonal(S, symmetric_colors)
41+
@test minimum(symmetric_colors) == 1
42+
@test maximum(symmetric_colors) < size(S, 2) ÷ 2
43+
end

DifferentiationInterfaceTest/test/forwarddiff.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
using ADTypes
12
using DifferentiationInterface
23
using DifferentiationInterfaceTest
34
using ForwardDiff: ForwardDiff
5+
using SparseConnectivityTracer
6+
7+
function MyAutoSparse(backend::AbstractADType)
8+
coloring_algorithm = GreedyColoringAlgorithm()
9+
sparsity_detector = TracerSparsityDetector()
10+
return AutoSparse(backend; sparsity_detector, coloring_algorithm)
11+
end
412

513
test_differentiation(AutoForwardDiff(); logging=get(ENV, "CI", "false") == "false")
614

DifferentiationInterfaceTest/test/runtests.jl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ if isdir(DI_PATH)
1010
Pkg.develop(; path=DI_PATH)
1111
end
1212

13-
function MyAutoSparse(backend::AbstractADType)
14-
coloring_algorithm = GreedyColoringAlgorithm()
15-
sparsity_detector = TracerSparsityDetector()
16-
return AutoSparse(backend; sparsity_detector, coloring_algorithm)
17-
end
18-
1913
## Main tests
2014

2115
@testset verbose = true "DifferentiationInterfaceTest.jl" begin

0 commit comments

Comments
 (0)