Skip to content

Commit 9cb05e6

Browse files
author
Release Manager
committed
sagemathgh-38435: add method `is_edge_cut` to graphs Similarly to sagemath#38418, this PR adds a method to check whether a set of edges forms an edge cut of the (di)graph, that is if the removal of these edges increases the number of (weakly) connected components. ### 📝 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#38435 Reported by: David Coudert Reviewer(s): David Coudert, Kwankyu Lee
2 parents b9d40ba + 56f380b commit 9cb05e6

File tree

2 files changed

+156
-18
lines changed

2 files changed

+156
-18
lines changed

src/sage/graphs/connectivity.pyx

+153-17
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Here is what the module can do:
2323
:meth:`connected_components_sizes` | Return the sizes of the connected components as a list.
2424
:meth:`blocks_and_cut_vertices` | Return the blocks and cut vertices of the graph.
2525
:meth:`blocks_and_cuts_tree` | Return the blocks-and-cuts tree of the graph.
26-
:meth:`is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge.
26+
:meth:`is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
27+
:meth:`is_edge_cut` | Check whether the input edges form an edge cut.
2728
:meth:`is_cut_vertex` | Check whether the input vertex is a cut-vertex.
2829
:meth:`edge_connectivity` | Return the edge connectivity of the graph.
2930
:meth:`vertex_connectivity` | Return the vertex connectivity of the graph.
@@ -70,6 +71,7 @@ Methods
7071
# ****************************************************************************
7172

7273
from sage.misc.superseded import deprecation
74+
from sage.sets.disjoint_set cimport DisjointSet
7375

7476

7577
def is_connected(G):
@@ -732,13 +734,160 @@ def blocks_and_cuts_tree(G):
732734
return g
733735

734736

737+
def is_edge_cut(G, edges):
738+
"""
739+
Check whether ``edges`` form an edge cut.
740+
741+
A set of edges is an edge cut of a graph if its removal increases the number
742+
of connected components. In a digraph, we consider the number of (weakly)
743+
connected components.
744+
745+
This method is not working for (di)graphs with multiple edges. Furthermore,
746+
edge labels are ignored.
747+
748+
INPUT:
749+
750+
- ``G`` -- a (di)graph
751+
752+
- ``edges`` -- a set of edges
753+
754+
EXAMPLES:
755+
756+
A cycle graph of order 4::
757+
758+
sage: from sage.graphs.connectivity import is_edge_cut
759+
sage: G = graphs.CycleGraph(4)
760+
sage: is_edge_cut(G, [(1, 2)])
761+
False
762+
sage: is_edge_cut(G, [(1, 2), (2, 3)])
763+
True
764+
sage: is_edge_cut(G, [(1, 2), (3, 0)])
765+
True
766+
767+
A pending edge is a cut-edge::
768+
769+
sage: G.add_edge((0, 5, 'silly'))
770+
sage: is_edge_cut(G, [(0, 5, 'silly')])
771+
True
772+
773+
Edge labels are ignored, even if specified::
774+
775+
sage: G.add_edge((2, 5, 'xyz'))
776+
sage: is_edge_cut(G, [(0, 5), (2, 5)])
777+
True
778+
sage: is_edge_cut(G, [(0, 5), (2, 5, 'xyz')])
779+
True
780+
sage: is_edge_cut(G, [(0, 5, 'silly'), (2, 5)])
781+
True
782+
sage: is_edge_cut(G, [(0, 5, 'aa'), (2, 5, 'bb')])
783+
True
784+
785+
The graph can have loops::
786+
787+
sage: G.allow_loops(True)
788+
sage: G.add_edge(0, 0)
789+
sage: is_edge_cut(G, [(0, 5), (2, 5)])
790+
True
791+
sage: is_edge_cut(G, [(0, 0), (0, 5), (2, 5)])
792+
True
793+
794+
Multiple edges are not allowed::
795+
796+
sage: G.allow_multiple_edges(True)
797+
sage: is_edge_cut(G, [(0, 5), (2, 5)])
798+
Traceback (most recent call last):
799+
...
800+
ValueError: This method is not known to work on graphs with
801+
multiedges. Perhaps this method can be updated to handle them, but in
802+
the meantime if you want to use it please disallow multiedges using
803+
allow_multiple_edges().
804+
805+
An error is raised if an element of ``edges`` is not an edge of `G`::
806+
807+
sage: G = graphs.CycleGraph(4)
808+
sage: is_edge_cut(G, [(0, 2)])
809+
Traceback (most recent call last):
810+
...
811+
ValueError: edge (0, 2) is not an edge of the graph
812+
813+
For digraphs, this method considers the number of (weakly) connected
814+
components::
815+
816+
sage: G = digraphs.Circuit(4)
817+
sage: is_edge_cut(G, [(0, 1)])
818+
False
819+
sage: G = digraphs.Circuit(4)
820+
sage: is_edge_cut(G, [(0, 1), (1, 2)])
821+
True
822+
823+
For disconnected (di)graphs, the method checks if the number of (weakly)
824+
connected components increases::
825+
826+
sage: G = graphs.CycleGraph(4) * 2
827+
sage: is_edge_cut(G, [(1, 2), (2, 3)])
828+
True
829+
sage: G = digraphs.Circuit(4) * 2
830+
sage: is_edge_cut(G, [(0, 1), (1, 2)])
831+
True
832+
"""
833+
G._scream_if_not_simple(allow_loops=True)
834+
835+
cdef set C = set() # set of edges of the potential cut
836+
cdef set S = set() # set of incident vertices
837+
for e in edges:
838+
u, v = e[0], e[1]
839+
if not G.has_edge(u, v):
840+
raise ValueError("edge {0} is not an edge of the graph".format(repr(e)))
841+
if u == v:
842+
# We ignore loops
843+
continue
844+
if G.degree(u) == 1 or G.degree(v) == 1:
845+
# e is a pending edge and so a cut-edge
846+
return True
847+
S.add(u)
848+
S.add(v)
849+
C.add((u, v))
850+
if not G.is_directed():
851+
C.add((v, u))
852+
853+
cdef list queue
854+
cdef set seen
855+
DS = DisjointSet(G)
856+
857+
for comp in G.connected_components():
858+
if not S.intersection(comp):
859+
# This component is not involved in the cut
860+
continue
861+
862+
# We run a DFS in comp from any vertex and avoid edges in C
863+
start = comp[0]
864+
queue = [start]
865+
seen = set(queue)
866+
while queue:
867+
v = queue.pop()
868+
for e in G.edge_iterator(vertices=[v], labels=False, ignore_direction=True, sort_vertices=False):
869+
if e in C:
870+
continue
871+
w = e[1] if e[0] == v else e[0]
872+
if w not in seen:
873+
seen.add(w)
874+
DS.union(v, w)
875+
queue.append(w)
876+
877+
# We now check if some vertices of comp have not been reached
878+
if len(set(DS.find(v) for v in comp)) > 1:
879+
return True
880+
881+
return False
882+
883+
735884
def is_cut_edge(G, u, v=None, label=None):
736885
"""
737-
Return ``True`` if the input edge is a cut-edge or a bridge.
886+
Check whether the edge ``(u, v)`` is a cut-edge or a bridge of graph ``G``.
738887
739888
A cut edge (or bridge) is an edge that when removed increases
740-
the number of connected components. This function works with
741-
simple graphs as well as graphs with loops and multiedges. In
889+
the number of connected components. This function works with
890+
simple graphs as well as graphs with loops and multiedges. In
742891
a digraph, a cut edge is an edge that when removed increases
743892
the number of (weakly) connected components.
744893
@@ -787,20 +936,7 @@ def is_cut_edge(G, u, v=None, label=None):
787936
Traceback (most recent call last):
788937
...
789938
ValueError: edge not in graph
790-
791-
TESTS:
792-
793-
If ``G`` is not a Sage graph, an error is raised::
794-
795-
sage: is_cut_edge('I am not a graph',0)
796-
Traceback (most recent call last):
797-
...
798-
TypeError: the input must be a Sage graph
799939
"""
800-
from sage.graphs.generic_graph import GenericGraph
801-
if not isinstance(G, GenericGraph):
802-
raise TypeError("the input must be a Sage graph")
803-
804940
if label is None:
805941
if v is None:
806942
try:

src/sage/graphs/generic_graph.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@
243243
:meth:`~GenericGraph.connected_components_sizes` | Return the sizes of the connected components as a list.
244244
:meth:`~GenericGraph.blocks_and_cut_vertices` | Compute the blocks and cut vertices of the graph.
245245
:meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph.
246-
:meth:`~GenericGraph.is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge.
246+
:meth:`~GenericGraph.is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
247+
:meth:`~GenericGraph.`is_edge_cut` | Check whether the input edges form an edge cut.
247248
:meth:`~GenericGraph.is_cut_vertex` | Return ``True`` if the input vertex is a cut-vertex.
248249
:meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t`
249250
:meth:`~GenericGraph.vertex_cut` | Return a minimum vertex cut between non-adjacent vertices `s` and `t`
@@ -25000,6 +25001,7 @@ def is_self_complementary(self):
2500025001
from sage.graphs.connectivity import blocks_and_cut_vertices
2500125002
from sage.graphs.connectivity import blocks_and_cuts_tree
2500225003
from sage.graphs.connectivity import is_cut_edge
25004+
from sage.graphs.connectivity import is_edge_cut
2500325005
from sage.graphs.connectivity import is_cut_vertex
2500425006
from sage.graphs.connectivity import edge_connectivity
2500525007
from sage.graphs.connectivity import vertex_connectivity

0 commit comments

Comments
 (0)