Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions src/sage/graphs/domination.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def is_redundant(G, dom, focus=None):
False
"""
dom = list(dom)
focus = list(G) if focus is None else list(focus)
focus = G if focus is None else set(focus)

# dominator[v] (for v in focus) will be equal to:
# - (0, None) if v has no neighbor in dom
Expand Down Expand Up @@ -345,13 +345,20 @@ def dominating_sets(g, k=1, independent=False, total=False,
Traceback (most recent call last):
...
ValueError: the domination distance must be a non-negative integer

The method is robust to vertices with incomparable labels::

sage: G = Graph([(1, 'A'), ('A', 2), (2, 3), (3, 1)])
sage: L = list(G.dominating_sets())
sage: len(L)
6
"""
g._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total)

if not k:
yield list(g)
return
elif k < 0:
if k < 0:
raise ValueError("the domination distance must be a non-negative integer")

from sage.numerical.mip import MixedIntegerLinearProgram
Expand Down Expand Up @@ -721,8 +728,8 @@ def _aux_with_rep(H, to_dom, u_next):

# Here we use aux_with_rep twice to enumerate the minimal
# dominating sets while avoiding repeated outputs
for (X, i) in _aux_with_rep(G, to_dom, u_next):
for (Y, j) in _aux_with_rep(G, to_dom, u_next):
for X, i in _aux_with_rep(G, to_dom, u_next):
for Y, j in _aux_with_rep(G, to_dom, u_next):
if j >= i:
# This is the first time we meet X: we output it
yield X
Expand All @@ -732,7 +739,7 @@ def _aux_with_rep(H, to_dom, u_next):
break


def minimal_dominating_sets(G, to_dominate=None, work_on_copy=False, k=1):
def minimal_dominating_sets(G, to_dominate=None, work_on_copy=True, k=1):
r"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a deprecation period for this change of default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a deprecation, but here I consider that we fix a bug. The default value of parameter work_on_copy was not consistent with the code: the graph was modified when set to True and a copy was created when set to False.

Furthermore, I think it is an error to modify the input graph without any explicitly action from the user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, makes sense.

Return an iterator over the minimal dominating sets of a graph.

Expand All @@ -743,7 +750,7 @@ def minimal_dominating_sets(G, to_dominate=None, work_on_copy=False, k=1):
- ``to_dominate`` -- vertex iterable or ``None`` (default: ``None``);
the set of vertices to be dominated.

- ``work_on_copy`` -- boolean (default: ``False``); whether or not to work on
- ``work_on_copy`` -- boolean (default: ``True``); whether or not to work on
a copy of the input graph; if set to ``False``, the input graph will be
modified (relabeled).

Expand Down Expand Up @@ -836,6 +843,19 @@ def minimal_dominating_sets(G, to_dominate=None, work_on_copy=False, k=1):
sage: list(G.minimal_dominating_sets(k=3))
[{(0, 0)}, {(0, 1)}, {(0, 2)}, {(1, 0)}, {(1, 1)}, {(1, 2)}]

When parameter ``work_on_copy`` is ``False``, the input graph is modified
(relabeled)::

sage: G = Graph([('A', 'B')])
sage: _ = list(G.minimal_dominating_sets(work_on_copy=True))
sage: set(G) == {'A', 'B'}
True
sage: _ = list(G.minimal_dominating_sets(work_on_copy=False))
sage: set(G) == {'A', 'B'}
False
sage: set(G) == {0, 1}
True

TESTS:

The empty graph is handled correctly::
Expand Down Expand Up @@ -891,6 +911,15 @@ def minimal_dominating_sets(G, to_dominate=None, work_on_copy=False, k=1):
Traceback (most recent call last):
...
ValueError: vertex (foo) is not a vertex of the graph

The method is robust to vertices with incomparable labels::

sage: G = Graph([(1, 'A'), ('A', 2), (2, 3), (3, 1)])
sage: L = list(G.minimal_dominating_sets())
sage: len(L)
6
sage: {3, 'A'} in L
True
"""
def tree_search(H, plng, dom, i):
r"""
Expand Down Expand Up @@ -944,7 +973,8 @@ def tree_search(H, plng, dom, i):
# We complete dom with can_ext -> canD
canD = set().union(can_ext, dom)

if (not H.is_redundant(canD, V_next)) and set(dom) == set(_parent(H, canD, plng[i][1])):
if (not H.is_redundant(canD, V_next)
and set(dom) == set(_parent(H, canD, plng[i][1]))):
# By construction, can_ext is a dominating set of
# `V_next - N[dom]`, so canD dominates V_next.
# If canD is a legitimate child of dom and is not redundant, we
Expand All @@ -954,6 +984,12 @@ def tree_search(H, plng, dom, i):
##
# end of tree-search routine

if k < 0:
raise ValueError("the domination distance must be a non-negative integer")
if not k:
yield set(G) if to_dominate is None else set(to_dominate)
return

int_to_vertex = list(G)
vertex_to_int = {u: i for i, u in enumerate(int_to_vertex)}

Expand All @@ -965,28 +1001,24 @@ def tree_search(H, plng, dom, i):
raise ValueError(f"vertex ({u}) is not a vertex of the graph")
vertices_to_dominate = {vertex_to_int[u] for u in to_dominate}

if k < 0:
raise ValueError("the domination distance must be a non-negative integer")
elif not k:
yield set(int_to_vertex) if to_dominate is None else set(to_dominate)
if not vertices_to_dominate:
# base case: vertices_to_dominate is empty
# the empty set/list is the only minimal DS of the empty set
yield set()
return
elif k > 1:
if k > 1:
# We build a graph H with an edge between u and v if these vertices are
# at distance at most k in G
H = G.__class__(G.order())
for u, ui in vertex_to_int.items():
H.add_edges((ui, vertex_to_int[v]) for v in G.breadth_first_search(u, distance=k) if u != v)
H.add_edges((ui, vertex_to_int[v])
for v in G.breadth_first_search(u, distance=k) if u != v)
G = H
elif work_on_copy:
G.relabel(perm=vertex_to_int)
else:
G = G.relabel(perm=vertex_to_int, inplace=False)

if not vertices_to_dominate:
# base case: vertices_to_dominate is empty
# the empty set/list is the only minimal DS of the empty set
yield set()
return
else:
# The input graph is modified
G.relabel(perm=vertex_to_int, inplace=True)

peeling = _peel(G, vertices_to_dominate)

Expand Down Expand Up @@ -1119,6 +1151,12 @@ def greedy_dominating_set(G, k=1, vertices=None, ordering=None, return_sets=Fals
sage: G = graphs.PathGraph(5)
sage: dom = greedy_dominating_set(G, vertices=[0, 1, 3, 4])

The method is robust to vertices with incomparable labels::

sage: G = Graph([(1, 'A')])
sage: len(greedy_dominating_set(G))
1

Check parameters::

sage: greedy_dominating_set(G, ordering="foo")
Expand Down