Skip to content

Commit 2ae226d

Browse files
author
Release Manager
committed
sagemathgh-39341: add an iterator over the minimal separators of a graph Fixes sagemath#37743. Adds an iterator over the minimal separators of an undirected graphs. We also fix the behavior of some methods using parameter `forbidden_vertices` (added in sagemath#39151) when the input is an iterator. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#39341 Reported by: David Coudert Reviewer(s): Dima Pasechnik
2 parents 5040ad7 + 2c56090 commit 2ae226d

File tree

3 files changed

+117
-5
lines changed

3 files changed

+117
-5
lines changed

src/doc/en/reference/references/index.rst

+5
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,11 @@ REFERENCES:
490490
"Spook: Sponge-Based Leakage-Resilient AuthenticatedEncryption with a Masked Tweakable Block Cipher"
491491
https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/Spook-spec.pdf
492492
493+
.. [BBC2000] Anne Berry, Jean-Paul Bordat, Olivier Cogis. *Generating all the
494+
minimal separators of a graph*. International Journal of
495+
Foundations of Computer Science, 11(3):397-403, 2000.
496+
:doi:`10.1142/S0129054100000211`
497+
493498
.. [BCDM2019] \T. Beyne, Y. L. Chen, C. Dobraunig, B. Mennink. *Elephant v1* (2019)
494499
https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/elephant-spec.pdf
495500

src/sage/graphs/connectivity.pyx

+110-5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Here is what the module can do:
5555
:meth:`is_triconnected` | Check whether the graph is triconnected.
5656
:meth:`spqr_tree` | Return a SPQR-tree representing the triconnected components of the graph.
5757
:meth:`spqr_tree_to_graph` | Return the graph represented by the SPQR-tree `T`.
58+
:meth:`minimal_separators` | Return an iterator over the minimal separators of ``G``.
5859
5960
Methods
6061
-------
@@ -134,11 +135,12 @@ def is_connected(G, forbidden_vertices=None):
134135
if not G.order():
135136
return True
136137

138+
forbidden = None if forbidden_vertices is None else set(forbidden_vertices)
139+
137140
try:
138-
return G._backend.is_connected(forbidden_vertices=forbidden_vertices)
141+
return G._backend.is_connected(forbidden_vertices=forbidden)
139142
except AttributeError:
140143
# Search for a vertex in G that is not forbidden
141-
forbidden = set(forbidden_vertices) if forbidden_vertices else set()
142144
if forbidden:
143145
for v in G:
144146
if v not in forbidden:
@@ -200,6 +202,9 @@ def connected_components(G, sort=None, key=None, forbidden_vertices=None):
200202
sage: G = graphs.PathGraph(5)
201203
sage: connected_components(G, sort=True, forbidden_vertices=[2])
202204
[[0, 1], [3, 4]]
205+
sage: connected_components(G, sort=True,
206+
....: forbidden_vertices=G.neighbor_iterator(2, closed=True))
207+
[[0], [4]]
203208
204209
TESTS:
205210
@@ -244,7 +249,7 @@ def connected_components(G, sort=None, key=None, forbidden_vertices=None):
244249
for v in G:
245250
if v not in seen:
246251
c = connected_component_containing_vertex(G, v, sort=sort, key=key,
247-
forbidden_vertices=forbidden_vertices)
252+
forbidden_vertices=seen)
248253
seen.update(c)
249254
components.append(c)
250255
components.sort(key=lambda comp: -len(comp))
@@ -423,12 +428,14 @@ def connected_component_containing_vertex(G, vertex, sort=None, key=None,
423428
if (not sort) and key:
424429
raise ValueError('sort keyword is False, yet a key function is given')
425430

431+
forbidden = None if forbidden_vertices is None else list(forbidden_vertices)
432+
426433
try:
427434
c = list(G._backend.depth_first_search(vertex, ignore_direction=True,
428-
forbidden_vertices=forbidden_vertices))
435+
forbidden_vertices=forbidden))
429436
except AttributeError:
430437
c = list(G.depth_first_search(vertex, ignore_direction=True,
431-
forbidden_vertices=forbidden_vertices))
438+
forbidden_vertices=forbidden))
432439

433440
if sort:
434441
return sorted(c, key=key)
@@ -1256,6 +1263,104 @@ def is_cut_vertex(G, u, weak=False):
12561263
return is_vertex_cut(G, [u], weak=weak)
12571264

12581265

1266+
def minimal_separators(G, forbidden_vertices=None):
1267+
r"""
1268+
Return an iterator over the minimal separators of ``G``.
1269+
1270+
A separator in a graph is a set of vertices whose removal increases the
1271+
number of connected components. In other words, a separator is a vertex
1272+
cut. This method implements the algorithm proposed in [BBC2000]_.
1273+
It computes the set `S` of minimal separators of a graph in `O(n^3)` time
1274+
per separator, and so overall in `O(n^3 |S|)` time.
1275+
1276+
.. WARNING::
1277+
1278+
Note that all separators are recorded during the execution of the
1279+
algorithm and so the memory consumption of this method might be huge.
1280+
1281+
INPUT:
1282+
1283+
- ``G`` -- an undirected graph
1284+
1285+
- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
1286+
avoid during the search
1287+
1288+
EXAMPLES::
1289+
1290+
sage: P = graphs.PathGraph(5)
1291+
sage: sorted(sorted(sep) for sep in P.minimal_separators())
1292+
[[1], [2], [3]]
1293+
sage: C = graphs.CycleGraph(6)
1294+
sage: sorted(sorted(sep) for sep in C.minimal_separators())
1295+
[[0, 2], [0, 3], [0, 4], [1, 3], [1, 4], [1, 5], [2, 4], [2, 5], [3, 5]]
1296+
sage: sorted(sorted(sep) for sep in C.minimal_separators(forbidden_vertices=[0]))
1297+
[[2], [3], [4]]
1298+
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators())
1299+
[[1], [2], [3], [5, 7], [5, 8], [5, 9], [6, 8],
1300+
[6, 9], [6, 10], [7, 9], [7, 10], [8, 10]]
1301+
sage: sorted(sorted(sep) for sep in (P + C).minimal_separators(forbidden_vertices=[10]))
1302+
[[1], [2], [3], [6], [7], [8]]
1303+
1304+
sage: G = graphs.RandomGNP(10, .3)
1305+
sage: all(G.is_vertex_cut(sep) for sep in G.minimal_separators())
1306+
True
1307+
1308+
TESTS::
1309+
1310+
sage: list(Graph().minimal_separators())
1311+
[]
1312+
sage: list(Graph(1).minimal_separators())
1313+
[]
1314+
sage: list(Graph(2).minimal_separators())
1315+
[]
1316+
sage: from sage.graphs.connectivity import minimal_separators
1317+
sage: list(minimal_separators(DiGraph()))
1318+
Traceback (most recent call last):
1319+
...
1320+
ValueError: the input must be an undirected graph
1321+
"""
1322+
from sage.graphs.graph import Graph
1323+
if not isinstance(G, Graph):
1324+
raise ValueError("the input must be an undirected graph")
1325+
1326+
if forbidden_vertices is not None and G.order() >= 3:
1327+
# Build the subgraph with active vertices
1328+
G = G.subgraph(set(G).difference(forbidden_vertices), immutable=True)
1329+
1330+
if G.order() < 3:
1331+
return
1332+
if not G.is_connected():
1333+
for cc in G.connected_components(sort=False):
1334+
if len(cc) > 2:
1335+
yield from minimal_separators(G.subgraph(cc))
1336+
return
1337+
1338+
# Initialization - identify separators needing further inspection
1339+
cdef list to_explore = []
1340+
for v in G:
1341+
# iterate over the connected components of G \ N[v]
1342+
for comp in G.connected_components(sort=False, forbidden_vertices=G.neighbor_iterator(v, closed=True)):
1343+
# The vertex boundary of comp in G is a separator
1344+
nh = G.vertex_boundary(comp)
1345+
if nh:
1346+
to_explore.append(frozenset(nh))
1347+
1348+
# Generation of all minimal separators
1349+
cdef set separators = set()
1350+
while to_explore:
1351+
sep = to_explore.pop()
1352+
if sep in separators:
1353+
continue
1354+
yield set(sep)
1355+
separators.add(sep)
1356+
for v in sep:
1357+
# iterate over the connected components of G \ sep \ N(v)
1358+
for comp in G.connected_components(sort=False, forbidden_vertices=sep.union(G.neighbor_iterator(v))):
1359+
nh = G.vertex_boundary(comp)
1360+
if nh:
1361+
to_explore.append(frozenset(nh))
1362+
1363+
12591364
def edge_connectivity(G,
12601365
value_only=True,
12611366
implementation=None,

src/sage/graphs/graph.py

+2
Original file line numberDiff line numberDiff line change
@@ -9270,6 +9270,7 @@ def bipartite_double(self, extended=False):
92709270
from sage.graphs.orientations import eulerian_orientation
92719271
from sage.graphs.connectivity import bridges, cleave, spqr_tree
92729272
from sage.graphs.connectivity import is_triconnected
9273+
from sage.graphs.connectivity import minimal_separators
92739274
from sage.graphs.comparability import is_comparability
92749275
from sage.graphs.comparability import is_permutation
92759276
geodetic_closure = LazyImport('sage.graphs.convexity_properties', 'geodetic_closure', at_startup=True)
@@ -9330,6 +9331,7 @@ def bipartite_double(self, extended=False):
93309331
"cleave" : "Connectivity, orientations, trees",
93319332
"spqr_tree" : "Connectivity, orientations, trees",
93329333
"is_triconnected" : "Connectivity, orientations, trees",
9334+
"minimal_separators" : "Connectivity, orientations, trees",
93339335
"is_dominating" : "Domination",
93349336
"is_redundant" : "Domination",
93359337
"private_neighbors" : "Domination",

0 commit comments

Comments
 (0)