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,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)
|