Repository: sagemath/sage

Issue:
Iterator over the distance-k dominating sets
### Is there an existing issue for this?

- [X] I have searched the existing issues for a bug report that matches the one I want to file, without success.

### Problem Description

[Question 67263 ](https://ask.sagemath.org/question/67263/is-there-any-code-to-list-all-minimal-k-distance-dominating-sets-of-a-simple-graph-g-in-sagemath/) on [ask.sagemath.org](https://ask.sagemath.org/) asks for a method to list all minimal distance-k dominating sets of a graph.


### Proposed Solution

To iterate over the minimal distance k dominating sets, we can modify the working copy of the graph in method `minimal_dominating_sets`, adding edges between vertices at distance at most k.

To iterate over minimum distance k dominating set, we can create a new method `dominating_sets` and use constraints generation to prevent finding twice a minimum dominating set.

### Alternatives Considered

None.

### Additional Information

The default value of parameter `work_on_copy` is `True`, and so by default the input graph is modified. This is a bad choice. We should at least change the default value to `False`. 


Discussion:

Resolving patch:
diff --git a/src/sage/graphs/domination.py b/src/sage/graphs/domination.py
--- a/src/sage/graphs/domination.py
+++ b/src/sage/graphs/domination.py
@@ -11,6 +11,7 @@
     :delim: |
 
     :meth:`~dominating_set` | Return a minimum distance-`k` dominating set of the graph.
+    :meth:`~dominating_sets` | Return an iterator over the minimum distance-`k` dominating sets of the graph.
     :meth:`~minimal_dominating_sets` | Return an iterator over the minimal dominating sets of a graph.
     :meth:`~is_dominating` | Check whether a set of vertices dominates a graph.
     :meth:`~is_redundant` | Check whether a set of vertices has redundant vertices (with respect to domination).
@@ -214,13 +215,14 @@ def private_neighbors(G, vertex, dom):
 
 
 # ==============================================================================
-# Computation of minimum dominating
+# Computation of minimum dominating sets
 # ==============================================================================
 
-def dominating_set(g, k=1, independent=False, total=False, value_only=False,
-                   solver=None, verbose=0, *, integrality_tolerance=1e-3):
+def dominating_sets(g, k=1, independent=False, total=False,
+                    solver=None, verbose=0, *, integrality_tolerance=1e-3):
     r"""
-    Return a minimum distance-`k` dominating set of the graph.
+    Return an iterator over the minimum distance-`k` dominating sets
+    of the graph.
 
     A minimum dominating set `S` of a graph `G` is a set of its vertices of
     minimal cardinality such that any vertex of `G` is in `S` or has one of its
@@ -242,21 +244,21 @@ def dominating_set(g, k=1, independent=False, total=False, value_only=False,
         \mbox{Such that : }&\forall v \in G, b_v+\sum_{u \in N^k(v)} b_u\geq 1\\
         &\forall x\in G, b_x\mbox{ is a binary variable}
 
+    We use constraints generation to iterate over the minimum distance-`k`
+    dominating sets. That is, after reporting a solution, we add a constraint to
+    discard it and solve the problem again until no more solution can be found.
+
     INPUT:
 
     - ``k`` -- a non-negative integer (default: ``1``); the domination distance
 
-    - ``independent`` -- boolean (default: ``False``); when ``True``, computes a
-      minimum independent dominating set, that is a minimum dominating set that
-      is also an independent set (see also
+    - ``independent`` -- boolean (default: ``False``); when ``True``, computes
+      minimum independent dominating sets, that is minimum dominating sets that
+      are also independent sets (see also
       :meth:`~sage.graphs.graph.independent_set`)
 
-    - ``total`` -- boolean (default: ``False``); when ``True``, computes a total
-      dominating set (see the See the :wikipedia:`Dominating_set`)
-
-    - ``value_only`` -- boolean (default: ``False``); whether to only return the
-      cardinality of the computed dominating set, or to return its list of
-      vertices (default)
+    - ``total`` -- boolean (default: ``False``); when ``True``, computes total
+      dominating sets (see the See the :wikipedia:`Dominating_set`)
 
     - ``solver`` -- string (default: ``None``); specify a Mixed Integer Linear
       Programming (MILP) solver to be used. If set to ``None``, the default one
@@ -275,11 +277,11 @@ def dominating_set(g, k=1, independent=False, total=False, value_only=False,
 
     EXAMPLES:
 
-    A basic illustration on a ``PappusGraph``::
+    Number of distance-`k` dominating sets of a Path graph of order 10::
 
-        sage: g = graphs.PappusGraph()
-        sage: g.dominating_set(value_only=True)
-        5
+        sage: g = graphs.PathGraph(10)
+        sage: [sum(1 for _ in g.dominating_sets(k=k)) for k in range(11)]
+        [1, 13, 1, 13, 25, 2, 4, 6, 8, 10, 10]
 
     If we build a graph from two disjoint stars, then link their centers we will
     find a difference between the cardinality of an independent set and a stable
@@ -287,16 +289,35 @@ def dominating_set(g, k=1, independent=False, total=False, value_only=False,
 
         sage: g = 2 * graphs.StarGraph(5)
         sage: g.add_edge(0, 6)
-        sage: len(g.dominating_set())
-        2
-        sage: len(g.dominating_set(independent=True))
-        6
+        sage: [sum(1 for _ in g.dominating_sets(k=k)) for k in range(11)]
+        [1, 1, 2, 12, 12, 12, 12, 12, 12, 12, 12]
 
     The total dominating set of the Petersen graph has cardinality 4::
 
         sage: G = graphs.PetersenGraph()
         sage: G.dominating_set(total=True, value_only=True)
         4
+        sage: sorted(G.dominating_sets(k=1))
+        [[0, 2, 6],
+         [0, 3, 9],
+         [0, 7, 8],
+         [1, 3, 7],
+         [1, 4, 5],
+         [1, 8, 9],
+         [2, 4, 8],
+         [2, 5, 9],
+         [3, 5, 6],
+         [4, 6, 7]]
+
+    Independent distance-`k` dominating sets of a Path graph::
+
+        sage: G = graphs.PathGraph(6)
+        sage: sorted(G.dominating_sets(k=1, independent=True))
+        [[1, 4]]
+        sage: sorted(G.dominating_sets(k=2, independent=True))
+        [[0, 3], [0, 4], [0, 5], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5]]
+        sage: sorted(G.dominating_sets(k=3, independent=True))
+        [[2], [3]]
 
     The dominating set is calculated for both the directed and undirected graphs
     (modification introduced in :trac:`17905`)::
@@ -304,33 +325,41 @@ def dominating_set(g, k=1, independent=False, total=False, value_only=False,
         sage: g = digraphs.Path(3)
         sage: g.dominating_set(value_only=True)
         2
+        sage: list(g.dominating_sets())
+        [[0, 1], [0, 2]]
+        sage: list(g.dominating_sets(k=2))
+        [[0]]
         sage: g = graphs.PathGraph(3)
         sage: g.dominating_set(value_only=True)
         1
+        sage: next(g.dominating_sets())
+        [1]
 
-    Cardinality of distance-`k` dominating sets::
+    TESTS::
 
-        sage: G = graphs.PetersenGraph()
-        sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)]
-        [10, 3, 1]
-        sage: G = graphs.PathGraph(5)
-        sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)]
-        [5, 2, 1]
+        sage: g = Graph([(0, 1)])
+        sage: next(g.dominating_sets(k=-1))
+        Traceback (most recent call last):
+        ...
+        ValueError: the domination distance must be a non-negative integer
     """
     g._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total)
 
     if not k:
-        return g.order() if value_only else list(g)
+        yield list(g)
+        return
     elif k < 0:
         raise ValueError("the domination distance must be a non-negative integer")
 
     from sage.numerical.mip import MixedIntegerLinearProgram
-    p = MixedIntegerLinearProgram(maximization=False, solver=solver)
+    from sage.numerical.mip import MIPSolverException
+    p = MixedIntegerLinearProgram(maximization=False, solver=solver,
+                                  constraint_generation=True)
     b = p.new_variable(binary=True)
 
     if k == 1:
         # For any vertex v, one of its neighbors or v itself is in the minimum
-        # dominating set. If g is directed, we use the in neighbors of v
+        # dominating set. If g is directed, we use the in-neighbors of v
         # instead.
         neighbors_iter = g.neighbor_in_iterator if g.is_directed() else g.neighbor_iterator
     else:
@@ -359,11 +388,128 @@ def neighbors_iter(x):
     # Minimizes the number of vertices used
     p.set_objective(p.sum(b[v] for v in g))
 
-    p.solve(log=verbose)
-    b = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
-    dom = [v for v in g if b[v]]
-    return Integer(len(dom)) if value_only else dom
+    best = g.order()
+    while True:
+        try:
+            p.solve(log=verbose)
+        except MIPSolverException:
+            # No more solutions
+            break
+        b_val = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
+        dom = [v for v in g if b_val[v]]
+        if len(dom) > best:
+            # All minimum solution have been reported
+            break
+        yield dom
+        best = len(dom)
+        # Prevent finding twice a solution
+        p.add_constraint(p.sum(b[u] for u in dom) <= best - 1)
+
+
+def dominating_set(g, k=1, independent=False, total=False, value_only=False,
+                   solver=None, verbose=0, *, integrality_tolerance=1e-3):
+    r"""
+    Return a minimum distance-`k` dominating set of the graph.
+
+    A minimum dominating set `S` of a graph `G` is a set of its vertices of
+    minimal cardinality such that any vertex of `G` is in `S` or has one of its
+    neighbors in `S`. See the :wikipedia:`Dominating_set`.
+
+    A minimum distance-`k` dominating set is a set `S` of vertices of `G` of
+    minimal cardinality such that any vertex of `G` is in `S` or at distance at
+    most `k` from a vertex in `S`. A distance-`0` dominating set is the set of
+    vertices itself, and when `k` is the radius of the graph, any vertex
+    dominates all the other vertices.
+
+    As an optimization problem, it can be expressed as follows, where `N^k(u)`
+    denotes the set of vertices at distance at most `k` from `u` (the set of
+    neighbors when `k=1`):
+
+    .. MATH::
+
+        \mbox{Minimize : }&\sum_{v\in G} b_v\\
+        \mbox{Such that : }&\forall v \in G, b_v+\sum_{u \in N^k(v)} b_u\geq 1\\
+        &\forall x\in G, b_x\mbox{ is a binary variable}
+
+    INPUT:
+
+    - ``k`` -- a non-negative integer (default: ``1``); the domination distance
+
+    - ``independent`` -- boolean (default: ``False``); when ``True``, computes a
+      minimum independent dominating set, that is a minimum dominating set that
+      is also an independent set (see also
+      :meth:`~sage.graphs.graph.independent_set`)
 
+    - ``total`` -- boolean (default: ``False``); when ``True``, computes a total
+      dominating set (see the See the :wikipedia:`Dominating_set`)
+
+    - ``value_only`` -- boolean (default: ``False``); whether to only return the
+      cardinality of the computed dominating set, or to return its list of
+      vertices (default)
+
+    - ``solver`` -- string (default: ``None``); specify a Mixed Integer Linear
+      Programming (MILP) solver to be used. If set to ``None``, the default one
+      is used. For more information on MILP solvers and which default solver is
+      used, see the method :meth:`solve
+      <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
+      :class:`MixedIntegerLinearProgram
+      <sage.numerical.mip.MixedIntegerLinearProgram>`.
+
+    - ``verbose`` -- integer (default: ``0``); sets the level of verbosity. Set
+      to 0 by default, which means quiet.
+
+    - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
+      over an inexact base ring; see
+      :meth:`MixedIntegerLinearProgram.get_values`.
+
+    EXAMPLES:
+
+    A basic illustration on a ``PappusGraph``::
+
+        sage: g = graphs.PappusGraph()
+        sage: g.dominating_set(value_only=True)
+        5
+
+    If we build a graph from two disjoint stars, then link their centers we will
+    find a difference between the cardinality of an independent set and a stable
+    independent set::
+
+        sage: g = 2 * graphs.StarGraph(5)
+        sage: g.add_edge(0, 6)
+        sage: len(g.dominating_set())
+        2
+        sage: len(g.dominating_set(independent=True))
+        6
+
+    The total dominating set of the Petersen graph has cardinality 4::
+
+        sage: G = graphs.PetersenGraph()
+        sage: G.dominating_set(total=True, value_only=True)
+        4
+
+    The dominating set is calculated for both the directed and undirected graphs
+    (modification introduced in :trac:`17905`)::
+
+        sage: g = digraphs.Path(3)
+        sage: g.dominating_set(value_only=True)
+        2
+        sage: g = graphs.PathGraph(3)
+        sage: g.dominating_set(value_only=True)
+        1
+
+    Cardinality of distance-`k` dominating sets::
+
+        sage: G = graphs.PetersenGraph()
+        sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)]
+        [10, 3, 1]
+        sage: G = graphs.PathGraph(5)
+        sage: [G.dominating_set(k=k, value_only=True) for k in range(G.radius() + 1)]
+        [5, 2, 1]
+    """
+    dom = next(dominating_sets(g, k=k, independent=independent, total=total,
+                               solver=solver, verbose=verbose,
+                               integrality_tolerance=integrality_tolerance))
+    return Integer(len(dom)) if value_only else dom
 
 # ==============================================================================
 # Enumeration of minimal dominating set as described in [BDHPR2019]_
diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py
--- a/src/sage/graphs/generic_graph.py
+++ b/src/sage/graphs/generic_graph.py
@@ -24145,6 +24145,7 @@ def is_self_complementary(self):
     from sage.graphs.connectivity import edge_connectivity
     from sage.graphs.connectivity import vertex_connectivity
     from sage.graphs.distances_all_pairs import szeged_index
+    from sage.graphs.domination import dominating_sets
     from sage.graphs.domination import dominating_set
     from sage.graphs.domination import greedy_dominating_set
     from sage.graphs.base.static_dense_graph import connected_subgraph_iterator


Test patch: