|
| 1 | +module SparseMatrixColoringsJuMPExt |
| 2 | + |
| 3 | +using ADTypes: ADTypes |
| 4 | +using JuMP: |
| 5 | + Model, |
| 6 | + is_solved_and_feasible, |
| 7 | + optimize!, |
| 8 | + primal_status, |
| 9 | + set_silent, |
| 10 | + set_start_value, |
| 11 | + value, |
| 12 | + @variable, |
| 13 | + @constraint, |
| 14 | + @objective |
| 15 | +using JuMP |
| 16 | +import MathOptInterface as MOI |
| 17 | +using SparseMatrixColorings: |
| 18 | + BipartiteGraph, OptimalColoringAlgorithm, nb_vertices, neighbors, pattern, vertices |
| 19 | + |
| 20 | +function optimal_distance2_coloring( |
| 21 | + bg::BipartiteGraph, |
| 22 | + ::Val{side}, |
| 23 | + optimizer::O; |
| 24 | + silent::Bool=true, |
| 25 | + assert_solved::Bool=true, |
| 26 | +) where {side,O} |
| 27 | + other_side = 3 - side |
| 28 | + n = nb_vertices(bg, Val(side)) |
| 29 | + model = Model(optimizer) |
| 30 | + silent && set_silent(model) |
| 31 | + # one variable per vertex to color, removing some renumbering symmetries |
| 32 | + @variable(model, 1 <= color[i=1:n] <= i, Int) |
| 33 | + # one variable to count the number of distinct colors |
| 34 | + @variable(model, ncolors, Int) |
| 35 | + @constraint(model, [ncolors; color] in MOI.CountDistinct(n + 1)) |
| 36 | + # distance-2 coloring: neighbors of the same vertex must have distinct colors |
| 37 | + for i in vertices(bg, Val(other_side)) |
| 38 | + neigh = neighbors(bg, Val(other_side), i) |
| 39 | + @constraint(model, color[neigh] in MOI.AllDifferent(length(neigh))) |
| 40 | + end |
| 41 | + # minimize the number of distinct colors (can't use maximum because they are not necessarily numbered contiguously) |
| 42 | + @objective(model, Min, ncolors) |
| 43 | + # actual solving step where time is spent |
| 44 | + optimize!(model) |
| 45 | + if assert_solved |
| 46 | + # assert feasibility and optimality |
| 47 | + @assert is_solved_and_feasible(model) |
| 48 | + else |
| 49 | + # only assert feasibility |
| 50 | + @assert primal_status(model) == MOI.FEASIBLE_POINT |
| 51 | + end |
| 52 | + # native solver solutions are floating point numbers |
| 53 | + color_int = round.(Int, value.(color)) |
| 54 | + # remap to 1:cmax in case they are not contiguous |
| 55 | + true_ncolors = 0 |
| 56 | + remap = fill(0, maximum(color_int)) |
| 57 | + for c in color_int |
| 58 | + if remap[c] == 0 |
| 59 | + true_ncolors += 1 |
| 60 | + remap[c] = true_ncolors |
| 61 | + end |
| 62 | + end |
| 63 | + return remap[color_int] |
| 64 | +end |
| 65 | + |
| 66 | +function ADTypes.column_coloring(A::AbstractMatrix, algo::OptimalColoringAlgorithm) |
| 67 | + bg = BipartiteGraph(A) |
| 68 | + return optimal_distance2_coloring( |
| 69 | + bg, Val(2), algo.optimizer; algo.silent, algo.assert_solved |
| 70 | + ) |
| 71 | +end |
| 72 | + |
| 73 | +function ADTypes.row_coloring(A::AbstractMatrix, algo::OptimalColoringAlgorithm) |
| 74 | + bg = BipartiteGraph(A) |
| 75 | + return optimal_distance2_coloring( |
| 76 | + bg, Val(1), algo.optimizer; algo.silent, algo.assert_solved |
| 77 | + ) |
| 78 | +end |
| 79 | + |
| 80 | +end |
0 commit comments