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.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- 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")
|