Skip to content

Commit 3bcb5f9

Browse files
committed
First algorithms
1 parent 614285e commit 3bcb5f9

16 files changed

+470
-5
lines changed

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ authors = ["Guillaume Dalle <22795598+gdalle@users.noreply.github.com>"]
44
version = "0.1.0"
55

66
[deps]
7+
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
8+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
79
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
810
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
911

1012
[compat]
13+
ADTypes = "1.2.1"
14+
LinearAlgebra = "1"
1115
Random = "1"
1216
SparseArrays = "1"
1317
julia = "1.6"

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ The algorithms implemented in this package are all taken from the following surv
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+
Some parts of the survey (like definitions and theorems) are also copied verbatim or referred to by their number in the documentation.
25+
2426
## Alternatives
2527

26-
- [ColPack.jl](https://github.com/michel2323/ColPack.jl)
27-
- [SparseDiffTools.jl](https://github.com/JuliaDiff/SparseDiffTools.jl)
28+
- [ColPack.jl](https://github.com/michel2323/ColPack.jl): a Julia interface to the C++ library [ColPack](https://github.com/CSCsw/ColPack)
29+
- [SparseDiffTools.jl](https://github.com/JuliaDiff/SparseDiffTools.jl): contains Julia implementations of some coloring algorithms

docs/src/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ Private = false
1515

1616
```@autodocs
1717
Modules = [SparseMatrixColorings]
18-
Private = false
18+
Public = false
1919
```

src/SparseMatrixColorings.jl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
1+
"""
2+
SparseMatrixColorings
3+
4+
Coloring algorithms for sparse Jacobian and Hessian matrices.
5+
6+
The algorithms implemented in this package are all taken from the following survey:
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+
Some parts of the survey (like definitions and theorems) are also copied verbatim or referred to by their number in the documentation.
11+
"""
112
module SparseMatrixColorings
213

14+
using ADTypes: ADTypes, AbstractColoringAlgorithm
15+
using LinearAlgebra: Transpose, parent, transpose
316
using Random: AbstractRNG
4-
using SparseArrays: SparseMatrixCSC
17+
using SparseArrays: SparseMatrixCSC, nzrange, rowvals
18+
19+
include("utils.jl")
20+
21+
include("bipartite_graph.jl")
22+
include("adjacency_graph.jl")
23+
24+
include("distance2_coloring.jl")
25+
include("star_coloring.jl")
26+
27+
include("adtypes.jl")
28+
include("check.jl")
29+
30+
export GreedyColoringAlgorithm
531

632
end

src/adjacency_graph.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
AdjacencyGraph
3+
4+
Represent a graph between the columns of a symmetric `n × n` matrix `A` with nonzero diagonal elements.
5+
6+
This graph is defined as `G = (C, E)` where `C = 1:n` is the set of columns and `(i, j) ∈ E` whenever `A[i, j]` is nonzero for some `j ∈ 1:m`, `j ≠ i`.
7+
8+
# Fields
9+
10+
- `A_colmajor::AbstractMatrix`: output of [`col_major`](@ref) applied to `A`
11+
"""
12+
struct AdjacencyGraph{M<:AbstractMatrix}
13+
A_colmajor::M
14+
15+
function AdjacencyGraph(A::AbstractMatrix)
16+
A_colmajor = col_major(A)
17+
return new{typeof(A_colmajor)}(A_colmajor)
18+
end
19+
end
20+
21+
rows(g::AdjacencyGraph) = axes(g.A_colmajor, 1)
22+
columns(g::AdjacencyGraph) = axes(g.A_colmajor, 2)
23+
24+
function neighbors(g::AdjacencyGraph, j::Integer)
25+
return filter(!isequal(j), nz_in_col(g.A_colmajor, j))
26+
end

src/adtypes.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
GreedyColoringAlgorithm <: ADTypes.AbstractColoringAlgorithm
3+
4+
Matrix coloring algorithm for sparse Jacobians and Hessians.
5+
6+
Compatible with the [ADTypes.jl coloring framework](https://sciml.github.io/ADTypes.jl/stable/#Coloring-algorithm).
7+
8+
# Implements
9+
10+
- `ADTypes.column_coloring` with a partial distance-2 coloring of the bipartite graph
11+
- `ADTypes.row_coloring` with a partial distance-2 coloring of the bipartite graph
12+
- `ADTypes.symmetric_coloring` with a star coloring of the adjacency graph
13+
"""
14+
struct GreedyColoringAlgorithm <: ADTypes.AbstractColoringAlgorithm end
15+
16+
function ADTypes.column_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
17+
g = BipartiteGraph(A)
18+
return distance2_column_coloring(g)
19+
end
20+
21+
function ADTypes.row_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
22+
g = BipartiteGraph(A)
23+
return distance2_row_coloring(g)
24+
end
25+
26+
function ADTypes.symmetric_coloring(A::AbstractMatrix, ::GreedyColoringAlgorithm)
27+
g = AdjacencyGraph(A)
28+
return star_coloring(g)
29+
end

src/bipartite_graph.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
BipartiteGraph
3+
4+
Represent a bipartite graph between the rows and the columns of a non-symmetric `m × n` matrix `A`.
5+
6+
This graph is defined as `G = (R, C, E)` where `R = 1:m` is the set of row indices, `C = 1:n` is the set of column indices and `(i, j) ∈ E` whenever `A[i, j]` is nonzero.
7+
8+
# Fields
9+
10+
- `A_colmajor::AbstractMatrix`: output of [`col_major`](@ref) applied to `A` (useful to get neighbors of a column)
11+
- `A_rowmajor::AbstractMatrix`: output of [`row_major`](@ref) applied to `A` (useful to get neighbors of a row)
12+
"""
13+
struct BipartiteGraph{M1<:AbstractMatrix,M2<:AbstractMatrix}
14+
A_colmajor::M1
15+
A_rowmajor::M2
16+
17+
function BipartiteGraph(A::AbstractMatrix)
18+
A_colmajor = col_major(A)
19+
A_rowmajor = row_major(A)
20+
return new{typeof(A_colmajor),typeof(A_rowmajor)}(A_colmajor, A_rowmajor)
21+
end
22+
end
23+
24+
rows(g::BipartiteGraph) = axes(g.A_colmajor, 1)
25+
columns(g::BipartiteGraph) = axes(g.A_colmajor, 2)
26+
27+
neighbors_of_column(g::BipartiteGraph, j::Integer) = nz_in_col(g.A_colmajor, j)
28+
neighbors_of_row(g::BipartiteGraph, i::Integer) = nz_in_row(g.A_rowmajor, i)

src/check.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
check_structurally_orthogonal_columns(A, colors; verbose=false)
3+
4+
Return `true` if coloring the columns of the matrix `A` with the vector `colors` results in a partition that is structurally orthogonal, and `false` otherwise.
5+
6+
Def 3.2: A partition of the columns of a matrix `A` is _structurally orthogonal_ if, for every nonzero element `A[i, j]`, the group containing column `A[:, j]` has no other column with a nonzero in row `i`.
7+
8+
Thm 3.5: The function [`distance2_column_coloring`](@ref) applied to the [`BipartiteGraph`](@ref) of `A` should return a suitable coloring.
9+
"""
10+
function check_structurally_orthogonal_columns(
11+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose=false
12+
)
13+
for c in unique(colors)
14+
js = filter(j -> colors[j] == c, axes(A, 2))
15+
Ajs = @view A[:, js]
16+
nonzeros_per_row = count(!iszero, Ajs; dims=2)
17+
if maximum(nonzeros_per_row) > 1
18+
verbose && @warn "Color $c has columns $js sharing nonzeros"
19+
return false
20+
end
21+
end
22+
return true
23+
end
24+
25+
"""
26+
check_structurally_orthogonal_rows(A, colors; verbose=false)
27+
28+
Return `true` if coloring the rows of the matrix `A` with the vector `colors` results in a partition that is structurally orthogonal, and `false` otherwise.
29+
30+
Def 3.2: A partition of the rows of a matrix `A` is _structurally orthogonal_ if, for every nonzero element `A[i, j]`, the group containing row `A[i, :]` has no other row with a nonzero in column `j`.
31+
32+
Thm 3.5: The function [`distance2_row_coloring`](@ref) applied to the [`BipartiteGraph`](@ref) of `A` should return a suitable coloring.
33+
"""
34+
function check_structurally_orthogonal_rows(
35+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose=false
36+
)
37+
for c in unique(colors)
38+
is = filter(i -> colors[i] == c, axes(A, 1))
39+
Ais = @view A[is, :]
40+
nonzeros_per_column = count(!iszero, Ais; dims=1)
41+
if maximum(nonzeros_per_column) > 1
42+
verbose && @warn "Color $c has rows $is sharing nonzeros"
43+
return false
44+
end
45+
end
46+
return true
47+
end
48+
49+
"""
50+
check_symmetrically_orthogonal(A, colors; verbose=false)
51+
52+
Return `true` if coloring the columns of the symmetric matrix `A` with the vector `colors` results in a partition that is symmetrically orthogonal, and `false` otherwise.
53+
54+
Def 4.2: A partition of the columns of a symmetrix matrix `A` is _symmetrically orthogonal_ if, for every nonzero element `A[i, j]`, either
55+
56+
1. the group containing the column `A[:, j]` has no other column with a nonzero in row `i`
57+
2. the group containing the column `A[:, i]` has no other column with a nonzero in row `j`
58+
"""
59+
function check_symmetrically_orthogonal(
60+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose=false
61+
)
62+
for i in axes(A, 2), j in axes(A, 2)
63+
if !iszero(A[i, j])
64+
group_i = filter(i2 -> (i2 != i) && (colors[i2] == colors[i]), axes(A, 2))
65+
group_j = filter(j2 -> (j2 != j) && (colors[j2] == colors[j]), axes(A, 2))
66+
A_group_i_column_j = @view A[group_i, j]
67+
A_group_j_column_i = @view A[group_j, i]
68+
nonzeros_group_i_column_j = count(!iszero, A_group_i_column_j)
69+
nonzeros_group_j_column_i = count(!iszero, A_group_j_column_i)
70+
if nonzeros_group_i_column_j > 0 && nonzeros_group_j_column_i > 0
71+
verbose && @warn """
72+
For coefficient $((i, j)), both of the following have confounding zeros:
73+
- color $(colors[j]) with group $group_j
74+
- color $(colors[i]) with group $group_i
75+
"""
76+
return false
77+
end
78+
end
79+
end
80+
return true
81+
end

src/distance2_coloring.jl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
distance2_column_coloring(g::BipartiteGraph)
3+
4+
Compute a distance-2 coloring of the column vertices in the [`BipartiteGraph`](@ref) `g` and return a vector of integer colors.
5+
6+
A _distance-2 coloring_ is such that two vertices have different colors if they are at distance at most 2.
7+
8+
The algorithm used is the greedy Algorithm 3.2.
9+
"""
10+
function distance2_column_coloring(g::BipartiteGraph)
11+
n = length(columns(g))
12+
colors = zeros(Int, n)
13+
forbidden_colors = zeros(Int, n)
14+
for v in sort(columns(g); by=j -> length(neighbors_of_column(g, j)), rev=true)
15+
for w in neighbors_of_column(g, v)
16+
for x in neighbors_of_row(g, w)
17+
if !iszero(colors[x])
18+
forbidden_colors[colors[x]] = v
19+
end
20+
end
21+
end
22+
for c in eachindex(forbidden_colors)
23+
if forbidden_colors[c] != v
24+
colors[v] = c
25+
break
26+
end
27+
end
28+
end
29+
return colors
30+
end
31+
32+
"""
33+
distance2_row_coloring(g::BipartiteGraph)
34+
35+
Compute a distance-2 coloring of the row vertices in the [`BipartiteGraph`](@ref) `g` and return a vector of integer colors.
36+
37+
A _distance-2 coloring_ is such that two vertices have different colors if they are at distance at most 2.
38+
39+
The algorithm used is the greedy Algorithm 3.2.
40+
"""
41+
function distance2_row_coloring(g::BipartiteGraph)
42+
m = length(rows(g))
43+
colors = zeros(Int, m)
44+
forbidden_colors = zeros(Int, m)
45+
for v in sort(rows(g); by=i -> length(neighbors_of_row(g, i)), rev=true)
46+
for w in neighbors_of_row(g, v)
47+
for x in neighbors_of_column(g, w)
48+
if !iszero(colors[x])
49+
forbidden_colors[colors[x]] = v
50+
end
51+
end
52+
end
53+
for c in eachindex(forbidden_colors)
54+
if forbidden_colors[c] != v
55+
colors[v] = c
56+
break
57+
end
58+
end
59+
end
60+
return colors
61+
end

src/star_coloring.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
star_coloring(g::BipartiteGraph)
3+
4+
Compute a star coloring of the column vertices in the [`AdjacencyGraph`](@ref) `g` and return a vector of integer colors.
5+
6+
Def 4.5: A _star coloring_ is a distance-1 coloring such that every path on four vertices uses at least three colors.
7+
8+
The algorithm used is the greedy Algorithm 4.1.
9+
"""
10+
function star_coloring(g::AdjacencyGraph)
11+
n = length(columns(g))
12+
colors = zeros(Int, n)
13+
forbidden_colors = zeros(Int, n)
14+
for v in sort(columns(g); by=j -> length(neighbors(g, j)), rev=true)
15+
for w in neighbors(g, v)
16+
if !iszero(colors[w]) # w is colored
17+
forbidden_colors[colors[w]] = v
18+
end
19+
for x in neighbors(g, w)
20+
if !iszero(colors[x]) && iszero(colors[w]) # w is not colored
21+
forbidden_colors[colors[x]] = v
22+
else
23+
for y in neighbors(g, x)
24+
if !iszero(colors[y]) && y != w
25+
if colors[y] == colors[w]
26+
forbidden_colors[colors[x]] = v
27+
break
28+
end
29+
end
30+
end
31+
end
32+
end
33+
end
34+
for c in eachindex(forbidden_colors)
35+
if forbidden_colors[c] != v
36+
colors[v] = c
37+
break
38+
end
39+
end
40+
end
41+
return colors
42+
end

src/utils.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Conversion between row- and col-major
2+
3+
"""
4+
col_major(A::AbstractMatrix)
5+
6+
Construct a column-major representation of the matrix `A`.
7+
"""
8+
col_major(A::M) where {M<:AbstractMatrix} = A
9+
col_major(A::Transpose{<:Any,M}) where {M<:AbstractMatrix} = M(A)
10+
11+
"""
12+
row_major(A::AbstractMatrix)
13+
14+
Construct a row-major representation of the matrix `A`.
15+
"""
16+
row_major(A::M) where {M<:AbstractMatrix} = transpose(M(transpose(A)))
17+
row_major(A::Transpose{<:Any,M}) where {M<:AbstractMatrix} = A
18+
19+
## Generic nz
20+
21+
function nz_in_col(A_colmajor::AbstractMatrix, j::Integer)
22+
return filter(i -> !iszero(A_colmajor[i, j]), axes(A_colmajor, 1))
23+
end
24+
25+
function nz_in_row(A_rowmajor::AbstractMatrix, i::Integer)
26+
return filter(j -> !iszero(A_rowmajor[i, j]), axes(A_rowmajor, 2))
27+
end
28+
29+
## Sparse nz
30+
31+
function nz_in_col(A_colmajor::SparseMatrixCSC{T}, j::Integer) where {T}
32+
rv = rowvals(A_colmajor)
33+
ind = nzrange(A_colmajor, j)
34+
return view(rv, ind)
35+
end
36+
37+
function nz_in_row(A_rowmajor::Transpose{T,<:SparseMatrixCSC{T}}, i::Integer) where {T}
38+
A_transpose_colmajor = parent(A_rowmajor)
39+
rv = rowvals(A_transpose_colmajor)
40+
ind = nzrange(A_transpose_colmajor, i)
41+
return view(rv, ind)
42+
end

test/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[deps]
2+
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
23
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
34
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
45
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
56
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
7+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
8+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
69
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

0 commit comments

Comments
 (0)