passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,2284 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ # distutils: language = c++
4
+ """
5
+ Graph coloring
6
+
7
+ This module gathers all methods related to graph coloring. Here is what it can
8
+ do :
9
+
10
+ **Proper vertex coloring**
11
+
12
+ .. csv-table::
13
+ :class: contentstable
14
+ :widths: 30, 70
15
+ :delim: |
16
+
17
+ :meth:`all_graph_colorings` | Compute all `n`-colorings a graph
18
+ :meth:`first_coloring` | Return the first vertex coloring found
19
+ :meth:`number_of_n_colorings` | Compute the number of `n`-colorings of a graph
20
+ :meth:`numbers_of_colorings` | Compute the number of colorings of a graph
21
+ :meth:`chromatic_number` | Return the chromatic number of the graph
22
+ :meth:`vertex_coloring` | Compute vertex colorings and chromatic numbers
23
+
24
+ **Fractional relaxations**
25
+
26
+ .. csv-table::
27
+ :class: contentstable
28
+ :widths: 30, 70
29
+ :delim: |
30
+
31
+ :meth:`fractional_chromatic_number` | Return the fractional chromatic number of the graph
32
+ :meth:`fractional_chromatic_index` | Return the fractional chromatic index of the graph
33
+
34
+ **Other colorings**
35
+
36
+ .. csv-table::
37
+ :class: contentstable
38
+ :widths: 30, 70
39
+ :delim: |
40
+
41
+ :meth:`grundy_coloring` | Compute Grundy numbers and Grundy colorings
42
+ :meth:`b_coloring` | Compute b-chromatic numbers and b-colorings
43
+ :meth:`edge_coloring` | Compute chromatic index and edge colorings
44
+ :meth:`round_robin` | Compute a round-robin coloring of the complete graph on `n` vertices
45
+ :meth:`linear_arboricity` | Compute the linear arboricity of the given graph
46
+ :meth:`acyclic_edge_coloring` | Compute an acyclic edge coloring of the current graph
47
+
48
+
49
+ AUTHORS:
50
+
51
+ - Tom Boothby (2008-02-21): Initial version
52
+ - Carlo Hamalainen (2009-03-28): minor change: switch to C++ DLX solver
53
+ - Nathann Cohen (2009-10-24): Coloring methods using linear programming
54
+
55
+ Methods
56
+ -------
57
+ """
58
+
59
+ # ****************************************************************************
60
+ # Copyright (C) 2008 Tom Boothby <boothby@u.washington.edu>
61
+ #
62
+ # Distributed under the terms of the GNU General Public License (GPL)
63
+ # https://www.gnu.org/licenses/
64
+ # ****************************************************************************
65
+
66
+ from copy import copy
67
+ from libcpp.vector cimport vector
68
+ from libcpp.pair cimport pair
69
+
70
+ from sage.graphs.independent_sets import IndependentSets
71
+ from sage.misc.lazy_import import LazyImport
72
+
73
+ DLXCPP = LazyImport('sage.combinat.matrices.dlxcpp', 'DLXCPP')
74
+ MixedIntegerLinearProgram = LazyImport('sage.numerical.mip', 'MixedIntegerLinearProgram')
75
+
76
+
77
+ def format_coloring(data, value_only=False, hex_colors=False, vertex_color_dict=False):
78
+ r"""
79
+ Helper method for vertex and edge coloring methods.
80
+
81
+ INPUT:
82
+
83
+ - ``data`` -- either a number when ``value_only`` is ``True`` or a list of
84
+ color classes
85
+
86
+ - ``value_only`` -- boolean (default: ``False``); when set to ``True``, it
87
+ simply returns ``data``
88
+
89
+ - ``hex_colors`` -- boolean (default: ``False``); when set to ``False``,
90
+ colors are labeled [0, 1, ..., `n - 1`], otherwise the RGB Hex labeling
91
+ is used
92
+
93
+ - ``vertex_color_dict`` -- boolean (default: ``False``); when set to
94
+ ``True``, it returns a dictionary ``{vertex: color}``, otherwise it
95
+ returns a dictionary ``{color: [list of vertices]}``
96
+
97
+ EXAMPLES::
98
+
99
+ sage: from sage.graphs.graph_coloring import format_coloring
100
+ sage: color_classes = [['a', 'b'], ['c'], ['d']]
101
+ sage: format_coloring(color_classes, value_only=True)
102
+ [['a', 'b'], ['c'], ['d']]
103
+ sage: format_coloring(len(color_classes), value_only=True)
104
+ 3
105
+ sage: format_coloring(color_classes, value_only=False)
106
+ {0: ['a', 'b'], 1: ['c'], 2: ['d']}
107
+ sage: format_coloring(color_classes, value_only=False, hex_colors=True) # needs sage.plot
108
+ {'#0000ff': ['d'], '#00ff00': ['c'], '#ff0000': ['a', 'b']}
109
+ sage: format_coloring(color_classes, value_only=False, hex_colors=False,
110
+ ....: vertex_color_dict=True)
111
+ {'a': 0, 'b': 0, 'c': 1, 'd': 2}
112
+ sage: format_coloring(color_classes, value_only=False, hex_colors=True, # needs sage.plot
113
+ ....: vertex_color_dict=True)
114
+ {'a': '#ff0000', 'b': '#ff0000', 'c': '#00ff00', 'd': '#0000ff'}
115
+
116
+ TESTS::
117
+
118
+ sage: from sage.graphs.graph_coloring import format_coloring
119
+ sage: format_coloring([], value_only=True)
120
+ []
121
+ sage: format_coloring([], value_only=False, hex_colors=True) # needs sage.plot
122
+ {}
123
+ sage: format_coloring([], value_only=False, hex_colors=True, # needs sage.plot
124
+ ....: vertex_color_dict=True)
125
+ {}
126
+ sage: format_coloring([], value_only=False, hex_colors=False,
127
+ ....: vertex_color_dict=True)
128
+ {}
129
+ """
130
+ if value_only:
131
+ return data
132
+ if hex_colors:
133
+ from sage.plot.colors import rainbow
134
+ colors = rainbow(len(data))
135
+ else:
136
+ colors = list(range(len(data)))
137
+ if vertex_color_dict:
138
+ return {u: col for col, C in zip(colors, data) for u in C}
139
+ return {col: C for col, C in zip(colors, data) if C}
140
+
141
+
142
+ def all_graph_colorings(G, n, count_only=False, hex_colors=False,
143
+ vertex_color_dict=False, color_classes=False):
144
+ r"""
145
+ Compute all `n`-colorings of a graph.
146
+
147
+ This method casts the graph coloring problem into an exact cover problem,
148
+ and passes this into an implementation of the Dancing Links algorithm
149
+ described by Knuth (who attributes the idea to Hitotumatu and Noshita).
150
+
151
+ INPUT:
152
+
153
+ - ``G`` -- a graph
154
+
155
+ - ``n`` -- positive integer; the number of colors
156
+
157
+ - ``count_only`` -- boolean (default: ``False``); when set to ``True``, it
158
+ returns 1 for each coloring and ignores other parameters
159
+
160
+ - ``hex_colors`` -- boolean (default: ``False``); when set to ``False``,
161
+ colors are labeled [0, 1, ..., `n - 1`], otherwise the RGB Hex labeling
162
+ is used
163
+
164
+ - ``vertex_color_dict`` -- boolean (default: ``False``); when set to
165
+ ``True``, it returns a dictionary ``{vertex: color}``, otherwise it
166
+ returns a dictionary ``{color: [list of vertices]}``
167
+
168
+ - ``color_classes`` -- boolean (default: ``False``); when set to ``True``,
169
+ the method returns only a list of the color classes and ignores parameters
170
+ ``hex_colors`` and ``vertex_color_dict``
171
+
172
+ .. WARNING::
173
+
174
+ This method considers only colorings using exactly `n` colors, even if a
175
+ coloring using fewer colors can be found.
176
+
177
+ The construction works as follows. Columns:
178
+
179
+ * The first `|V|` columns correspond to a vertex -- a `1` in this column
180
+ indicates that this vertex has a color.
181
+
182
+ * After those `|V|` columns, we add `n*|E|` columns -- a `1` in these
183
+ columns indicate that a particular edge is incident to a vertex with a
184
+ certain color.
185
+
186
+ Rows:
187
+
188
+ * For each vertex, add `n` rows; one for each color `c`. Place a `1` in the
189
+ column corresponding to the vertex, and a `1` in the appropriate column
190
+ for each edge incident to the vertex, indicating that that edge is
191
+ incident to the color `c`.
192
+
193
+ * If `n > 2`, the above construction cannot be exactly covered since each
194
+ edge will be incident to only two vertices (and hence two colors) - so we
195
+ add `n*|E|` rows, each one containing a `1` for each of the `n*|E|`
196
+ columns. These get added to the cover solutions "for free" during the
197
+ backtracking.
198
+
199
+ Note that this construction results in `n*|V| + 2*n*|E| + n*|E|` entries in
200
+ the matrix. The Dancing Links algorithm uses a sparse representation, so if
201
+ the graph is simple, `|E| \leq |V|^2` and `n <= |V|`, this construction runs
202
+ in `O(|V|^3)` time. Back-conversion to a coloring solution is a simple scan
203
+ of the solutions, which will contain `|V| + (n-2)*|E|` entries, so runs in
204
+ `O(|V|^3)` time also. For most graphs, the conversion will be much faster
205
+ -- for example, a planar graph will be transformed for `4`-coloring in
206
+ linear time since `|E| = O(|V|)`.
207
+
208
+ REFERENCES:
209
+
210
+ http://www-cs-staff.stanford.edu/~uno/papers/dancing-color.ps.gz
211
+
212
+ EXAMPLES::
213
+
214
+ sage: from sage.graphs.graph_coloring import all_graph_colorings
215
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
216
+ sage: n = 0
217
+ sage: for C in all_graph_colorings(G, 3, hex_colors=True): # needs sage.plot
218
+ ....: parts = [C[k] for k in C]
219
+ ....: for P in parts:
220
+ ....: l = len(P)
221
+ ....: for i in range(l):
222
+ ....: for j in range(i + 1, l):
223
+ ....: if G.has_edge(P[i], P[j]):
224
+ ....: raise RuntimeError("Coloring Failed.")
225
+ ....: n += 1
226
+ sage: print("G has %s 3-colorings." % n) # needs sage.plot
227
+ G has 12 3-colorings.
228
+
229
+ TESTS::
230
+
231
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
232
+ sage: for C in all_graph_colorings(G, 0):
233
+ ....: print(C)
234
+ sage: for C in all_graph_colorings(G, -1):
235
+ ....: print(C)
236
+ Traceback (most recent call last):
237
+ ...
238
+ ValueError: n must be nonnegative
239
+ sage: G = Graph({0: [1], 1: [2]})
240
+ sage: for c in all_graph_colorings(G, 2, vertex_color_dict=True):
241
+ ....: print(c)
242
+ {0: 0, 2: 0, 1: 1}
243
+ {1: 0, 0: 1, 2: 1}
244
+ sage: for c in all_graph_colorings(G, 2, hex_colors=True): # needs sage.plot
245
+ ....: print(sorted(c.items()))
246
+ [('#00ffff', [1]), ('#ff0000', [0, 2])]
247
+ [('#00ffff', [0, 2]), ('#ff0000', [1])]
248
+ sage: for c in all_graph_colorings(G, 2, hex_colors=True, # needs sage.plot
249
+ ....: vertex_color_dict=True):
250
+ ....: print(c)
251
+ {0: '#ff0000', 2: '#ff0000', 1: '#00ffff'}
252
+ {1: '#ff0000', 0: '#00ffff', 2: '#00ffff'}
253
+ sage: for c in all_graph_colorings(G, 2, vertex_color_dict=True):
254
+ ....: print(c)
255
+ {0: 0, 2: 0, 1: 1}
256
+ {1: 0, 0: 1, 2: 1}
257
+ sage: for c in all_graph_colorings(G, 2, count_only=True, vertex_color_dict=True):
258
+ ....: print(c)
259
+ 1
260
+ 1
261
+ sage: for c in all_graph_colorings(G, 2, color_classes=True):
262
+ ....: print(c)
263
+ [[0, 2], [1]]
264
+ [[1], [0, 2]]
265
+ """
266
+ G._scream_if_not_simple(allow_multiple_edges=True)
267
+
268
+ if not n or n > G.order():
269
+ return
270
+ if n < 0:
271
+ raise ValueError("n must be nonnegative")
272
+
273
+ cdef list V = list(G)
274
+
275
+ cdef int nV = G.order()
276
+ cdef int nE = G.size()
277
+
278
+ cdef vector[pair[int, vector[int]]] ones
279
+ cdef dict Vd = {}
280
+ cdef dict colormap = {}
281
+ cdef int k = 0
282
+ for i, v in enumerate(V):
283
+ Vd[v] = i
284
+ for c in range(n):
285
+ ones.push_back((k, [i]))
286
+ colormap[k] = (v, c)
287
+ k += 1
288
+
289
+ cdef int kk = nV
290
+ cdef int v0, v1
291
+ for e in G.edges(labels=False, sort=False):
292
+ v0 = n * Vd[e[0]]
293
+ v1 = n * Vd[e[1]]
294
+ for c in range(n):
295
+ ones[v0].second.push_back(kk + c)
296
+ ones[v1].second.push_back(kk + c)
297
+ v0 += 1
298
+ v1 += 1
299
+ kk += n
300
+
301
+ if n > 2:
302
+ for i in range(n * nE):
303
+ ones.push_back((k + i, [nV + i]))
304
+
305
+ cdef list ones_second = [ones[i].second for i in range(len(ones))]
306
+ cdef list coloring
307
+ cdef set used_colors
308
+
309
+ try:
310
+ for a in DLXCPP(ones_second):
311
+ coloring = [[] for _ in range(n)]
312
+ used_colors = set()
313
+ if count_only:
314
+ used_colors = set(colormap[x][1] for x in a if x in colormap)
315
+ else:
316
+ for x in a:
317
+ if x in colormap:
318
+ v, c = colormap[x]
319
+ used_colors.add(c)
320
+ coloring[c].append(v)
321
+ if len(used_colors) == n:
322
+ if count_only:
323
+ yield 1
324
+ else:
325
+ yield format_coloring(coloring, value_only=color_classes,
326
+ hex_colors=hex_colors,
327
+ vertex_color_dict=vertex_color_dict)
328
+ except RuntimeError:
329
+ raise RuntimeError("too much recursion, Graph coloring failed")
330
+
331
+
332
+ cpdef first_coloring(G, n=0, hex_colors=False):
333
+ r"""
334
+ Return the first vertex coloring found.
335
+
336
+ If a natural number `n` is provided, returns the first found coloring with
337
+ at least `n` colors. That is, `n` is a lower bound on the number of colors
338
+ to use.
339
+
340
+ INPUT:
341
+
342
+ - ``n`` -- integer (default: 0); the minimal number of colors to try
343
+
344
+ - ``hex_colors`` -- boolean (default: ``False``); when set to ``True``, the
345
+ partition returned is a dictionary whose keys are colors and whose values
346
+ are the color classes (ideal for plotting)
347
+
348
+ EXAMPLES::
349
+
350
+ sage: from sage.graphs.graph_coloring import first_coloring
351
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
352
+ sage: sorted(first_coloring(G, 3))
353
+ [[0], [1, 3], [2]]
354
+
355
+ TESTS:
356
+
357
+ :issue:`33554` is fixed::
358
+
359
+ sage: P3 = graphs.PathGraph(3)
360
+ sage: [len(graph_coloring.first_coloring(P3, k)) for k in range(P3.order() + 1)]
361
+ [2, 2, 2, 3]
362
+ """
363
+ G._scream_if_not_simple(allow_multiple_edges=True)
364
+ cdef int o = G.order()
365
+ for m in range(n, o + 1):
366
+ for C in all_graph_colorings(G, m, hex_colors=hex_colors, color_classes=not hex_colors):
367
+ return C
368
+
369
+
370
+ cpdef number_of_n_colorings(G, n):
371
+ r"""
372
+ Compute the number of `n`-colorings of a graph.
373
+
374
+ INPUT:
375
+
376
+ - ``G`` -- a graph
377
+
378
+ - ``n`` -- positive integer; the number of colors
379
+
380
+ EXAMPLES::
381
+
382
+ sage: from sage.graphs.graph_coloring import number_of_n_colorings
383
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
384
+ sage: number_of_n_colorings(G, 3)
385
+ 12
386
+ """
387
+ # Take care of the stupid stuff
388
+ if not n:
389
+ return int(G.order() == 0)
390
+ if n == 1:
391
+ return int(G.size() == 0)
392
+ if n < 0:
393
+ # negative colors?? what does that even mean?
394
+ return 0
395
+
396
+ m = 0
397
+ for C in all_graph_colorings(G, n, count_only=True):
398
+ m += 1
399
+ return m
400
+
401
+
402
+ cpdef numbers_of_colorings(G):
403
+ r"""
404
+ Compute the number of colorings of a graph.
405
+
406
+ Return the number of `n`-colorings of the graph `G` for all `n` from `0` to
407
+ `|V|`.
408
+
409
+ EXAMPLES::
410
+
411
+ sage: from sage.graphs.graph_coloring import numbers_of_colorings
412
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
413
+ sage: numbers_of_colorings(G)
414
+ [0, 0, 0, 12, 24]
415
+ """
416
+ cdef int o = G.order()
417
+ cdef list answer = [number_of_n_colorings(G, n) for n in range(o + 1)]
418
+ return answer
419
+
420
+
421
+ cpdef chromatic_number(G):
422
+ r"""
423
+ Return the chromatic number of the graph.
424
+
425
+ The chromatic number is the minimal number of colors needed to color the
426
+ vertices of the graph `G`.
427
+
428
+ EXAMPLES::
429
+
430
+ sage: from sage.graphs.graph_coloring import chromatic_number
431
+ sage: G = Graph({0: [1, 2, 3], 1: [2]})
432
+ sage: chromatic_number(G) # needs cliquer
433
+ 3
434
+
435
+ sage: G = graphs.PetersenGraph()
436
+ sage: G.chromatic_number() # needs cliquer
437
+ 3
438
+ """
439
+ G._scream_if_not_simple(allow_multiple_edges=True)
440
+ cdef int o = G.order()
441
+ if not o:
442
+ return 0
443
+ if not G.size():
444
+ return 1
445
+ elif G.is_bipartite(): # can we do it in linear time?
446
+ return 2
447
+
448
+ # counting cliques is faster than our brute-force method...
449
+ cdef int m = G.clique_number()
450
+ if m >= o - 1:
451
+ # marginal improvement... if there is an o-1 clique and not an o clique,
452
+ # don't waste our time coloring.
453
+ return m
454
+ for n in range(m, o + 1):
455
+ for C in all_graph_colorings(G, n, count_only=True):
456
+ return n
457
+
458
+
459
+ def vertex_coloring(g, k=None, value_only=False, hex_colors=False, solver=None, verbose=0,
460
+ *, integrality_tolerance=1e-3):
461
+ r"""
462
+ Compute Vertex colorings and chromatic numbers.
463
+
464
+ This function can compute the chromatic number of the given graph or test
465
+ its `k`-colorability.
466
+
467
+ See the :wikipedia:`Graph_coloring` for further details on graph coloring.
468
+
469
+ INPUT:
470
+
471
+ - ``g`` -- a graph
472
+
473
+ - ``k`` -- integer (default: ``None``); tests whether the graph is
474
+ `k`-colorable. The function returns a partition of the vertex set in `k`
475
+ independent sets if possible and ``False`` otherwise.
476
+
477
+ - ``value_only`` -- boolean (default: ``False``):
478
+
479
+ - When set to ``True``, only the chromatic number is returned.
480
+
481
+ - When set to ``False`` (default), a partition of the vertex set into
482
+ independent sets is returned if possible.
483
+
484
+ - ``hex_colors`` -- boolean (default: ``False``); when set to ``True``, the
485
+ partition returned is a dictionary whose keys are colors and whose values
486
+ are the color classes (ideal for plotting).
487
+
488
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
489
+ Programming (MILP) solver to be used. If set to ``None``, the default one
490
+ is used. For more information on MILP solvers and which default solver is
491
+ used, see the method :meth:`solve
492
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
493
+ :class:`MixedIntegerLinearProgram
494
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
495
+
496
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
497
+ to 0 by default, which means quiet.
498
+
499
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
500
+ over an inexact base ring; see
501
+ :meth:`MixedIntegerLinearProgram.get_values`.
502
+
503
+ OUTPUT:
504
+
505
+ - If ``k=None`` and ``value_only=False``, then return a partition of the
506
+ vertex set into the minimum possible of independent sets.
507
+
508
+ - If ``k=None`` and ``value_only=True``, return the chromatic number.
509
+
510
+ - If ``k`` is set and ``value_only=None``, return ``False`` if the graph is
511
+ not `k`-colorable, and a partition of the vertex set into `k` independent
512
+ sets otherwise.
513
+
514
+ - If ``k`` is set and ``value_only=True``, test whether the graph is
515
+ `k`-colorable, and return ``True`` or ``False`` accordingly.
516
+
517
+ EXAMPLES::
518
+
519
+ sage: from sage.graphs.graph_coloring import vertex_coloring
520
+ sage: g = graphs.PetersenGraph()
521
+ sage: vertex_coloring(g, value_only=True) # needs cliquer sage.numerical.mip
522
+ 3
523
+
524
+ TESTS:
525
+
526
+ Empty graph::
527
+
528
+ sage: from sage.graphs.graph_coloring import vertex_coloring
529
+ sage: empty = Graph()
530
+ sage: vertex_coloring(empty, value_only=True)
531
+ 0
532
+ sage: vertex_coloring(empty, hex_colors=True)
533
+ {}
534
+ sage: vertex_coloring(empty)
535
+ []
536
+
537
+ :issue:`33559` is fixed::
538
+
539
+ sage: G = Graph('MgCgS?_O@IeTHKG??')
540
+ sage: len(G.coloring(algorithm='MILP')) # needs cliquer sage.numerical.mip
541
+ 4
542
+ """
543
+ g._scream_if_not_simple(allow_multiple_edges=True)
544
+ cdef list colorings
545
+ cdef set vertices
546
+ cdef list deg
547
+ cdef list neighbors
548
+ cdef list classes
549
+
550
+ # If k is None, tries to find an optimal coloring
551
+ if k is None:
552
+ # No need to start a linear program if the graph is an
553
+ # independent set, is bipartite, or is empty.
554
+ # - Empty graph
555
+ if not g.order():
556
+ if value_only:
557
+ return 0
558
+ elif hex_colors:
559
+ return dict()
560
+ return []
561
+ # - Independent set
562
+ if not g.size():
563
+ if value_only:
564
+ return 1
565
+ return format_coloring([list(g)], value_only=not hex_colors,
566
+ hex_colors=hex_colors)
567
+ # - Bipartite set
568
+ if g.is_bipartite():
569
+ if value_only:
570
+ return 2
571
+ return format_coloring(g.bipartite_sets(),
572
+ value_only=not hex_colors,
573
+ hex_colors=hex_colors)
574
+
575
+ # - No need to try any k smaller than the maximum clique in the graph
576
+ # - No need to try k less than |G|/alpha(G), as each color
577
+ # class is at most alpha(G)
578
+ # - max, because we know it is not bipartite
579
+ from math import ceil
580
+ k = int(max([3, g.clique_number(), ceil(g.order() / len(g.independent_set()))]))
581
+
582
+ while True:
583
+ # tries to color the graph, increasing k each time it fails.
584
+ tmp = vertex_coloring(g, k=k, value_only=value_only,
585
+ hex_colors=hex_colors, solver=solver, verbose=verbose,
586
+ integrality_tolerance=integrality_tolerance)
587
+ if tmp is not False:
588
+ if value_only:
589
+ return k
590
+ else:
591
+ return tmp
592
+ k += 1
593
+ else:
594
+ # Is the graph empty?
595
+ # If the graph is empty, something should be returned.
596
+ # This is not so stupid, as the graph could be emptied
597
+ # by the test of degeneracy.
598
+ if not g.order():
599
+ if value_only:
600
+ return True
601
+ return format_coloring([[] for i in range(k)],
602
+ value_only=not hex_colors,
603
+ hex_colors=hex_colors)
604
+ # Is the graph connected?
605
+ # This is not so stupid, as the graph could be disconnected
606
+ # by the test of degeneracy (as previously).
607
+ if not g.is_connected():
608
+ if value_only:
609
+ for component in g.connected_components(sort=False):
610
+ tmp = vertex_coloring(g.subgraph(component), k=k,
611
+ value_only=value_only,
612
+ hex_colors=hex_colors,
613
+ solver=solver, verbose=verbose,
614
+ integrality_tolerance=integrality_tolerance)
615
+ if tmp is False:
616
+ return False
617
+ return True
618
+ colorings = []
619
+ for component in g.connected_components(sort=False):
620
+ tmp = vertex_coloring(g.subgraph(component), k=k,
621
+ value_only=value_only,
622
+ hex_colors=False,
623
+ solver=solver, verbose=verbose,
624
+ integrality_tolerance=integrality_tolerance)
625
+ if tmp is False:
626
+ return False
627
+ colorings.append(tmp)
628
+ value = [[] for color in range(k)]
629
+ for color in range(k):
630
+ for component in colorings:
631
+ value[color].extend(component[color])
632
+
633
+ return format_coloring(value, value_only=not hex_colors,
634
+ hex_colors=hex_colors)
635
+
636
+ # Degeneracy
637
+ # Vertices whose degree is less than k are of no importance in
638
+ # the coloring.
639
+ if min(g.degree()) < k:
640
+ vertices = set(g)
641
+ deg = []
642
+ tmp = [v for v in vertices if g.degree(v) < k]
643
+ while tmp:
644
+ v = tmp.pop(0)
645
+ neighbors = list(set(g.neighbor_iterator(v)) & vertices)
646
+ if v in vertices and len(neighbors) < k:
647
+ vertices.remove(v)
648
+ tmp.extend(neighbors)
649
+ deg.append(v)
650
+ if value_only:
651
+ return vertex_coloring(g.subgraph(list(vertices)), k=k,
652
+ value_only=value_only,
653
+ hex_colors=hex_colors,
654
+ solver=solver, verbose=verbose,
655
+ integrality_tolerance=integrality_tolerance)
656
+ value = vertex_coloring(g.subgraph(list(vertices)), k=k,
657
+ value_only=value_only,
658
+ hex_colors=False,
659
+ solver=solver, verbose=verbose,
660
+ integrality_tolerance=integrality_tolerance)
661
+ if value is False:
662
+ return False
663
+ while deg:
664
+ for classe in value:
665
+ if set(classe).isdisjoint(g.neighbor_iterator(deg[-1])):
666
+ classe.append(deg[-1])
667
+ deg.pop(-1)
668
+ break
669
+
670
+ return format_coloring(value, value_only=not hex_colors,
671
+ hex_colors=hex_colors)
672
+
673
+ p = MixedIntegerLinearProgram(maximization=True, solver=solver)
674
+ color = p.new_variable(binary=True)
675
+
676
+ # a vertex has exactly one color
677
+ for v in g:
678
+ p.add_constraint(p.sum(color[v, i] for i in range(k)), min=1, max=1)
679
+
680
+ # adjacent vertices have different colors
681
+ for u, v in g.edge_iterator(labels=None):
682
+ for i in range(k):
683
+ p.add_constraint(color[u, i] + color[v, i], max=1)
684
+
685
+ # The first vertex is colored with 1. It costs nothing to say
686
+ # it, and it can help.
687
+ p.add_constraint(color[next(g.vertex_iterator()), 0], max=1, min=1)
688
+
689
+ from sage.numerical.mip import MIPSolverException
690
+ try:
691
+ if value_only:
692
+ p.solve(objective_only=True, log=verbose)
693
+ return True
694
+ else:
695
+ p.solve(log=verbose)
696
+ except MIPSolverException:
697
+ return False
698
+
699
+ color = p.get_values(color, convert=bool, tolerance=integrality_tolerance)
700
+ # builds the color classes
701
+ classes = [[] for i in range(k)]
702
+
703
+ for v in g:
704
+ for i in range(k):
705
+ if color[v, i]:
706
+ classes[i].append(v)
707
+ break
708
+
709
+ return format_coloring(classes, value_only=not hex_colors,
710
+ hex_colors=hex_colors)
711
+
712
+
713
+ # Fractional relaxations
714
+ def fractional_chromatic_number(G, solver='PPL', verbose=0,
715
+ check_components=True, check_bipartite=True):
716
+ r"""
717
+ Return the fractional chromatic number of the graph.
718
+
719
+ Fractional coloring is a relaxed version of vertex coloring with several
720
+ equivalent definitions, such as the optimum value in a linear relaxation of
721
+ the integer program that gives the usual chromatic number. It is also equal
722
+ to the fractional clique number by LP-duality.
723
+
724
+ ALGORITHM:
725
+
726
+ The fractional chromatic number is computed via the usual Linear Program.
727
+ The LP solved by sage is essentially,
728
+
729
+ .. MATH::
730
+
731
+ \mbox{Minimize : }&\sum_{I\in \mathcal{I}(G)} x_{I}\\
732
+ \mbox{Such that : }&\\
733
+ &\forall v\in V(G), \sum_{I\in \mathcal{I}(G),\, v\in I}x_{v}\geq 1\\
734
+ &\forall I\in \mathcal{I}(G), x_{I} \geq 0
735
+
736
+ where `\mathcal{I}(G)` is the set of maximal independent sets of `G` (see
737
+ Section 2.1 of [CFKPR2010]_ to know why it is sufficient to consider maximal
738
+ independent sets). As optional optimisations, we construct the LP on each
739
+ biconnected component of `G` (and output the maximum value), and avoid using
740
+ the LP if G is bipartite (as then the output must be 1 or 2).
741
+
742
+ .. NOTE::
743
+
744
+ Computing the fractional chromatic number can be very slow. Since the
745
+ variables of the LP are independent sets, in general the LP has size
746
+ exponential in the order of the graph. In the current implementation a
747
+ list of all maximal independent sets is created and stored, which can be
748
+ both slow and memory-hungry.
749
+
750
+ INPUT:
751
+
752
+ - ``G`` -- a graph
753
+
754
+ - ``solver`` -- (default: ``'PPL'``) specify a Linear Program (LP) solver
755
+ to be used. If set to ``None``, the default one is used. For more
756
+ information on LP solvers and which default solver is used, see the method
757
+ :meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the
758
+ class :class:`MixedIntegerLinearProgram
759
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
760
+
761
+ .. NOTE::
762
+
763
+ The default solver used here is ``'PPL'`` which provides exact
764
+ results, i.e. a rational number, although this may be slower that
765
+ using other solvers.
766
+
767
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity of
768
+ the LP solver
769
+
770
+ - ``check_components`` -- boolean (default: ``True``); whether the method is
771
+ called on each biconnected component of `G`
772
+
773
+ - ``check_bipartite`` -- boolean (default: ``True``); whether the graph is
774
+ checked for bipartiteness. If the graph is bipartite then we can avoid
775
+ creating and solving the LP.
776
+
777
+ EXAMPLES:
778
+
779
+ The fractional chromatic number of a `C_5` is `5/2`::
780
+
781
+ sage: g = graphs.CycleGraph(5)
782
+ sage: g.fractional_chromatic_number() # needs sage.numerical.mip
783
+ 5/2
784
+
785
+ TESTS::
786
+
787
+ sage: G = graphs.RandomGNP(20, .2)
788
+ sage: a = G.fractional_chromatic_number(check_components=True) # needs sage.numerical.mip
789
+ sage: b = G.fractional_chromatic_number(check_components=False) # needs sage.numerical.mip
790
+ sage: a == b # needs sage.numerical.mip
791
+ True
792
+ """
793
+ G._scream_if_not_simple()
794
+
795
+ if not G.order():
796
+ return 0
797
+ if not G.size():
798
+ # The fractional chromatic number of an independent set is 1
799
+ return 1
800
+ if check_bipartite and G.is_bipartite():
801
+ # at this point we've already ascertained g.size() > 0
802
+ # so g is a bipartite graph with at least one edge
803
+ return 2
804
+ if check_components:
805
+ return max(fractional_chromatic_number(G.subgraph(b), solver=solver,
806
+ verbose=verbose,
807
+ check_components=False,
808
+ check_bipartite=check_bipartite)
809
+ for b in G.blocks_and_cut_vertices()[0])
810
+
811
+ Is = [frozenset(S) for S in IndependentSets(G, maximal=True)]
812
+
813
+ # Initialize LP for fractional chromatic number, we want to minimize the
814
+ # total weight
815
+ p = MixedIntegerLinearProgram(solver=solver, maximization=False)
816
+
817
+ # One nonnegative variable per maximal independent set
818
+ w = p.new_variable(nonnegative=True)
819
+
820
+ # the objective is the sum of weights of the independent sets
821
+ p.set_objective(p.sum(w[S] for S in Is))
822
+
823
+ # such that each vertex gets total weight at least 1
824
+ for v in G:
825
+ p.add_constraint(p.sum(w[S] for S in Is if v in S), min=1)
826
+
827
+ obj = p.solve(log=verbose)
828
+
829
+ return obj
830
+
831
+
832
+ def fractional_chromatic_index(G, solver='PPL', verbose_constraints=False, verbose=0):
833
+ r"""
834
+ Return the fractional chromatic index of the graph.
835
+
836
+ The fractional chromatic index is a relaxed version of edge-coloring. An
837
+ edge coloring of a graph being actually a covering of its edges into the
838
+ smallest possible number of matchings, the fractional chromatic index of a
839
+ graph `G` is the smallest real value `\chi_f(G)` such that there exists a
840
+ list of matchings `M_1, \ldots, M_k` of `G` and coefficients `\alpha_1,
841
+ \ldots, \alpha_k` with the property that each edge is covered by the
842
+ matchings in the following relaxed way
843
+
844
+ .. MATH::
845
+
846
+ \forall e \in E(G), \sum_{e \in M_i} \alpha_i \geq 1.
847
+
848
+ For more information, see the :wikipedia:`Fractional_coloring`.
849
+
850
+ ALGORITHM:
851
+
852
+ The fractional chromatic index is computed through Linear Programming
853
+ through its dual. The LP solved by sage is actually:
854
+
855
+ .. MATH::
856
+
857
+ \mbox{Maximize : }&\sum_{e\in E(G)} r_{e}\\
858
+ \mbox{Such that : }&\\
859
+ &\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\
860
+
861
+ INPUT:
862
+
863
+ - ``G`` -- a graph
864
+
865
+ - ``solver`` -- (default: ``'PPL'``) specify a Linear Program (LP) solver
866
+ to be used. If set to ``None``, the default one is used. For more
867
+ information on LP solvers and which default solver is used, see the method
868
+ :meth:`solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the
869
+ class :class:`MixedIntegerLinearProgram
870
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
871
+
872
+ .. NOTE::
873
+
874
+ The default solver used here is ``'PPL'`` which provides exact
875
+ results, i.e. a rational number, although this may be slower that
876
+ using other solvers. Be aware that this method may loop endlessly when
877
+ using some non exact solvers as reported in :issue:`23658` and
878
+ :issue:`23798`.
879
+
880
+ - ``verbose_constraints`` -- boolean (default: ``False``); whether to
881
+ display which constraints are being generated
882
+
883
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity of the
884
+ LP solver
885
+
886
+ EXAMPLES:
887
+
888
+ The fractional chromatic index of a `C_5` is `5/2`::
889
+
890
+ sage: g = graphs.CycleGraph(5)
891
+ sage: g.fractional_chromatic_index() # needs sage.numerical.mip
892
+ 5/2
893
+
894
+ TESTS:
895
+
896
+ Issue reported in :issue:`23658` and :issue:`23798` with non exact
897
+ solvers::
898
+
899
+ sage: g = graphs.PetersenGraph()
900
+ sage: g.fractional_chromatic_index(solver='GLPK') # known bug # needs sage.numerical.mip
901
+ 3.0
902
+ sage: g.fractional_chromatic_index(solver='PPL') # needs sage.numerical.mip
903
+ 3
904
+ """
905
+ G._scream_if_not_simple()
906
+
907
+ if not G.order():
908
+ return 0
909
+ if not G.size():
910
+ return 1
911
+
912
+ frozen_edges = [frozenset(e) for e in G.edges(labels=False, sort=False)]
913
+
914
+ # Initialize LP for maximum weight matching
915
+ M = MixedIntegerLinearProgram(solver=solver, constraint_generation=True)
916
+
917
+ # One variable per edge
918
+ b = M.new_variable(binary=True, nonnegative=True)
919
+
920
+ # We want to select at most one incident edge per vertex (matching)
921
+ for u in G:
922
+ M.add_constraint(M.sum(b[frozenset(e)] for e in G.edges_incident(u, labels=False)) <= 1)
923
+
924
+ #
925
+ # Initialize LP for fractional chromatic number
926
+ p = MixedIntegerLinearProgram(solver=solver, constraint_generation=True)
927
+
928
+ # One variable per edge
929
+ r = p.new_variable(nonnegative=True)
930
+
931
+ # We want to maximize the sum of weights on the edges
932
+ p.set_objective(p.sum(r[fe] for fe in frozen_edges))
933
+
934
+ # Each edge being by itself a matching, its weight cannot be more than 1
935
+ for fe in frozen_edges:
936
+ p.add_constraint(r[fe] <= 1)
937
+
938
+ obj = p.solve(log=verbose)
939
+
940
+ while True:
941
+
942
+ # Update the weights of edges for the matching problem
943
+ M.set_objective(M.sum(p.get_values(r[fe]) * b[fe] for fe in frozen_edges))
944
+
945
+ # If the maximum matching has weight at most 1, we are done !
946
+ if M.solve(log=verbose) <= 1:
947
+ break
948
+
949
+ # Otherwise, we add a new constraint
950
+ matching = [fe for fe in frozen_edges if M.get_values(b[fe]) == 1]
951
+ p.add_constraint(p.sum(r[fe] for fe in matching) <= 1)
952
+ if verbose_constraints:
953
+ print("Adding a constraint on matching : {}".format(matching))
954
+
955
+ # And solve again
956
+ obj = p.solve(log=verbose)
957
+
958
+ # Accomplished !
959
+ return obj
960
+
961
+
962
+ def grundy_coloring(g, k, value_only=True, solver=None, verbose=0,
963
+ *, integrality_tolerance=1e-3):
964
+ r"""
965
+ Compute Grundy numbers and Grundy colorings.
966
+
967
+ The method computes the worst-case of a first-fit coloring with less than
968
+ `k` colors.
969
+
970
+ Definition:
971
+
972
+ A first-fit coloring is obtained by sequentially coloring the vertices of a
973
+ graph, assigning them the smallest color not already assigned to one of its
974
+ neighbors. The result is clearly a proper coloring, which usually requires
975
+ much more colors than an optimal vertex coloring of the graph, and heavily
976
+ depends on the ordering of the vertices.
977
+
978
+ The number of colors required by the worst-case application of this
979
+ algorithm on a graph `G` is called the Grundy number, written `\Gamma (G)`.
980
+
981
+ Equivalent formulation:
982
+
983
+ Equivalently, a Grundy coloring is a proper vertex coloring such that any
984
+ vertex colored with `i` has, for every `j < i`, a neighbor colored with
985
+ `j`. This can define a Linear Program, which is used here to compute the
986
+ Grundy number of a graph.
987
+
988
+ .. NOTE::
989
+
990
+ This method computes a grundy coloring using at *MOST* `k` colors. If
991
+ this method returns a value equal to `k`, it cannot be assumed that `k`
992
+ is equal to `\Gamma(G)`. Meanwhile, if it returns any value `k' < k`,
993
+ this is a certificate that the Grundy number of the given graph is `k'`.
994
+
995
+ As `\Gamma(G)\leq \Delta(G)+1`, it can also be assumed that `\Gamma(G) =
996
+ k` if ``grundy_coloring(g, k)`` returns `k` when `k = \Delta(G) +1`.
997
+
998
+ INPUT:
999
+
1000
+ - ``k`` -- integer; maximum number of colors
1001
+
1002
+ - ``value_only`` -- boolean (default: ``True``); when set to ``True``, only
1003
+ the number of colors is returned. Otherwise, the pair ``(nb_colors,
1004
+ coloring)`` is returned, where ``coloring`` is a dictionary associating
1005
+ its color (integer) to each vertex of the graph.
1006
+
1007
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1008
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1009
+ is used. For more information on MILP solvers and which default solver is
1010
+ used, see the method :meth:`solve
1011
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1012
+ :class:`MixedIntegerLinearProgram
1013
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1014
+
1015
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1016
+ to 0 by default, which means quiet.
1017
+
1018
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1019
+ over an inexact base ring; see
1020
+ :meth:`MixedIntegerLinearProgram.get_values`.
1021
+
1022
+ ALGORITHM:
1023
+
1024
+ Integer Linear Program.
1025
+
1026
+ EXAMPLES:
1027
+
1028
+ The Grundy number of a `P_4` is equal to 3::
1029
+
1030
+ sage: from sage.graphs.graph_coloring import grundy_coloring
1031
+ sage: g = graphs.PathGraph(4)
1032
+ sage: grundy_coloring(g, 4) # needs sage.numerical.mip
1033
+ 3
1034
+
1035
+ The Grundy number of the PetersenGraph is equal to 4::
1036
+
1037
+ sage: g = graphs.PetersenGraph()
1038
+ sage: grundy_coloring(g, 5) # needs sage.numerical.mip
1039
+ 4
1040
+
1041
+ It would have been sufficient to set the value of ``k`` to 4 in
1042
+ this case, as `4 = \Delta(G)+1`.
1043
+ """
1044
+ g._scream_if_not_simple(allow_multiple_edges=True)
1045
+
1046
+ p = MixedIntegerLinearProgram(solver=solver)
1047
+
1048
+ # b[v,i] is set to 1 if and only if v is colored with i
1049
+ b = p.new_variable(binary=True)
1050
+
1051
+ # is_used[i] is set to 1 if and only if color[i] is used by some vertex
1052
+ is_used = p.new_variable(binary=True)
1053
+
1054
+ # Each vertex is in exactly one color class
1055
+ for v in g:
1056
+ p.add_constraint(p.sum(b[v, i] for i in range(k)), max=1, min=1)
1057
+
1058
+ # Two adjacent vertices have different colors
1059
+ for u, v in g.edge_iterator(labels=None):
1060
+ for i in range(k):
1061
+ p.add_constraint(b[v, i] + b[u, i], max=1)
1062
+
1063
+ # The following constraints ensure that if v is colored with i, then it has
1064
+ # a neighbor colored with j for every j<i
1065
+
1066
+ for i in range(k):
1067
+ for j in range(i):
1068
+ for v in g:
1069
+ # If b[v,i] == 0, then the following constraint is always
1070
+ # satisfied, as a sum of binary variables is always positive.
1071
+ # If it is equal to 1, then at least one of the other variables
1072
+ # must be set to 1 too.
1073
+
1074
+ p.add_constraint(p.sum(b[u, j] for u in g.neighbor_iterator(v)) - b[v, i], min=0)
1075
+
1076
+ # is_used[i] can be set to 1 only if the color is used
1077
+ for i in range(k):
1078
+ p.add_constraint(p.sum(b[v, i] for v in g) - is_used[i], min=0)
1079
+
1080
+ # Trying to use as many colors as possible
1081
+ p.set_objective(p.sum(is_used[i] for i in range(k)))
1082
+
1083
+ from sage.numerical.mip import MIPSolverException
1084
+ try:
1085
+ p.solve(log=verbose)
1086
+ except MIPSolverException:
1087
+ raise ValueError("this graph cannot be colored with k colors")
1088
+
1089
+ from sage.rings.integer import Integer
1090
+ is_used = p.get_values(is_used, convert=bool, tolerance=integrality_tolerance)
1091
+ obj = Integer(sum(1 for i in range(k) if is_used[i]))
1092
+
1093
+ if value_only:
1094
+ return obj
1095
+
1096
+ # Building the dictionary associating its color to every vertex
1097
+ b = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1098
+ cdef dict coloring = {}
1099
+
1100
+ for v in g:
1101
+ for i in range(k):
1102
+ if b[v, i]:
1103
+ coloring[v] = i
1104
+ break
1105
+
1106
+ return obj, coloring
1107
+
1108
+
1109
+ def b_coloring(g, k, value_only=True, solver=None, verbose=0,
1110
+ *, integrality_tolerance=1e-3):
1111
+ r"""
1112
+ Compute b-chromatic numbers and b-colorings.
1113
+
1114
+ This function computes a b-coloring with at most `k` colors that maximizes
1115
+ the number of colors, if such a coloring exists.
1116
+
1117
+ Definition :
1118
+
1119
+ Given a proper coloring of a graph `G` and a color class `C` such that none
1120
+ of its vertices have neighbors in all the other color classes, one can
1121
+ eliminate color class `C` assigning to each of its elements a missing color
1122
+ in its neighborhood.
1123
+
1124
+ Let a b-vertex be a vertex with neighbors in all other colorings. Then, one
1125
+ can repeat the above procedure until a coloring is obtained where every
1126
+ color class contains a b-vertex, in which case none of the color classes can
1127
+ be eliminated with the same idea. So, one can define a b-coloring as a
1128
+ proper coloring where each color class has a b-vertex.
1129
+
1130
+ In the worst case, after successive applications of the above procedure, one
1131
+ get a proper coloring that uses a number of colors equal to the b-chromatic
1132
+ number of `G` (denoted `\chi_b(G)`): the maximum `k` such that `G` admits a
1133
+ b-coloring with `k` colors.
1134
+
1135
+ A useful upper bound for calculating the b-chromatic number is the
1136
+ following. If `G` admits a b-coloring with `k` colors, then there are `k`
1137
+ vertices of degree at least `k - 1` (the b-vertices of each color
1138
+ class). So, if we set `m(G) = \max \{k | \text{there are } k \text{ vertices
1139
+ of degree at least } k - 1 \}`, we have that `\chi_b(G) \leq m(G)`.
1140
+
1141
+
1142
+ .. NOTE::
1143
+
1144
+ This method computes a b-coloring that uses at *MOST* `k` colors. If this
1145
+ method returns a value equal to `k`, it cannot be assumed that `k` is
1146
+ equal to `\chi_b(G)`. Meanwhile, if it returns any value `k' < k`, this
1147
+ is a certificate that the Grundy number of the given graph is `k'`.
1148
+
1149
+ As `\chi_b(G)\leq m(G)`, it can be assumed that `\chi_b(G) = k` if
1150
+ ``b_coloring(g, k)`` returns `k` when `k = m(G)`.
1151
+
1152
+ INPUT:
1153
+
1154
+ - ``k`` -- integer; maximum number of colors
1155
+
1156
+ - ``value_only`` -- boolean (default: ``True``); when set to ``True``, only
1157
+ the number of colors is returned. Otherwise, the pair ``(nb_colors,
1158
+ coloring)`` is returned, where ``coloring`` is a dictionary associating
1159
+ its color (integer) to each vertex of the graph.
1160
+
1161
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1162
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1163
+ is used. For more information on MILP solvers and which default solver is
1164
+ used, see the method :meth:`solve
1165
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1166
+ :class:`MixedIntegerLinearProgram
1167
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1168
+
1169
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1170
+ to 0 by default, which means quiet.
1171
+
1172
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1173
+ over an inexact base ring; see
1174
+ :meth:`MixedIntegerLinearProgram.get_values`.
1175
+
1176
+ ALGORITHM:
1177
+
1178
+ Integer Linear Program.
1179
+
1180
+ EXAMPLES:
1181
+
1182
+ The b-chromatic number of a `P_5` is equal to 3::
1183
+
1184
+ sage: from sage.graphs.graph_coloring import b_coloring
1185
+ sage: g = graphs.PathGraph(5)
1186
+ sage: b_coloring(g, 5) # needs sage.numerical.mip
1187
+ 3
1188
+
1189
+ The b-chromatic number of the Petersen Graph is equal to 3::
1190
+
1191
+ sage: g = graphs.PetersenGraph()
1192
+ sage: b_coloring(g, 5) # needs sage.numerical.mip
1193
+ 3
1194
+
1195
+ It would have been sufficient to set the value of ``k`` to 4 in this case,
1196
+ as `4 = m(G)`.
1197
+ """
1198
+ g._scream_if_not_simple(allow_multiple_edges=True)
1199
+
1200
+ # Calculate the upper bound m(G)
1201
+ # To do so, it takes the list of degrees in non-increasing order and
1202
+ # computes the largest i, such that the ith degree on the list is at least
1203
+ # i - 1 (note that in the code we need to take in consideration that the
1204
+ # indices of the list starts with 0)
1205
+
1206
+ cdef list deg = g.degree_sequence()
1207
+ cdef int n = g.order()
1208
+ for i in range(n):
1209
+ if deg[i] < i:
1210
+ break
1211
+ if i != n - 1:
1212
+ m = i
1213
+ else:
1214
+ m = n
1215
+
1216
+ # In case the k specified by the user is greater than m(G), make k = m(G)
1217
+ if k > m:
1218
+ k = m
1219
+
1220
+ p = MixedIntegerLinearProgram(solver=solver)
1221
+
1222
+ # color[v,i] is set to 1 if and only if v is colored i
1223
+ color = p.new_variable(binary=True)
1224
+
1225
+ # b[v,i] is set to 1 if and only if v is a b-vertex from color class i
1226
+ b = p.new_variable(binary=True)
1227
+
1228
+ # is_used[i] is set to 1 if and only if color[i] is used by some vertex
1229
+ is_used = p.new_variable(binary=True)
1230
+
1231
+ # Each vertex is in exactly one class
1232
+ for v in g:
1233
+ p.add_constraint(p.sum(color[v, i] for i in range(k)), min=1, max=1)
1234
+
1235
+ # Adjacent vertices have distinct colors
1236
+ for u, v in g.edge_iterator(labels=None):
1237
+ for i in range(k):
1238
+ p.add_constraint(color[u, i] + color[v, i], max=1)
1239
+
1240
+ # The following constraints ensure that if v is a b-vertex of color i
1241
+ # then it has a neighbor colored j for every j != i
1242
+
1243
+ for v in g:
1244
+ for i in range(k):
1245
+ for j in range(k):
1246
+ if j != i:
1247
+ # If v is not a b-vertex of color i, the constraint
1248
+ # is always satisfied, since the only possible
1249
+ # negative term in this case is -is_used[j] which is
1250
+ # cancelled by + 1. If v is a b-vertex of color i
1251
+ # then we MUST have sum(color[w,j] for w in g.neighbors(v))
1252
+ # valued at least 1, which means that v has a neighbour in
1253
+ # color j, as desired.
1254
+ p.add_constraint(p.sum(color[w, j] for w in g.neighbor_iterator(v))
1255
+ - b[v, i] + 1 - is_used[j], min=0)
1256
+
1257
+ # if color i is used, there is a vertex colored i
1258
+ for i in range(k):
1259
+ p.add_constraint(p.sum(color[v, i] for v in g) - is_used[i], min=0)
1260
+
1261
+ # if there is a vertex colored with color i, then i is used
1262
+ for v in g:
1263
+ for i in range(k):
1264
+ p.add_constraint(color[v, i] - is_used[i], max=0)
1265
+
1266
+ # a color class is used if and only if it has one b-vertex
1267
+ for i in range(k):
1268
+ p.add_constraint(p.sum(b[w, i] for w in g) - is_used[i], min=0, max=0)
1269
+
1270
+ # We want to maximize the number of used colors
1271
+ p.set_objective(p.sum(is_used[i] for i in range(k)))
1272
+
1273
+ from sage.numerical.mip import MIPSolverException
1274
+ try:
1275
+ p.solve(log=verbose)
1276
+ except MIPSolverException:
1277
+ raise ValueError("this graph cannot be colored with k colors")
1278
+
1279
+ from sage.rings.integer import Integer
1280
+ is_used = p.get_values(is_used, convert=bool, tolerance=integrality_tolerance)
1281
+ obj = Integer(sum(1 for i in range(k) if is_used[i]))
1282
+
1283
+ if value_only:
1284
+ return obj
1285
+
1286
+ # Building the dictionary associating its color to every vertex
1287
+ c = p.get_values(color, convert=bool, tolerance=integrality_tolerance)
1288
+ cdef dict coloring = {}
1289
+
1290
+ for v in g:
1291
+ for i in range(k):
1292
+ if c[v, i]:
1293
+ coloring[v] = i
1294
+ break
1295
+
1296
+ return obj, coloring
1297
+
1298
+
1299
+ def edge_coloring(g, value_only=False, vizing=False, hex_colors=False, solver=None, verbose=0,
1300
+ *, integrality_tolerance=1e-3):
1301
+ r"""
1302
+ Compute chromatic index and edge colorings.
1303
+
1304
+ INPUT:
1305
+
1306
+ - ``g`` -- a graph
1307
+
1308
+ - ``value_only`` -- boolean (default: ``False``):
1309
+
1310
+ - When set to ``True``, only the chromatic index is returned
1311
+
1312
+ - When set to ``False``, a partition of the edge set into matchings is
1313
+ returned if possible
1314
+
1315
+ - ``vizing`` -- boolean (default: ``False``):
1316
+
1317
+ - When set to ``True``, finds an edge coloring using the algorithm
1318
+ described at [MG1992]_. This always results in a coloring with `\Delta + 1`
1319
+ colors, where `\Delta` is equal to the maximum degree in the graph, even
1320
+ if one of the colors is empty, for the sake of consistency.
1321
+
1322
+ - When set to ``False``, tries to find a `\Delta`-edge-coloring using
1323
+ Mixed Integer Linear Programming (MILP). If impossible, returns a
1324
+ `(\Delta + 1)`-edge-coloring. Please note that determining if the
1325
+ chromatic index of a graph equals `\Delta` is computationally difficult,
1326
+ and could take a long time.
1327
+
1328
+ - ``hex_colors`` -- boolean (default: ``False``); when set to ``True``, the
1329
+ partition returned is a dictionary whose keys are colors and whose values
1330
+ are the color classes (ideal for plotting)
1331
+
1332
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1333
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1334
+ is used. For more information on MILP solvers and which default solver is
1335
+ used, see the method :meth:`solve
1336
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1337
+ :class:`MixedIntegerLinearProgram
1338
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1339
+
1340
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1341
+ to 0 by default, which means quiet.
1342
+
1343
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1344
+ over an inexact base ring; see
1345
+ :meth:`MixedIntegerLinearProgram.get_values`.
1346
+
1347
+ OUTPUT:
1348
+
1349
+ In the following, `\Delta` is equal to the maximum degree in the graph
1350
+ ``g``.
1351
+
1352
+ - If ``vizing=True`` and ``value_only=False``, return a partition of the
1353
+ edge set into `\Delta + 1` matchings.
1354
+
1355
+ - If ``vizing=False`` and ``value_only=True``, return the chromatic index.
1356
+
1357
+ - If ``vizing=False`` and ``value_only=False``, return a partition of the
1358
+ edge set into the minimum number of matchings.
1359
+
1360
+ - If ``vizing=True`` and ``value_only=True``, should return something, but
1361
+ mainly you are just trying to compute the maximum degree of the graph, and
1362
+ this is not the easiest way. By Vizing's theorem, a graph has a chromatic
1363
+ index equal to `\Delta` or to `\Delta + 1`.
1364
+
1365
+ .. NOTE::
1366
+
1367
+ In a few cases, it is possible to find very quickly the chromatic index
1368
+ of a graph, while it remains a tedious job to compute a corresponding
1369
+ coloring. For this reason, ``value_only = True`` can sometimes be much
1370
+ faster, and it is a bad idea to compute the whole coloring if you do not
1371
+ need it !
1372
+
1373
+ .. SEEALSO::
1374
+
1375
+ - :wikipedia:`Edge_coloring` for further details on edge coloring
1376
+ - :meth:`~Graph.chromatic_index`
1377
+ - :meth:`~Graph.fractional_chromatic_index`
1378
+ - :meth:`~Graph.chromatic_number`
1379
+ - :meth:`sage.graphs.graph_coloring.vertex_coloring`
1380
+
1381
+ EXAMPLES:
1382
+
1383
+ The Petersen graph has chromatic index 4::
1384
+
1385
+ sage: # needs sage.numerical.mip
1386
+ sage: from sage.graphs.graph_coloring import edge_coloring
1387
+ sage: g = graphs.PetersenGraph()
1388
+ sage: edge_coloring(g, value_only=True, solver='GLPK')
1389
+ 4
1390
+ sage: color_classes = edge_coloring(g, value_only=False, solver='GLPK')
1391
+ sage: len(color_classes)
1392
+ 4
1393
+ sage: len(set(frozenset(e) for C in color_classes for e in C)) == g.size()
1394
+ True
1395
+ sage: all(g.has_edge(e) for C in color_classes for e in C)
1396
+ True
1397
+ sage: all(len(Graph(C).matching()) == len(C) for C in color_classes) # needs networkx
1398
+ True
1399
+ sage: color_classes = edge_coloring(g, value_only=False, # needs sage.plot
1400
+ ....: hex_colors=True, solver='GLPK')
1401
+ sage: sorted(color_classes.keys()) # needs sage.plot
1402
+ ['#00ffff', '#7f00ff', '#7fff00', '#ff0000']
1403
+
1404
+ Complete graphs are colored using the linear-time round-robin coloring::
1405
+
1406
+ sage: from sage.graphs.graph_coloring import edge_coloring
1407
+ sage: len(edge_coloring(graphs.CompleteGraph(20))) # needs sage.numerical.mip
1408
+ 19
1409
+
1410
+ The chromatic index of a non connected graph is the maximum over its
1411
+ connected components::
1412
+
1413
+ sage: g = graphs.CompleteGraph(4) + graphs.CompleteGraph(10)
1414
+ sage: edge_coloring(g, value_only=True) # needs sage.numerical.mip
1415
+ 9
1416
+
1417
+ TESTS:
1418
+
1419
+ Graph without edge::
1420
+
1421
+ sage: g = Graph(2)
1422
+ sage: edge_coloring(g) # needs sage.numerical.mip
1423
+ []
1424
+ sage: edge_coloring(g, value_only=True) # needs sage.numerical.mip
1425
+ 0
1426
+ sage: edge_coloring(g, hex_colors=True) # needs sage.numerical.mip
1427
+ {}
1428
+ """
1429
+ g._scream_if_not_simple()
1430
+
1431
+ if not g.order() or not g.size():
1432
+ if value_only:
1433
+ return 0
1434
+ return dict() if hex_colors else list()
1435
+
1436
+ # The chromatic index of g is the maximum value over its connected
1437
+ # components, and the edge coloring is the union of the edge
1438
+ # coloring of its connected components
1439
+ cdef list L = [g] if g.is_connected() else g.connected_components_subgraphs()
1440
+ cdef int chi = 0
1441
+ cdef list classes = [], vertices
1442
+
1443
+ if vizing:
1444
+ classes = _vizing_edge_coloring(g)
1445
+ if len(classes) == max(g.degree()):
1446
+ # guaranteeing that vizing=True always returns Delta+1 colors
1447
+ # for backward compatibility
1448
+ classes.append([])
1449
+ else:
1450
+ def extend_color_classes(classes, coloring):
1451
+ # create missing color classes, if any
1452
+ for _ in range(len(classes), len(coloring)):
1453
+ classes.append([])
1454
+ # add edges to classes
1455
+ for i, edges in enumerate(coloring):
1456
+ classes[i].extend(edges)
1457
+
1458
+ for h in L:
1459
+
1460
+ if not h.size():
1461
+ continue
1462
+
1463
+ # We get the vertex of maximum degree and its degree
1464
+ Delta, X = max([(d, v) for v, d in h.degree_iterator(labels=True)], key=lambda x: x[0])
1465
+
1466
+ if Delta + 1 <= chi:
1467
+ c = _vizing_edge_coloring(h)
1468
+ extend_color_classes(classes, c)
1469
+ continue
1470
+
1471
+ if value_only:
1472
+ if Delta + 1 <= chi:
1473
+ continue
1474
+ if h.is_overfull():
1475
+ chi = max(chi, Delta + 1)
1476
+ continue
1477
+
1478
+ if h.is_clique():
1479
+ if value_only:
1480
+ chi = max(chi, h.order() if h.order() % 2 else (h.order() - 1))
1481
+ continue
1482
+ vertices = list(h)
1483
+ r = round_robin(h.order())
1484
+ # create missing color classes, if any
1485
+ for i in range(len(classes), max(r.edge_labels()) + 1):
1486
+ classes.append([])
1487
+ # add edges to classes
1488
+ r.relabel(perm=vertices, inplace=True)
1489
+ for u, v, c in r.edge_iterator():
1490
+ classes[c].append((u, v))
1491
+ continue
1492
+
1493
+ p = MixedIntegerLinearProgram(maximization=True, solver=solver)
1494
+ color = p.new_variable(binary=True)
1495
+ # A vertex cannot have two incident edges with the same color.
1496
+ for v in h:
1497
+ for i in range(Delta):
1498
+ p.add_constraint(p.sum(color[frozenset((u, v)), i] for u in h.neighbor_iterator(v)) <= 1)
1499
+ # An edge must have a color
1500
+ for u, v in h.edge_iterator(labels=False):
1501
+ p.add_constraint(p.sum(color[frozenset((u, v)), i] for i in range(Delta)) == 1)
1502
+ # We color the edges of the vertex of maximum degree
1503
+ for i, v in enumerate(h.neighbor_iterator(X)):
1504
+ p.add_constraint(color[frozenset((v, X)), i] == 1)
1505
+
1506
+ from sage.numerical.mip import MIPSolverException
1507
+ try:
1508
+ p.solve(objective_only=value_only, log=verbose)
1509
+ except MIPSolverException:
1510
+ # The coloring fails with Delta colors
1511
+ chi = max(chi, Delta + 1)
1512
+ if not value_only:
1513
+ c = _vizing_edge_coloring(h)
1514
+ extend_color_classes(classes, c)
1515
+ continue
1516
+
1517
+ chi = max(chi, Delta)
1518
+ if not value_only:
1519
+ # create missing color classes, if any
1520
+ for i in range(len(classes), Delta):
1521
+ classes.append([])
1522
+ # add edges to color classes
1523
+ color = p.get_values(color, convert=bool, tolerance=integrality_tolerance)
1524
+ for e in h.edge_iterator(labels=False):
1525
+ fe = frozenset(e)
1526
+ for i in range(Delta):
1527
+ if color[fe, i]:
1528
+ classes[i].append(e)
1529
+ break
1530
+
1531
+ if value_only:
1532
+ return chi
1533
+
1534
+ # if needed, builds a dictionary from the color classes adding colors
1535
+ return format_coloring(classes, value_only=not hex_colors, hex_colors=hex_colors)
1536
+
1537
+
1538
+ def _vizing_edge_coloring(g):
1539
+ r"""
1540
+ Compute an edge coloring with at most `\Delta + 1` colors.
1541
+
1542
+ INPUT:
1543
+
1544
+ - ``g`` -- a graph
1545
+
1546
+ OUTPUT: a partition of the edge set into at most `\Delta + 1` matchings
1547
+
1548
+ .. SEEALSO::
1549
+
1550
+ - :wikipedia:`Edge_coloring` for further details on edge coloring
1551
+ - :wikipedia:`Vizing's_theorem` for further details on Vizing's theorem
1552
+
1553
+ ALGORITHM:
1554
+
1555
+ This function's implementation is based on the algorithm described at [MG1992]_
1556
+
1557
+ EXAMPLES:
1558
+
1559
+ Coloring the edges of the Petersen Graph::
1560
+
1561
+ sage: from sage.graphs.graph_coloring import _vizing_edge_coloring
1562
+ sage: g = graphs.PetersenGraph()
1563
+ sage: color_classes = _vizing_edge_coloring(g)
1564
+ sage: len(color_classes) == max(g.degree()) + 1
1565
+ True
1566
+ sage: len(set(frozenset(e) for C in color_classes for e in C)) == g.size()
1567
+ True
1568
+ sage: all(g.has_edge(e) for C in color_classes for e in C)
1569
+ True
1570
+ sage: all(len(Graph(C).matching()) == len(C) for C in color_classes) # needs networkx
1571
+ True
1572
+
1573
+ Coloring the edges of the Star Graph::
1574
+
1575
+ sage: from sage.graphs.graph_coloring import _vizing_edge_coloring
1576
+ sage: g = graphs.StarGraph(5)
1577
+ sage: len(_vizing_edge_coloring(g))
1578
+ 5
1579
+
1580
+ TESTS:
1581
+
1582
+ Graph without edge::
1583
+
1584
+ sage: g = Graph(2)
1585
+ sage: _vizing_edge_coloring(g)
1586
+ []
1587
+
1588
+ Random graphs::
1589
+
1590
+ sage: from sage.graphs.generators.random import RandomGNP
1591
+ sage: g = RandomGNP(randint(1, 20), random())
1592
+ sage: colors = _vizing_edge_coloring(g)
1593
+ sage: Delta = max(g.degree())
1594
+ sage: len(colors) in [Delta, Delta + 1]
1595
+ True
1596
+ sage: len(set(frozenset(e) for C in colors for e in C)) == g.size()
1597
+ True
1598
+ sage: all(g.has_edge(e) for C in colors for e in C)
1599
+ True
1600
+ sage: all(len(Graph(C).matching()) == len(C) for C in colors) # needs networkx
1601
+ True
1602
+ """
1603
+ # This implementation was discussed in github issue #34809
1604
+
1605
+ # dictionary mapping edges to colors
1606
+ e_colors = {frozenset(e): None for e in g.edge_iterator(labels=False, sort_vertices=False)}
1607
+
1608
+ # finds every color adjacent to vertex v
1609
+ def colors_of(v):
1610
+ colors = {e_colors[frozenset((u, v))] for u in g.neighbor_iterator(v)}
1611
+ colors.discard(None)
1612
+ return colors
1613
+
1614
+ # constructs a maximal fan <f..l> of X where X is edge[0] and f is edge[1]
1615
+ def maximal_fan(edge):
1616
+ fan_center, rear = edge
1617
+ cdef set rear_colors = colors_of(rear)
1618
+ cdef list neighbors = [n for n in g.neighbor_iterator(fan_center)
1619
+ if e_colors[frozenset((n, fan_center))] is not None]
1620
+ cdef list fan = [rear]
1621
+ cdef bint can_extend_fan = True
1622
+ while can_extend_fan:
1623
+ can_extend_fan = False
1624
+ new_neighbors = []
1625
+ for n in neighbors:
1626
+ if e_colors[frozenset((fan_center, n))] not in rear_colors:
1627
+ fan.append(n)
1628
+ rear = n
1629
+ rear_colors = colors_of(rear)
1630
+ can_extend_fan = True
1631
+ else:
1632
+ new_neighbors.append(n)
1633
+ neighbors = new_neighbors
1634
+ return fan
1635
+
1636
+ # gives each edge Xu in the fan <f..w> the color of Xu+ and uncolors Xw
1637
+ def rotate_fan(fan_center, fan):
1638
+ for i in range(1, len(fan)):
1639
+ e_colors[frozenset((fan_center, fan[i - 1]))] = e_colors[frozenset((fan_center, fan[i]))]
1640
+ e_colors[frozenset((fan_center, fan[-1]))] = None
1641
+
1642
+ # computes the maximal ab-path starting at v
1643
+ def find_path(v, a, b):
1644
+ cdef list path = [v]
1645
+ cdef bint stop = False
1646
+ while not stop:
1647
+ stop = True
1648
+ for u in g.neighbor_iterator(v):
1649
+ if e_colors[frozenset((u, v))] == a:
1650
+ path.append(u)
1651
+ # exchange the role of colors a and b and go to next vertex
1652
+ a, b = b, a
1653
+ v = u
1654
+ stop = False
1655
+ break
1656
+ return path
1657
+
1658
+ # exchanges the color of every edge on the ab-path starting at v
1659
+ def invert_path(v, a, b):
1660
+ cdef list path = find_path(v, a, b)
1661
+ for e in zip(path[:-1], path[1:]):
1662
+ f = frozenset(e)
1663
+ e_colors[f] = a if e_colors[f] == b else b
1664
+
1665
+ # returns the ´smallest´ color free at v
1666
+ def find_free_color(v):
1667
+ colors = colors_of(v)
1668
+ for c in range(g.degree(v) + 1):
1669
+ if c not in colors:
1670
+ return c
1671
+
1672
+ for e in g.edge_iterator(labels=False, sort_vertices=False):
1673
+ fan = maximal_fan(e)
1674
+ fan_center = e[0]
1675
+ rear = fan[-1]
1676
+ c = find_free_color(fan_center)
1677
+ d = find_free_color(rear)
1678
+ invert_path(fan_center, d, c)
1679
+ for i, v in enumerate(fan):
1680
+ if d not in colors_of(v):
1681
+ fan = fan[:i + 1]
1682
+ break
1683
+ rotate_fan(fan_center, fan)
1684
+ e_colors[frozenset((fan_center, fan[-1]))] = d
1685
+
1686
+ matchings = {}
1687
+ for edge, c in e_colors.items():
1688
+ matchings[c] = matchings.get(c, []) + [tuple(edge)]
1689
+ classes = list(matchings.values())
1690
+
1691
+ return classes
1692
+
1693
+
1694
+ def round_robin(n):
1695
+ r"""
1696
+ Compute a round-robin coloring of the complete graph on `n` vertices.
1697
+
1698
+ A round-robin coloring of the complete graph `G` on `2n` vertices
1699
+ (`V = [0, \dots, 2n - 1]`) is a proper coloring of its edges such that
1700
+ the edges with color `i` are all the `(i + j, i - j)` plus the
1701
+ edge `(2n - 1, i)`.
1702
+
1703
+ If `n` is odd, one obtain a round-robin coloring of the complete graph
1704
+ through the round-robin coloring of the graph with `n + 1` vertices.
1705
+
1706
+ INPUT:
1707
+
1708
+ - ``n`` -- the number of vertices in the complete graph
1709
+
1710
+ OUTPUT:
1711
+
1712
+ - A :meth:`~sage.graphs.graph_generators.GraphGenerators.CompleteGraph` with
1713
+ labelled edges such that the label of each edge is its color.
1714
+
1715
+ EXAMPLES::
1716
+
1717
+ sage: from sage.graphs.graph_coloring import round_robin
1718
+ sage: round_robin(3).edges(sort=True)
1719
+ [(0, 1, 2), (0, 2, 1), (1, 2, 0)]
1720
+
1721
+ ::
1722
+
1723
+ sage: round_robin(4).edges(sort=True)
1724
+ [(0, 1, 2), (0, 2, 1), (0, 3, 0), (1, 2, 0), (1, 3, 1), (2, 3, 2)]
1725
+
1726
+
1727
+ For higher orders, the coloring is still proper and uses the expected
1728
+ number of colors::
1729
+
1730
+ sage: g = round_robin(9)
1731
+ sage: sum(Set(e[2] for e in g.edges_incident(v)).cardinality() for v in g) == 2 * g.size()
1732
+ True
1733
+ sage: Set(e[2] for e in g.edge_iterator()).cardinality()
1734
+ 9
1735
+
1736
+ ::
1737
+
1738
+ sage: g = round_robin(10)
1739
+ sage: sum(Set(e[2] for e in g.edges_incident(v)).cardinality() for v in g) == 2 * g.size()
1740
+ True
1741
+ sage: Set(e[2] for e in g.edge_iterator()).cardinality()
1742
+ 9
1743
+ """
1744
+ if n <= 1:
1745
+ raise ValueError("there must be at least two vertices in the graph")
1746
+
1747
+ def my_mod(x, y):
1748
+ return x - y * (x // y)
1749
+ if not n % 2:
1750
+ from sage.graphs.generators.basic import CompleteGraph
1751
+ g = CompleteGraph(n)
1752
+ for i in range(n - 1):
1753
+ g.set_edge_label(n - 1, i, i)
1754
+ for j in range(1, (n - 1) // 2 + 1):
1755
+ g.set_edge_label(my_mod(i - j, n - 1), my_mod(i + j, n - 1), i)
1756
+ else:
1757
+ g = round_robin(n + 1)
1758
+ g.delete_vertex(n)
1759
+ return g
1760
+
1761
+
1762
+ def linear_arboricity(g, plus_one=None, hex_colors=False, value_only=False,
1763
+ solver=None, verbose=0, *, integrality_tolerance=1e-3):
1764
+ r"""
1765
+ Compute the linear arboricity of the given graph.
1766
+
1767
+ The linear arboricity of a graph `G` is the least number `la(G)` such that
1768
+ the edges of `G` can be partitioned into linear forests (i.e. into forests
1769
+ of paths).
1770
+
1771
+ Obviously, `la(G)\geq \left\lceil \frac{\Delta(G)}{2} \right\rceil`.
1772
+
1773
+ It is conjectured in [Aki1980]_ that `la(G)\leq \left\lceil
1774
+ \frac{\Delta(G)+1}{2} \right\rceil`.
1775
+
1776
+ INPUT:
1777
+
1778
+ - ``plus_one`` -- integer (default: ``None``); whether to use `\left\lceil
1779
+ \frac{\Delta(G)}{2} \right\rceil` or `\left\lceil \frac{\Delta(G)+1}{2}
1780
+ \right\rceil` colors.
1781
+
1782
+ - If ``0``, computes a decomposition of `G` into `\left\lceil
1783
+ \frac{\Delta(G)}{2} \right\rceil` forests of paths
1784
+
1785
+ - If ``1``, computes a decomposition of `G` into `\left\lceil
1786
+ \frac{\Delta(G)+1}{2} \right\rceil` colors, which is the conjectured
1787
+ general bound.
1788
+
1789
+ - If ``plus_one = None`` (default), computes a decomposition using the
1790
+ least possible number of colors.
1791
+
1792
+ - ``hex_colors`` -- boolean (default: ``False``):
1793
+
1794
+ - If ``hex_colors = True``, the function returns a dictionary associating
1795
+ to each color a list of edges (meant as an argument to the
1796
+ ``edge_colors`` keyword of the ``plot`` method).
1797
+
1798
+ - If ``hex_colors = False`` (default value), returns a list of graphs
1799
+ corresponding to each color class.
1800
+
1801
+ - ``value_only`` -- boolean (default: ``False``):
1802
+
1803
+ - If ``value_only = True``, only returns the linear arboricity as an
1804
+ integer value.
1805
+
1806
+ - If ``value_only = False``, returns the color classes according to the
1807
+ value of ``hex_colors``
1808
+
1809
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1810
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1811
+ is used. For more information on MILP solvers and which default solver is
1812
+ used, see the method :meth:`solve
1813
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1814
+ :class:`MixedIntegerLinearProgram
1815
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1816
+
1817
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1818
+ to 0 by default, which means quiet.
1819
+
1820
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1821
+ over an inexact base ring; see
1822
+ :meth:`MixedIntegerLinearProgram.get_values`.
1823
+
1824
+ ALGORITHM:
1825
+
1826
+ Linear Programming
1827
+
1828
+ COMPLEXITY:
1829
+
1830
+ NP-Hard
1831
+
1832
+ EXAMPLES:
1833
+
1834
+ Obviously, a square grid has a linear arboricity of 2, as the set of
1835
+ horizontal lines and the set of vertical lines are an admissible partition::
1836
+
1837
+ sage: from sage.graphs.graph_coloring import linear_arboricity
1838
+ sage: g = graphs.Grid2dGraph(4, 4) # needs sage.numerical.mip
1839
+ sage: g1,g2 = linear_arboricity(g) # needs sage.numerical.mip
1840
+
1841
+ Each graph is of course a forest::
1842
+
1843
+ sage: g1.is_forest() and g2.is_forest() # needs sage.numerical.mip
1844
+ True
1845
+
1846
+ Of maximum degree 2::
1847
+
1848
+ sage: max(g1.degree()) <= 2 and max(g2.degree()) <= 2 # needs sage.numerical.mip
1849
+ True
1850
+
1851
+ Which constitutes a partition of the whole edge set::
1852
+
1853
+ sage: all((g1.has_edge(e) or g2.has_edge(e)) # needs sage.numerical.mip
1854
+ ....: for e in g.edge_iterator(labels=None))
1855
+ True
1856
+
1857
+ TESTS:
1858
+
1859
+ Asking for the value of the linear arboricity only (:issue:`24991`)::
1860
+
1861
+ sage: from sage.graphs.graph_coloring import linear_arboricity
1862
+ sage: sorted(linear_arboricity(G, value_only=True) for G in graphs(4)) # needs sage.numerical.mip
1863
+ [0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2]
1864
+
1865
+ Test parameter ``hex_color`` (:issue:`26228`)::
1866
+
1867
+ sage: from sage.graphs.graph_coloring import linear_arboricity
1868
+ sage: g = graphs.Grid2dGraph(4, 4)
1869
+ sage: d = linear_arboricity(g, hex_colors=True) # needs sage.numerical.mip sage.plot
1870
+ sage: sorted(d) # needs sage.numerical.mip sage.plot
1871
+ ['#00ffff', '#ff0000']
1872
+ """
1873
+ g._scream_if_not_simple()
1874
+ from sage.rings.integer import Integer
1875
+
1876
+ if plus_one is None:
1877
+ try:
1878
+ return linear_arboricity(g,
1879
+ plus_one=0,
1880
+ value_only=value_only,
1881
+ hex_colors=hex_colors,
1882
+ solver=solver,
1883
+ verbose=verbose,
1884
+ integrality_tolerance=integrality_tolerance)
1885
+ except ValueError:
1886
+ return linear_arboricity(g,
1887
+ plus_one=1,
1888
+ value_only=value_only,
1889
+ hex_colors=hex_colors,
1890
+ solver=solver,
1891
+ verbose=verbose,
1892
+ integrality_tolerance=integrality_tolerance)
1893
+ elif plus_one == 1:
1894
+ k = (Integer(1 + max(g.degree())) / 2).ceil()
1895
+ elif not plus_one:
1896
+ k = (Integer(max(g.degree())) / 2).ceil()
1897
+ else:
1898
+ raise ValueError("plus_one must be equal to 0,1, or to None!")
1899
+
1900
+ p = MixedIntegerLinearProgram(solver=solver)
1901
+
1902
+ # c is a boolean value such that c[i,(u,v)] = 1 if and only if (u,v) is
1903
+ # colored with i
1904
+ c = p.new_variable(binary=True)
1905
+
1906
+ # relaxed value
1907
+ r = p.new_variable(nonnegative=True)
1908
+
1909
+ MAD = 1 - 1 / (Integer(g.order()) * 2)
1910
+
1911
+ # Partition of the edges
1912
+ for u, v in g.edge_iterator(labels=None):
1913
+ p.add_constraint(p.sum(c[i, frozenset((u, v))] for i in range(k)), max=1, min=1)
1914
+
1915
+ for i in range(k):
1916
+
1917
+ # r greater than c
1918
+ for u, v in g.edge_iterator(labels=None):
1919
+ p.add_constraint(r[i, (u, v)] + r[i, (v, u)] - c[i, frozenset((u, v))], max=0, min=0)
1920
+
1921
+ # Maximum degree 2
1922
+ for u in g:
1923
+ p.add_constraint(p.sum(c[i, frozenset((u, v))] for v in g.neighbor_iterator(u)), max=2)
1924
+
1925
+ # no cycles
1926
+ p.add_constraint(p.sum(r[i, (u, v)] for v in g.neighbor_iterator(u)), max=MAD)
1927
+
1928
+ from sage.numerical.mip import MIPSolverException
1929
+ try:
1930
+ p.solve(objective_only=value_only, log=verbose)
1931
+ if value_only:
1932
+ return k
1933
+
1934
+ except MIPSolverException:
1935
+ if plus_one:
1936
+ raise RuntimeError("It looks like you have found a counterexample "
1937
+ "to a very old conjecture. Please do not loose "
1938
+ "it ! Please publish it, and send a post to "
1939
+ "sage-devel to warn us. We implore you!")
1940
+ else:
1941
+ raise ValueError("this graph cannot be colored with the given number of colors")
1942
+
1943
+ c = p.get_values(c, convert=bool, tolerance=integrality_tolerance)
1944
+
1945
+ cdef list answer
1946
+
1947
+ if hex_colors:
1948
+ answer = [[] for i in range(k)]
1949
+
1950
+ def add(uv, i):
1951
+ return answer[i].append(uv)
1952
+ else:
1953
+ gg = copy(g)
1954
+ gg.delete_edges(g.edge_iterator())
1955
+ answer = [copy(gg) for i in range(k)]
1956
+
1957
+ def add(uv, i):
1958
+ return answer[i].add_edge(uv)
1959
+
1960
+ for i in range(k):
1961
+ for u, v in g.edge_iterator(labels=None):
1962
+ if c[i, frozenset((u, v))]:
1963
+ add((u, v), i)
1964
+
1965
+ return format_coloring(answer, value_only=not hex_colors, hex_colors=hex_colors)
1966
+
1967
+
1968
+ def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0,
1969
+ solver=None, verbose=0, *, integrality_tolerance=1e-3):
1970
+ r"""
1971
+ Compute an acyclic edge coloring of the current graph.
1972
+
1973
+ An edge coloring of a graph is a assignment of colors to the edges of a
1974
+ graph such that :
1975
+
1976
+ - the coloring is proper (no adjacent edges share a color)
1977
+ - For any two colors `i,j`, the union of the edges colored with `i` or `j`
1978
+ is a forest.
1979
+
1980
+ The least number of colors such that such a coloring exists for a graph `G`
1981
+ is written `\chi'_a(G)`, also called the acyclic chromatic index of `G`.
1982
+
1983
+ It is conjectured that this parameter cannot be too different from the
1984
+ obvious lower bound `\Delta(G) \leq \chi'_a(G)`, `\Delta(G)` being the
1985
+ maximum degree of `G`, which is given by the first of the two constraints.
1986
+ Indeed, it is conjectured that `\Delta(G)\leq \chi'_a(G)\leq \Delta(G) + 2`.
1987
+
1988
+ INPUT:
1989
+
1990
+ - ``hex_colors`` -- boolean (default: ``False``):
1991
+
1992
+ - If ``hex_colors = True``, the function returns a dictionary associating
1993
+ to each color a list of edges (meant as an argument to the
1994
+ ``edge_colors`` keyword of the ``plot`` method).
1995
+
1996
+ - If ``hex_colors = False`` (default value), returns a list of graphs
1997
+ corresponding to each color class.
1998
+
1999
+ - ``value_only`` -- boolean (default: ``False``):
2000
+
2001
+ - If ``value_only = True``, only returns the acyclic chromatic index as an
2002
+ integer value
2003
+
2004
+ - If ``value_only = False``, returns the color classes according to the
2005
+ value of ``hex_colors``
2006
+
2007
+ - ``k`` -- integer; the number of colors to use
2008
+
2009
+ - If ``k > 0``, computes an acyclic edge coloring using `k` colors.
2010
+
2011
+ - If ``k = 0`` (default), computes a coloring of `G` into `\Delta(G) + 2`
2012
+ colors, which is the conjectured general bound.
2013
+
2014
+ - If ``k = None``, computes a decomposition using the least possible
2015
+ number of colors.
2016
+
2017
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
2018
+ Programming (MILP) solver to be used. If set to ``None``, the default one
2019
+ is used. For more information on MILP solvers and which default solver is
2020
+ used, see the method :meth:`solve
2021
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
2022
+ :class:`MixedIntegerLinearProgram
2023
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
2024
+
2025
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
2026
+ to 0 by default, which means quiet.
2027
+
2028
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
2029
+ over an inexact base ring; see
2030
+ :meth:`MixedIntegerLinearProgram.get_values`.
2031
+
2032
+ ALGORITHM:
2033
+
2034
+ Linear Programming
2035
+
2036
+ EXAMPLES:
2037
+
2038
+ The complete graph on 8 vertices cannot be acyclically edge-colored with
2039
+ less `\Delta + 1` colors, but it can be colored with `\Delta + 2 = 9`::
2040
+
2041
+ sage: from sage.graphs.graph_coloring import acyclic_edge_coloring
2042
+ sage: g = graphs.CompleteGraph(8)
2043
+ sage: colors = acyclic_edge_coloring(g) # needs sage.numerical.mip
2044
+
2045
+ Each color class is of course a matching ::
2046
+
2047
+ sage: all(max(gg.degree()) <= 1 for gg in colors) # needs sage.numerical.mip
2048
+ True
2049
+
2050
+ These matchings being a partition of the edge set::
2051
+
2052
+ sage: all(any(gg.has_edge(e) for gg in colors) # needs sage.numerical.mip
2053
+ ....: for e in g.edge_iterator(labels=False))
2054
+ True
2055
+
2056
+ Besides, the union of any two of them is a forest ::
2057
+
2058
+ sage: all(g1.union(g2).is_forest() for g1 in colors for g2 in colors) # needs sage.numerical.mip
2059
+ True
2060
+
2061
+ If one wants to acyclically color a cycle on `4` vertices, at least 3 colors
2062
+ will be necessary. The function raises an exception when asked to color it
2063
+ with only 2::
2064
+
2065
+ sage: g = graphs.CycleGraph(4)
2066
+ sage: acyclic_edge_coloring(g, k=2) # needs sage.numerical.mip
2067
+ Traceback (most recent call last):
2068
+ ...
2069
+ ValueError: this graph cannot be colored with the given number of colors
2070
+
2071
+ The optimal coloring give us `3` classes::
2072
+
2073
+ sage: colors = acyclic_edge_coloring(g, k=None) # needs sage.numerical.mip
2074
+ sage: len(colors) # needs sage.numerical.mip
2075
+ 3
2076
+
2077
+ TESTS:
2078
+
2079
+ Issue :issue:`24991` is fixed::
2080
+
2081
+ sage: from sage.graphs.graph_coloring import acyclic_edge_coloring
2082
+ sage: sorted(acyclic_edge_coloring(G, value_only=True) for G in graphs(4)) # needs sage.numerical.mip
2083
+ [2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]
2084
+
2085
+ Test parameter ``hex_color`` (:issue:`26228`)::
2086
+
2087
+ sage: from sage.graphs.graph_coloring import acyclic_edge_coloring
2088
+ sage: g = graphs.CompleteGraph(4)
2089
+ sage: d = acyclic_edge_coloring(g, hex_colors=True) # needs sage.numerical.mip sage.plot
2090
+ sage: sorted(d) # needs sage.numerical.mip sage.plot
2091
+ ['#0066ff', '#00ff66', '#cbff00', '#cc00ff', '#ff0000']
2092
+
2093
+ The acyclic chromatic index of a graph without edge is 0 (:issue:`27079`)::
2094
+
2095
+ sage: from sage.graphs.graph_coloring import acyclic_edge_coloring
2096
+ sage: g = Graph(3)
2097
+ sage: acyclic_edge_coloring(g, k=None, value_only=True) # needs sage.numerical.mip
2098
+ 0
2099
+ sage: acyclic_edge_coloring(g, k=None, hex_colors=True) # needs sage.numerical.mip sage.plot
2100
+ {}
2101
+ sage: acyclic_edge_coloring(g, k=None, hex_colors=False) # needs sage.numerical.mip sage.plot
2102
+ []
2103
+
2104
+ Empty graph (:issue:`27079`)::
2105
+
2106
+ sage: from sage.graphs.graph_coloring import acyclic_edge_coloring
2107
+ sage: acyclic_edge_coloring(Graph(), k=None, value_only=True) # needs sage.numerical.mip
2108
+ 0
2109
+ """
2110
+ g._scream_if_not_simple(allow_multiple_edges=True)
2111
+
2112
+ from sage.rings.integer import Integer
2113
+ from sage.combinat.subset import Subsets
2114
+
2115
+ if not g.order() or not g.size():
2116
+ if k == 0:
2117
+ k = 2
2118
+ if value_only:
2119
+ return 0 if k is None else k
2120
+ else:
2121
+ if k is None:
2122
+ return {} if hex_colors else []
2123
+ if hex_colors:
2124
+ return format_coloring([[] for _ in range(k)], hex_colors=True)
2125
+ else:
2126
+ return [copy(g) for _ in range(k)]
2127
+
2128
+ if k is None:
2129
+ k = max(g.degree())
2130
+
2131
+ while True:
2132
+ try:
2133
+ return acyclic_edge_coloring(g,
2134
+ value_only=value_only,
2135
+ hex_colors=hex_colors,
2136
+ k=k,
2137
+ solver=solver,
2138
+ verbose=verbose,
2139
+ integrality_tolerance=integrality_tolerance)
2140
+ except ValueError:
2141
+ k += 1
2142
+
2143
+ raise RuntimeError("this should not happen, please report a bug!")
2144
+
2145
+ elif not k:
2146
+ k = max(g.degree()) + 2
2147
+
2148
+ p = MixedIntegerLinearProgram(solver=solver)
2149
+
2150
+ # c is a binary variable such that c[i,(u,v)] = 1 if and only if (u,v) is
2151
+ # colored with i
2152
+ c = p.new_variable(binary=True)
2153
+
2154
+ # relaxed value
2155
+ r = p.new_variable(nonnegative=True)
2156
+
2157
+ def E(x, y):
2158
+ return frozenset((x, y))
2159
+
2160
+ MAD = 1 - 1/(Integer(g.order()) * 2)
2161
+
2162
+ # Partition of the edges: each edge is assigned a unique color
2163
+ for u, v in g.edge_iterator(labels=None):
2164
+ p.add_constraint(p.sum(c[i, E(u, v)] for i in range(k)), max=1, min=1)
2165
+
2166
+ for i in range(k):
2167
+
2168
+ # Maximum degree 1
2169
+ for u in g:
2170
+ p.add_constraint(p.sum(c[i, E(u, v)] for v in g.neighbor_iterator(u)), max=1)
2171
+
2172
+ for i, j in Subsets(range(k), 2):
2173
+ # r is greater than c
2174
+ for u in g:
2175
+ p.add_constraint(p.sum(r[(i, j), (u, v)] for v in g.neighbor_iterator(u)), max=MAD)
2176
+
2177
+ # r greater than c
2178
+ for u, v in g.edge_iterator(labels=None):
2179
+ p.add_constraint(r[(i, j), (u, v)] + r[(i, j), (v, u)] - c[i, E(u, v)] - c[j, E(u, v)], max=0, min=0)
2180
+
2181
+ p.set_objective(None)
2182
+
2183
+ from sage.numerical.mip import MIPSolverException
2184
+ try:
2185
+ p.solve(objective_only=value_only, log=verbose)
2186
+ if value_only:
2187
+ return k
2188
+
2189
+ except MIPSolverException:
2190
+ if k == max(g.degree()) + 2:
2191
+ raise RuntimeError("It looks like you have found a counterexample to "
2192
+ "a very old conjecture. Please do not loose it ! "
2193
+ "Please publish it, and send a post to sage-devel "
2194
+ "to warn us. We implore you!")
2195
+ else:
2196
+ raise ValueError("this graph cannot be colored with the given number of colors")
2197
+
2198
+ c = p.get_values(c, convert=bool, tolerance=integrality_tolerance)
2199
+
2200
+ if hex_colors:
2201
+ answer = [[] for i in range(k)]
2202
+
2203
+ def add(uv, i):
2204
+ return answer[i].append(uv)
2205
+ else:
2206
+ gg = copy(g)
2207
+ gg.delete_edges(g.edge_iterator())
2208
+ answer = [copy(gg) for i in range(k)]
2209
+
2210
+ def add(uv, i):
2211
+ return answer[i].add_edge(uv)
2212
+
2213
+ for i in range(k):
2214
+ for u, v in g.edge_iterator(labels=None):
2215
+ if c[i, E(u, v)]:
2216
+ add((u, v), i)
2217
+
2218
+ return format_coloring(answer, value_only=not hex_colors, hex_colors=hex_colors)
2219
+
2220
+
2221
+ cdef class Test:
2222
+ r"""
2223
+ This class performs randomized testing for :func:`all_graph_colorings`.
2224
+
2225
+ Since everything else in this file is derived from :func:`all_graph_colorings`, this
2226
+ is a pretty good randomized tester for the entire file. Note that for a
2227
+ graph `G`, ``G.chromatic_polynomial()`` uses an entirely different
2228
+ algorithm, so we provide a good, independent test.
2229
+ """
2230
+
2231
+ def random(self, tests=1000):
2232
+ r"""
2233
+ Call ``self.random_all_graph_colorings()``.
2234
+
2235
+ In the future, if other methods are added, it should call them, too.
2236
+
2237
+ TESTS::
2238
+
2239
+ sage: from sage.graphs.graph_coloring import Test
2240
+ sage: Test().random(1) # needs cliquer sage.libs.flint
2241
+ """
2242
+ self.random_all_graph_colorings(tests)
2243
+
2244
+ def random_all_graph_colorings(self, tests=2):
2245
+ r"""
2246
+ Verify the results of ``all_graph_colorings()`` in three ways:
2247
+
2248
+ #. all colorings are unique
2249
+
2250
+ #. number of m-colorings is `P(m)` (where `P` is the chromatic
2251
+ polynomial of the graph being tested)
2252
+
2253
+ #. colorings are valid -- that is, that no two vertices of the same
2254
+ color share an edge.
2255
+
2256
+ TESTS::
2257
+
2258
+ sage: from sage.graphs.graph_coloring import Test
2259
+ sage: Test().random_all_graph_colorings(1) # needs cliquer sage.libs.flint
2260
+ """
2261
+ from sage.graphs.generators.random import RandomGNP
2262
+ cdef set S
2263
+ cdef list parts
2264
+ for _ in range(tests):
2265
+ G = RandomGNP(10, .5)
2266
+ Q = G.chromatic_polynomial()
2267
+ chi = G.chromatic_number()
2268
+
2269
+ S = set()
2270
+
2271
+ for C in all_graph_colorings(G, chi):
2272
+ parts = [C[k] for k in C]
2273
+ for P in parts:
2274
+ lenP = len(P)
2275
+ for i in range(lenP):
2276
+ for j in range(i + 1, lenP):
2277
+ if G.has_edge(P[i], P[j]):
2278
+ raise RuntimeError("coloring failed")
2279
+
2280
+ # make the dict into a frozenset for quick uniqueness checking
2281
+ S.add(frozenset((k, frozenset(C[k])) for k in C))
2282
+
2283
+ if len(S) != Q(chi):
2284
+ raise RuntimeError("incorrect number of unique colorings")