Skip to content

Commit 64c50f5

Browse files
Merge pull request #60 from pkj-m/acyclic
Added acyclic coloring
2 parents 43a81f9 + fe494d5 commit 64c50f5

File tree

7 files changed

+355
-22
lines changed

7 files changed

+355
-22
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
88
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
99
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
1010
BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0"
11+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1112
DiffEqDiffTools = "01453d9d-ee7c-5054-8395-0335cb756afa"
1213
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1314
LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d"
@@ -20,6 +21,7 @@ VertexSafeGraphs = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
2021
ArrayInterface = ">= 1.1"
2122
DiffEqDiffTools = ">= 1.3"
2223
julia = "1"
24+
DataStructures = "0.17"
2325

2426
[extras]
2527
IterativeSolvers = "42fd0dbc-a981-5370-80f2-aaf504508153"

src/SparseDiffTools.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using SparseArrays, ArrayInterface
1414

1515
using BlockBandedMatrices: blocksize, nblocks
1616
using ForwardDiff: Dual, jacobian, partials, DEFAULT_CHUNK_THRESHOLD
17+
using DataStructures: DisjointSets, find_root, union!
1718

1819
using ArrayInterface: matrix_colors
1920

@@ -39,6 +40,7 @@ include("coloring/high_level.jl")
3940
include("coloring/backtracking_coloring.jl")
4041
include("coloring/contraction_coloring.jl")
4142
include("coloring/greedy_d1_coloring.jl")
43+
include("coloring/acyclic_coloring.jl")
4244
include("coloring/greedy_star1_coloring.jl")
4345
include("coloring/greedy_star2_coloring.jl")
4446
include("coloring/matrix2graph.jl")

src/coloring/acyclic_coloring.jl

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"""
2+
color_graph(g::LightGraphs.AbstractGraphs, ::AcyclicColoring)
3+
4+
Returns a coloring vector following the acyclic coloring rules (1) the coloring
5+
corresponds to a distance-1 coloring, and (2) vertices in every cycle of the
6+
graph are assigned at least three distinct colors. This variant of coloring is
7+
called acyclic since every subgraph induced by vertices assigned any two colors
8+
is a collection of trees—and hence is acyclic.
9+
10+
Reference: Gebremedhin AH, Manne F, Pothen A. **New Acyclic and Star Coloring Algorithms with Application to Computing Hessians**
11+
"""
12+
function color_graph(g::LightGraphs.AbstractGraph, ::AcyclicColoring)
13+
14+
color = zeros(Int, nv(g))
15+
forbidden_colors = zeros(Int, nv(g))
16+
17+
set = DisjointSets{LightGraphs.Edge}([])
18+
19+
first_visit_to_tree = Array{Tuple{Int, Int}, 1}(undef, ne(g))
20+
first_neighbor = Array{Tuple{Int, Int}, 1}(undef, nv(g))
21+
22+
for v in vertices(g)
23+
#enforces the first condition of acyclic coloring
24+
for w in outneighbors(g, v)
25+
if color[w] != 0
26+
forbidden_colors[color[w]] = v
27+
end
28+
end
29+
#enforces the second condition of acyclic coloring
30+
for w in outneighbors(g, v)
31+
if color[w] != 0 #colored neighbor
32+
for x in outneighbors(g, w)
33+
if color[x] != 0 #colored x
34+
if forbidden_colors[color[x]] != v
35+
prevent_cycle(v, w, x, g, color, forbidden_colors, first_visit_to_tree, set)
36+
end
37+
end
38+
end
39+
end
40+
end
41+
42+
color[v] = min_index(forbidden_colors, v)
43+
44+
#grow star for every edge connecting colored vertices v and w
45+
for w in outneighbors(g, v)
46+
if color[w] != 0
47+
grow_star!(set, v, w, g, first_neighbor, color)
48+
end
49+
end
50+
51+
#merge the newly formed stars into existing trees if possible
52+
for w in outneighbors(g, v)
53+
if color[w] != 0
54+
for x in outneighbors(g, w)
55+
if color[x] != 0 && x != v
56+
if color[x] == color[v]
57+
merge_trees!(set, v, w, x, g)
58+
end
59+
end
60+
end
61+
end
62+
end
63+
end
64+
65+
return color
66+
end
67+
68+
"""
69+
prevent_cycle(v::Integer,
70+
w::Integer,
71+
x::Integer,
72+
g::LightGraphs.AbstractGraph,
73+
color::AbstractVector{<:Integer},
74+
forbidden_colors::AbstractVector{<:Integer},
75+
first_visit_to_tree::Array{Tuple{Integer, Integer}, 1},
76+
set::DisjointSets{LightGraphs.Edge})
77+
78+
Subroutine to avoid generation of 2-colored cycle due to coloring of vertex v,
79+
which is adjacent to vertices w and x in graph g. Disjoint set is used to store
80+
the induced 2-colored subgraphs/trees where the id of set is a key edge of g
81+
"""
82+
function prevent_cycle(v::Integer,
83+
w::Integer,
84+
x::Integer,
85+
g::LightGraphs.AbstractGraph,
86+
color::AbstractVector{<:Integer},
87+
forbidden_colors::AbstractVector{<:Integer},
88+
first_visit_to_tree::AbstractVector{<: Tuple{Integer, Integer}},
89+
set::DisjointSets{LightGraphs.Edge})
90+
91+
edge = find_edge(g, w, x)
92+
e = find_root(set, edge)
93+
p, q = first_visit_to_tree[edge_index(g, e)]
94+
if p != v
95+
first_visit_to_tree[edge_index(g, e)] = (v, w)
96+
elseif q != w
97+
forbidden_colors[color[x]] = v
98+
end
99+
end
100+
101+
"""
102+
min_index(forbidden_colors::AbstractVector{<:Integer}, v::Integer)
103+
104+
Returns min{i > 0 such that forbidden_colors[i] != v}
105+
"""
106+
function min_index(forbidden_colors::AbstractVector{<:Integer}, v::Integer)
107+
return findfirst(!isequal(v), forbidden_colors)
108+
end
109+
110+
"""
111+
grow_star!(set::DisjointSets{LightGraphs.Edge},
112+
v::Integer,
113+
w::Integer,
114+
g::LightGraphs.AbstractGraph
115+
first_neighbor::Array{Tuple{Integer, Integer}, 1})
116+
117+
Subroutine to grow a 2-colored star after assigning a new color to the
118+
previously uncolored vertex v, by comparing it with the adjacent vertex w.
119+
Disjoint set is used to store stars in sets, which are identified through key
120+
edges present in g.
121+
"""
122+
function grow_star!(set::DisjointSets{LightGraphs.Edge},
123+
v::Integer,
124+
w::Integer,
125+
g::LightGraphs.AbstractGraph,
126+
first_neighbor::AbstractArray{<: Tuple{Integer, Integer}, 1},
127+
color::AbstractVector{<: Integer})
128+
edge = find_edge(g, v, w)
129+
push!(set, edge)
130+
p, q = first_neighbor[color[w]]
131+
if p != v
132+
first_neighbor[color[w]] = (v, w)
133+
else
134+
edge1 = find_edge(g, v, w)
135+
edge2 = find_edge(g, p, q)
136+
e1 = find_root(set, edge1)
137+
e2 = find_root(set, edge2)
138+
union!(set, e1, e2)
139+
end
140+
return nothing
141+
end
142+
143+
144+
"""
145+
merge_trees!(v::Integer,
146+
w::Integer,
147+
x::Integer,
148+
g::LightGraphs.AbstractGraph,
149+
set::DisjointSets{LightGraphs.Edge})
150+
151+
Subroutine to merge trees present in the disjoint set which have a
152+
common edge.
153+
"""
154+
function merge_trees!(set::DisjointSets{LightGraphs.Edge},
155+
v::Integer,
156+
w::Integer,
157+
x::Integer,
158+
g::LightGraphs.AbstractGraph)
159+
edge1 = find_edge(g, v, w)
160+
edge2 = find_edge(g, w, x)
161+
e1 = find_root(set, edge1)
162+
e2 = find_root(set, edge2)
163+
if (e1 != e2)
164+
union!(set, e1, e2)
165+
end
166+
end
167+
168+
169+
"""
170+
find_edge(g::LightGraphs.AbstractGraph, v::Integer, w::Integer)
171+
172+
Returns an edge object of the type LightGraphs.Edge which represents the
173+
edge connecting vertices v and w of the undirected graph g
174+
"""
175+
function find_edge(g::LightGraphs.AbstractGraph,
176+
v::Integer,
177+
w::Integer)
178+
for e in edges(g)
179+
if (src(e) == v && dst(e) == w) || (src(e) == w && dst(e) == v)
180+
return e
181+
end
182+
end
183+
throw(ArgumentError("$v and $w are not connected in graph g"))
184+
end
185+
186+
"""
187+
edge_index(g::LightGraphs.AbstractGraph, e::LightGraphs.Edge)
188+
189+
Returns an Integer value which uniquely identifies the edge e in graph
190+
g. Used as an index in main function to avoid custom arrays with non-
191+
numerical indices.
192+
"""
193+
function edge_index(g::LightGraphs.AbstractGraph,
194+
e::LightGraphs.Edge)
195+
for (i, edge) in enumerate(edges(g))
196+
if edge == e
197+
return i
198+
end
199+
end
200+
throw(ArgumentError("Edge $e is not present in graph g"))
201+
end

src/coloring/backtracking_coloring.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using LightGraphs
2-
31
"""
42
color_graph(g::LightGraphs, ::BacktrackingColor)
53

src/coloring/high_level.jl

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
abstract type SparseDiffToolsColoringAlgorithm <: ArrayInterface.ColoringAlgorithm end
2-
struct GreedyD1Color <: SparseDiffToolsColoringAlgorithm end
3-
struct BacktrackingColor <: SparseDiffToolsColoringAlgorithm end
4-
struct ContractionColor <: SparseDiffToolsColoringAlgorithm end
5-
struct GreedyStar1Color <: SparseDiffToolsColoringAlgorithm end
6-
struct GreedyStar2Color <: SparseDiffToolsColoringAlgorithm end
7-
8-
"""
9-
matrix_colors(A,alg::ColoringAlgorithm = GreedyD1Color())
10-
11-
Returns the colorvec vector for the matrix A using the chosen coloring
12-
algorithm. If a known analytical solution exists, that is used instead.
13-
The coloring defaults to a greedy distance-1 coloring.
14-
15-
"""
16-
function ArrayInterface.matrix_colors(A::AbstractMatrix,alg::SparseDiffToolsColoringAlgorithm = GreedyD1Color(); partition_by_rows::Bool = false)
17-
_A = A isa SparseMatrixCSC ? A : sparse(A) # Avoid the copy
18-
A_graph = matrix2graph(_A, partition_by_rows)
19-
color_graph(A_graph,alg)
20-
end
1+
abstract type SparseDiffToolsColoringAlgorithm <: ArrayInterface.ColoringAlgorithm end
2+
struct GreedyD1Color <: SparseDiffToolsColoringAlgorithm end
3+
struct BacktrackingColor <: SparseDiffToolsColoringAlgorithm end
4+
struct ContractionColor <: SparseDiffToolsColoringAlgorithm end
5+
struct GreedyStar1Color <: SparseDiffToolsColoringAlgorithm end
6+
struct GreedyStar2Color <: SparseDiffToolsColoringAlgorithm end
7+
struct AcyclicColoring <: SparseDiffToolsColoringAlgorithm end
8+
9+
"""
10+
matrix_colors(A,alg::ColoringAlgorithm = GreedyD1Color())
11+
12+
Returns the colorvec vector for the matrix A using the chosen coloring
13+
algorithm. If a known analytical solution exists, that is used instead.
14+
The coloring defaults to a greedy distance-1 coloring.
15+
16+
"""
17+
function ArrayInterface.matrix_colors(A::AbstractMatrix, alg::SparseDiffToolsColoringAlgorithm = GreedyD1Color(); partition_by_rows::Bool = false)
18+
_A = A isa SparseMatrixCSC ? A : sparse(A) # Avoid the copy
19+
A_graph = matrix2graph(_A, partition_by_rows)
20+
return color_graph(A_graph, alg)
21+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ if GROUP == "All"
88
@time @safetestset "Exact coloring via contraction" begin include("test_contraction.jl") end
99
@time @safetestset "Greedy distance-1 coloring" begin include("test_greedy_d1.jl") end
1010
@time @safetestset "Greedy star coloring" begin include("test_greedy_star.jl") end
11+
@time @safetestset "Acyclic coloring" begin include("test_acyclic.jl") end
1112
@time @safetestset "Matrix to graph conversion" begin include("test_matrix2graph.jl") end
1213
@time @safetestset "AD using colorvec vector" begin include("test_ad.jl") end
1314
@time @safetestset "Integration test" begin include("test_integration.jl") end

0 commit comments

Comments
 (0)