Skip to content

Commit db4235d

Browse files
committed
Acyclic coloring -- enhanced edition
1 parent b601dce commit db4235d

3 files changed

Lines changed: 112 additions & 126 deletions

File tree

src/coloring.jl

Lines changed: 96 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,12 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::
8383
ne = nb_edges(g)
8484
color = zeros(Int, nv)
8585
forbidden_colors = zeros(Int, nv)
86+
edge_to_index = Vector{Int}(undef, nnz(S))
8687
first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered
8788
treated = zeros(Int, nv)
88-
edge_to_index = Vector{Int}(undef, nnz(S))
8989
star = Vector{Int}(undef, ne)
9090
hub = Int[] # one hub for each star, including the trivial ones
91-
sizehint!(hub, ne)
9291
nb_spokes = Int[] # number of spokes for each star
93-
sizehint!(nb_spokes, ne)
9492
vertices_in_order = vertices(g, order)
9593

9694
# edge_to_index gives an index for each edge
@@ -130,7 +128,7 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::
130128
else
131129
first_neighbor[color[w]] = (v, w, index_vw)
132130
for (ix, x) in enumerate(neighbors2(g, w))
133-
(x == w || x == v || iszero(color[x])) && continue
131+
(w == x || x == v || iszero(color[x])) && continue
134132
index_wx = edge_to_index[S.colptr[w] + ix - 1]
135133
if x == hub[star[index_wx]] # potential Case 2 (which is always false for trivial stars with two vertices, since the associated hub is negative)
136134
forbidden_colors[color[x]] = v
@@ -190,7 +188,7 @@ function _update_stars!(
190188
index_vw = edge_to_index[S.colptr[v] + iw - 1]
191189
x_exists = false
192190
for (ix, x) in enumerate(neighbors2(g, w))
193-
(x == w) && continue
191+
(w == x) && continue
194192
if x != v && color[x] == color[v] # vw, wx ∈ E
195193
index_wx = edge_to_index[S.colptr[w] + ix - 1]
196194
star_wx = star[index_wx]
@@ -278,8 +276,6 @@ function StarSet(
278276
return StarSet(star, hub, spokes)
279277
end
280278

281-
_sort(u, v) = (min(u, v), max(u, v))
282-
283279
"""
284280
acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::Bool)
285281
@@ -305,27 +301,58 @@ If `postprocessing=true`, some colors might be replaced with `0` (the "neutral"
305301
"""
306302
function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::Bool)
307303
# Initialize data structures
304+
S = pattern(g)
308305
nv = nb_vertices(g)
309306
ne = nb_edges(g)
310307
color = zeros(Int, nv)
311308
forbidden_colors = zeros(Int, nv)
312-
first_neighbor = fill((0, 0), nv) # at first no neighbors have been encountered
309+
edge_to_index = Vector{Int}(undef, nnz(S))
310+
first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered
313311
first_visit_to_tree = fill((0, 0), ne)
314312
forest = Forest{Int}(ne)
315313
vertices_in_order = vertices(g, order)
316314

315+
# edge_to_index gives an index for each edge
316+
# use forbidden_colors (or color) for the offsets of each column
317+
offsets = forbidden_colors
318+
counter = 0
319+
rvS = rowvals(S)
320+
for j in axes(S, 2)
321+
for k in nzrange(S, j)
322+
i = rvS[k]
323+
if i > j
324+
counter += 1
325+
edge_to_index[k] = counter
326+
k2 = S.colptr[i] + offsets[i]
327+
edge_to_index[k2] = counter
328+
offsets[i] += 1
329+
end
330+
end
331+
end
332+
fill!(offsets, 0)
333+
# Note that we don't need to do that for bicoloring,
334+
# we can build that in the same time than the transposed sparsity pattern of A
335+
317336
for v in vertices_in_order
318337
for w in neighbors(g, v)
319338
iszero(color[w]) && continue
320339
forbidden_colors[color[w]] = v
321340
end
322341
for w in neighbors(g, v)
323342
iszero(color[w]) && continue
324-
for x in neighbors(g, w)
325-
iszero(color[x]) && continue
343+
for (ix, x) in enumerate(neighbors2(g, w))
344+
(w == x || iszero(color[x])) && continue
326345
if forbidden_colors[color[x]] != v
346+
index_wx = edge_to_index[S.colptr[w] + ix - 1]
327347
_prevent_cycle!(
328-
v, w, x, color, first_visit_to_tree, forbidden_colors, forest
348+
v,
349+
w,
350+
x,
351+
index_wx,
352+
color,
353+
first_visit_to_tree,
354+
forbidden_colors,
355+
forest,
329356
)
330357
end
331358
end
@@ -336,22 +363,25 @@ function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessin
336363
break
337364
end
338365
end
339-
for w in neighbors(g, v) # grow two-colored stars around the vertex v
340-
iszero(color[w]) && continue
341-
_grow_star!(v, w, color, first_neighbor, forest)
366+
for (iw, w) in enumerate(neighbors2(g, v)) # grow two-colored stars around the vertex v
367+
(v == w || iszero(color[w])) && continue
368+
index_vw = edge_to_index[S.colptr[v] + iw - 1]
369+
_grow_star!(v, w, index_vw, color, first_neighbor, forest)
342370
end
343-
for w in neighbors(g, v)
344-
iszero(color[w]) && continue
345-
for x in neighbors(g, w)
346-
(x == v || iszero(color[x])) && continue
371+
for (iw, w) in enumerate(neighbors2(g, v))
372+
(v == w || iszero(color[w])) && continue
373+
for (ix, x) in enumerate(neighbors2(g, w))
374+
(w == x || x == v || iszero(color[x])) && continue
347375
if color[x] == color[v]
348-
_merge_trees!(v, w, x, forest) # merge trees T₁ ∋ vw and T₂ ∋ wx if T₁ != T₂
376+
index_vw = edge_to_index[S.colptr[v] + iw - 1]
377+
index_wx = edge_to_index[S.colptr[w] + ix - 1]
378+
_merge_trees!(v, w, x, index_vw, index_wx, forest) # merge trees T₁ ∋ vw and T₂ ∋ wx if T₁ != T₂
349379
end
350380
end
351381
end
352382
end
353383

354-
tree_set = TreeSet(forest, nb_vertices(g))
384+
tree_set = TreeSet(g, forest, edge_to_index)
355385
if postprocessing
356386
# Reuse the vector forbidden_colors to compute offsets during post-processing
357387
postprocess!(color, tree_set, g, forbidden_colors)
@@ -364,17 +394,16 @@ function _prevent_cycle!(
364394
v::Integer,
365395
w::Integer,
366396
x::Integer,
397+
index_wx::Integer,
367398
color::AbstractVector{<:Integer},
368399
# modified
369400
first_visit_to_tree::AbstractVector{<:Tuple},
370401
forbidden_colors::AbstractVector{<:Integer},
371402
forest::Forest{<:Integer},
372403
)
373-
wx = _sort(w, x)
374-
id = find_root!(forest, wx) # The edge wx belongs to the 2-colored tree T, represented by an edge with an integer ID
404+
id = find_root!(forest, index_wx) # The edge wx belongs to the 2-colored tree T, represented by an edge with an integer ID
375405
(p, q) = first_visit_to_tree[id]
376406
if p != v # T is being visited from vertex v for the first time
377-
vw = _sort(v, w)
378407
first_visit_to_tree[id] = (v, w)
379408
elseif q != w # T is connected to vertex v via at least two edges
380409
forbidden_colors[color[x]] = v
@@ -386,21 +415,19 @@ function _grow_star!(
386415
# not modified
387416
v::Integer,
388417
w::Integer,
418+
index_vw::Integer,
389419
color::AbstractVector{<:Integer},
390420
# modified
391421
first_neighbor::AbstractVector{<:Tuple},
392422
forest::Forest{<:Integer},
393423
)
394-
vw = _sort(v, w)
395-
push!(forest, vw) # Create a new tree T_{vw} consisting only of edge vw
396-
(p, q) = first_neighbor[color[w]]
424+
# Create a new tree T_{vw} consisting only of edge vw
425+
(p, q, index_pq) = first_neighbor[color[w]]
397426
if p != v # a neighbor of v with color[w] encountered for the first time
398-
first_neighbor[color[w]] = (v, w)
427+
first_neighbor[color[w]] = (v, w, index_vw)
399428
else # merge T_{vw} with a two-colored star being grown around v
400-
vw = _sort(v, w)
401-
pq = _sort(p, q)
402-
root1 = find_root!(forest, vw)
403-
root2 = find_root!(forest, pq)
429+
root1 = find_root!(forest, index_vw)
430+
root2 = find_root!(forest, index_pq)
404431
root_union!(forest, root1, root2)
405432
end
406433
return nothing
@@ -411,13 +438,13 @@ function _merge_trees!(
411438
v::Integer,
412439
w::Integer,
413440
x::Integer,
441+
index_vw::Integer,
442+
index_wx::Integer,
414443
# modified
415444
forest::Forest{<:Integer},
416445
)
417-
vw = _sort(v, w)
418-
wx = _sort(w, x)
419-
root1 = find_root!(forest, vw)
420-
root2 = find_root!(forest, wx)
446+
root1 = find_root!(forest, index_vw)
447+
root2 = find_root!(forest, index_wx)
421448
if root1 != root2
422449
root_union!(forest, root1, root2)
423450
end
@@ -438,10 +465,9 @@ struct TreeSet
438465
is_star::Vector{Bool}
439466
end
440467

441-
function TreeSet(forest::Forest{Int}, nvertices::Int)
442-
# Forest is a structure defined in forest.jl
443-
# - forest.intmap: a dictionary that maps an edge (i, j) to an integer k
444-
# - forest.num_trees: the number of trees in the forest
468+
function TreeSet(g::AdjacencyGraph, forest::Forest{Int}, edge_to_index::Vector{Int})
469+
S = pattern(g)
470+
nv = nb_vertices(g)
445471
nt = forest.num_trees
446472

447473
# dictionary that maps a tree's root to the index of the tree
@@ -451,38 +477,45 @@ function TreeSet(forest::Forest{Int}, nvertices::Int)
451477
# vector of dictionaries where each dictionary stores the neighbors of each vertex in a tree
452478
trees = [Dict{Int,Vector{Int}}() for i in 1:nt]
453479

454-
# counter of the number of roots found
455-
k = 0
456-
for edge in keys(forest.intmap)
457-
i, j = edge
458-
root = find_root!(forest, edge)
480+
# current number of roots found
481+
nr = 0
459482

460-
# Update roots
461-
if !haskey(roots, root)
462-
k += 1
463-
roots[root] = k
464-
end
483+
rvS = rowvals(S)
484+
for j in axes(S, 2)
485+
for pos in nzrange(S, j)
486+
i = rvS[pos]
487+
if i > j
488+
index_ij = edge_to_index[pos]
489+
root = find_root!(forest, index_ij)
465490

466-
# index of the tree T that contains this edge
467-
index_tree = roots[root]
491+
# Update roots
492+
if !haskey(roots, root)
493+
nr += 1
494+
roots[root] = nr
495+
end
468496

469-
# Update the neighbors of i in the tree T
470-
if !haskey(trees[index_tree], i)
471-
trees[index_tree][i] = [j]
472-
else
473-
push!(trees[index_tree][i], j)
474-
end
497+
# index of the tree T that contains this edge
498+
index_tree = roots[root]
475499

476-
# Update the neighbors of j in the tree T
477-
if !haskey(trees[index_tree], j)
478-
trees[index_tree][j] = [i]
479-
else
480-
push!(trees[index_tree][j], i)
500+
# Update the neighbors of i in the tree T
501+
if !haskey(trees[index_tree], i)
502+
trees[index_tree][i] = [j]
503+
else
504+
push!(trees[index_tree][i], j)
505+
end
506+
507+
# Update the neighbors of j in the tree T
508+
if !haskey(trees[index_tree], j)
509+
trees[index_tree][j] = [i]
510+
else
511+
push!(trees[index_tree][j], i)
512+
end
513+
end
481514
end
482515
end
483516

484517
# degrees is a vector of integers that stores the degree of each vertex in a tree
485-
degrees = Vector{Int}(undef, nvertices)
518+
degrees = Vector{Int}(undef, nv)
486519

487520
# reverse breadth first (BFS) traversal order for each tree in the forest
488521
reverse_bfs_orders = [Tuple{Int,Int}[] for i in 1:nt]

src/forest.jl

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,19 @@ Structure that provides fast union-find operations for constructing a forest dur
1010
$TYPEDFIELDS
1111
"""
1212
mutable struct Forest{T<:Integer}
13-
"current number of edges in the forest"
14-
num_edges::T
1513
"current number of distinct trees in the forest"
1614
num_trees::T
17-
"dictionary mapping each edge represented as a tuple of vertices to its unique integer index"
18-
intmap::Dict{Tuple{T,T},T}
1915
"vector storing the index of a parent in the tree for each edge, used in union-find operations"
2016
parents::Vector{T}
2117
"vector approximating the depth of each tree to optimize path compression"
2218
ranks::Vector{T}
2319
end
2420

2521
function Forest{T}(n::Integer) where {T<:Integer}
26-
num_edges = zero(T)
27-
num_trees = zero(T)
28-
intmap = Dict{Tuple{T,T},T}()
29-
sizehint!(intmap, n)
22+
num_trees = T(n)
3023
parents = collect(Base.OneTo(T(n)))
3124
ranks = zeros(T, T(n))
32-
return Forest{T}(num_edges, num_trees, intmap, parents, ranks)
33-
end
34-
35-
function Base.push!(forest::Forest{T}, edge::Tuple{T,T}) where {T<:Integer}
36-
forest.num_edges += 1
37-
forest.intmap[edge] = forest.num_edges
38-
forest.num_trees += one(T)
39-
return forest
25+
return Forest{T}(num_trees, parents, ranks)
4026
end
4127

4228
function _find_root!(parents::Vector{T}, index_edge::T) where {T<:Integer}
@@ -47,8 +33,8 @@ function _find_root!(parents::Vector{T}, index_edge::T) where {T<:Integer}
4733
return p
4834
end
4935

50-
function find_root!(forest::Forest{T}, edge::Tuple{T,T}) where {T<:Integer}
51-
return _find_root!(forest.parents, forest.intmap[edge])
36+
function find_root!(forest::Forest{T}, index_edge::T) where {T<:Integer}
37+
return _find_root!(forest.parents, index_edge)
5238
end
5339

5440
function root_union!(forest::Forest{T}, index_edge1::T, index_edge2::T) where {T<:Integer}

0 commit comments

Comments
 (0)