Skip to content

Commit 678b26b

Browse files
committed
Merge remote-tracking branch 'origin/main' into gd/abstractcoloring
2 parents c9d3ebc + e77226a commit 678b26b

8 files changed

Lines changed: 245 additions & 49 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.21"
4+
version = "0.4.22"
55

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

docs/src/tutorial.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ problem = ColoringProblem()
3131

3232
The algorithm defines how you want to solve it. It can be either a [`GreedyColoringAlgorithm`](@ref) or a [`ConstantColoringAlgorithm`](@ref). For `GreedyColoringAlgorithm`, you can select options such as
3333

34-
- the order in which vertices are processed (a subtype of [`AbstractOrder`](@ref SparseMatrixColorings.AbstractOrder))
34+
- the order in which vertices are processed (a subtype of [`AbstractOrder`](@ref SparseMatrixColorings.AbstractOrder) , or a tuple of such objects)
3535
- the type of decompression you want (`:direct` or `:substitution`)
3636

3737
```@example tutorial

src/interface.jl

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ It is passed as an argument to the main function [`coloring`](@ref).
7272
GreedyColoringAlgorithm{decompression}(order=NaturalOrder(); postprocessing=false)
7373
GreedyColoringAlgorithm(order=NaturalOrder(); postprocessing=false, decompression=:direct)
7474
75-
- `order::AbstractOrder`: the order in which the columns or rows are colored, which can impact the number of colors.
75+
- `order::Union{AbstractOrder,Tuple}`: the order in which the columns or rows are colored, which can impact the number of colors. Can also be a tuple of different orders to try out, from which the best order (the one with the lowest total number of colors) will be used.
7676
- `postprocessing::Bool`: whether or not the coloring will be refined by assigning the neutral color `0` to some vertices.
7777
- `decompression::Symbol`: either `:direct` or `:substitution`. Usually `:substitution` leads to fewer colors, at the cost of a more expensive coloring (and decompression). When `:substitution` is not applicable, it falls back on `:direct` decompression.
7878
@@ -94,26 +94,31 @@ See their respective docstrings for details.
9494
- [`AbstractOrder`](@ref)
9595
- [`decompress`](@ref)
9696
"""
97-
struct GreedyColoringAlgorithm{decompression,O<:AbstractOrder} <:
97+
struct GreedyColoringAlgorithm{decompression,N,O<:NTuple{N,AbstractOrder}} <:
9898
ADTypes.AbstractColoringAlgorithm
99-
order::O
99+
orders::O
100100
postprocessing::Bool
101-
end
102101

103-
function GreedyColoringAlgorithm{decompression}(
104-
order::AbstractOrder=NaturalOrder(); postprocessing::Bool=false
105-
) where {decompression}
106-
check_valid_algorithm(decompression)
107-
return GreedyColoringAlgorithm{decompression,typeof(order)}(order, postprocessing)
102+
function GreedyColoringAlgorithm{decompression}(
103+
order_or_orders::Union{AbstractOrder,Tuple}=NaturalOrder();
104+
postprocessing::Bool=false,
105+
) where {decompression}
106+
check_valid_algorithm(decompression)
107+
if order_or_orders isa AbstractOrder
108+
orders = (order_or_orders,)
109+
else
110+
orders = order_or_orders
111+
end
112+
return new{decompression,length(orders),typeof(orders)}(orders, postprocessing)
113+
end
108114
end
109115

110116
function GreedyColoringAlgorithm(
111-
order::AbstractOrder=NaturalOrder();
117+
order_or_orders::Union{AbstractOrder,Tuple}=NaturalOrder();
112118
postprocessing::Bool=false,
113119
decompression::Symbol=:direct,
114120
)
115-
check_valid_algorithm(decompression)
116-
return GreedyColoringAlgorithm{decompression,typeof(order)}(order, postprocessing)
121+
return GreedyColoringAlgorithm{decompression}(order_or_orders; postprocessing)
117122
end
118123

119124
## Coloring
@@ -230,8 +235,11 @@ function _coloring(
230235
)
231236
symmetric_pattern = symmetric_pattern || A isa Union{Symmetric,Hermitian}
232237
bg = BipartiteGraph(A; symmetric_pattern)
233-
vertices_in_order = vertices(bg, Val(2), algo.order)
234-
color = partial_distance2_coloring(bg, Val(2), vertices_in_order; forced_colors)
238+
color_by_order = map(algo.orders) do order
239+
vertices_in_order = vertices(bg, Val(2), order)
240+
return partial_distance2_coloring(bg, Val(2), vertices_in_order)
241+
end
242+
color = argmin(maximum, color_by_order)
235243
if speed_setting isa WithResult
236244
return ColumnColoringResult(A, bg, color)
237245
else
@@ -250,8 +258,11 @@ function _coloring(
250258
)
251259
symmetric_pattern = symmetric_pattern || A isa Union{Symmetric,Hermitian}
252260
bg = BipartiteGraph(A; symmetric_pattern)
253-
vertices_in_order = vertices(bg, Val(1), algo.order)
254-
color = partial_distance2_coloring(bg, Val(1), vertices_in_order; forced_colors)
261+
color_by_order = map(algo.orders) do order
262+
vertices_in_order = vertices(bg, Val(1), order)
263+
return partial_distance2_coloring(bg, Val(1), vertices_in_order)
264+
end
265+
color = argmin(maximum, color_by_order)
255266
if speed_setting isa WithResult
256267
return RowColoringResult(A, bg, color)
257268
else
@@ -269,10 +280,11 @@ function _coloring(
269280
forced_colors::Union{AbstractVector{<:Integer},Nothing}=nothing,
270281
)
271282
ag = AdjacencyGraph(A; has_diagonal=true)
272-
vertices_in_order = vertices(ag, algo.order)
273-
color, star_set = star_coloring(
274-
ag, vertices_in_order, algo.postprocessing; forced_colors
275-
)
283+
color_and_star_set_by_order = map(algo.orders) do order
284+
vertices_in_order = vertices(ag, order)
285+
return star_coloring(ag, vertices_in_order, algo.postprocessing)
286+
end
287+
color, star_set = argmin(maximum first, color_and_star_set_by_order)
276288
if speed_setting isa WithResult
277289
return StarSetColoringResult(A, ag, color, star_set)
278290
else
@@ -290,10 +302,11 @@ function _coloring(
290302
forced_colors::Union{AbstractVector{<:Integer},Nothing}=nothing,
291303
) where {R}
292304
ag = AdjacencyGraph(A; has_diagonal=true)
293-
vertices_in_order = vertices(ag, algo.order)
294-
color, tree_set = acyclic_coloring(
295-
ag, vertices_in_order, algo.postprocessing; forced_colors
296-
)
305+
color_and_tree_set_by_order = map(algo.orders) do order
306+
vertices_in_order = vertices(ag, order)
307+
return acyclic_coloring(ag, vertices_in_order, algo.postprocessing)
308+
end
309+
color, tree_set = argmin(maximum first, color_and_tree_set_by_order)
297310
if speed_setting isa WithResult
298311
return TreeSetColoringResult(A, ag, color, tree_set, R)
299312
else
@@ -312,17 +325,37 @@ function _coloring(
312325
) where {R}
313326
A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern)
314327
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; has_diagonal=false)
315-
vertices_in_order = vertices(ag, algo.order)
316-
color, star_set = star_coloring(
317-
ag, vertices_in_order, algo.postprocessing; forced_colors
318-
)
328+
outputs_by_order = map(algo.orders) do order
329+
vertices_in_order = vertices(ag, order)
330+
_color, _star_set = star_coloring(ag, vertices_in_order, algo.postprocessing)
331+
(_row_color, _column_color, _symmetric_to_row, _symmetric_to_column) = remap_colors(
332+
eltype(ag), _color, maximum(_color), size(A)...
333+
)
334+
return (
335+
_color,
336+
_star_set,
337+
_row_color,
338+
_column_color,
339+
_symmetric_to_row,
340+
_symmetric_to_column,
341+
)
342+
end
343+
(color, star_set, row_color, column_color, symmetric_to_row, symmetric_to_column) = argmin(
344+
t -> maximum(t[3]) + maximum(t[4]), outputs_by_order
345+
) # can't use ncolors without computing the full result
319346
if speed_setting isa WithResult
320347
symmetric_result = StarSetColoringResult(A_and_Aᵀ, ag, color, star_set)
321-
return BicoloringResult(A, ag, symmetric_result, R)
322-
else
323-
row_color, column_color, _ = remap_colors(
324-
eltype(ag), color, maximum(color), size(A)...
348+
return BicoloringResult(
349+
A,
350+
ag,
351+
symmetric_result,
352+
row_color,
353+
column_color,
354+
symmetric_to_row,
355+
symmetric_to_column,
356+
R,
325357
)
358+
else
326359
return row_color, column_color
327360
end
328361
end
@@ -338,17 +371,37 @@ function _coloring(
338371
) where {R}
339372
A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern)
340373
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; has_diagonal=false)
341-
vertices_in_order = vertices(ag, algo.order)
342-
color, tree_set = acyclic_coloring(
343-
ag, vertices_in_order, algo.postprocessing; forced_colors
344-
)
374+
outputs_by_order = map(algo.orders) do order
375+
vertices_in_order = vertices(ag, order)
376+
_color, _tree_set = acyclic_coloring(ag, vertices_in_order, algo.postprocessing)
377+
(_row_color, _column_color, _symmetric_to_row, _symmetric_to_column) = remap_colors(
378+
eltype(ag), _color, maximum(_color), size(A)...
379+
)
380+
return (
381+
_color,
382+
_tree_set,
383+
_row_color,
384+
_column_color,
385+
_symmetric_to_row,
386+
_symmetric_to_column,
387+
)
388+
end
389+
(color, tree_set, row_color, column_color, symmetric_to_row, symmetric_to_column) = argmin(
390+
t -> maximum(t[3]) + maximum(t[4]), outputs_by_order
391+
) # can't use ncolors without computing the full result
345392
if speed_setting isa WithResult
346393
symmetric_result = TreeSetColoringResult(A_and_Aᵀ, ag, color, tree_set, R)
347-
return BicoloringResult(A, ag, symmetric_result, R)
348-
else
349-
row_color, column_color, _ = remap_colors(
350-
eltype(ag), color, maximum(color), size(A)...
394+
return BicoloringResult(
395+
A,
396+
ag,
397+
symmetric_result,
398+
row_color,
399+
column_color,
400+
symmetric_to_row,
401+
symmetric_to_column,
402+
R,
351403
)
404+
else
352405
return row_color, column_color
353406
end
354407
end

src/result.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,14 +686,15 @@ function BicoloringResult(
686686
A::AbstractMatrix,
687687
ag::AdjacencyGraph{T},
688688
symmetric_result::AbstractColoringResult{:symmetric,:column},
689+
row_color::Vector{T},
690+
column_color::Vector{T},
691+
symmetric_to_row::Vector{T},
692+
symmetric_to_column::Vector{T},
689693
decompression_eltype::Type{R},
690694
) where {T,R}
691695
m, n = size(A)
692696
symmetric_color = column_colors(symmetric_result)
693697
num_sym_colors = maximum(symmetric_color)
694-
row_color, column_color, symmetric_to_row, symmetric_to_column = remap_colors(
695-
T, symmetric_color, num_sym_colors, m, n
696-
)
697698
column_group = group_by_color(T, column_color)
698699
row_group = group_by_color(T, row_color)
699700
Br_and_Bc = Matrix{R}(undef, n + m, num_sym_colors)

test/order.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,99 @@ end;
146146
@test isperm(π)
147147
end
148148
end
149+
150+
@testset "Multiple orders" begin
151+
# I used brute force to find examples where LargestFirst is *strictly* better than NaturalOrder, just to check that the best order is indeed selected when multiple orders are provided
152+
@testset "Column coloring" begin
153+
A = [
154+
0 0 1 1
155+
0 1 0 1
156+
0 0 1 1
157+
1 1 0 0
158+
]
159+
problem = ColoringProblem{:nonsymmetric,:column}()
160+
algo = GreedyColoringAlgorithm(NaturalOrder())
161+
better_algo = GreedyColoringAlgorithm((NaturalOrder(), LargestFirst()))
162+
@test ncolors(coloring(A, problem, better_algo)) <
163+
ncolors(coloring(A, problem, algo))
164+
end
165+
@testset "Row coloring" begin
166+
A = [
167+
1 0 0 0
168+
0 0 1 0
169+
0 1 1 1
170+
1 0 0 1
171+
]
172+
problem = ColoringProblem{:nonsymmetric,:row}()
173+
algo = GreedyColoringAlgorithm(NaturalOrder())
174+
better_algo = GreedyColoringAlgorithm((NaturalOrder(), LargestFirst()))
175+
@test ncolors(coloring(A, problem, better_algo)) <
176+
ncolors(coloring(A, problem, algo))
177+
end
178+
@testset "Star coloring" begin
179+
A = [
180+
0 1 0 1 1
181+
1 1 0 1 0
182+
0 0 1 0 1
183+
1 1 0 1 0
184+
1 0 1 0 0
185+
]
186+
problem = ColoringProblem{:symmetric,:column}()
187+
algo = GreedyColoringAlgorithm(NaturalOrder())
188+
better_algo = GreedyColoringAlgorithm((NaturalOrder(), LargestFirst()))
189+
@test ncolors(coloring(A, problem, better_algo)) <
190+
ncolors(coloring(A, problem, algo))
191+
end
192+
@testset "Acyclic coloring" begin
193+
A = [
194+
1 0 0 0 0 1 0
195+
0 0 0 1 0 0 0
196+
0 0 0 1 0 0 0
197+
0 1 1 1 0 1 1
198+
0 0 0 0 0 0 1
199+
1 0 0 1 0 0 1
200+
0 0 0 1 1 1 1
201+
]
202+
problem = ColoringProblem{:symmetric,:column}()
203+
algo = GreedyColoringAlgorithm{:substitution}(NaturalOrder())
204+
better_algo = GreedyColoringAlgorithm{:substitution}((
205+
NaturalOrder(), LargestFirst()
206+
))
207+
@test ncolors(coloring(A, problem, better_algo)) <
208+
ncolors(coloring(A, problem, algo))
209+
end
210+
@testset "Star bicoloring" begin
211+
A = [
212+
0 1 0 0 0
213+
1 0 1 0 0
214+
0 1 0 0 1
215+
0 0 0 0 0
216+
0 0 1 0 1
217+
]
218+
problem = ColoringProblem{:nonsymmetric,:bidirectional}()
219+
algo = GreedyColoringAlgorithm(NaturalOrder())
220+
better_algo = GreedyColoringAlgorithm((NaturalOrder(), LargestFirst()))
221+
@test ncolors(coloring(A, problem, better_algo)) <
222+
ncolors(coloring(A, problem, algo))
223+
end
224+
@testset "Acyclic bicoloring" begin
225+
A = [
226+
0 1 0 1 1 0 1 0 1
227+
1 0 0 0 0 0 0 0 1
228+
0 0 0 0 0 0 0 0 0
229+
1 0 0 1 1 0 1 0 0
230+
1 0 0 1 0 0 0 0 0
231+
0 0 0 0 0 0 0 0 0
232+
1 0 0 1 0 0 0 0 0
233+
0 0 0 0 0 0 0 0 0
234+
1 1 0 0 0 0 0 0 0
235+
]
236+
problem = ColoringProblem{:nonsymmetric,:bidirectional}()
237+
algo = GreedyColoringAlgorithm{:substitution}(NaturalOrder())
238+
better_algo = GreedyColoringAlgorithm{:substitution}((
239+
NaturalOrder(), LargestFirst()
240+
))
241+
@test ncolors(coloring(A, problem, better_algo)) <
242+
ncolors(coloring(A, problem, algo))
243+
end
244+
end

test/random.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ end;
8181
problem = ColoringProblem(; structure=:nonsymmetric, partition=:bidirectional)
8282
@testset for algo in (
8383
GreedyColoringAlgorithm(
84-
RandomOrder(rng); postprocessing=false, decompression=:direct
84+
RandomOrder(StableRNG(0), 0); postprocessing=false, decompression=:direct
8585
),
8686
GreedyColoringAlgorithm(
87-
RandomOrder(rng); postprocessing=true, decompression=:direct
87+
RandomOrder(StableRNG(0), 0); postprocessing=true, decompression=:direct
8888
),
8989
)
9090
@testset "$((; m, n, p))" for (m, n, p) in asymmetric_params
@@ -102,10 +102,10 @@ end;
102102
problem = ColoringProblem(; structure=:nonsymmetric, partition=:bidirectional)
103103
@testset for algo in (
104104
GreedyColoringAlgorithm(
105-
RandomOrder(rng); postprocessing=false, decompression=:substitution
105+
RandomOrder(StableRNG(0), 0); postprocessing=false, decompression=:substitution
106106
),
107107
GreedyColoringAlgorithm(
108-
RandomOrder(rng); postprocessing=true, decompression=:substitution
108+
RandomOrder(StableRNG(0), 0); postprocessing=true, decompression=:substitution
109109
),
110110
)
111111
@testset "$((; m, n, p))" for (m, n, p) in asymmetric_params

test/type_stability.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,21 @@ rng = StableRNG(63)
4040
ColoringProblem(; structure, partition),
4141
GreedyColoringAlgorithm(order; decompression),
4242
)
43+
@test_opt coloring(
44+
A,
45+
ColoringProblem(; structure, partition),
46+
GreedyColoringAlgorithm((NaturalOrder(), order); decompression),
47+
)
4348
@inferred coloring(
4449
A,
4550
ColoringProblem(; structure, partition),
4651
GreedyColoringAlgorithm(order; decompression),
4752
)
53+
@inferred coloring(
54+
A,
55+
ColoringProblem(; structure, partition),
56+
GreedyColoringAlgorithm((NaturalOrder(), order); decompression),
57+
)
4858
end
4959
end
5060
end;

0 commit comments

Comments
 (0)