Skip to content

Commit

Permalink
gh-35780: Reduce dependency to rainbow in sage.graphs.graph_coloring
Browse files Browse the repository at this point in the history
We add a method to format colorings to reduce the number of places in
which method rainbow from `sage.plot.colors` is called in
`sage.graphs.graph_coloring`.

This is a first step toward the resolution of #35777

### 📝 Checklist

- [x] The title is concise, informative, and self-explanatory.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.

URL: #35780
Reported by: David Coudert
Reviewer(s): Matthias Köppe
  • Loading branch information
Release Manager committed Jun 18, 2023
2 parents af6ec5a + e6be1da commit cf43e35
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 91 deletions.
6 changes: 3 additions & 3 deletions build/pkgs/configure/checksums.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
tarball=configure-VERSION.tar.gz
sha1=c7c1743bdc3941301764422bef5694849cd1e265
md5=610ce114c46a794ffd70346581012808
cksum=2506747194
sha1=1def339a9f1b9f34ee5fb82f38a95914876618d8
md5=520b4c716ee0ebc94fbb5d81f17efa85
cksum=2018559679
2 changes: 1 addition & 1 deletion build/pkgs/configure/package-version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
464d3d20aaa0a04237ebc98378cf1eec48505e17
c418a82d8985d727cb63c5d3f62c60264b52fd13
200 changes: 113 additions & 87 deletions src/sage/graphs/graph_coloring.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ do :
:meth:`acyclic_edge_coloring` | Compute an acyclic edge coloring of the current graph
AUTHORS:
- Tom Boothby (2008-02-21): Initial version
Expand Down Expand Up @@ -75,7 +74,69 @@ DLXCPP = LazyImport('sage.combinat.matrices.dlxcpp', 'DLXCPP')
MixedIntegerLinearProgram = LazyImport('sage.numerical.mip', 'MixedIntegerLinearProgram')


def all_graph_colorings(G, n, count_only=False, hex_colors=False, vertex_color_dict=False):
def format_coloring(data, value_only=False, hex_colors=False, vertex_color_dict=False):
r"""
Helper method for vertex and edge coloring methods.
INPUT:
- ``data`` -- either a number when ``value_only`` is ``True`` or a list of
color classes
- ``value_only`` -- boolean (default: ``False``); when set to ``True``, it
simply returns ``data``
- ``hex_colors`` -- boolean (default: ``False``); when set to ``False``,
colors are labeled [0, 1, ..., `n - 1`], otherwise the RGB Hex labeling
is used
- ``vertex_color_dict`` -- boolean (default: ``False``); when set to
``True``, it returns a dictionary ``{vertex: color}``, otherwise it
returns a dictionary ``{color: [list of vertices]}``
EXAMPLES::
sage: from sage.graphs.graph_coloring import format_coloring
sage: color_classes = [['a', 'b'], ['c'], ['d']]
sage: format_coloring(color_classes, value_only=True)
[['a', 'b'], ['c'], ['d']]
sage: format_coloring(len(color_classes), value_only=True)
3
sage: format_coloring(color_classes, value_only=False)
{0: ['a', 'b'], 1: ['c'], 2: ['d']}
sage: format_coloring(color_classes, value_only=False, hex_colors=True)
{'#0000ff': ['d'], '#00ff00': ['c'], '#ff0000': ['a', 'b']}
sage: format_coloring(color_classes, value_only=False, hex_colors=False, vertex_color_dict=True)
{'a': 0, 'b': 0, 'c': 1, 'd': 2}
sage: format_coloring(color_classes, value_only=False, hex_colors=True, vertex_color_dict=True)
{'a': '#ff0000', 'b': '#ff0000', 'c': '#00ff00', 'd': '#0000ff'}
TESTS::
sage: from sage.graphs.graph_coloring import format_coloring
sage: format_coloring([], value_only=True)
[]
sage: format_coloring([], value_only=False, hex_colors=True)
{}
sage: format_coloring([], value_only=False, hex_colors=True, vertex_color_dict=True)
{}
sage: format_coloring([], value_only=False, hex_colors=False, vertex_color_dict=True)
{}
"""
if value_only:
return data
if hex_colors:
from sage.plot.colors import rainbow
colors = rainbow(len(data))
else:
colors = list(range(len(data)))
if vertex_color_dict:
return {u: col for col, C in zip(colors, data) for u in C}
return {col: C for col, C in zip(colors, data) if C}


def all_graph_colorings(G, n, count_only=False, hex_colors=False,
vertex_color_dict=False, color_classes=False):
r"""
Compute all `n`-colorings of a graph.
Expand All @@ -90,7 +151,7 @@ def all_graph_colorings(G, n, count_only=False, hex_colors=False, vertex_color_d
* ``n`` -- a positive integer; the number of colors
* ``count_only`` -- boolean (default: ``False``); when set to ``True``, it
returns 1 for each coloring
returns 1 for each coloring and ignores other parameters
* ``hex_colors`` -- boolean (default: ``False``); when set to ``False``,
colors are labeled [0, 1, ..., `n - 1`], otherwise the RGB Hex labeling
Expand All @@ -100,6 +161,10 @@ def all_graph_colorings(G, n, count_only=False, hex_colors=False, vertex_color_d
``True``, it returns a dictionary ``{vertex: color}``, otherwise it
returns a dictionary ``{color: [list of vertices]}``
* ``color_classes`` -- boolean (default: ``False``); when set to ``True``,
the method returns only a list of the color classes and ignores parameters
``hex_colors`` and ``vertex_color_dict``
.. WARNING::
This method considers only colorings using exactly `n` colors, even if a
Expand Down Expand Up @@ -170,27 +235,29 @@ def all_graph_colorings(G, n, count_only=False, hex_colors=False, vertex_color_d
sage: G = Graph({0: [1], 1: [2]})
sage: for c in all_graph_colorings(G, 2, vertex_color_dict=True):
....: print(c)
{0: 0, 1: 1, 2: 0}
{0: 1, 1: 0, 2: 1}
{0: 0, 2: 0, 1: 1}
{1: 0, 0: 1, 2: 1}
sage: for c in all_graph_colorings(G, 2, hex_colors=True):
....: print(sorted(c.items()))
[('#00ffff', [1]), ('#ff0000', [0, 2])]
[('#00ffff', [0, 2]), ('#ff0000', [1])]
sage: for c in all_graph_colorings(G, 2, hex_colors=True, vertex_color_dict=True):
....: print(c)
{0: '#ff0000', 1: '#00ffff', 2: '#ff0000'}
{0: '#00ffff', 1: '#ff0000', 2: '#00ffff'}
{0: '#ff0000', 2: '#ff0000', 1: '#00ffff'}
{1: '#ff0000', 0: '#00ffff', 2: '#00ffff'}
sage: for c in all_graph_colorings(G, 2, vertex_color_dict=True):
....: print(c)
{0: 0, 1: 1, 2: 0}
{0: 1, 1: 0, 2: 1}
{0: 0, 2: 0, 1: 1}
{1: 0, 0: 1, 2: 1}
sage: for c in all_graph_colorings(G, 2, count_only=True, vertex_color_dict=True):
....: print(c)
1
1
sage: for c in all_graph_colorings(G, 2, color_classes=True):
....: print(c)
[[0, 2], [1]]
[[1], [0, 2]]
"""
from sage.plot.colors import rainbow

G._scream_if_not_simple(allow_multiple_edges=True)

if not n or n > G.order():
Expand Down Expand Up @@ -230,48 +297,29 @@ def all_graph_colorings(G, n, count_only=False, hex_colors=False, vertex_color_d
for i in range(n * nE):
ones.push_back((k + i, [nV + i]))

cdef list colors = rainbow(n)
cdef dict color_dict = {col: i for i, col in enumerate(colors)}

cdef list ones_second = [ones[i].second for i in range(len(ones))]
cdef dict coloring
cdef list coloring
cdef set used_colors

try:
for a in DLXCPP(ones_second):
coloring = {}
coloring = [[] for _ in range(n)]
used_colors = set()
if count_only:
used_colors = set(colormap[x][1] for x in a if x in colormap)
elif vertex_color_dict:
for x in a:
if x in colormap:
v, c = colormap[x]
used_colors.add(c)
if hex_colors:
coloring[v] = colors[c]
else:
coloring[v] = color_dict[colors[c]]
else:
for x in a:
if x in colormap:
v, c = colormap[x]
used_colors.add(c)
if hex_colors:
if colors[c] in coloring:
coloring[colors[c]].append(v)
else:
coloring[colors[c]] = [v]
else:
if color_dict[colors[c]] in coloring:
coloring[color_dict[colors[c]]].append(v)
else:
coloring[color_dict[colors[c]]] = [v]
coloring[c].append(v)
if len(used_colors) == n:
if count_only:
yield 1
else:
yield coloring
yield format_coloring(coloring, value_only=color_classes,
hex_colors=hex_colors,
vertex_color_dict=vertex_color_dict)
except RuntimeError:
raise RuntimeError("too much recursion, Graph coloring failed")

Expand Down Expand Up @@ -310,11 +358,8 @@ cpdef first_coloring(G, n=0, hex_colors=False):
G._scream_if_not_simple(allow_multiple_edges=True)
cdef int o = G.order()
for m in range(n, o + 1):
for C in all_graph_colorings(G, m, hex_colors=True):
if hex_colors:
return C
else:
return list(C.values())
for C in all_graph_colorings(G, m, hex_colors=hex_colors, color_classes=not hex_colors):
return C


cpdef number_of_n_colorings(G, n):
Expand Down Expand Up @@ -402,7 +447,7 @@ cpdef chromatic_number(G):
# don't waste our time coloring.
return m
for n in range(m, o + 1):
for C in all_graph_colorings(G, n):
for C in all_graph_colorings(G, n, count_only=True):
return n


Expand Down Expand Up @@ -491,7 +536,6 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
4
"""
g._scream_if_not_simple(allow_multiple_edges=True)
from sage.plot.colors import rainbow
cdef list colorings
cdef set vertices
cdef list deg
Expand All @@ -514,18 +558,15 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
if not g.size():
if value_only:
return 1
elif hex_colors:
return {rainbow(1)[0]: list(g)}
else:
return [list(g)]
return format_coloring([list(g)], value_only=not hex_colors,
hex_colors=hex_colors)
# - Bipartite set
if g.is_bipartite():
if value_only:
return 2
elif hex_colors:
return dict(zip(rainbow(2), g.bipartite_sets()))
else:
return g.bipartite_sets()
return format_coloring(g.bipartite_sets(),
value_only=not hex_colors,
hex_colors=hex_colors)

# - No need to try any k smaller than the maximum clique in the graph
# - No need to try k less than |G|/alpha(G), as each color
Expand Down Expand Up @@ -553,10 +594,9 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
if not g.order():
if value_only:
return True
elif hex_colors:
return {color: [] for color in rainbow(k)}
else:
return [[] for i in range(k)]
return format_coloring([[] for i in range(k)],
value_only=not hex_colors,
hex_colors=hex_colors)
# Is the graph connected?
# This is not so stupid, as the graph could be disconnected
# by the test of degeneracy (as previously).
Expand Down Expand Up @@ -585,10 +625,9 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
for color in range(k):
for component in colorings:
value[color].extend(component[color])
if hex_colors:
return dict(zip(rainbow(k), value))
else:
return value

return format_coloring(value, value_only=not hex_colors,
hex_colors=hex_colors)

# Degeneracy
# Vertices whose degree is less than k are of no importance in
Expand Down Expand Up @@ -623,10 +662,9 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
classe.append(deg[-1])
deg.pop(-1)
break
if hex_colors:
return dict(zip(rainbow(k), value))
else:
return value

return format_coloring(value, value_only=not hex_colors,
hex_colors=hex_colors)

p = MixedIntegerLinearProgram(maximization=True, solver=solver)
color = p.new_variable(binary=True)
Expand Down Expand Up @@ -664,10 +702,8 @@ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None,
classes[i].append(v)
break

if hex_colors:
return dict(zip(rainbow(len(classes)), classes))
else:
return classes
return format_coloring(classes, value_only=not hex_colors,
hex_colors=hex_colors)


# Fractional relaxations
Expand Down Expand Up @@ -1088,9 +1124,9 @@ def b_coloring(g, k, value_only=True, solver=None, verbose=0,
proper coloring where each color class has a b-vertex.
In the worst case, after successive applications of the above procedure, one
get a proper coloring that uses a number of colors equal to the the
b-chromatic number of `G` (denoted `\chi_b(G)`): the maximum `k` such that
`G` admits a b-coloring with `k` colors.
get a proper coloring that uses a number of colors equal to the b-chromatic
number of `G` (denoted `\chi_b(G)`): the maximum `k` such that `G` admits a
b-coloring with `k` colors.
A useful upper bound for calculating the b-chromatic number is the
following. If `G` admits a b-coloring with `k` colors, then there are `k`
Expand Down Expand Up @@ -1385,7 +1421,6 @@ def edge_coloring(g, value_only=False, vizing=False, hex_colors=False, solver=No
{}
"""
g._scream_if_not_simple()
from sage.plot.colors import rainbow

if not g.order() or not g.size():
if value_only:
Expand Down Expand Up @@ -1492,10 +1527,7 @@ def edge_coloring(g, value_only=False, vizing=False, hex_colors=False, solver=No
return chi

# if needed, builds a dictionary from the color classes adding colors
if hex_colors:
return dict(zip(rainbow(len(classes)), classes))
else:
return classes
return format_coloring(classes, value_only=not hex_colors, hex_colors=hex_colors)


def _vizing_edge_coloring(g):
Expand Down Expand Up @@ -1861,8 +1893,6 @@ def linear_arboricity(g, plus_one=None, hex_colors=False, value_only=False,
else:
raise ValueError("plus_one must be equal to 0,1, or to None!")

from sage.plot.colors import rainbow

p = MixedIntegerLinearProgram(solver=solver)

# c is a boolean value such that c[i,(u,v)] = 1 if and only if (u,v) is
Expand Down Expand Up @@ -1928,10 +1958,7 @@ def linear_arboricity(g, plus_one=None, hex_colors=False, value_only=False,
if c[i, frozenset((u, v))]:
add((u, v), i)

if hex_colors:
return dict(zip(rainbow(len(answer)), answer))
else:
return answer
return format_coloring(answer, value_only=not hex_colors, hex_colors=hex_colors)


def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0,
Expand Down Expand Up @@ -2079,7 +2106,6 @@ def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0,

from sage.rings.integer import Integer
from sage.combinat.subset import Subsets
from sage.plot.colors import rainbow

if not g.order() or not g.size():
if k == 0:
Expand All @@ -2089,7 +2115,10 @@ def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0,
else:
if k is None:
return {} if hex_colors else []
return {c: [] for c in rainbow(k)} if hex_colors else [copy(g) for _ in range(k)]
if hex_colors:
return format_coloring([[] for _ in range(k)], hex_colors=True)
else:
return [copy(g) for _ in range(k)]

if k is None:
k = max(g.degree())
Expand Down Expand Up @@ -2181,10 +2210,7 @@ def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0,
if c[i, E(u, v)]:
add((u, v), i)

if hex_colors:
return dict(zip(rainbow(len(answer)), answer))
else:
return answer
return format_coloring(answer, value_only=not hex_colors, hex_colors=hex_colors)


cdef class Test:
Expand Down

0 comments on commit cf43e35

Please sign in to comment.