Skip to content

Commit 35c22cd

Browse files
authored
Compare colorings against values from papers (#3)
* Test against reference papers * Rng * Using * Rename * CSV * Add performance tests * Correct column intersection graph * Missing using
1 parent 947a213 commit 35c22cd

16 files changed

Lines changed: 412 additions & 114 deletions

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ version = "0.1.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
8+
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
89
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
910
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1011
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1112

1213
[compat]
1314
ADTypes = "1.2.1"
15+
DocStringExtensions = "0.9"
1416
LinearAlgebra = "1"
1517
Random = "1"
1618
SparseArrays = "1"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The algorithms implemented in this package are mainly taken from the following a
2121

2222
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
2323
24-
> [ColPack: Software for graph coloring and related problems in scientific computing](https://dl.acm.org/doi/10.1145/2513109.2513110), Gebremedhin et al. (2013)
24+
> [_ColPack: Software for graph coloring and related problems in scientific computing_](https://dl.acm.org/doi/10.1145/2513109.2513110), Gebremedhin et al. (2013)
2525
2626
Some parts of the articles (like definitions) are thus copied verbatim in the documentation.
2727

src/SparseMatrixColorings.jl

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
"""
22
SparseMatrixColorings
33
4-
Coloring algorithms for sparse Jacobian and Hessian matrices.
5-
6-
The algorithms implemented in this package are mainly taken from the following articles:
7-
8-
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
9-
10-
> [ColPack: Software for graph coloring and related problems in scientific computing](https://dl.acm.org/doi/10.1145/2513109.2513110), Gebremedhin et al. (2013)
11-
12-
Some parts of the articles (like definitions) are thus copied verbatim in the documentation.
4+
$README
135
"""
146
module SparseMatrixColorings
157

168
using ADTypes: ADTypes, AbstractColoringAlgorithm
9+
using DocStringExtensions
1710
using LinearAlgebra: Diagonal, Transpose, checksquare, parent, transpose
1811
using Random: AbstractRNG, default_rng, randperm
19-
using SparseArrays: SparseArrays, SparseMatrixCSC, nzrange, rowvals, spzeros
12+
using SparseArrays:
13+
SparseArrays,
14+
SparseMatrixCSC,
15+
dropzeros,
16+
dropzeros!,
17+
nnz,
18+
nzrange,
19+
rowvals,
20+
sparse,
21+
spzeros
2022

2123
include("graph.jl")
2224
include("order.jl")

src/adtypes.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@ end
2020

2121
GreedyColoringAlgorithm() = GreedyColoringAlgorithm(NaturalOrder())
2222

23+
function Base.show(io::IO, algo::GreedyColoringAlgorithm)
24+
return print(io, "GreedyColoringAlgorithm($(algo.order))")
25+
end
26+
2327
function ADTypes.column_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)
24-
bg = BipartiteGraph(A)
28+
bg = bipartite_graph(A)
2529
return partial_distance2_coloring(bg, Val(2), algo.order)
2630
end
2731

2832
function ADTypes.row_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)
29-
bg = BipartiteGraph(A)
33+
bg = bipartite_graph(A)
3034
return partial_distance2_coloring(bg, Val(1), algo.order)
3135
end
3236

3337
function ADTypes.symmetric_coloring(A::AbstractMatrix, algo::GreedyColoringAlgorithm)
34-
ag = AdjacencyGraph(A)
35-
return star_coloring(ag, algo.order)
38+
ag = adjacency_graph(A)
39+
return star_coloring1(ag, algo.order)
3640
end

src/coloring.jl

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,24 @@ The vertices are colored in a greedy fashion, following the `order` supplied.
1515
function partial_distance2_coloring(
1616
bg::BipartiteGraph, ::Val{side}, order::AbstractOrder
1717
) where {side}
18+
colors = Vector{Int}(undef, length(bg, Val(side)))
19+
forbidden_colors = Vector{Int}(undef, length(bg, Val(side)))
20+
vertices_in_order = vertices(bg, Val(side), order)
21+
partial_distance2_coloring!(colors, forbidden_colors, bg, Val(side), vertices_in_order)
22+
return colors
23+
end
24+
25+
function partial_distance2_coloring!(
26+
colors::Vector{Int},
27+
forbidden_colors::Vector{Int},
28+
bg::BipartiteGraph,
29+
::Val{side},
30+
vertices_in_order::AbstractVector{<:Integer},
31+
) where {side}
32+
colors .= 0
33+
forbidden_colors .= 0
1834
other_side = 3 - side
19-
colors = zeros(Int, length(bg, Val(side)))
20-
forbidden_colors = zeros(Int, length(bg, Val(side)))
21-
for v in vertices(bg, Val(side), order)
35+
for v in vertices_in_order
2236
for w in neighbors(bg, Val(side), v)
2337
for x in neighbors(bg, Val(other_side), w)
2438
if !iszero(colors[x])
@@ -33,37 +47,48 @@ function partial_distance2_coloring(
3347
end
3448
end
3549
end
36-
return colors
3750
end
3851

3952
"""
40-
star_coloring(ag::AdjacencyGraph, order::AbstractOrder)
53+
star_coloring1(g::Graph, order::AbstractOrder)
4154
42-
Compute a star coloring of all vertices in the adjacency graph `ag` and return a vector of integer colors.
55+
Compute a star coloring of all vertices in the adjacency graph `g` and return a vector of integer colors.
4356
4457
A _star coloring_ is a distance-1 coloring such that every path on 4 vertices uses at least 3 colors.
4558
4659
The vertices are colored in a greedy fashion, following the `order` supplied.
4760
4861
# See also
4962
50-
- [`AdjacencyGraph`](@ref)
63+
- [`Graph`](@ref)
5164
- [`AbstractOrder`](@ref)
5265
"""
53-
function star_coloring(ag::AdjacencyGraph, order::AbstractOrder)
54-
n = length(ag)
55-
colors = zeros(Int, n)
56-
forbidden_colors = zeros(Int, n)
57-
for v in vertices(ag, order)
58-
for w in neighbors(ag, v)
66+
function star_coloring1(g::Graph, order::AbstractOrder)
67+
colors = Vector{Int}(undef, length(g))
68+
forbidden_colors = Vector{Int}(undef, length(g))
69+
vertices_in_order = vertices(g, order)
70+
star_coloring1!(colors, forbidden_colors, g, vertices_in_order)
71+
return colors
72+
end
73+
74+
function star_coloring1!(
75+
colors::Vector{Int},
76+
forbidden_colors::Vector{Int},
77+
g::Graph,
78+
vertices_in_order::AbstractVector{<:Integer},
79+
)
80+
colors .= 0
81+
forbidden_colors .= 0
82+
for v in vertices_in_order
83+
for w in neighbors(g, v)
5984
if !iszero(colors[w]) # w is colored
6085
forbidden_colors[colors[w]] = v
6186
end
62-
for x in neighbors(ag, w)
87+
for x in neighbors(g, w)
6388
if !iszero(colors[x]) && iszero(colors[w]) # w is not colored
6489
forbidden_colors[colors[x]] = v
6590
else
66-
for y in neighbors(ag, x)
91+
for y in neighbors(g, x)
6792
if !iszero(colors[y]) && y != w
6893
if colors[y] == colors[w]
6994
forbidden_colors[colors[x]] = v
@@ -81,5 +106,4 @@ function star_coloring(ag::AdjacencyGraph, order::AbstractOrder)
81106
end
82107
end
83108
end
84-
return colors
85109
end

src/graph.jl

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,129 @@
1+
## Standard graph
2+
3+
"""
4+
Graph{T}
5+
6+
Undirected graph structure stored in Compressed Sparse Column (CSC) format.
7+
8+
# Fields
9+
10+
- `colptr::Vector{T}`: same as for `SparseMatrixCSC`
11+
- `rowval::Vector{T}`: same as for `SparseMatrixCSC`
12+
"""
113
struct Graph{T<:Integer}
214
colptr::Vector{T}
315
rowval::Vector{T}
416
end
517

618
Graph(A::SparseMatrixCSC) = Graph(A.colptr, A.rowval)
19+
Graph(A::AbstractMatrix) = Graph(sparse(A))
720

821
Base.length(g::Graph) = length(g.colptr) - 1
22+
SparseArrays.nnz(g::Graph) = length(g.rowval)
923

24+
vertices(g::Graph) = 1:length(g)
1025
neighbors(g::Graph, v::Integer) = view(g.rowval, g.colptr[v]:(g.colptr[v + 1] - 1))
26+
degree(g::Graph, v::Integer) = length(g.colptr[v]:(g.colptr[v + 1] - 1))
1127

12-
## Adjacency graph
28+
maximum_degree(g::Graph) = maximum(Base.Fix1(degree, g), vertices(g))
29+
minimum_degree(g::Graph) = minimum(Base.Fix1(degree, g), vertices(g))
30+
31+
## Bipartite graph
1332

1433
"""
15-
AdjacencyGraph
34+
BipartiteGraph{T}
1635
17-
Undirected graph representing the nonzeros of a symmetrix matrix (typically a Hessian matrix).
36+
Undirected bipartite graph structure stored in bidirectional Compressed Sparse Column format (redundancy allows for faster access).
1837
19-
The adjacency graph of a symmetrix matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)` where
38+
A bipartite graph has two "sides", which we number `1` and `2`.
2039
21-
- `V = 1:n` is the set of rows or columns
22-
- `(i, j) ∈ E` whenever `A[i, j] ≠ 0` and `i ≠ j`
40+
# Fields
41+
42+
- `g1::Graph{T}`: contains the neighbors for vertices on side `1`
43+
- `g2::Graph{T}`: contains the neighbors for vertices on side `2`
2344
"""
24-
struct AdjacencyGraph{T}
25-
g::Graph{T}
45+
struct BipartiteGraph{T<:Integer}
46+
g1::Graph{T}
47+
g2::Graph{T}
2648
end
2749

28-
function AdjacencyGraph(H::SparseMatrixCSC)
29-
g = Graph(H - Diagonal(H))
30-
return AdjacencyGraph(g)
31-
end
50+
Base.length(bg::BipartiteGraph, ::Val{1}) = length(bg.g1)
51+
Base.length(bg::BipartiteGraph, ::Val{2}) = length(bg.g2)
52+
SparseArrays.nnz(bg::BipartiteGraph) = nnz(bg.g1)
3253

33-
Base.length(ag::AdjacencyGraph) = length(ag.g)
54+
"""
55+
vertices(bg::BipartiteGraph, Val(side))
3456
57+
Return the list of vertices of `bg` from the specified `side` as a range `1:n`.
3558
"""
36-
neighbors(ag::AdjacencyGraph, v::Integer)
59+
vertices(bg::BipartiteGraph, ::Val{side}) where {side} = 1:length(bg, Val(side))
3760

38-
Return the neighbors of `v` in the graph `ag`.
3961
"""
40-
neighbors(ag::AdjacencyGraph, v::Integer) = neighbors(ag.g, v)
62+
neighbors(bg::BipartiteGraph, Val(side), v::Integer)
4163
42-
degree(ag::AdjacencyGraph, v::Integer) = length(neighbors(ag, v))
64+
Return the neighbors of `v` (a vertex from the specified `side`, `1` or `2`), in the graph `bg`.
65+
"""
66+
neighbors(bg::BipartiteGraph, ::Val{1}, v::Integer) = neighbors(bg.g1, v)
67+
neighbors(bg::BipartiteGraph, ::Val{2}, v::Integer) = neighbors(bg.g2, v)
4368

44-
## Bipartite graph
69+
degree(bg::BipartiteGraph, ::Val{1}, v::Integer) = degree(bg.g1, v)
70+
degree(bg::BipartiteGraph, ::Val{2}, v::Integer) = degree(bg.g2, v)
71+
72+
function maximum_degree(bg::BipartiteGraph, ::Val{side}) where {side}
73+
return maximum(v -> degree(bg, Val(side), v), vertices(bg, Val(side)))
74+
end
75+
76+
function minimum_degree(bg::BipartiteGraph, ::Val{side}) where {side}
77+
return minimum(v -> degree(bg, Val(side), v), vertices(bg, Val(side)))
78+
end
79+
80+
## Construct from matrices
81+
82+
"""
83+
adjacency_graph(H::AbstractMatrix)
84+
85+
Return a [`Graph`](@ref) representing the nonzeros of a symmetric matrix (typically a Hessian matrix).
86+
87+
The adjacency graph of a symmetrix matric `A ∈ ℝ^{n × n}` is `G(A) = (V, E)` where
88+
89+
- `V = 1:n` is the set of rows or columns `i`/`j`
90+
- `(i, j) ∈ E` whenever `A[i, j] ≠ 0` and `i ≠ j`
91+
"""
92+
adjacency_graph(H::SparseMatrixCSC) = Graph(H - Diagonal(H))
93+
adjacency_graph(H::AbstractMatrix) = adjacency_graph(sparse(H))
4594

4695
"""
47-
BipartiteGraph
96+
bipartite_graph(J::AbstractMatrix)
4897
49-
Undirected bipartite graph representing the nonzeros of a non-symmetric matrix (typically a Jacobian matrix).
98+
Return a [`BipartiteGraph`](@ref) representing the nonzeros of a non-symmetric matrix (typically a Jacobian matrix).
5099
51100
The bipartite graph of a matrix `A ∈ ℝ^{m × n}` is `Gb(A) = (V₁, V₂, E)` where
52101
53102
- `V₁ = 1:m` is the set of rows `i`
54103
- `V₂ = 1:n` is the set of columns `j`
55104
- `(i, j) ∈ E` whenever `A[i, j] ≠ 0`
56105
"""
57-
struct BipartiteGraph{T}
58-
g1::Graph{T}
59-
g2::Graph{T}
60-
end
61-
62-
function BipartiteGraph(J::SparseMatrixCSC)
63-
g1 = Graph(SparseMatrixCSC(transpose(J))) # rows to columns
106+
function bipartite_graph(J::SparseMatrixCSC)
107+
g1 = Graph(transpose(J)) # rows to columns
64108
g2 = Graph(J) # columns to rows
65109
return BipartiteGraph(g1, g2)
66110
end
67111

68-
Base.length(bg::BipartiteGraph, ::Val{1}) = length(bg.g1)
69-
Base.length(bg::BipartiteGraph, ::Val{2}) = length(bg.g2)
112+
bipartite_graph(J::AbstractMatrix) = bipartite_graph(sparse(J))
70113

71114
"""
72-
neighbors(bg::BipartiteGraph, Val(side), v::Integer)
115+
column_intersection_graph(J::AbstractMatrix)
73116
74-
Return the neighbors of `v`, which is a vertex from the specified `side` (`1` or `2`), in the graph `bg`.
75-
"""
76-
neighbors(bg::BipartiteGraph, ::Val{1}, v::Integer) = neighbors(bg.g1, v)
77-
neighbors(bg::BipartiteGraph, ::Val{2}, v::Integer) = neighbors(bg.g2, v)
117+
Return a [`Graph`](@ref) representing the column intersections of a non-symmetric matrix (typically a Jacobian matrix).
118+
119+
The column intersection graph of a matrix `A ∈ ℝ^{m × n}` is `Gc(A) = (V, E)` where
78120
79-
function degree(bg::BipartiteGraph, ::Val{side}, v::Integer) where {side}
80-
return length(neighbors(bg, Val(side), v))
121+
- `V = 1:n` is the set of columns `j`
122+
- `(j1, j2) ∈ E` whenever `A[:, j1] ∩ A[:, j2] ≠ ∅`
123+
"""
124+
function column_intersection_graph(J::SparseMatrixCSC)
125+
A = transpose(J) * J
126+
return adjacency_graph(A - Diagonal(A))
81127
end
128+
129+
column_intersection_graph(J::AbstractMatrix) = column_intersection_graph(sparse(J))

src/order.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ Order vertices as they come in the graph.
1818
"""
1919
struct NaturalOrder <: AbstractOrder end
2020

21-
function vertices(ag::AdjacencyGraph, ::NaturalOrder)
22-
return 1:length(ag)
21+
function vertices(g::Graph, ::NaturalOrder)
22+
return vertices(g)
2323
end
2424

2525
function vertices(bg::BipartiteGraph, ::Val{side}, ::NaturalOrder) where {side}
26-
return 1:length(bg, Val(side))
26+
return vertices(bg, Val(side))
2727
end
2828

2929
"""
@@ -37,8 +37,8 @@ end
3737

3838
RandomOrder() = RandomOrder(default_rng())
3939

40-
function vertices(ag::AdjacencyGraph, order::RandomOrder)
41-
return randperm(order.rng, length(ag))
40+
function vertices(g::Graph, order::RandomOrder)
41+
return randperm(order.rng, length(g))
4242
end
4343

4444
function vertices(bg::BipartiteGraph, ::Val{side}, order::RandomOrder) where {side}
@@ -52,12 +52,12 @@ Order vertices by decreasing degree.
5252
"""
5353
struct LargestFirst <: AbstractOrder end
5454

55-
function vertices(ag::AdjacencyGraph, ::LargestFirst)
56-
criterion(v) = degree(ag, v)
57-
return sort(1:length(ag); by=criterion, rev=true)
55+
function vertices(g::Graph, ::LargestFirst)
56+
criterion(v) = degree(g, v)
57+
return sort(vertices(g); by=criterion, rev=true)
5858
end
5959

6060
function vertices(bg::BipartiteGraph, ::Val{side}, ::LargestFirst) where {side}
6161
criterion(v) = degree(bg, Val(side), v)
62-
return sort(1:length(bg, Val(side)); by=criterion, rev=true)
62+
return sort(vertices(bg, Val(side)); by=criterion, rev=true)
6363
end

0 commit comments

Comments
 (0)