passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1363 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Domination
4
+
5
+ This module implements methods related to the notion of domination in graphs,
6
+ and more precisely:
7
+
8
+ .. csv-table::
9
+ :class: contentstable
10
+ :widths: 30, 70
11
+ :delim: |
12
+
13
+ :meth:`~dominating_set` | Return a minimum distance-`k` dominating set of the graph.
14
+ :meth:`~dominating_sets` | Return an iterator over the minimum distance-`k` dominating sets of the graph.
15
+ :meth:`~minimal_dominating_sets` | Return an iterator over the minimal dominating sets of a graph.
16
+ :meth:`~is_dominating` | Check whether a set of vertices dominates a graph.
17
+ :meth:`~is_redundant` | Check whether a set of vertices has redundant vertices (with respect to domination).
18
+ :meth:`~private_neighbors` | Return the private neighbors of a vertex with respect to other vertices.
19
+ :meth:`~greedy_dominating_set` | Return a greedy distance-`k` dominating set of the graph.
20
+ :meth:`~maximum_leaf_number` | Return the maximum leaf number of the graph.
21
+
22
+ EXAMPLES:
23
+
24
+ We compute the size of a minimum dominating set of the Petersen graph::
25
+
26
+ sage: g = graphs.PetersenGraph()
27
+ sage: g.dominating_set(value_only=True) # needs sage.numerical.mip
28
+ 3
29
+
30
+ We enumerate the minimal dominating sets of the 5-star graph::
31
+
32
+ sage: g = graphs.StarGraph(5)
33
+ sage: list(g.minimal_dominating_sets())
34
+ [{0}, {1, 2, 3, 4, 5}]
35
+
36
+ Now only those that dominate the middle vertex::
37
+
38
+ sage: list(g.minimal_dominating_sets([0]))
39
+ [{0}, {1}, {2}, {3}, {4}, {5}]
40
+
41
+ Now the minimal dominating sets of the 5-path graph::
42
+
43
+ sage: g = graphs.PathGraph(5)
44
+ sage: list(g.minimal_dominating_sets())
45
+ [{0, 2, 4}, {1, 4}, {0, 3}, {1, 3}]
46
+
47
+ We count the minimal dominating sets of the Petersen graph::
48
+
49
+ sage: sum(1 for _ in graphs.PetersenGraph().minimal_dominating_sets())
50
+ 27
51
+
52
+ Methods
53
+ -------
54
+ """
55
+
56
+ # ****************************************************************************
57
+ # Copyright (C) 2009 Nathann Cohen <nathann.cohen@gmail.com>
58
+ # 2019 Jean-Florent Raymond <j-florent.raymond@uca.fr>
59
+ # 2023 David Coudert <david.coudert@inria.fr>
60
+ #
61
+ # This program is free software: you can redistribute it and/or modify
62
+ # it under the terms of the GNU General Public License as published by
63
+ # the Free Software Foundation, either version 2 of the License, or
64
+ # (at your option) any later version.
65
+ # https://www.gnu.org/licenses/
66
+ # ****************************************************************************
67
+
68
+ from copy import copy
69
+ from sage.rings.integer import Integer
70
+
71
+
72
+ def is_dominating(G, dom, focus=None):
73
+ r"""
74
+ Check whether ``dom`` is a dominating set of ``G``.
75
+
76
+ We say that a set `D` of vertices of a graph `G` dominates a set `S` if
77
+ every vertex of `S` either belongs to `D` or is adjacent to a vertex of `D`.
78
+ Also, `D` is a dominating set of `G` if it dominates `V(G)`.
79
+
80
+ INPUT:
81
+
82
+ - ``dom`` -- iterable of vertices of ``G``; the vertices of the supposed
83
+ dominating set
84
+
85
+ - ``focus`` -- iterable of vertices of ``G`` (default: ``None``); if
86
+ specified, this method checks instead if ``dom`` dominates the vertices in
87
+ ``focus``
88
+
89
+ EXAMPLES::
90
+
91
+ sage: g = graphs.CycleGraph(5)
92
+ sage: g.is_dominating([0,1], [4, 2])
93
+ True
94
+
95
+ sage: g.is_dominating([0,1])
96
+ False
97
+ """
98
+ to_dom = set(G) if focus is None else set(focus)
99
+
100
+ for v in dom:
101
+ if not to_dom:
102
+ return True
103
+ to_dom.difference_update(G.neighbor_iterator(v, closed=True))
104
+
105
+ return not to_dom
106
+
107
+
108
+ def is_redundant(G, dom, focus=None):
109
+ r"""
110
+ Check whether ``dom`` has redundant vertices.
111
+
112
+ For a graph `G` and sets `D` and `S` of vertices, we say that a vertex `v
113
+ \in D` is *redundant* in `S` if `v` has no private neighbor with respect to
114
+ `D` in `S`. In other words, there is no vertex in `S` that is dominated by
115
+ `v` but not by `D \setminus \{v\}`.
116
+
117
+ INPUT:
118
+
119
+ - ``dom`` -- iterable of vertices of ``G``; where we look for redundant
120
+ vertices
121
+
122
+ - ``focus`` -- iterable of vertices of ``G`` (default: ``None``); if
123
+ specified, this method checks instead whether ``dom`` has a redundant
124
+ vertex in ``focus``
125
+
126
+ .. WARNING::
127
+
128
+ The assumption is made that ``focus`` (if provided) does not contain
129
+ repeated vertices.
130
+
131
+ EXAMPLES::
132
+
133
+ sage: G = graphs.CubeGraph(3)
134
+ sage: G.is_redundant(['000', '101'], ['011'])
135
+ True
136
+ sage: G.is_redundant(['000', '101'])
137
+ False
138
+ """
139
+ dom = list(dom)
140
+ focus = G if focus is None else set(focus)
141
+
142
+ # dominator[v] (for v in focus) will be equal to:
143
+ # - (0, None) if v has no neighbor in dom
144
+ # - (1, u) if v has a unique neighbor in dom, u
145
+ # - (2, None) if v has >= 2 neighbors in dom
146
+
147
+ # Initialization
148
+ dominator = {v: (0, None) for v in focus}
149
+
150
+ for x in dom:
151
+ for v in G.neighbor_iterator(x, closed=True):
152
+ if v in focus:
153
+ # remember about x only if we never encountered
154
+ # neighbors of v so far
155
+ if dominator[v][0] == 0:
156
+ dominator[v] = (1, x)
157
+ elif dominator[v][0] == 1:
158
+ dominator[v] = (2, None)
159
+
160
+ # We now compute the subset of vertices of dom that have a private neighbor
161
+ # in focus. A vertex v in dom has a private neighbor in focus if it is
162
+ # dominated by a unique vertex, that is if dominator[v][0] == 1
163
+ with_private = set()
164
+ for v in focus:
165
+ if dominator[v][0] == 1:
166
+ with_private.add(dominator[v][1])
167
+
168
+ # By construction with_private is a subset of dom and we assume the elements
169
+ # of dom to be unique, so the following is equivalent to checking
170
+ # with_private != set(dom)
171
+ return len(with_private) != len(dom)
172
+
173
+
174
+ def private_neighbors(G, vertex, dom):
175
+ r"""
176
+ Return the private neighbors of a vertex with respect to other vertices.
177
+
178
+ A private neighbor of a vertex `v` with respect to a vertex subset `D`
179
+ is a closed neighbor of `v` that is not dominated by a vertex of `D
180
+ \setminus \{v\}`.
181
+
182
+ INPUT:
183
+
184
+ - ``vertex`` -- a vertex of ``G``
185
+
186
+ - ``dom`` -- iterable of vertices of ``G``; the vertices possibly stealing
187
+ private neighbors from ``vertex``
188
+
189
+ OUTPUT:
190
+
191
+ Return the closed neighbors of ``vertex`` that are not closed neighbors
192
+ of any other vertex of ``dom``.
193
+
194
+ EXAMPLES::
195
+
196
+ sage: g = graphs.PathGraph(5)
197
+ sage: list(g.private_neighbors(1, [1, 3, 4]))
198
+ [1, 0]
199
+
200
+ sage: list(g.private_neighbors(1, [3, 4]))
201
+ [1, 0]
202
+
203
+ sage: list(g.private_neighbors(1, [3, 4, 0]))
204
+ []
205
+ """
206
+ # The set of all vertices that are dominated by vertex_subset - vertex:
207
+ closed_neighborhood_vs = set()
208
+ for u in dom:
209
+ if u != vertex:
210
+ closed_neighborhood_vs.update(
211
+ G.neighbor_iterator(u, closed=True))
212
+
213
+ return (neighbor
214
+ for neighbor in G.neighbor_iterator(vertex, closed=True)
215
+ if neighbor not in closed_neighborhood_vs)
216
+
217
+
218
+ # ==============================================================================
219
+ # Computation of minimum dominating sets
220
+ # ==============================================================================
221
+
222
+ def dominating_sets(g, k=1, independent=False, total=False, connected=False,
223
+ solver=None, verbose=0, *, integrality_tolerance=1e-3):
224
+ r"""
225
+ Return an iterator over the minimum distance-`k` dominating sets
226
+ of the graph.
227
+
228
+ A minimum dominating set `S` of a graph `G` is a set of its vertices of
229
+ minimal cardinality such that any vertex of `G` is in `S` or has one of its
230
+ neighbors in `S`. See the :wikipedia:`Dominating_set`.
231
+
232
+ A minimum distance-`k` dominating set is a set `S` of vertices of `G` of
233
+ minimal cardinality such that any vertex of `G` is in `S` or at distance at
234
+ most `k` from a vertex in `S`. A distance-`0` dominating set is the set of
235
+ vertices itself, and when `k` is the radius of the graph, any vertex
236
+ dominates all the other vertices.
237
+
238
+ As an optimization problem, it can be expressed as follows, where `N^k(u)`
239
+ denotes the set of vertices at distance at most `k` from `u` (the set of
240
+ neighbors when `k=1`):
241
+
242
+ .. MATH::
243
+
244
+ \mbox{Minimize : }&\sum_{v\in G} b_v\\
245
+ \mbox{Such that : }&\forall v \in G, b_v+\sum_{u \in N^k(v)} b_u\geq 1\\
246
+ &\forall x\in G, b_x\mbox{ is a binary variable}
247
+
248
+ We use constraints generation to iterate over the minimum distance-`k`
249
+ dominating sets. That is, after reporting a solution, we add a constraint to
250
+ discard it and solve the problem again until no more solution can be found.
251
+
252
+ INPUT:
253
+
254
+ - ``k`` -- nonnegative integer (default: `1`); the domination distance
255
+
256
+ - ``independent`` -- boolean (default: ``False``); when ``True``, computes
257
+ minimum independent dominating sets, that is minimum dominating sets that
258
+ are also independent sets (see also
259
+ :meth:`~sage.graphs.graph.independent_set`)
260
+
261
+ - ``total`` -- boolean (default: ``False``); when ``True``, computes total
262
+ dominating sets (see the See the :wikipedia:`Dominating_set`)
263
+
264
+ - ``connected`` -- boolean (default: ``False``); when ``True``, computes
265
+ connected dominating sets (see :wikipedia:`Connected_dominating_set`)
266
+
267
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
268
+ Programming (MILP) solver to be used. If set to ``None``, the default one
269
+ is used. For more information on MILP solvers and which default solver is
270
+ used, see the method :meth:`solve
271
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
272
+ :class:`MixedIntegerLinearProgram
273
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
274
+
275
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
276
+ to 0 by default, which means quiet.
277
+
278
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
279
+ over an inexact base ring; see
280
+ :meth:`MixedIntegerLinearProgram.get_values`.
281
+
282
+ EXAMPLES:
283
+
284
+ Number of distance-`k` dominating sets of a Path graph of order 10::
285
+
286
+ sage: g = graphs.PathGraph(10)
287
+ sage: [sum(1 for _ in g.dominating_sets(k=k)) for k in range(11)] # needs sage.numerical.mip
288
+ [1, 13, 1, 13, 25, 2, 4, 6, 8, 10, 10]
289
+
290
+ If we build a graph from two disjoint stars, then link their centers we will
291
+ find a difference between the cardinality of an independent set and a stable
292
+ independent set::
293
+
294
+ sage: g = 2 * graphs.StarGraph(5)
295
+ sage: g.add_edge(0, 6)
296
+ sage: [sum(1 for _ in g.dominating_sets(k=k)) for k in range(11)] # needs sage.numerical.mip
297
+ [1, 1, 2, 12, 12, 12, 12, 12, 12, 12, 12]
298
+
299
+ The total dominating set of the Petersen graph has cardinality 4::
300
+
301
+ sage: G = graphs.PetersenGraph()
302
+ sage: G.dominating_set(total=True, value_only=True) # needs sage.numerical.mip
303
+ 4
304
+ sage: sorted(G.dominating_sets(k=1)) # needs sage.numerical.mip
305
+ [[0, 2, 6],
306
+ [0, 3, 9],
307
+ [0, 7, 8],
308
+ [1, 3, 7],
309
+ [1, 4, 5],
310
+ [1, 8, 9],
311
+ [2, 4, 8],
312
+ [2, 5, 9],
313
+ [3, 5, 6],
314
+ [4, 6, 7]]
315
+
316
+ Independent distance-`k` dominating sets of a Path graph::
317
+
318
+ sage: # needs sage.numerical.mip
319
+ sage: G = graphs.PathGraph(6)
320
+ sage: sorted(G.dominating_sets(k=1, independent=True))
321
+ [[1, 4]]
322
+ sage: sorted(G.dominating_sets(k=2, independent=True))
323
+ [[0, 3], [0, 4], [0, 5], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5]]
324
+ sage: sorted(G.dominating_sets(k=3, independent=True))
325
+ [[2], [3]]
326
+
327
+ The dominating set is calculated for both the directed and undirected graphs
328
+ (modification introduced in :issue:`17905`)::
329
+
330
+ sage: # needs sage.numerical.mip
331
+ sage: g = digraphs.Path(3)
332
+ sage: g.dominating_set(value_only=True)
333
+ 2
334
+ sage: list(g.dominating_sets())
335
+ [[0, 1], [0, 2]]
336
+ sage: list(g.dominating_sets(k=2))
337
+ [[0]]
338
+ sage: g = graphs.PathGraph(3)
339
+ sage: g.dominating_set(value_only=True)
340
+ 1
341
+ sage: next(g.dominating_sets())
342
+ [1]
343
+
344
+ Minimum connected dominating sets of the Petersen graph::
345
+
346
+ sage: G = graphs.PetersenGraph()
347
+ sage: G.dominating_set(total=True, value_only=True) # needs sage.numerical.mip
348
+ 4
349
+ sage: sorted(G.dominating_sets(k=1, connected=True)) # needs sage.numerical.mip
350
+ [[0, 1, 2, 6],
351
+ [0, 1, 4, 5],
352
+ [0, 3, 4, 9],
353
+ [0, 5, 7, 8],
354
+ [1, 2, 3, 7],
355
+ [1, 6, 8, 9],
356
+ [2, 3, 4, 8],
357
+ [2, 5, 7, 9],
358
+ [3, 5, 6, 8],
359
+ [4, 6, 7, 9]]
360
+
361
+ Subgraph induced by the dominating set is connected::
362
+
363
+ sage: G = graphs.PetersenGraph()
364
+ sage: all(G.subgraph(vertices=dom).is_connected() # needs sage.numerical.mip
365
+ ....: for dom in G.dominating_set(k=1, connected=True))
366
+ True
367
+
368
+ Minimum distance-k connected dominating sets of the Tietze graph::
369
+
370
+ sage: G = graphs.TietzeGraph()
371
+ sage: sorted(G.dominating_sets(k=2, connected=True)) # needs sage.numerical.mip
372
+ [[0, 9], [1, 0], [2, 3], [4, 3], [5, 6], [7, 6], [8, 0], [10, 3], [11, 6]]
373
+ sage: sorted(G.dominating_sets(k=3, connected=True)) # needs sage.numerical.mip
374
+ [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11]]
375
+
376
+ TESTS::
377
+
378
+ sage: g = Graph([(0, 1)])
379
+ sage: next(g.dominating_sets(k=-1))
380
+ Traceback (most recent call last):
381
+ ...
382
+ ValueError: the domination distance must be a nonnegative integer
383
+
384
+ The method is robust to vertices with incomparable labels::
385
+
386
+ sage: G = Graph([(1, 'A'), ('A', 2), (2, 3), (3, 1)])
387
+ sage: L = list(G.dominating_sets()) # needs sage.numerical.mip
388
+ sage: len(L) # needs sage.numerical.mip
389
+ 6
390
+ """
391
+ g._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total)
392
+
393
+ if not k:
394
+ yield list(g)
395
+ return
396
+ if k < 0:
397
+ raise ValueError("the domination distance must be a nonnegative integer")
398
+
399
+ from sage.numerical.mip import MixedIntegerLinearProgram
400
+ from sage.numerical.mip import MIPSolverException
401
+ p = MixedIntegerLinearProgram(maximization=False, solver=solver,
402
+ constraint_generation=True)
403
+ b = p.new_variable(binary=True)
404
+
405
+ if k == 1:
406
+ # For any vertex v, one of its neighbors or v itself is in the minimum
407
+ # dominating set. If g is directed, we use the in-neighbors of v
408
+ # instead.
409
+ neighbors_iter = g.neighbor_in_iterator if g.is_directed() else g.neighbor_iterator
410
+ else:
411
+ # When k > 1, we use BFS to determine the vertices that can reach v
412
+ # through a path of length at most k
413
+ gg = g.reverse() if g.is_directed() else g
414
+
415
+ def neighbors_iter(x):
416
+ it = gg.breadth_first_search(x, distance=k)
417
+ _ = next(it)
418
+ yield from it
419
+
420
+ if total:
421
+ # We want a total dominating set
422
+ for v in g:
423
+ p.add_constraint(p.sum(b[u] for u in neighbors_iter(v)), min=1)
424
+ else:
425
+ for v in g:
426
+ p.add_constraint(b[v] + p.sum(b[u] for u in neighbors_iter(v)), min=1)
427
+
428
+ if independent:
429
+ # no two adjacent vertices are in the set
430
+ for u, v in g.edge_iterator(labels=None):
431
+ p.add_constraint(b[u] + b[v], max=1)
432
+
433
+ if connected:
434
+ E = set(frozenset(e) for e in g.edge_iterator(labels=False))
435
+ # edges used in the spanning tree
436
+ edge = p.new_variable(binary=True, name='e')
437
+ # relaxed edges to test for acyclicity
438
+ r_edge = p.new_variable(nonnegative=True, name='re')
439
+
440
+ # 1. We want a tree
441
+ p.add_constraint(p.sum(edge[fe] for fe in E)
442
+ == p.sum(b[u] for u in g) - 1)
443
+
444
+ # 2. An edge can be in the tree if its end vertices are selected
445
+ for fe in E:
446
+ u, v = fe
447
+ p.add_constraint(edge[fe] <= b[u])
448
+ p.add_constraint(edge[fe] <= b[v])
449
+
450
+ # 3. Subtour elimination constraints
451
+ for fe in E:
452
+ u, v = fe
453
+ p.add_constraint(edge[fe] <= r_edge[u, v] + r_edge[v, u])
454
+
455
+ eps = 1 / (5 * Integer(g.order()))
456
+ for v in g:
457
+ p.add_constraint(p.sum(r_edge[u, v] for u in g.neighbor_iterator(v)), max=1 - eps)
458
+
459
+ # Minimizes the number of vertices used
460
+ p.set_objective(p.sum(b[v] for v in g))
461
+
462
+ best = g.order()
463
+ while True:
464
+ try:
465
+ p.solve(log=verbose)
466
+ except MIPSolverException:
467
+ # No more solutions
468
+ break
469
+ b_val = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
470
+ dom = [v for v in g if b_val[v]]
471
+ if len(dom) > best:
472
+ # All minimum solution have been reported
473
+ break
474
+ yield dom
475
+ best = len(dom)
476
+ # Prevent finding twice a solution
477
+ p.add_constraint(p.sum(b[u] for u in dom) <= best - 1)
478
+
479
+
480
+ def dominating_set(g, k=1, independent=False, total=False, connected=False, value_only=False,
481
+ solver=None, verbose=0, *, integrality_tolerance=1e-3):
482
+ r"""
483
+ Return a minimum distance-`k` dominating set of the graph.
484
+
485
+ A minimum dominating set `S` of a graph `G` is a set of its vertices of
486
+ minimal cardinality such that any vertex of `G` is in `S` or has one of its
487
+ neighbors in `S`. See the :wikipedia:`Dominating_set`.
488
+
489
+ A minimum distance-`k` dominating set is a set `S` of vertices of `G` of
490
+ minimal cardinality such that any vertex of `G` is in `S` or at distance at
491
+ most `k` from a vertex in `S`. A distance-`0` dominating set is the set of
492
+ vertices itself, and when `k` is the radius of the graph, any vertex
493
+ dominates all the other vertices.
494
+
495
+ As an optimization problem, it can be expressed as follows, where `N^k(u)`
496
+ denotes the set of vertices at distance at most `k` from `u` (the set of
497
+ neighbors when `k=1`):
498
+
499
+ .. MATH::
500
+
501
+ \mbox{Minimize : }&\sum_{v\in G} b_v\\
502
+ \mbox{Such that : }&\forall v \in G, b_v+\sum_{u \in N^k(v)} b_u\geq 1\\
503
+ &\forall x\in G, b_x\mbox{ is a binary variable}
504
+
505
+ INPUT:
506
+
507
+ - ``k`` -- nonnegative integer (default: `1`); the domination distance
508
+
509
+ - ``independent`` -- boolean (default: ``False``); when ``True``, computes a
510
+ minimum independent dominating set, that is a minimum dominating set that
511
+ is also an independent set (see also
512
+ :meth:`~sage.graphs.graph.independent_set`)
513
+
514
+ - ``total`` -- boolean (default: ``False``); when ``True``, computes a total
515
+ dominating set (see the See the :wikipedia:`Dominating_set`)
516
+
517
+ - ``connected`` -- boolean (default: ``False``); when ``True``, computes a
518
+ connected dominating set (see :wikipedia:`Connected_dominating_set`)
519
+
520
+ - ``value_only`` -- boolean (default: ``False``); whether to only return the
521
+ cardinality of the computed dominating set, or to return its list of
522
+ vertices (default)
523
+
524
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
525
+ Programming (MILP) solver to be used. If set to ``None``, the default one
526
+ is used. For more information on MILP solvers and which default solver is
527
+ used, see the method :meth:`solve
528
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
529
+ :class:`MixedIntegerLinearProgram
530
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
531
+
532
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
533
+ to 0 by default, which means quiet.
534
+
535
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
536
+ over an inexact base ring; see
537
+ :meth:`MixedIntegerLinearProgram.get_values`.
538
+
539
+ EXAMPLES:
540
+
541
+ A basic illustration on a ``PappusGraph``::
542
+
543
+ sage: g = graphs.PappusGraph()
544
+ sage: g.dominating_set(value_only=True) # needs sage.numerical.mip
545
+ 5
546
+
547
+ If we build a graph from two disjoint stars, then link their centers we will
548
+ find a difference between the cardinality of an independent set and a stable
549
+ independent set::
550
+
551
+ sage: g = 2 * graphs.StarGraph(5)
552
+ sage: g.add_edge(0, 6)
553
+ sage: len(g.dominating_set()) # needs sage.numerical.mip
554
+ 2
555
+ sage: len(g.dominating_set(independent=True)) # needs sage.numerical.mip
556
+ 6
557
+
558
+ The total dominating set of the Petersen graph has cardinality 4::
559
+
560
+ sage: G = graphs.PetersenGraph()
561
+ sage: G.dominating_set(total=True, value_only=True) # needs sage.numerical.mip
562
+ 4
563
+
564
+ The dominating set is calculated for both the directed and undirected graphs
565
+ (modification introduced in :issue:`17905`)::
566
+
567
+ sage: g = digraphs.Path(3)
568
+ sage: g.dominating_set(value_only=True) # needs sage.numerical.mip
569
+ 2
570
+ sage: g = graphs.PathGraph(3)
571
+ sage: g.dominating_set(value_only=True) # needs sage.numerical.mip
572
+ 1
573
+
574
+ Cardinality of distance-`k` dominating sets::
575
+
576
+ sage: G = graphs.PetersenGraph()
577
+ sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)] # needs sage.numerical.mip
578
+ [10, 3, 1]
579
+ sage: G = graphs.PathGraph(5)
580
+ sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)] # needs sage.numerical.mip
581
+ [5, 2, 1]
582
+ """
583
+ dom = next(dominating_sets(g, k=k, independent=independent, total=total,
584
+ connected=connected, solver=solver, verbose=verbose,
585
+ integrality_tolerance=integrality_tolerance))
586
+ return Integer(len(dom)) if value_only else dom
587
+
588
+ # ==============================================================================
589
+ # Enumeration of minimal dominating set as described in [BDHPR2019]_
590
+ # ==============================================================================
591
+
592
+
593
+ def _parent(G, dom, V_prev):
594
+ r"""
595
+ Return a subset of dom that is irredundant in ``V_prev``.
596
+
597
+ For internal use.
598
+
599
+ INPUT:
600
+
601
+ - ``G`` -- a graph
602
+
603
+ - ``dom`` -- an iterable of vertices of ``G``
604
+
605
+ - ``V_prev`` -- an iterable of vertices of ``G``
606
+
607
+ OUTPUT:
608
+
609
+ Return the list obtained from ``dom`` by iteratively removing those
610
+ vertices of minimum index that have no private neighbor in ``V_prev``.
611
+
612
+ TESTS::
613
+
614
+ sage: from sage.graphs.domination import _parent
615
+ sage: G = graphs.PathGraph(4)
616
+ sage: G.add_vertices([4, 5])
617
+ sage: G.add_edges([(4, 1), (5, 2)])
618
+ sage: _parent(G, [0, 2, 4, 5], [1, 2])
619
+ [4, 5]
620
+ sage: _parent(G, [0, 2, 4, 5], [1, 3])
621
+ [2]
622
+ """
623
+ # The list where we search vertices
624
+ D_start = sorted(dom, reverse=True)
625
+
626
+ # The list to be output at the end, that we construct:
627
+ D_end = []
628
+
629
+ while D_start:
630
+ v = D_start.pop() # element of min index
631
+ priv = set(G.neighbor_iterator(v, closed=True))
632
+ # We remove the vertices already dominated
633
+ # by other vertices of (D_end union D_start)
634
+ priv.difference_update(*(G.neighbor_iterator(u, closed=True)
635
+ for u in D_start if u != v))
636
+ priv.difference_update(*(G.neighbor_iterator(u, closed=True)
637
+ for u in D_end if u != v))
638
+ # Now priv is the private neighborhood of v in G wrt D_start + D_end
639
+ if priv.intersection(V_prev) != set():
640
+ # if v has a private in V_prev, we keep it
641
+ D_end.append(v)
642
+
643
+ return D_end
644
+
645
+
646
+ def _peel(G, A):
647
+ r"""
648
+ Return a peeling of a vertex iterable of a graph.
649
+
650
+ For internal use.
651
+ Given a graph `G` and a subset `A` of its vertices, a peeling of `(G,A)` is
652
+ a list `[(u_0, V_0), \dots, (u_p, V_p)]` such that `u_0` is ``None``,
653
+ `V_0` is the empty set, `V_p = A` and for every `i \in \{1, \dots, p\}`,
654
+ `V_{i-1} = V_i \setminus N[v_i]`, for some vertex `u_i` of `V_i`.
655
+
656
+ INPUT:
657
+
658
+ - ``G`` -- a graph
659
+
660
+ - ``A`` -- set of vertices of `G`
661
+
662
+ OUTPUT:
663
+
664
+ A peeling of `(G, A)`.
665
+
666
+ TESTS::
667
+
668
+ sage: from sage.graphs.domination import _peel
669
+ sage: G = Graph(10); _peel(G, {0, 1, 2, 3, 4})
670
+ [(None, set()),
671
+ (4, {4}),
672
+ (3, {3, 4}),
673
+ (2, {2, 3, 4}),
674
+ (1, {1, 2, 3, 4}),
675
+ (0, {0, 1, 2, 3, 4})]
676
+
677
+
678
+ sage: from sage.graphs.domination import _peel
679
+ sage: G = graphs.PathGraph(10); _peel(G, set((i for i in range(10) if i%2==0)))
680
+ [(None, set()),
681
+ (8, {8}),
682
+ (6, {6, 8}),
683
+ (4, {4, 6, 8}),
684
+ (2, {2, 4, 6, 8}),
685
+ (0, {0, 2, 4, 6, 8})]
686
+ """
687
+ Acomp = set(G)
688
+ Acomp.difference_update(A) # Acomp = V - A
689
+
690
+ peeling = []
691
+ H = copy(G)
692
+ H.delete_vertices(list(Acomp))
693
+ del Acomp
694
+
695
+ while H:
696
+ ui = next(H.vertex_iterator()) # pick some vertex of H
697
+ Vi = set(H)
698
+ peeling.append((ui, Vi))
699
+ H.delete_vertices(H.neighbor_iterator(ui, closed=True))
700
+ peeling.append((None, set()))
701
+ peeling.reverse()
702
+ return peeling
703
+
704
+
705
+ def _cand_ext_enum(G, to_dom, u_next):
706
+ r"""
707
+ Return the minimal dominating sets of ``to_dom``.
708
+
709
+ For internal use.
710
+ Assumption: ``u_next`` dominates ``to_dom``.
711
+
712
+ INPUT:
713
+
714
+ - ``G`` -- a graph
715
+
716
+ - ``to_dom`` -- a ``set()`` of vertices of ``G``
717
+
718
+ - ``u_next`` -- a vertex of ``G`` that dominates ``to_dom``
719
+
720
+ OUTPUT: an iterator over the minimal dominating sets of ``to_dom``
721
+
722
+ TESTS::
723
+
724
+ sage: from sage.graphs.domination import _cand_ext_enum
725
+ sage: g = graphs.DiamondGraph()
726
+ sage: l = list(_cand_ext_enum(g, {0, 2, 3}, 1,))
727
+ sage: len(l) == 3 and {1} in l and {2} in l and {0, 3} in l
728
+ True
729
+ """
730
+
731
+ def _aux_with_rep(H, to_dom, u_next):
732
+ """
733
+ Return the minimal dominating sets of ``to_dom``, with the
734
+ assumption that ``u_next`` dominates ``to_som``.
735
+
736
+ .. WARNING::
737
+
738
+ The same output may be output several times (up to `|H|` times).
739
+
740
+ In order to later remove duplicates, we here output pairs ``(ext, i)``
741
+ where ``ext`` is the output candidate extension and ``i`` counts how
742
+ many elements have already been output.
743
+ """
744
+ if u_next not in to_dom:
745
+ # In this case, enumerating the minimal DSs of the subset
746
+ # to_dom is a smaller instance as it excludes u_next:
747
+
748
+ cand_ext_index = 0
749
+
750
+ for ext in H.minimal_dominating_sets(to_dom):
751
+ yield (ext, cand_ext_index)
752
+ cand_ext_index += 1
753
+
754
+ elif to_dom == {u_next}:
755
+ # In this case, only u_next has to be dominated
756
+ cand_ext_index = 0
757
+ for w in H.neighbor_iterator(u_next, closed=True):
758
+ # Notice that the case w = u_next is included
759
+ yield ({w}, cand_ext_index)
760
+ cand_ext_index += 1
761
+
762
+ else:
763
+ # In this case, both u_next and to_dom-u_next have to be dominated
764
+
765
+ # We first output the trivial output
766
+ # (as to_dom is subset of N(u_next)):
767
+ yield ({u_next}, 0)
768
+ # Start from 1 because we already output the 0-th elt:
769
+ cand_ext_index = 1
770
+
771
+ # When u_next is not in the DS, one of its neighbors w should be:
772
+ for w in H.neighbor_iterator(u_next):
773
+
774
+ remains_to_dom = set(to_dom)
775
+ remains_to_dom.difference_update(
776
+ H.neighbor_iterator(w, closed=True))
777
+ # Here again we recurse on a smaller instance at it
778
+ # excludes u_next (and w)
779
+ for Q in H.minimal_dominating_sets(remains_to_dom):
780
+ ext = set(Q)
781
+ ext.add(w)
782
+ # By construction w dominates u_next and Q dominates
783
+ # to_dom - N[w], so ext dominates to_dom: it is a
784
+ # valid output iff it is not redundant
785
+ if not H.is_redundant(ext):
786
+ yield (ext, cand_ext_index)
787
+ cand_ext_index += 1
788
+ #
789
+ # End of aux_with_rep routine
790
+
791
+ # Here we use aux_with_rep twice to enumerate the minimal
792
+ # dominating sets while avoiding repeated outputs
793
+ for X, i in _aux_with_rep(G, to_dom, u_next):
794
+ for Y, j in _aux_with_rep(G, to_dom, u_next):
795
+ if j >= i:
796
+ # This is the first time we meet X: we output it
797
+ yield X
798
+ break
799
+ elif Y == X: # These are sets
800
+ # X has already been output in the past: we ignore it
801
+ break
802
+
803
+
804
+ def minimal_dominating_sets(G, to_dominate=None, work_on_copy=True, k=1):
805
+ r"""
806
+ Return an iterator over the minimal dominating sets of a graph.
807
+
808
+ INPUT:
809
+
810
+ - ``G`` -- a graph
811
+
812
+ - ``to_dominate`` -- vertex iterable or ``None`` (default: ``None``);
813
+ the set of vertices to be dominated
814
+
815
+ - ``work_on_copy`` -- boolean (default: ``True``); whether or not to work on
816
+ a copy of the input graph; if set to ``False``, the input graph will be
817
+ modified (relabeled)
818
+
819
+ - ``k`` -- nonnegative integer (default: `1`); the domination distance
820
+
821
+ OUTPUT:
822
+
823
+ An iterator over the inclusion-minimal sets of vertices of ``G``.
824
+ If ``to_dominate`` is provided, return an iterator over the
825
+ inclusion-minimal sets of vertices that dominate the vertices of
826
+ ``to_dominate``.
827
+
828
+ ALGORITHM: The algorithm described in [BDHPR2019]_.
829
+
830
+ AUTHOR: Jean-Florent Raymond (2019-03-04) -- initial version.
831
+
832
+ EXAMPLES::
833
+
834
+ sage: G = graphs.ButterflyGraph()
835
+ sage: ll = list(G.minimal_dominating_sets())
836
+ sage: pp = [{0, 1}, {1, 3}, {0, 2}, {2, 3}, {4}]
837
+ sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
838
+ True
839
+
840
+ sage: ll = list(G.minimal_dominating_sets([0,3]))
841
+ sage: pp = [{0}, {3}, {4}]
842
+ sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
843
+ True
844
+
845
+ sage: ll = list(G.minimal_dominating_sets([4]))
846
+ sage: pp = [{4}, {0}, {1}, {2}, {3}]
847
+ sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
848
+ True
849
+
850
+ ::
851
+
852
+ sage: ll = list(graphs.PetersenGraph().minimal_dominating_sets())
853
+ sage: pp = [{0, 2, 6},
854
+ ....: {0, 9, 3},
855
+ ....: {0, 8, 7},
856
+ ....: {1, 3, 7},
857
+ ....: {1, 4, 5},
858
+ ....: {8, 1, 9},
859
+ ....: {8, 2, 4},
860
+ ....: {9, 2, 5},
861
+ ....: {3, 5, 6},
862
+ ....: {4, 6, 7},
863
+ ....: {0, 8, 2, 9},
864
+ ....: {0, 3, 6, 7},
865
+ ....: {1, 3, 5, 9},
866
+ ....: {8, 1, 4, 7},
867
+ ....: {2, 4, 5, 6},
868
+ ....: {0, 1, 2, 3, 4},
869
+ ....: {0, 1, 2, 5, 7},
870
+ ....: {0, 1, 4, 6, 9},
871
+ ....: {0, 1, 5, 6, 8},
872
+ ....: {0, 8, 3, 4, 5},
873
+ ....: {0, 9, 4, 5, 7},
874
+ ....: {8, 1, 2, 3, 6},
875
+ ....: {1, 2, 9, 6, 7},
876
+ ....: {9, 2, 3, 4, 7},
877
+ ....: {8, 2, 3, 5, 7},
878
+ ....: {8, 9, 3, 4, 6},
879
+ ....: {8, 9, 5, 6, 7}]
880
+ sage: len(ll) == len(pp) and all(x in pp for x in ll) and all(x in ll for x in pp)
881
+ True
882
+
883
+ Listing minimal distance-`k` dominating sets::
884
+
885
+ sage: G = graphs.Grid2dGraph(2, 3)
886
+ sage: list(G.minimal_dominating_sets(k=0))
887
+ [{(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)}]
888
+ sage: list(G.minimal_dominating_sets(k=1))
889
+ [{(0, 0), (0, 2), (1, 1)},
890
+ {(0, 1), (1, 1)},
891
+ {(0, 0), (0, 1), (0, 2)},
892
+ {(0, 2), (1, 0)},
893
+ {(0, 0), (1, 2)},
894
+ {(0, 1), (1, 0), (1, 2)},
895
+ {(1, 0), (1, 1), (1, 2)}]
896
+ sage: list(G.minimal_dominating_sets(k=2))
897
+ [{(0, 0), (1, 2)},
898
+ {(0, 2), (1, 2)},
899
+ {(1, 0), (1, 2)},
900
+ {(0, 1)},
901
+ {(0, 0), (0, 2)},
902
+ {(0, 2), (1, 0)},
903
+ {(0, 0), (1, 0)},
904
+ {(1, 1)}]
905
+ sage: list(G.minimal_dominating_sets(k=3))
906
+ [{(0, 0)}, {(0, 1)}, {(0, 2)}, {(1, 0)}, {(1, 1)}, {(1, 2)}]
907
+
908
+ When parameter ``work_on_copy`` is ``False``, the input graph is modified
909
+ (relabeled)::
910
+
911
+ sage: G = Graph([('A', 'B')])
912
+ sage: _ = list(G.minimal_dominating_sets(work_on_copy=True))
913
+ sage: set(G) == {'A', 'B'}
914
+ True
915
+ sage: _ = list(G.minimal_dominating_sets(work_on_copy=False))
916
+ sage: set(G) == {'A', 'B'}
917
+ False
918
+ sage: set(G) == {0, 1}
919
+ True
920
+
921
+ TESTS:
922
+
923
+ The empty graph is handled correctly::
924
+
925
+ sage: list(Graph().minimal_dominating_sets())
926
+ [set()]
927
+
928
+ Test on all graphs on 6 vertices::
929
+
930
+ sage: from sage.combinat.subset import Subsets
931
+ sage: def minimal_dominating_sets_naive(G):
932
+ ....: return (S for S in Subsets(G.vertices(sort=False))
933
+ ....: if not(G.is_redundant(S)) and G.is_dominating(S))
934
+ sage: def big_check(n):
935
+ ....: for G in graphs(n):
936
+ ....: ll = list(G.minimal_dominating_sets())
937
+ ....: pp = list(minimal_dominating_sets_naive(G))
938
+ ....: if (len(pp) != len(pp)
939
+ ....: or any(x not in pp for x in ll)
940
+ ....: or any(x not in ll for x in pp)):
941
+ ....: return False
942
+ ....: return True
943
+ sage: big_check(6) # long time
944
+ True
945
+
946
+ Outputs are unique::
947
+
948
+ sage: def check_uniqueness(g):
949
+ ....: counter_1 = 0
950
+ ....: for dom_1 in g.minimal_dominating_sets():
951
+ ....: counter_1 += 1
952
+ ....: counter_2 = 0
953
+ ....: for dom_2 in g.minimal_dominating_sets():
954
+ ....: counter_2 += 1
955
+ ....: if counter_2 >= counter_1:
956
+ ....: break
957
+ ....: if dom_1 == dom_2:
958
+ ....: return False
959
+ ....: return True
960
+ sage: check_uniqueness(graphs.RandomGNP(9, 0.5))
961
+ True
962
+
963
+ Asking for a negative distance::
964
+
965
+ sage: next(Graph(1).minimal_dominating_sets(k=-1))
966
+ Traceback (most recent call last):
967
+ ...
968
+ ValueError: the domination distance must be a nonnegative integer
969
+
970
+ Trying to dominate vertices that are not part of the graph::
971
+
972
+ sage: next(Graph(1).minimal_dominating_sets(to_dominate=['foo']))
973
+ Traceback (most recent call last):
974
+ ...
975
+ ValueError: vertex (foo) is not a vertex of the graph
976
+
977
+ The method is robust to vertices with incomparable labels::
978
+
979
+ sage: G = Graph([(1, 'A'), ('A', 2), (2, 3), (3, 1)])
980
+ sage: L = list(G.minimal_dominating_sets())
981
+ sage: len(L)
982
+ 6
983
+ sage: {3, 'A'} in L
984
+ True
985
+ """
986
+ def tree_search(H, plng, dom, i):
987
+ r"""
988
+ Enumerate minimal dominating sets recursively.
989
+
990
+ INPUT:
991
+
992
+ - ``H`` -- a graph
993
+
994
+ - ``plng`` -- a peeling of H (result of :func:`_peel`)
995
+
996
+ - ``dom`` -- a minimal dominating set of ``plng[i][1]``
997
+
998
+ - ``i`` -- integer; the current position in ``plng``
999
+
1000
+ OUTPUT:
1001
+
1002
+ An iterator over those minimal dominating sets (in ``H``) of
1003
+ ``plng[-1][1]`` that are children of ``dom`` (with respect to the
1004
+ :func:`parent` function).
1005
+
1006
+ ALGORITHM:
1007
+
1008
+ We iterate over those minimal dominating sets of ``plng[i + 1][1]`` that
1009
+ are children of dom and call recursively on each. The fact that we
1010
+ iterate over children (with respect to the `parent` function) ensures
1011
+ that we do not have repeated outputs.
1012
+ """
1013
+ if i == len(plng) - 1:
1014
+ # we reached a leaf, i.e. dom is a minimal dominating set
1015
+ # of plng[i][1] = plng[-1][1]
1016
+ yield dom
1017
+ return
1018
+
1019
+ u_next, V_next = plng[i + 1]
1020
+
1021
+ if H.is_dominating(dom, V_next):
1022
+ # if dom dominates V_next
1023
+ # then dom is its unique extension: we recurse on it
1024
+ for Di in tree_search(H, plng, dom, i + 1):
1025
+ yield Di
1026
+ return
1027
+
1028
+ # Otherwise, V_next - <what dom dominates> is what we have to dominate
1029
+ to_dom = V_next - set().union(
1030
+ *(G.neighbor_iterator(vert, closed=True)
1031
+ for vert in dom))
1032
+
1033
+ for can_ext in _cand_ext_enum(H, to_dom, u_next):
1034
+
1035
+ # We complete dom with can_ext -> canD
1036
+ canD = set().union(can_ext, dom)
1037
+
1038
+ if (not H.is_redundant(canD, V_next)
1039
+ and set(dom) == set(_parent(H, canD, plng[i][1]))):
1040
+ # By construction, can_ext is a dominating set of
1041
+ # `V_next - N[dom]`, so canD dominates V_next.
1042
+ # If canD is a legitimate child of dom and is not redundant, we
1043
+ # recurse on it:
1044
+ for Di in tree_search(H, plng, canD, i + 1):
1045
+ yield Di
1046
+ ##
1047
+ # end of tree-search routine
1048
+
1049
+ if k < 0:
1050
+ raise ValueError("the domination distance must be a nonnegative integer")
1051
+ if not k:
1052
+ yield set(G) if to_dominate is None else set(to_dominate)
1053
+ return
1054
+
1055
+ int_to_vertex = list(G)
1056
+ vertex_to_int = {u: i for i, u in enumerate(int_to_vertex)}
1057
+
1058
+ if to_dominate is None:
1059
+ vertices_to_dominate = set(range(G.order()))
1060
+ else:
1061
+ for u in to_dominate:
1062
+ if u not in G:
1063
+ raise ValueError(f"vertex ({u}) is not a vertex of the graph")
1064
+ vertices_to_dominate = {vertex_to_int[u] for u in to_dominate}
1065
+
1066
+ if not vertices_to_dominate:
1067
+ # base case: vertices_to_dominate is empty
1068
+ # the empty set/list is the only minimal DS of the empty set
1069
+ yield set()
1070
+ return
1071
+ if k > 1:
1072
+ # We build a graph H with an edge between u and v if these vertices are
1073
+ # at distance at most k in G
1074
+ H = G.__class__(G.order())
1075
+ for u, ui in vertex_to_int.items():
1076
+ H.add_edges((ui, vertex_to_int[v])
1077
+ for v in G.breadth_first_search(u, distance=k) if u != v)
1078
+ G = H
1079
+ elif work_on_copy:
1080
+ G = G.relabel(perm=vertex_to_int, inplace=False)
1081
+ else:
1082
+ # The input graph is modified
1083
+ G.relabel(perm=vertex_to_int, inplace=True)
1084
+
1085
+ peeling = _peel(G, vertices_to_dominate)
1086
+
1087
+ for dom in tree_search(G, peeling, set(), 0):
1088
+ yield {int_to_vertex[v] for v in dom}
1089
+
1090
+
1091
+ # ==============================================================================
1092
+ # Greedy heuristic for dominating set
1093
+ # ==============================================================================
1094
+
1095
+ def greedy_dominating_set(G, k=1, vertices=None, ordering=None, return_sets=False, closest=False):
1096
+ r"""
1097
+ Return a greedy distance-`k` dominating set of the graph.
1098
+
1099
+ A distance-`k` dominating set `S` of a graph `G` is a set of its vertices of
1100
+ minimal cardinality such that any vertex of `G` is in `S` or is at distance
1101
+ at most `k` from a vertex in `S`. See the :wikipedia:`Dominating_set`.
1102
+
1103
+ When `G` is directed, vertex `u` can be a dominator of vertex `v` if there
1104
+ is a directed path of length at most `k` from `u` to `v`.
1105
+
1106
+ This method implements a greedy heuristic to find a minimal dominatic set.
1107
+
1108
+ INPUT:
1109
+
1110
+ - ``G`` -- a Graph
1111
+
1112
+ - ``k`` -- integer (default: `1`); the domination distance to consider
1113
+
1114
+ - ``vertices`` -- iterable container of vertices (default: ``None``); when
1115
+ specified, return a dominating set of the specified vertices only
1116
+
1117
+ - ``ordering`` -- string (default: ``None``); specify the order in which to
1118
+ consider the vertices
1119
+
1120
+ - ``None`` -- if ``vertices`` is ``None``, then consider the vertices in
1121
+ the order given by ``list(G)``. Otherwise, consider the vertices in the
1122
+ order of iteration of ``vertices``.
1123
+
1124
+ - ``'degree_min'`` -- consider the vertices by increasing degree
1125
+
1126
+ - ``'degree_max'`` -- consider the vertices by decreasing degree
1127
+
1128
+ - ``return_sets`` -- boolean (default: ``False``); whether to return the
1129
+ vertices of the dominating set only (default), or a dictionary mapping
1130
+ each vertex of the dominating set to the set of vertices it dominates.
1131
+
1132
+ - ``closest`` -- boolean (default: ``False``); whether to attach a vertex to
1133
+ its closest dominator or not. This parameter is use only when
1134
+ ``return_sets`` is ``True``.
1135
+
1136
+ EXAMPLES:
1137
+
1138
+ Dominating sets of a path::
1139
+
1140
+ sage: from sage.graphs.domination import greedy_dominating_set
1141
+ sage: G = graphs.PathGraph(5)
1142
+ sage: sorted(greedy_dominating_set(G, ordering=None))
1143
+ [0, 2, 4]
1144
+ sage: sorted(greedy_dominating_set(G, ordering='degree_min'))
1145
+ [0, 2, 4]
1146
+ sage: sorted(greedy_dominating_set(G, ordering='degree_max'))
1147
+ [1, 3]
1148
+ sage: sorted(greedy_dominating_set(G, k=2, ordering=None))
1149
+ [0, 3]
1150
+ sage: sorted(greedy_dominating_set(G, k=2, ordering='degree_min'))
1151
+ [0, 4]
1152
+ sage: sorted(greedy_dominating_set(G, k=2, ordering='degree_max'))
1153
+ [1, 4]
1154
+ sage: greedy_dominating_set(G, k=3, ordering='degree_min', return_sets=True, closest=False)
1155
+ {0: {0, 1, 2, 3}, 4: {4}}
1156
+ sage: greedy_dominating_set(G, k=3, ordering='degree_min', return_sets=True, closest=True)
1157
+ {0: {0, 2, 3}, 4: {1, 4}}
1158
+
1159
+ Asking for a dominating set of a subset of vertices::
1160
+
1161
+ sage: from sage.graphs.domination import greedy_dominating_set
1162
+ sage: from sage.graphs.domination import is_dominating
1163
+ sage: G = graphs.PetersenGraph()
1164
+ sage: vertices = {0, 1, 2, 3, 4, 5}
1165
+ sage: dom = greedy_dominating_set(G, vertices=vertices, return_sets=True)
1166
+ sage: sorted(dom)
1167
+ [0, 2]
1168
+ sage: is_dominating(G, dom, focus=vertices)
1169
+ True
1170
+ sage: is_dominating(G, dom)
1171
+ False
1172
+ sage: dominated = [u for v in dom for u in dom[v]]
1173
+ sage: sorted(dominated) == sorted(vertices)
1174
+ True
1175
+
1176
+ Influence of the ordering of the vertices on the result::
1177
+
1178
+ sage: from sage.graphs.domination import greedy_dominating_set
1179
+ sage: G = graphs.StarGraph(4)
1180
+ sage: greedy_dominating_set(G, vertices=[0, 1, 2, 3, 4])
1181
+ [0]
1182
+ sage: sorted(greedy_dominating_set(G, vertices=[1, 2, 3, 4, 0]))
1183
+ [1, 2, 3, 4]
1184
+
1185
+ Dominating set of a directed graph::
1186
+
1187
+ sage: from sage.graphs.domination import greedy_dominating_set
1188
+ sage: D = digraphs.Path(3)
1189
+ sage: sorted(greedy_dominating_set(D, vertices=[0, 1, 2]))
1190
+ [0, 2]
1191
+
1192
+ TESTS:
1193
+
1194
+ Random tests::
1195
+
1196
+ sage: from sage.graphs.domination import greedy_dominating_set
1197
+ sage: from sage.graphs.domination import is_dominating
1198
+ sage: G = graphs.RandomGNP(15, .2)
1199
+ sage: for o in [None, "degree_min", "degree_max"]:
1200
+ ....: for c in [True, False]:
1201
+ ....: dom = greedy_dominating_set(G, ordering=o, closest=c)
1202
+ ....: if not is_dominating(G, dom):
1203
+ ....: print("something goes wrong")
1204
+
1205
+ Corner cases::
1206
+
1207
+ sage: greedy_dominating_set(Graph())
1208
+ []
1209
+ sage: greedy_dominating_set(Graph(1))
1210
+ [0]
1211
+ sage: greedy_dominating_set(Graph(2))
1212
+ [0, 1]
1213
+ sage: G = graphs.PathGraph(5)
1214
+ sage: dom = greedy_dominating_set(G, vertices=[0, 1, 3, 4])
1215
+
1216
+ The method is robust to vertices with incomparable labels::
1217
+
1218
+ sage: G = Graph([(1, 'A')])
1219
+ sage: len(greedy_dominating_set(G))
1220
+ 1
1221
+
1222
+ Check parameters::
1223
+
1224
+ sage: greedy_dominating_set(G, ordering='foo')
1225
+ Traceback (most recent call last):
1226
+ ...
1227
+ ValueError: ordering must be None, "degree_min" or "degree_max"
1228
+ """
1229
+ if vertices is None:
1230
+ vertices = list(G)
1231
+ else:
1232
+ vertices = [u for u in vertices if u in G]
1233
+
1234
+ if ordering in ["degree_min", "degree_max"]:
1235
+ vertices = sorted(vertices, key=G.degree, reverse=ordering.endswith("max"))
1236
+ elif ordering is not None:
1237
+ raise ValueError('ordering must be None, "degree_min" or "degree_max"')
1238
+
1239
+ if not G:
1240
+ return dict() if return_sets else []
1241
+ if not k:
1242
+ return vertices
1243
+
1244
+ n = G.order()
1245
+ dom = dict()
1246
+ seen = set()
1247
+ # We want to dominate only the set vertices
1248
+ to_avoid = set() if len(vertices) == n else set(G).difference(vertices)
1249
+
1250
+ if closest:
1251
+ # Attach each dominated vertex to its closest dominator
1252
+ from sage.rings.infinity import Infinity
1253
+ dominator = {u: (u, +Infinity) for u in vertices}
1254
+ for u in vertices:
1255
+ if u in seen:
1256
+ continue
1257
+ dom[u] = set()
1258
+ for v, d in G.breadth_first_search(u, distance=k, report_distance=True):
1259
+ if v in to_avoid:
1260
+ continue
1261
+ if v not in seen:
1262
+ dom[u].add(v)
1263
+ seen.add(v)
1264
+ dominator[v] = (u, d)
1265
+ else:
1266
+ x, dx = dominator[v]
1267
+ if dx < d:
1268
+ dom[x].discard(v)
1269
+ dom[u].add(v)
1270
+ dominator[v] = (u, d)
1271
+
1272
+ else:
1273
+ for u in vertices:
1274
+ if u in seen:
1275
+ continue
1276
+ dom[u] = set()
1277
+ for v in G.breadth_first_search(u, distance=k):
1278
+ if v not in to_avoid and v not in seen:
1279
+ dom[u].add(v)
1280
+ seen.add(v)
1281
+
1282
+ if return_sets:
1283
+ return dom
1284
+ else:
1285
+ return list(dom)
1286
+
1287
+
1288
+ def maximum_leaf_number(G, solver=None, verbose=0, integrality_tolerance=1e-3):
1289
+ r"""
1290
+ Return the maximum leaf number of the graph.
1291
+
1292
+ The maximum leaf number is the maximum possible number of leaves of a
1293
+ spanning tree of `G`. This is also the cardinality of the complement of a
1294
+ minimum connected dominating set.
1295
+ See the :wikipedia:`Connected_dominating_set`.
1296
+
1297
+ The MLN of a graph with less than 2 vertices is 0, while the MLN of a connected
1298
+ graph with 2 or 3 vertices is 1 or 2 respectively.
1299
+
1300
+ INPUT:
1301
+
1302
+ - ``G`` -- a Graph
1303
+
1304
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1305
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1306
+ is used. For more information on MILP solvers and which default solver is
1307
+ used, see the method :meth:`solve
1308
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1309
+ :class:`MixedIntegerLinearProgram
1310
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1311
+
1312
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1313
+ to 0 by default, which means quiet.
1314
+
1315
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1316
+ over an inexact base ring; see
1317
+ :meth:`MixedIntegerLinearProgram.get_values`.
1318
+
1319
+ EXAMPLES:
1320
+
1321
+ Empty graph::
1322
+
1323
+ sage: G = Graph()
1324
+ sage: G.maximum_leaf_number()
1325
+ 0
1326
+
1327
+ Petersen graph::
1328
+
1329
+ sage: G = graphs.PetersenGraph()
1330
+ sage: G.maximum_leaf_number() # needs sage.numerical.mip
1331
+ 6
1332
+
1333
+ TESTS:
1334
+
1335
+ One vertex::
1336
+
1337
+ sage: G = Graph(1)
1338
+ sage: G.maximum_leaf_number()
1339
+ 0
1340
+
1341
+ Two vertices::
1342
+
1343
+ sage: G = graphs.PathGraph(2)
1344
+ sage: G.maximum_leaf_number()
1345
+ 1
1346
+
1347
+ Unconnected graph::
1348
+
1349
+ sage: G = Graph(2)
1350
+ sage: G.maximum_leaf_number()
1351
+ Traceback (most recent call last):
1352
+ ...
1353
+ ValueError: the graph must be connected
1354
+ """
1355
+ if G.order() <= 1:
1356
+ return 0
1357
+ if not G.is_connected():
1358
+ raise ValueError('the graph must be connected')
1359
+ if G.order() <= 3:
1360
+ return G.order() - 1
1361
+ return G.order() - dominating_set(G, connected=True, value_only=True,
1362
+ solver=solver, verbose=verbose,
1363
+ integrality_tolerance=integrality_tolerance)