Skip to content

Commit d02912a

Browse files
authored
Merge branch 'main' into acyclic_tree
2 parents 408ed3b + ba7da77 commit d02912a

5 files changed

Lines changed: 174 additions & 53 deletions

File tree

src/graph.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,23 +148,23 @@ function bidirectional_pattern(S::SparsityPatternCSC{T}; symmetric_pattern::Bool
148148
counter = 1
149149
for col in (n + 1):p
150150
nnz_col = colptr[col]
151-
colptr[col] = counter
151+
colptr[col] = nnzS + counter
152152
counter += nnz_col
153153
end
154154

155155
for j in 1:n
156156
for index in S.colptr[j]:(S.colptr[j + 1] - 1)
157157
i = S.rowval[index]
158158
pos = colptr[n + i]
159-
rowval[nnzS + pos] = j
160-
edge_to_index[nnzS + pos] = edge_to_index[index]
159+
rowval[pos] = j
160+
edge_to_index[pos] = edge_to_index[index]
161161
colptr[n + i] += 1
162162
end
163163
end
164164

165165
colptr[p + 1] = nnzS + counter
166166
for col in p:-1:(n + 2)
167-
colptr[col] = nnzS + colptr[col - 1]
167+
colptr[col] = colptr[col - 1]
168168
end
169169
colptr[n + 1] = nnzS + 1
170170
end

src/order.jl

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,18 @@ function vertices(bg::BipartiteGraph{T}, ::Val{side}, ::LargestFirst) where {T,s
104104
return sort(vertices(bg, Val(side)); by=criterion, rev=true)
105105
end
106106

107+
const COLPACK_WARNING = """
108+
!!! danger
109+
The option `reproduce_colpack=true` induces a large slowdown to mirror the original implementation details of ColPack, it should not be used in performance-sensitive applications.
110+
This setting is mostly for the purpose of reproducing past research results which rely on implementation details.
111+
"""
112+
107113
"""
108-
DynamicDegreeBasedOrder{degtype,direction}
114+
DynamicDegreeBasedOrder{degtype,direction}(; reproduce_colpack=false)
109115
110116
Instance of [`AbstractOrder`](@ref) which sorts vertices using a dynamically computed degree.
111117
112-
!!! danger
113-
This order is still experimental and needs more tests, correctness is not yet guaranteed.
118+
This order works by assigning vertices to buckets based on their dynamic degree, and then updating buckets iteratively by transfering vertices between them.
114119
115120
# Type parameters
116121
@@ -123,21 +128,43 @@ Instance of [`AbstractOrder`](@ref) which sorts vertices using a dynamically com
123128
- [`SmallestLast`](@ref)
124129
- [`DynamicLargestFirst`](@ref)
125130
131+
# Settings
132+
133+
- `reproduce_colpack::Bool`: whether to manage the buckets in the exact same way as the original ColPack implementation.
134+
- When `reproduce_colpack=true`, we always append and remove vertices at the end of a bucket (unilateral).
135+
- When `reproduce_colpack=false` (the default), we can append and remove vertices either at the start or at the end of a bucket (bilateral).
136+
137+
Allowing modifications on both sides of a bucket enables storage optimization, with a single fixed-size vector for all buckets instead of one dynamically-sized vector per bucket.
138+
Our implementation is optimized for this bilateral setting, which means we pay a large performance penalty to artificially imitate the unilateral setting.
139+
140+
$COLPACK_WARNING
141+
126142
# References
127143
128144
- [_ColPack: Software for graph coloring and related problems in scientific computing_](https://dl.acm.org/doi/10.1145/2513109.2513110), Gebremedhin et al. (2013), Section 5
129145
"""
130-
struct DynamicDegreeBasedOrder{degtype,direction} <: AbstractOrder end
146+
struct DynamicDegreeBasedOrder{degtype,direction} <: AbstractOrder
147+
reproduce_colpack::Bool
148+
end
149+
150+
function DynamicDegreeBasedOrder{degtype,direction}(;
151+
reproduce_colpack::Bool=false
152+
) where {degtype,direction}
153+
return DynamicDegreeBasedOrder{degtype,direction}(reproduce_colpack)
154+
end
131155

132156
struct DegreeBuckets{T}
133157
degrees::Vector{T}
134158
bucket_storage::Vector{T}
135159
bucket_low::Vector{T}
136160
bucket_high::Vector{T}
137161
positions::Vector{T}
162+
reproduce_colpack::Bool
138163
end
139164

140-
function DegreeBuckets(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
165+
function DegreeBuckets(
166+
::Type{T}, degrees::Vector{T}, dmax::Integer; reproduce_colpack::Bool
167+
) where {T}
141168
# number of vertices per degree class
142169
deg_count = zeros(T, dmax + 1)
143170
for d in degrees
@@ -157,7 +184,9 @@ function DegreeBuckets(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
157184
bucket_storage[positions[v]] = v
158185
deg_count[d + 1] -= 1
159186
end
160-
return DegreeBuckets(degrees, bucket_storage, bucket_low, bucket_high, positions)
187+
return DegreeBuckets(
188+
degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack
189+
)
161190
end
162191

163192
maxdeg(db::DegreeBuckets) = length(db.bucket_low) - 1
@@ -205,8 +234,42 @@ function pop_next_candidate!(db::DegreeBuckets; direction::Symbol)
205234
return candidate
206235
end
207236

237+
function rotate_bucket_left!(db::DegreeBuckets, d::Integer)
238+
(; bucket_storage, bucket_high, bucket_low, positions) = db
239+
low, high = bucket_low[d + 1], bucket_high[d + 1]
240+
# remember first element v
241+
v = bucket_storage[low]
242+
# shift everyone else one index down
243+
for i in (low + 1):high
244+
w = bucket_storage[i]
245+
bucket_storage[i - 1] = w
246+
positions[w] = i - 1
247+
end
248+
# put v back at the end
249+
bucket_storage[high] = v
250+
positions[v] = high
251+
return nothing
252+
end
253+
254+
function rotate_bucket_right!(db::DegreeBuckets, d::Integer)
255+
(; bucket_storage, bucket_high, bucket_low, positions) = db
256+
low, high = bucket_low[d + 1], bucket_high[d + 1]
257+
# remember last element v
258+
v = bucket_storage[high]
259+
# shift everyone else one index up
260+
for i in (high - 1):-1:low
261+
w = bucket_storage[i]
262+
bucket_storage[i + 1] = w
263+
positions[w] = i + 1
264+
end
265+
# put v back at the start
266+
bucket_storage[low] = v
267+
positions[v] = low
268+
return nothing
269+
end
270+
208271
function update_bucket!(db::DegreeBuckets, v::Integer; degtype, direction)
209-
(; degrees, bucket_storage, bucket_low, bucket_high, positions) = db
272+
(; degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack) = db
210273
d, p = degrees[v], positions[v]
211274
low, high = bucket_low[d + 1], bucket_high[d + 1]
212275
# select previous or next bucket for the move
@@ -227,11 +290,27 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype, direction)
227290
# update v's stats
228291
degrees[v] = d_new
229292
positions[v] = low_new - 1
293+
if reproduce_colpack
294+
# move v from start to end of the next bucket, preserving order
295+
rotate_bucket_left!(db, d_new) # expensive
296+
end
230297
else
231-
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
232-
w = bucket_storage[low]
233-
bucket_storage[p] = w
234-
positions[w] = p
298+
if reproduce_colpack
299+
# move the vertex w located at the end of the current bucket to v's position
300+
w = bucket_storage[high]
301+
bucket_storage[p] = w
302+
positions[w] = p
303+
# explicitly put v at the end
304+
bucket_storage[high] = v
305+
positions[v] = high
306+
# move v from end to start of the current bucket, preserving order
307+
rotate_bucket_right!(db, d) # expensive
308+
else
309+
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
310+
w = bucket_storage[low]
311+
bucket_storage[p] = w
312+
positions[w] = p
313+
end
235314
# shrink current bucket from the left
236315
# morally we put v at the start and then ignore it
237316
bucket_low[d + 1] += 1
@@ -249,14 +328,16 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype, direction)
249328
end
250329

251330
function vertices(
252-
g::AdjacencyGraph{T}, ::DynamicDegreeBasedOrder{degtype,direction}
331+
g::AdjacencyGraph{T}, order::DynamicDegreeBasedOrder{degtype,direction}
253332
) where {T<:Integer,degtype,direction}
254333
if degree_increasing(; degtype, direction)
255334
degrees = zeros(T, nb_vertices(g))
256335
else
257336
degrees = T[degree(g, v) for v in vertices(g)]
258337
end
259-
db = DegreeBuckets(T, degrees, maximum_degree(g))
338+
db = DegreeBuckets(
339+
T, degrees, maximum_degree(g); reproduce_colpack=order.reproduce_colpack
340+
)
260341
π = T[]
261342
sizehint!(π, nb_vertices(g))
262343
for _ in 1:nb_vertices(g)
@@ -272,7 +353,7 @@ function vertices(
272353
end
273354

274355
function vertices(
275-
g::BipartiteGraph{T}, ::Val{side}, ::DynamicDegreeBasedOrder{degtype,direction}
356+
g::BipartiteGraph{T}, ::Val{side}, order::DynamicDegreeBasedOrder{degtype,direction}
276357
) where {T<:Integer,side,degtype,direction}
277358
other_side = 3 - side
278359
# compute dist-2 degrees in an optimized way
@@ -294,7 +375,7 @@ function vertices(
294375
degrees = degrees_dist2
295376
end
296377
maxd2 = maximum(degrees_dist2)
297-
db = DegreeBuckets(T, degrees, maxd2)
378+
db = DegreeBuckets(T, degrees, maxd2; reproduce_colpack=order.reproduce_colpack)
298379
π = T[]
299380
sizehint!(π, n)
300381
visited = falses(n)
@@ -318,12 +399,11 @@ function vertices(
318399
end
319400

320401
"""
321-
IncidenceDegree()
402+
IncidenceDegree(; reproduce_colpack=false)
322403
323404
Instance of [`AbstractOrder`](@ref) which sorts vertices from lowest to highest using the dynamic back degree.
324405
325-
!!! danger
326-
This order is still experimental and needs more tests, correctness is not yet guaranteed.
406+
$COLPACK_WARNING
327407
328408
# See also
329409
@@ -332,12 +412,11 @@ Instance of [`AbstractOrder`](@ref) which sorts vertices from lowest to highest
332412
const IncidenceDegree = DynamicDegreeBasedOrder{:back,:low2high}
333413

334414
"""
335-
SmallestLast()
415+
SmallestLast(; reproduce_colpack=false)
336416
337417
Instance of [`AbstractOrder`](@ref) which sorts vertices from highest to lowest using the dynamic back degree.
338418
339-
!!! danger
340-
This order is still experimental and needs more tests, correctness is not yet guaranteed.
419+
$COLPACK_WARNING
341420
342421
# See also
343422
@@ -346,12 +425,11 @@ Instance of [`AbstractOrder`](@ref) which sorts vertices from highest to lowest
346425
const SmallestLast = DynamicDegreeBasedOrder{:back,:high2low}
347426

348427
"""
349-
DynamicLargestFirst()
428+
DynamicLargestFirst(; reproduce_colpack=false)
350429
351430
Instance of [`AbstractOrder`](@ref) which sorts vertices from lowest to highest using the dynamic forward degree.
352431
353-
!!! danger
354-
This order is still experimental and needs more tests, correctness is not yet guaranteed.
432+
$COLPACK_WARNING
355433
356434
# See also
357435

test/order.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,14 @@ end;
8989
end;
9090

9191
@testset "Dynamic degree-based orders" begin
92-
@testset "$order" for order in
93-
[SmallestLast(), IncidenceDegree(), DynamicLargestFirst()]
92+
@testset "$order" for order in [
93+
SmallestLast(),
94+
SmallestLast(; reproduce_colpack=true),
95+
IncidenceDegree(),
96+
IncidenceDegree(; reproduce_colpack=true),
97+
DynamicLargestFirst(),
98+
DynamicLargestFirst(; reproduce_colpack=true),
99+
]
94100
@testset "AdjacencyGraph" begin
95101
for (n, p) in Iterators.product(20:20:100, 0.0:0.1:0.2)
96102
yield()
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
row,modified,group,name,V1,V2,E,Δ1,Δ2,LF2,N2,LF1,N1
2-
1,false,LPnetlib,lp_cre_a,3516,7248,18168,360,14,360,360,14,16
3-
2,false,LPnetlib,lp_ken_11,14694,21349,49058,122,3,128,130,4,5
4-
3,false,LPnetlib,lp_ken_13,28632,42659,97246,170,3,174,176,5,4
5-
4,false,LPnetlib,lp_maros_r7,3136,9408,144848,48,46,70,74,100,72
6-
5,false,LPnetlib,lp_cre_d,8926,73948,246614,808,13,808,813,13,15
7-
6,false,LPnetlib,lp_ken_18,105127,154699,358171,325,3,325,330,5,5
8-
7,false,Bai,af23560,23560,23560,460598,21,21,59,44,43,43
9-
8,false,Shen,e40r0100,17281,17281,553562,62,62,85,95,85,87
10-
9,false,vanHeukelum,cage11,39082,39082,559722,31,31,70,81,67,81
11-
10,false,vanHeukelum,cage12,130228,130228,2032536,33,33,79,96,73,96
1+
row,modified,group,name,V1,V2,E,Δ1,Δ2,SL2,ID2,LF2,DLF2,N2,SL1,ID1,LF1,DLF1,N1
2+
1,false,LPnetlib,lp_cre_a,3516,7248,18168,360,14,360,360,360,360,360,14,14,14,14,16
3+
2,false,LPnetlib,lp_ken_11,14694,21349,49058,122,3,125,124,128,122,130,4,4,4,5,5
4+
3,false,LPnetlib,lp_ken_13,28632,42659,97246,170,3,171,171,174,170,176,4,5,5,5,4
5+
4,false,LPnetlib,lp_maros_r7,3136,9408,144848,48,46,83,90,70,114,74,80,88,100,113,72
6+
5,false,LPnetlib,lp_cre_d,8926,73948,246614,808,13,808,808,808,808,813,15,15,13,14,15
7+
6,false,LPnetlib,lp_ken_18,105127,154699,358171,325,3,325,326,328,325,330,4,5,5,5,5
8+
7,false,Bai,af23560,23560,23560,460598,21,21,42,42,43,59,44,42,43,43,60,43
9+
8,false,Shen,e40r0100,17281,17281,553562,62,62,70,71,87,85,95,70,71,85,86,87
10+
9,false,vanHeukelum,cage11,39082,39082,559722,31,31,64,67,67,70,81,64,67,67,70,81
11+
10,false,vanHeukelum,cage12,130228,130228,2032536,33,33,67,72,73,79,96,67,72,73,79,96

test/suitesparse.jl

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ using DataFrames
33
using LinearAlgebra
44
using MatrixDepot
55
using SparseArrays
6+
using SparseMatrixColorings
67
using SparseMatrixColorings:
78
AdjacencyGraph,
89
BipartiteGraph,
9-
LargestFirst,
10-
NaturalOrder,
1110
degree,
1211
minimum_degree,
1312
maximum_degree,
@@ -19,6 +18,14 @@ using SparseMatrixColorings:
1918
vertices
2019
using Test
2120

21+
nbunique(x) = length(unique(x))
22+
23+
_N() = NaturalOrder()
24+
_LF() = LargestFirst()
25+
_SL() = SmallestLast(; reproduce_colpack=true)
26+
_ID() = IncidenceDegree(; reproduce_colpack=true)
27+
_DLF() = DynamicLargestFirst(; reproduce_colpack=true)
28+
2229
## Distance-2 coloring
2330

2431
#=
@@ -29,20 +36,50 @@ colpack_table_6_7 = CSV.read(
2936
joinpath(@__DIR__, "reference", "colpack_table_6_7.csv"), DataFrame
3037
)
3138

32-
@testset "Distance-2 coloring (ColPack paper)" begin
39+
@testset verbose = true "Distance-2 coloring (ColPack paper)" begin
3340
@testset "$(row[:name])" for row in eachrow(colpack_table_6_7)
3441
original_mat = matrixdepot("$(row[:group])/$(row[:name])")
3542
mat = dropzeros(original_mat)
3643
bg = BipartiteGraph(mat)
37-
@test nb_vertices(bg, Val(1)) == row[:V1]
38-
@test nb_vertices(bg, Val(2)) == row[:V2]
39-
@test nb_edges(bg) == row[:E]
40-
@test maximum_degree(bg, Val(1)) == row[:Δ1]
41-
@test maximum_degree(bg, Val(2)) == row[:Δ2]
42-
color_N1 = partial_distance2_coloring(bg, Val(1), NaturalOrder())
43-
color_N2 = partial_distance2_coloring(bg, Val(2), NaturalOrder())
44-
@test length(unique(color_N1)) == row[:N1]
45-
@test length(unique(color_N2)) == row[:N2]
44+
@testset "Graph features" begin
45+
@test nb_vertices(bg, Val(1)) == row[:V1]
46+
@test nb_vertices(bg, Val(2)) == row[:V2]
47+
@test nb_edges(bg) == row[:E]
48+
@test maximum_degree(bg, Val(1)) == row[:Δ1]
49+
@test maximum_degree(bg, Val(2)) == row[:Δ2]
50+
end
51+
@testset "Natural" begin
52+
@test nbunique(partial_distance2_coloring(bg, Val(1), _N())) == row[:N1]
53+
@test nbunique(partial_distance2_coloring(bg, Val(2), _N())) == row[:N2]
54+
end
55+
yield()
56+
@testset "LargestFirst" begin
57+
@test nbunique(partial_distance2_coloring(bg, Val(1), _LF())) == row[:LF1]
58+
@test nbunique(partial_distance2_coloring(bg, Val(2), _LF())) == row[:LF2]
59+
end
60+
yield()
61+
if row[:name] == "af23560"
62+
# orders differ for this one, not sure why
63+
continue
64+
end
65+
if row[:E] > 200_000
66+
# just to spare computational resources, but the larger tests pass too
67+
continue
68+
end
69+
@testset "SmallestLast" begin
70+
@test nbunique(partial_distance2_coloring(bg, Val(1), _SL())) == row[:SL1]
71+
@test nbunique(partial_distance2_coloring(bg, Val(2), _SL())) == row[:SL2]
72+
end
73+
yield()
74+
@testset "IncidenceDegree" begin
75+
@test nbunique(partial_distance2_coloring(bg, Val(1), _ID())) == row[:ID1]
76+
@test nbunique(partial_distance2_coloring(bg, Val(2), _ID())) == row[:ID2]
77+
end
78+
yield()
79+
@testset "DynamicLargestFirst" begin
80+
@test nbunique(partial_distance2_coloring(bg, Val(1), _DLF())) == row[:DLF1]
81+
@test nbunique(partial_distance2_coloring(bg, Val(2), _DLF())) == row[:DLF2]
82+
end
4683
yield()
4784
end
4885
end;

0 commit comments

Comments
 (0)