Skip to content

Commit d1bbe5a

Browse files
committed
Efficient implementation of ColPack's buckets
1 parent df101d7 commit d1bbe5a

3 files changed

Lines changed: 130 additions & 100 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseMatrixColorings"
22
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
33
authors = ["Guillaume Dalle", "Alexis Montoison"]
4-
version = "0.4.17"
4+
version = "0.4.18"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

src/order.jl

Lines changed: 117 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -145,28 +145,50 @@ $COLPACK_WARNING
145145
146146
- [_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
147147
"""
148-
struct DynamicDegreeBasedOrder{degtype,direction} <: AbstractOrder
149-
reproduce_colpack::Bool
150-
end
148+
struct DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack} <: AbstractOrder end
151149

152150
function DynamicDegreeBasedOrder{degtype,direction}(;
153151
reproduce_colpack::Bool=false
154152
) where {degtype,direction}
155-
return DynamicDegreeBasedOrder{degtype,direction}(reproduce_colpack)
153+
return DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack}()
156154
end
157155

158-
struct DegreeBuckets{T}
156+
abstract type AbstractDegreeBuckets{T} end
157+
158+
struct DegreeBucketsColPack{T} <: AbstractDegreeBuckets{T}
159+
degrees::Vector{T}
160+
buckets::Vector{Vector{T}}
161+
positions::Vector{T}
162+
end
163+
164+
struct DegreeBucketsFast{T} <: AbstractDegreeBuckets{T}
159165
degrees::Vector{T}
160166
bucket_storage::Vector{T}
161167
bucket_low::Vector{T}
162168
bucket_high::Vector{T}
163169
positions::Vector{T}
164-
reproduce_colpack::Bool
165170
end
166171

167-
function DegreeBuckets(
168-
::Type{T}, degrees::Vector{T}, dmax::Integer; reproduce_colpack::Bool
169-
) where {T}
172+
function DegreeBucketsColPack(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
173+
# number of vertices per degree class
174+
deg_count = zeros(T, dmax + 1)
175+
for d in degrees
176+
deg_count[d + 1] += 1
177+
end
178+
# one vector per bucket
179+
buckets = [Vector{T}(undef, deg_count[d + 1]) for d in 0:dmax]
180+
positions = similar(degrees, T)
181+
# assign each vertex to the correct local position inside its bucket
182+
for v in eachindex(positions, degrees)
183+
d = degrees[v]
184+
positions[v] = length(buckets[d + 1]) - deg_count[d + 1] + 1
185+
buckets[d + 1][positions[v]] = v
186+
deg_count[d + 1] -= 1
187+
end
188+
return DegreeBucketsColPack(degrees, buckets, positions)
189+
end
190+
191+
function DegreeBucketsFast(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
170192
# number of vertices per degree class
171193
deg_count = zeros(T, dmax + 1)
172194
for d in degrees
@@ -177,7 +199,7 @@ function DegreeBuckets(
177199
bucket_low = similar(bucket_high)
178200
bucket_low[1] = 1
179201
bucket_low[2:end] .= @view(bucket_high[1:(end - 1)]) .+ 1
180-
# assign each vertex to the correct position inside its degree class
202+
# assign each vertex to the correct global position inside its bucket
181203
bucket_storage = similar(degrees, T)
182204
positions = similar(degrees, T)
183205
for v in eachindex(positions, degrees)
@@ -186,12 +208,18 @@ function DegreeBuckets(
186208
bucket_storage[positions[v]] = v
187209
deg_count[d + 1] -= 1
188210
end
189-
return DegreeBuckets(
190-
degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack
191-
)
211+
return DegreeBucketsFast(degrees, bucket_storage, bucket_low, bucket_high, positions)
192212
end
193213

194-
maxdeg(db::DegreeBuckets) = length(db.bucket_low) - 1
214+
maxdeg(db::DegreeBucketsColPack) = length(db.buckets) - 1
215+
maxdeg(db::DegreeBucketsFast) = length(db.bucket_low) - 1
216+
217+
function nonempty_bucket(db::DegreeBucketsFast, d::Integer)
218+
return db.bucket_high[d + 1] >= db.bucket_low[d + 1]
219+
end
220+
function nonempty_bucket(db::DegreeBucketsColPack, d::Integer)
221+
return !isempty(db.buckets[d + 1])
222+
end
195223

196224
function degree_increasing(; degtype, direction)
197225
increasing =
@@ -200,78 +228,52 @@ function degree_increasing(; degtype, direction)
200228
return increasing
201229
end
202230

203-
function mark_ordered!(db::DegreeBuckets{T}, v::Integer) where {T}
231+
function mark_ordered!(db::AbstractDegreeBuckets{T}, v::Integer) where {T}
204232
db.degrees[v] = -1
205233
db.positions[v] = typemin(T)
206234
return nothing
207235
end
208236

209-
already_ordered(db::DegreeBuckets, v::Integer) = db.degrees[v] == -1
237+
already_ordered(db::AbstractDegreeBuckets, v::Integer) = db.degrees[v] == -1
210238

211-
function pop_next_candidate!(db::DegreeBuckets; direction::Symbol)
212-
(; bucket_storage, bucket_low, bucket_high) = db
239+
function pop_next_candidate!(db::AbstractDegreeBuckets; direction::Symbol)
213240
dmax = maxdeg(db)
214241
if direction == :low2high
215242
candidate_degree = dmax + 1
216243
for d in dmax:-1:0
217-
if bucket_high[d + 1] >= bucket_low[d + 1] # not empty
244+
if nonempty_bucket(db, d)
218245
candidate_degree = d
219246
break
220247
end
221248
end
222249
else
223250
candidate_degree = -1
224251
for d in 0:dmax
225-
if bucket_high[d + 1] >= bucket_low[d + 1] # not empty
252+
if nonempty_bucket(db, d)
226253
candidate_degree = d
227254
break
228255
end
229256
end
230257
end
231-
high = bucket_high[candidate_degree + 1]
232-
candidate = bucket_storage[high]
233-
bucket_storage[high] = -1
234-
bucket_high[candidate_degree + 1] -= 1
258+
if db isa DegreeBucketsColPack
259+
(; buckets) = db
260+
bucket = buckets[candidate_degree + 1]
261+
candidate = pop!(bucket)
262+
else
263+
(; bucket_storage, bucket_high) = db
264+
high = bucket_high[candidate_degree + 1]
265+
candidate = bucket_storage[high]
266+
bucket_storage[high] = -1
267+
bucket_high[candidate_degree + 1] -= 1
268+
end
235269
mark_ordered!(db, candidate)
236270
return candidate
237271
end
238272

239-
function rotate_bucket_left!(db::DegreeBuckets, d::Integer)
240-
(; bucket_storage, bucket_high, bucket_low, positions) = db
241-
low, high = bucket_low[d + 1], bucket_high[d + 1]
242-
# remember first element v
243-
v = bucket_storage[low]
244-
# shift everyone else one index down
245-
for i in (low + 1):high
246-
w = bucket_storage[i]
247-
bucket_storage[i - 1] = w
248-
positions[w] = i - 1
249-
end
250-
# put v back at the end
251-
bucket_storage[high] = v
252-
positions[v] = high
253-
return nothing
254-
end
255-
256-
function rotate_bucket_right!(db::DegreeBuckets, d::Integer)
257-
(; bucket_storage, bucket_high, bucket_low, positions) = db
258-
low, high = bucket_low[d + 1], bucket_high[d + 1]
259-
# remember last element v
260-
v = bucket_storage[high]
261-
# shift everyone else one index up
262-
for i in (high - 1):-1:low
263-
w = bucket_storage[i]
264-
bucket_storage[i + 1] = w
265-
positions[w] = i + 1
266-
end
267-
# put v back at the start
268-
bucket_storage[low] = v
269-
positions[v] = low
270-
return nothing
271-
end
272-
273-
function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, direction::Symbol)
274-
(; degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack) = db
273+
function update_bucket!(
274+
db::DegreeBucketsFast, v::Integer; degtype::Symbol, direction::Symbol
275+
)
276+
(; degrees, bucket_storage, bucket_low, bucket_high, positions) = db
275277
d, p = degrees[v], positions[v]
276278
low, high = bucket_low[d + 1], bucket_high[d + 1]
277279
# select previous or next bucket for the move
@@ -292,27 +294,11 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, directio
292294
# update v's stats
293295
degrees[v] = d_new
294296
positions[v] = low_new - 1
295-
if reproduce_colpack
296-
# move v from start to end of the next bucket, preserving order
297-
rotate_bucket_left!(db, d_new) # expensive
298-
end
299297
else
300-
if reproduce_colpack
301-
# move the vertex w located at the end of the current bucket to v's position
302-
w = bucket_storage[high]
303-
bucket_storage[p] = w
304-
positions[w] = p
305-
# explicitly put v at the end
306-
bucket_storage[high] = v
307-
positions[v] = high
308-
# move v from end to start of the current bucket, preserving order
309-
rotate_bucket_right!(db, d) # expensive
310-
else
311-
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
312-
w = bucket_storage[low]
313-
bucket_storage[p] = w
314-
positions[w] = p
315-
end
298+
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
299+
w = bucket_storage[low]
300+
bucket_storage[p] = w
301+
positions[w] = p
316302
# shrink current bucket from the left
317303
# morally we put v at the start and then ignore it
318304
bucket_low[d + 1] += 1
@@ -329,15 +315,42 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, directio
329315
return nothing
330316
end
331317

318+
function update_bucket!(
319+
db::DegreeBucketsColPack, v::Integer; degtype::Symbol, direction::Symbol
320+
)
321+
(; degrees, buckets, positions) = db
322+
d, p = degrees[v], positions[v]
323+
bucket = buckets[d + 1]
324+
# select previous or next bucket for the move
325+
d_new = degree_increasing(; degtype, direction) ? d + 1 : d - 1
326+
bucket_new = buckets[d_new + 1]
327+
# put v at the end of its bucket by swapping
328+
w = bucket[end]
329+
bucket[p] = w
330+
positions[w] = p
331+
bucket[end] = v
332+
positions[v] = length(bucket)
333+
# move v from the old bucket to the new one
334+
@assert pop!(bucket) == v
335+
push!(bucket_new, v)
336+
degrees[v] = d_new
337+
positions[v] = length(bucket_new)
338+
return nothing
339+
end
340+
332341
function vertices(
333-
g::AdjacencyGraph{T}, order::DynamicDegreeBasedOrder{degtype,direction}
334-
) where {T<:Integer,degtype,direction}
342+
g::AdjacencyGraph{T}, ::DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack}
343+
) where {T<:Integer,degtype,direction,reproduce_colpack}
335344
true_degrees = degrees = T[degree(g, v) for v in vertices(g)]
336345
max_degrees = maximum(true_degrees)
337346
if degree_increasing(; degtype, direction)
338347
fill!(degrees, zero(T))
339348
end
340-
db = DegreeBuckets(T, degrees, max_degrees; reproduce_colpack=order.reproduce_colpack)
349+
db = if reproduce_colpack
350+
DegreeBucketsColPack(T, degrees, max_degrees)
351+
else
352+
DegreeBucketsFast(T, degrees, max_degrees)
353+
end
341354
nv = nb_vertices(g)
342355
π = Vector{T}(undef, nv)
343356
index_π = (direction == :low2high) ? (1:nv) : (nv:-1:1)
@@ -354,8 +367,10 @@ function vertices(
354367
end
355368

356369
function vertices(
357-
g::BipartiteGraph{T}, ::Val{side}, order::DynamicDegreeBasedOrder{degtype,direction}
358-
) where {T<:Integer,side,degtype,direction}
370+
g::BipartiteGraph{T},
371+
::Val{side},
372+
::DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack},
373+
) where {T<:Integer,side,degtype,direction,reproduce_colpack}
359374
other_side = 3 - side
360375
# compute dist-2 degrees in an optimized way
361376
n = nb_vertices(g, Val(side))
@@ -375,7 +390,11 @@ function vertices(
375390
if degree_increasing(; degtype, direction)
376391
fill!(degrees, zero(T))
377392
end
378-
db = DegreeBuckets(T, degrees, maxd2; reproduce_colpack=order.reproduce_colpack)
393+
db = if reproduce_colpack
394+
DegreeBucketsColPack(T, degrees, maxd2)
395+
else
396+
DegreeBucketsFast(T, degrees, maxd2)
397+
end
379398
π = Vector{T}(undef, n)
380399
index_π = (direction == :low2high) ? (1:n) : (n:-1:1)
381400
for index in index_π
@@ -406,7 +425,9 @@ $COLPACK_WARNING
406425
407426
- [`DynamicDegreeBasedOrder`](@ref)
408427
"""
409-
const IncidenceDegree = DynamicDegreeBasedOrder{:back,:low2high}
428+
function IncidenceDegree(; reproduce_colpack::Bool=false)
429+
DynamicDegreeBasedOrder{:back,:low2high,reproduce_colpack}()
430+
end
410431

411432
"""
412433
SmallestLast(; reproduce_colpack=false)
@@ -419,7 +440,9 @@ $COLPACK_WARNING
419440
420441
- [`DynamicDegreeBasedOrder`](@ref)
421442
"""
422-
const SmallestLast = DynamicDegreeBasedOrder{:back,:high2low}
443+
function SmallestLast(; reproduce_colpack::Bool=false)
444+
DynamicDegreeBasedOrder{:back,:high2low,reproduce_colpack}()
445+
end
423446

424447
"""
425448
DynamicLargestFirst(; reproduce_colpack=false)
@@ -432,7 +455,9 @@ $COLPACK_WARNING
432455
433456
- [`DynamicDegreeBasedOrder`](@ref)
434457
"""
435-
const DynamicLargestFirst = DynamicDegreeBasedOrder{:forward,:low2high}
458+
function DynamicLargestFirst(; reproduce_colpack::Bool=false)
459+
DynamicDegreeBasedOrder{:forward,:low2high,reproduce_colpack}()
460+
end
436461

437462
"""
438463
PerfectEliminationOrder(elimination_algorithm=CliqueTrees.MCS())
@@ -461,7 +486,10 @@ function all_orders()
461486
RandomOrder(),
462487
LargestFirst(),
463488
SmallestLast(),
489+
SmallestLast(; reproduce_colpack=true),
464490
IncidenceDegree(),
491+
IncidenceDegree(; reproduce_colpack=true),
465492
DynamicLargestFirst(),
493+
DynamicLargestFirst(; reproduce_colpack=true),
466494
]
467495
end

test/type_stability.jl

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,18 @@ rng = StableRNG(63)
3434
(:nonsymmetric, :bidirectional, :direct),
3535
(:nonsymmetric, :bidirectional, :substitution),
3636
]
37-
@test_opt coloring(
38-
A,
39-
ColoringProblem(; structure, partition),
40-
GreedyColoringAlgorithm(; decompression),
41-
)
42-
@inferred coloring(
43-
A,
44-
ColoringProblem(; structure, partition),
45-
GreedyColoringAlgorithm(; decompression),
46-
)
37+
@testset for order in all_orders()
38+
@test_opt coloring(
39+
A,
40+
ColoringProblem(; structure, partition),
41+
GreedyColoringAlgorithm(order; decompression),
42+
)
43+
@inferred coloring(
44+
A,
45+
ColoringProblem(; structure, partition),
46+
GreedyColoringAlgorithm(order; decompression),
47+
)
48+
end
4749
end
4850
end;
4951

0 commit comments

Comments
 (0)