Skip to content

add an iterator over the minimal separators of a graph #39341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions src/doc/en/reference/references/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ REFERENCES:
"Spook: Sponge-Based Leakage-Resilient AuthenticatedEncryption with a Masked Tweakable Block Cipher"
https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/Spook-spec.pdf

.. [BBC2000] Anne Berry, Jean-Paul Bordat, Olivier Cogis. *Generating all the
minimal separators of a graph*. International Journal of
Foundations of Computer Science, 11(3):397-403, 2000.
:doi:`10.1142/S0129054100000211`

.. [BCDM2019] \T. Beyne, Y. L. Chen, C. Dobraunig, B. Mennink. *Elephant v1* (2019)
https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/elephant-spec.pdf

Expand Down
115 changes: 110 additions & 5 deletions src/sage/graphs/connectivity.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Here is what the module can do:
:meth:`is_triconnected` | Check whether the graph is triconnected.
:meth:`spqr_tree` | Return a SPQR-tree representing the triconnected components of the graph.
:meth:`spqr_tree_to_graph` | Return the graph represented by the SPQR-tree `T`.
:meth:`minimal_separators` | Return an iterator over the minimal separators of ``G``.

Methods
-------
Expand Down Expand Up @@ -134,11 +135,12 @@ def is_connected(G, forbidden_vertices=None):
if not G.order():
return True

forbidden = None if forbidden_vertices is None else set(forbidden_vertices)

try:
return G._backend.is_connected(forbidden_vertices=forbidden_vertices)
return G._backend.is_connected(forbidden_vertices=forbidden)
except AttributeError:
# Search for a vertex in G that is not forbidden
forbidden = set(forbidden_vertices) if forbidden_vertices else set()
if forbidden:
for v in G:
if v not in forbidden:
Expand Down Expand Up @@ -200,6 +202,9 @@ def connected_components(G, sort=None, key=None, forbidden_vertices=None):
sage: G = graphs.PathGraph(5)
sage: connected_components(G, sort=True, forbidden_vertices=[2])
[[0, 1], [3, 4]]
sage: connected_components(G, sort=True,
....: forbidden_vertices=G.neighbor_iterator(2, closed=True))
[[0], [4]]

TESTS:

Expand Down Expand Up @@ -244,7 +249,7 @@ def connected_components(G, sort=None, key=None, forbidden_vertices=None):
for v in G:
if v not in seen:
c = connected_component_containing_vertex(G, v, sort=sort, key=key,
forbidden_vertices=forbidden_vertices)
forbidden_vertices=seen)
seen.update(c)
components.append(c)
components.sort(key=lambda comp: -len(comp))
Expand Down Expand Up @@ -423,12 +428,14 @@ def connected_component_containing_vertex(G, vertex, sort=None, key=None,
if (not sort) and key:
raise ValueError('sort keyword is False, yet a key function is given')

forbidden = None if forbidden_vertices is None else list(forbidden_vertices)

try:
c = list(G._backend.depth_first_search(vertex, ignore_direction=True,
forbidden_vertices=forbidden_vertices))
forbidden_vertices=forbidden))
except AttributeError:
c = list(G.depth_first_search(vertex, ignore_direction=True,
forbidden_vertices=forbidden_vertices))
forbidden_vertices=forbidden))

if sort:
return sorted(c, key=key)
Expand Down Expand Up @@ -1256,6 +1263,104 @@ def is_cut_vertex(G, u, weak=False):
return is_vertex_cut(G, [u], weak=weak)


def minimal_separators(G, forbidden_vertices=None):
r"""
Return an iterator over the minimal separators of ``G``.

A separator in a graph is a set of vertices whose removal increases the
number of connected components. In other words, a separator is a vertex
cut. This method implements the algorithm proposed in [BBC2000]_.
It computes the set `S` of minimal separators of a graph in `O(n^3)` time
per separator, and so overall in `O(n^3 |S|)` time.

.. WARNING::

Note that all separators are recorded during the execution of the
algorithm and so the memory consumption of this method might be huge.

INPUT:

- ``G`` -- an undirected graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search

EXAMPLES::

sage: P = graphs.PathGraph(5)
sage: sorted(sorted(sep) for sep in P.minimal_separators())
[[1], [2], [3]]
sage: C = graphs.CycleGraph(6)
sage: sorted(sorted(sep) for sep in C.minimal_separators())
[[0, 2], [0, 3], [0, 4], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5], [3, 5]]
sage: sorted(sorted(sep) for sep in C.minimal_separators(forbidden_vertices=[0]))
[[2], [3], [4]]
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators())
[[1], [2], [3], [5, 7], [5, 8], [5, 9], [6, 8],
[6, 9], [6, 10], [7, 9], [7, 10], [8, 10]]
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators(forbidden_vertices=[10]))
[[1], [2], [3], [6], [7], [8]]

sage: G = graphs.RandomGNP(10, .3)
sage: all(G.is_vertex_cut(sep) for sep in G.minimal_separators())
True

TESTS::

sage: list(Graph().minimal_separators())
[]
sage: list(Graph(1).minimal_separators())
[]
sage: list(Graph(2).minimal_separators())
[]
sage: from sage.graphs.connectivity import minimal_separators
sage: list(minimal_separators(DiGraph()))
Traceback (most recent call last):
...
ValueError: the input must be an undirected graph
"""
from sage.graphs.graph import Graph
if not isinstance(G, Graph):
raise ValueError("the input must be an undirected graph")

if forbidden_vertices is not None and G.order() >= 3:
# Build the subgraph with active vertices
G = G.subgraph(set(G).difference(forbidden_vertices), immutable=True)

if G.order() < 3:
return
if not G.is_connected():
for cc in G.connected_components(sort=False):
if len(cc) > 2:
yield from minimal_separators(G.subgraph(cc))
return

# Initialization - identify separators needing further inspection
cdef list to_explore = []
for v in G:
# iterate over the connected components of G \ N[v]
for comp in G.connected_components(sort=False, forbidden_vertices=G.neighbor_iterator(v, closed=True)):
# The vertex boundary of comp in G is a separator
nh = G.vertex_boundary(comp)
if nh:
to_explore.append(frozenset(nh))

# Generation of all minimal separators
cdef set separators = set()
while to_explore:
sep = to_explore.pop()
if sep in separators:
continue
yield set(sep)
separators.add(sep)
for v in sep:
# iterate over the connected components of G \ sep \ N(v)
for comp in G.connected_components(sort=False, forbidden_vertices=sep.union(G.neighbor_iterator(v))):
nh = G.vertex_boundary(comp)
if nh:
to_explore.append(frozenset(nh))


def edge_connectivity(G,
value_only=True,
implementation=None,
Expand Down
2 changes: 2 additions & 0 deletions src/sage/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -9300,6 +9300,7 @@ def bipartite_double(self, extended=False):
from sage.graphs.orientations import eulerian_orientation
from sage.graphs.connectivity import bridges, cleave, spqr_tree
from sage.graphs.connectivity import is_triconnected
from sage.graphs.connectivity import minimal_separators
from sage.graphs.comparability import is_comparability
from sage.graphs.comparability import is_permutation
geodetic_closure = LazyImport('sage.graphs.convexity_properties', 'geodetic_closure', at_startup=True)
Expand Down Expand Up @@ -9360,6 +9361,7 @@ def bipartite_double(self, extended=False):
"cleave" : "Connectivity, orientations, trees",
"spqr_tree" : "Connectivity, orientations, trees",
"is_triconnected" : "Connectivity, orientations, trees",
"minimal_separators" : "Connectivity, orientations, trees",
"is_dominating" : "Domination",
"is_redundant" : "Domination",
"private_neighbors" : "Domination",
Expand Down
Loading