Skip to content

Commit ac12b9a

Browse files
amontoisongdalle
andauthored
Add an acyclic coloring (#36)
* Add an acyclic coloring * Group by color * Typo * Noref * Fix test * Working indirect decompression * Large random tests pass --------- Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com>
1 parent 4de73c3 commit ac12b9a

9 files changed

Lines changed: 415 additions & 52 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.4.0"
66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
88
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
9+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
910
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1011
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -15,6 +16,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1516
ADTypes = "1.2.1"
1617
Compat = "3.46,4.2"
1718
DocStringExtensions = "0.8,0.9"
19+
DataStructures = "0.18"
1820
LinearAlgebra = "<0.0.1, 1"
1921
Random = "<0.0.1, 1"
2022
SparseArrays = "<0.0.1, 1"

docs/src/api.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ SparseMatrixColorings.bipartite_graph
6666
SparseMatrixColorings.partial_distance2_coloring
6767
SparseMatrixColorings.symmetric_coefficient
6868
SparseMatrixColorings.star_coloring
69-
SparseMatrixColorings.StarSet
69+
SparseMatrixColorings.acyclic_coloring
7070
SparseMatrixColorings.group_by_color
7171
SparseMatrixColorings.get_matrix
72+
SparseMatrixColorings.StarSet
73+
SparseMatrixColorings.TreeSet
7274
```
7375

7476
### Concrete coloring results
@@ -99,5 +101,7 @@ SparseMatrixColorings.matrix_versions
99101
```@docs
100102
SparseMatrixColorings.Example
101103
SparseMatrixColorings.what_fig_41
104+
SparseMatrixColorings.what_fig_61
102105
SparseMatrixColorings.efficient_fig_1
106+
SparseMatrixColorings.efficient_fig_4
103107
```

src/SparseMatrixColorings.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ $EXPORTS
99
"""
1010
module SparseMatrixColorings
1111

12+
using DataStructures: DisjointSets, find_root!, root_union!, num_groups
1213
using ADTypes: ADTypes
1314
using Compat: @compat, stack
1415
using DocStringExtensions: README, EXPORTS, SIGNATURES, TYPEDEF, TYPEDFIELDS

src/coloring.jl

Lines changed: 163 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ The vertices are colored in a greedy fashion, following the `order` supplied.
7676
"""
7777
function star_coloring(g::Graph, order::AbstractOrder)
7878
# Initialize data structures
79-
color = zeros(Int, length(g))
80-
forbidden_colors = zeros(Int, length(g))
81-
first_neighbor = fill((0, 0), length(g)) # at first no neighbors have been encountered
82-
treated = zeros(Int, length(g))
79+
nvertices = length(g)
80+
color = zeros(Int, nvertices)
81+
forbidden_colors = zeros(Int, nvertices)
82+
first_neighbor = fill((0, 0), nvertices) # at first no neighbors have been encountered
83+
treated = zeros(Int, nvertices)
8384
star = Dict{Tuple{Int,Int},Int}()
8485
hub = Int[]
8586
vertices_in_order = vertices(g, order)
@@ -119,17 +120,13 @@ function star_coloring(g::Graph, order::AbstractOrder)
119120
end
120121

121122
"""
122-
$TYPEDEF
123+
StarSet
123124
124-
Encode a set of 2-colored stars resulting from the star coloring algorithm.
125+
Encode a set of 2-colored stars resulting from the [`star_coloring`](@ref) algorithm.
125126
126127
# Fields
127128
128129
$TYPEDFIELDS
129-
130-
# References
131-
132-
> [_New Acyclic and Star Coloring Algorithms with Application to Computing Hessians_](https://epubs.siam.org/doi/abs/10.1137/050639879), Gebremedhin et al. (2007), Algorithm 4.1
133130
"""
134131
struct StarSet
135132
"a mapping from edges (pair of vertices) their to star index"
@@ -262,3 +259,159 @@ function symmetric_coefficient(
262259
return j, color[h]
263260
end
264261
end
262+
263+
"""
264+
acyclic_coloring(g::Graph, order::AbstractOrder)
265+
266+
Compute an acyclic coloring of all vertices in the adjacency graph `g` and return a vector of integer colors.
267+
268+
An _acyclic coloring_ is a distance-1 coloring with the further restriction that every cycle uses at least 3 colors.
269+
270+
The vertices are colored in a greedy fashion, following the `order` supplied.
271+
272+
# See also
273+
274+
- [`Graph`](@ref)
275+
- [`AbstractOrder`](@ref)
276+
277+
# References
278+
279+
> [_New Acyclic and Star Coloring Algorithms with Application to Computing Hessians_](https://epubs.siam.org/doi/abs/10.1137/050639879), Gebremedhin et al. (2007), Algorithm 3.1
280+
"""
281+
function acyclic_coloring(g::Graph, order::AbstractOrder)
282+
# Initialize data structures
283+
nvertices = length(g)
284+
color = zeros(Int, nvertices)
285+
forbidden_colors = zeros(Int, nvertices)
286+
first_neighbor = fill((0, 0), nvertices) # at first no neighbors have been encountered
287+
first_visit_to_tree = fill((0, 0), nnz(g)) # Could we use less than nnz(g) values?
288+
disjoint_sets = DisjointSets{Tuple{Int,Int}}()
289+
vertices_in_order = vertices(g, order)
290+
parent = Int[] # Could be a vector of Bool
291+
292+
for v in vertices_in_order
293+
for w in neighbors(g, v)
294+
iszero(color[w]) && continue
295+
forbidden_colors[color[w]] = v
296+
end
297+
for w in neighbors(g, v)
298+
iszero(color[w]) && continue
299+
for x in neighbors(g, w)
300+
iszero(color[x]) && continue
301+
if forbidden_colors[color[x]] != v
302+
_prevent_cycle!(
303+
v, w, x, color, first_visit_to_tree, forbidden_colors, disjoint_sets
304+
)
305+
end
306+
end
307+
end
308+
for i in eachindex(forbidden_colors)
309+
if forbidden_colors[i] != v
310+
color[v] = i
311+
break
312+
end
313+
end
314+
for w in neighbors(g, v) # grow two-colored stars around the vertex v
315+
iszero(color[w]) && continue
316+
_grow_star!(v, w, color, first_neighbor, disjoint_sets, parent)
317+
end
318+
for w in neighbors(g, v)
319+
iszero(color[w]) && continue
320+
for x in neighbors(g, w)
321+
(x == v || iszero(color[x])) && continue
322+
if color[x] == color[v]
323+
_merge_trees!(v, w, x, disjoint_sets) # merge trees T₁ ∋ vw and T₂ ∋ wx if T₁ != T₂
324+
end
325+
end
326+
end
327+
end
328+
return color, TreeSet(disjoint_sets, parent)
329+
end
330+
331+
function _prevent_cycle!(
332+
# not modified
333+
v::Integer,
334+
w::Integer,
335+
x::Integer,
336+
color::AbstractVector{<:Integer},
337+
# modified
338+
first_visit_to_tree::AbstractVector{<:Tuple},
339+
forbidden_colors::AbstractVector{<:Integer},
340+
disjoint_sets::DisjointSets{<:Tuple{Int,Int}},
341+
)
342+
wx = _sort(w, x)
343+
root = find_root!(disjoint_sets, wx) # edge wx belongs to the 2-colored tree T represented by edge "root"
344+
id = disjoint_sets.intmap[root] # ID of the representative edge "root" of a two-colored tree.
345+
(p, q) = first_visit_to_tree[id]
346+
if p != v # T is being visited from vertex v for the first time
347+
vw = _sort(v, w)
348+
first_visit_to_tree[id] = (v, w)
349+
elseif q != w # T is connected to vertex v via at least two edges
350+
forbidden_colors[color[x]] = v
351+
end
352+
return nothing
353+
end
354+
355+
function _grow_star!(
356+
# not modified
357+
v::Integer,
358+
w::Integer,
359+
color::AbstractVector{<:Integer},
360+
# modified
361+
first_neighbor::AbstractVector{<:Tuple},
362+
disjoint_sets::DisjointSets{Tuple{Int,Int}},
363+
parent::Vector{Int},
364+
)
365+
vw = _sort(v, w)
366+
push!(disjoint_sets, vw) # Create a new tree T_{vw} consisting only of edge vw
367+
push!(parent, v) # the vertex v is the parent of the vertex w in tree T_{vw} -- parent[disjoint_sets.intmap[vw]] == v
368+
(p, q) = first_neighbor[color[w]]
369+
if p != v # a neighbor of v with color[w] encountered for the first time
370+
first_neighbor[color[w]] = (v, w)
371+
else # merge T_{vw} with a two-colored star being grown around v
372+
vw = _sort(v, w)
373+
pq = _sort(p, q)
374+
root1 = find_root!(disjoint_sets, vw)
375+
root2 = find_root!(disjoint_sets, pq)
376+
root_union!(disjoint_sets, root1, root2)
377+
end
378+
return nothing
379+
end
380+
381+
function _merge_trees!(
382+
# not modified
383+
v::Integer,
384+
w::Integer,
385+
x::Integer,
386+
# modified
387+
disjoint_sets::DisjointSets{Tuple{Int,Int}},
388+
)
389+
vw = _sort(v, w)
390+
wx = _sort(w, x)
391+
root1 = find_root!(disjoint_sets, vw)
392+
root2 = find_root!(disjoint_sets, wx)
393+
if root1 != root2
394+
root_union!(disjoint_sets, root1, root2)
395+
end
396+
return nothing
397+
end
398+
399+
"""
400+
TreeSet
401+
402+
Encode a set of 2-colored trees resulting from the acyclic coloring algorithm.
403+
404+
# Fields
405+
406+
$TYPEDFIELDS
407+
408+
# References
409+
410+
> [_New Acyclic and Star Coloring Algorithms with Application to Computing Hessians_](https://epubs.siam.org/doi/abs/10.1137/050639879), Gebremedhin et al. (2007), Algorithm 4.1
411+
"""
412+
struct TreeSet
413+
"set of 2-colored trees"
414+
disjoint_sets::DisjointSets{Tuple{Int,Int}}
415+
"???" # TODO: fill this
416+
parent::Vector{Int}
417+
end

src/decompression.jl

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,140 @@ function decompress_aux!(
9191
end
9292
return A
9393
end
94+
95+
function decompress_aux!(
96+
A::AbstractMatrix{R},
97+
B::AbstractMatrix{R},
98+
result::AbstractColoringResult{:symmetric,:column,:substitution},
99+
) where {R<:Real}
100+
# build T such that T * upper_nonzeros(A) = B and invert the linear system
101+
# only consider the upper triangle of A because of symmetry
102+
# TODO: make more efficient
103+
A .= zero(R)
104+
S = sparse(get_matrix(result))
105+
color = column_colors(result)
106+
107+
n = checksquare(S)
108+
upper_nonzero_inds = Tuple{Int,Int}[]
109+
I, J, _ = findnz(S)
110+
for (i, j) in zip(I, J)
111+
i >= j && push!(upper_nonzero_inds, (i, j))
112+
end
113+
114+
T = spzeros(float(R), length(B), length(upper_nonzero_inds))
115+
for (l, (i, j)) in enumerate(upper_nonzero_inds)
116+
ci = color[i]
117+
cj = color[j]
118+
ki = (ci - 1) * n + j # A[i, j] appears in B[j, ci]
119+
kj = (cj - 1) * n + i # A[i, j] appears in B[i, cj]
120+
T[ki, l] = one(float(R))
121+
if i != j
122+
T[kj, l] = one(float(R))
123+
end
124+
end
125+
126+
upper_nonzeros_A = T \ vec(B)
127+
for (l, (i, j)) in enumerate(upper_nonzero_inds)
128+
A[i, j] = upper_nonzeros_A[l]
129+
if i != j
130+
A[j, i] = upper_nonzeros_A[l]
131+
end
132+
end
133+
return A
134+
end
135+
136+
#=
137+
function decompress_aux!(
138+
A::AbstractMatrix{R},
139+
B::AbstractMatrix{R},
140+
result::AbstractColoringResult{:symmetric,:column,:substitution},
141+
) where {R<:Real}
142+
@compat (; disjoint_sets, parent) = result
143+
144+
# to be optimized!
145+
set_roots = Set{Int}()
146+
ntrees = 0
147+
for edge in tree_set.disjoint_sets.revmap
148+
# ensure that all paths are compressed
149+
root_edge = find_root!(disjoint_sets, edge)
150+
root_index = tree_set.disjoint_sets.intmap[root_edge]
151+
152+
# we exclude trees related to diagonal coefficients
153+
if (edge[1] != edge[2])
154+
push!(set_roots, root_index)
155+
end
156+
end
157+
roots = disjoint_sets.internal.parents
158+
159+
# DEBUG
160+
println(set_roots)
161+
ntrees = length(set_roots)
162+
println(ntrees)
163+
164+
trees = [Int[] for i in 1:ntrees]
165+
k = 0
166+
for root in set_roots
167+
k += 1
168+
for (pos, val) in enumerate(roots)
169+
if root == val
170+
push!(trees[k], pos)
171+
end
172+
end
173+
end
174+
# for k in 1:ntrees
175+
# nedges = length(trees[k])
176+
# if nedges > 1
177+
# tree_edges = trees[k]
178+
# p = ...
179+
# trees[k] = tree_edges[p]
180+
# end
181+
# end
182+
183+
# DEBUG
184+
display(trees)
185+
186+
n = checksquare(A)
187+
stored_values = Vector{R}(undef, n)
188+
if !same_sparsity_pattern(A, S)
189+
throw(DimensionMismatch("`A` and `S` must have the same sparsity pattern."))
190+
end
191+
A .= zero(R)
192+
for i in axes(A, 1)
193+
if !iszero(S[i, i])
194+
A[i, i] = B[i, color[i]]
195+
end
196+
end
197+
for tree in trees
198+
nedges = length(tree)
199+
if nedges == 1
200+
edge_index = tree[1]
201+
i, j = disjoint_sets.revmap[edge_index]
202+
val = B[i, color[j]]
203+
A[i, j] = val
204+
A[j, i] = val
205+
else
206+
for edge_index in tree
207+
i, j = disjoint_sets.revmap[edge_index]
208+
stored_values[i] = zero(R)
209+
stored_values[j] = zero(R)
210+
end
211+
for edge_index in tree # edges are sorted by their distance to the root
212+
i, j = disjoint_sets.revmap[edge_index]
213+
parent_index = disjoint_sets.internal.parents[edge_index]
214+
k, l = disjoint_sets.revmap[parent_index]
215+
# k = parent[edge_index]
216+
if edge_index != parent_index
217+
if i == k || i == l # vertex i is the parent of vertex j
218+
i, j = j, i # ensure that i always denotes a leaf vertex
219+
end
220+
end
221+
val = B[i, color[j]] - stored_values[i]
222+
stored_values[j] = stored_values[j] + val
223+
A[i, j] = val
224+
A[j, i] = val
225+
end
226+
end
227+
end
228+
return A
229+
end
230+
=#

0 commit comments

Comments
 (0)