diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index b390e0c9877..ba515980093 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -23,7 +23,8 @@ Here is what the module can do: :meth:`connected_components_sizes` | Return the sizes of the connected components as a list. :meth:`blocks_and_cut_vertices` | Return the blocks and cut vertices of the graph. :meth:`blocks_and_cuts_tree` | Return the blocks-and-cuts tree of the graph. - :meth:`is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge. + :meth:`is_cut_edge` | Check whether the input edge is a cut-edge or a bridge. + :meth:`is_edge_cut` | Check whether the input edges form an edge cut. :meth:`is_cut_vertex` | Check whether the input vertex is a cut-vertex. :meth:`edge_connectivity` | Return the edge connectivity of the graph. :meth:`vertex_connectivity` | Return the vertex connectivity of the graph. @@ -70,6 +71,7 @@ Methods # **************************************************************************** from sage.misc.superseded import deprecation +from sage.sets.disjoint_set cimport DisjointSet def is_connected(G): @@ -732,13 +734,160 @@ def blocks_and_cuts_tree(G): return g +def is_edge_cut(G, edges): + """ + Check whether ``edges`` form an edge cut. + + A set of edges is an edge cut of a graph if its removal increases the number + of connected components. In a digraph, we consider the number of (weakly) + connected components. + + This method is not working for (di)graphs with multiple edges. Furthermore, + edge labels are ignored. + + INPUT: + + - ``G`` -- a (di)graph + + - ``edges`` -- a set of edges + + EXAMPLES: + + A cycle graph of order 4:: + + sage: from sage.graphs.connectivity import is_edge_cut + sage: G = graphs.CycleGraph(4) + sage: is_edge_cut(G, [(1, 2)]) + False + sage: is_edge_cut(G, [(1, 2), (2, 3)]) + True + sage: is_edge_cut(G, [(1, 2), (3, 0)]) + True + + A pending edge is a cut-edge:: + + sage: G.add_edge((0, 5, 'silly')) + sage: is_edge_cut(G, [(0, 5, 'silly')]) + True + + Edge labels are ignored, even if specified:: + + sage: G.add_edge((2, 5, 'xyz')) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 5), (2, 5, 'xyz')]) + True + sage: is_edge_cut(G, [(0, 5, 'silly'), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 5, 'aa'), (2, 5, 'bb')]) + True + + The graph can have loops:: + + sage: G.allow_loops(True) + sage: G.add_edge(0, 0) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 0), (0, 5), (2, 5)]) + True + + Multiple edges are not allowed:: + + sage: G.allow_multiple_edges(True) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + Traceback (most recent call last): + ... + ValueError: This method is not known to work on graphs with + multiedges. Perhaps this method can be updated to handle them, but in + the meantime if you want to use it please disallow multiedges using + allow_multiple_edges(). + + An error is raised if an element of ``edges`` is not an edge of `G`:: + + sage: G = graphs.CycleGraph(4) + sage: is_edge_cut(G, [(0, 2)]) + Traceback (most recent call last): + ... + ValueError: edge (0, 2) is not an edge of the graph + + For digraphs, this method considers the number of (weakly) connected + components:: + + sage: G = digraphs.Circuit(4) + sage: is_edge_cut(G, [(0, 1)]) + False + sage: G = digraphs.Circuit(4) + sage: is_edge_cut(G, [(0, 1), (1, 2)]) + True + + For disconnected (di)graphs, the method checks if the number of (weakly) + connected components increases:: + + sage: G = graphs.CycleGraph(4) * 2 + sage: is_edge_cut(G, [(1, 2), (2, 3)]) + True + sage: G = digraphs.Circuit(4) * 2 + sage: is_edge_cut(G, [(0, 1), (1, 2)]) + True + """ + G._scream_if_not_simple(allow_loops=True) + + cdef set C = set() # set of edges of the potential cut + cdef set S = set() # set of incident vertices + for e in edges: + u, v = e[0], e[1] + if not G.has_edge(u, v): + raise ValueError("edge {0} is not an edge of the graph".format(repr(e))) + if u == v: + # We ignore loops + continue + if G.degree(u) == 1 or G.degree(v) == 1: + # e is a pending edge and so a cut-edge + return True + S.add(u) + S.add(v) + C.add((u, v)) + if not G.is_directed(): + C.add((v, u)) + + cdef list queue + cdef set seen + DS = DisjointSet(G) + + for comp in G.connected_components(): + if not S.intersection(comp): + # This component is not involved in the cut + continue + + # We run a DFS in comp from any vertex and avoid edges in C + start = comp[0] + queue = [start] + seen = set(queue) + while queue: + v = queue.pop() + for e in G.edge_iterator(vertices=[v], labels=False, ignore_direction=True, sort_vertices=False): + if e in C: + continue + w = e[1] if e[0] == v else e[0] + if w not in seen: + seen.add(w) + DS.union(v, w) + queue.append(w) + + # We now check if some vertices of comp have not been reached + if len(set(DS.find(v) for v in comp)) > 1: + return True + + return False + + def is_cut_edge(G, u, v=None, label=None): """ - Return ``True`` if the input edge is a cut-edge or a bridge. + Check whether the edge ``(u, v)`` is a cut-edge or a bridge of graph ``G``. A cut edge (or bridge) is an edge that when removed increases - the number of connected components. This function works with - simple graphs as well as graphs with loops and multiedges. In + the number of connected components. This function works with + simple graphs as well as graphs with loops and multiedges. In a digraph, a cut edge is an edge that when removed increases the number of (weakly) connected components. @@ -787,20 +936,7 @@ def is_cut_edge(G, u, v=None, label=None): Traceback (most recent call last): ... ValueError: edge not in graph - - TESTS: - - If ``G`` is not a Sage graph, an error is raised:: - - sage: is_cut_edge('I am not a graph',0) - Traceback (most recent call last): - ... - TypeError: the input must be a Sage graph """ - from sage.graphs.generic_graph import GenericGraph - if not isinstance(G, GenericGraph): - raise TypeError("the input must be a Sage graph") - if label is None: if v is None: try: diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index f789b1d7f8d..6ed8446ae1c 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -243,7 +243,8 @@ :meth:`~GenericGraph.connected_components_sizes` | Return the sizes of the connected components as a list. :meth:`~GenericGraph.blocks_and_cut_vertices` | Compute the blocks and cut vertices of the graph. :meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph. - :meth:`~GenericGraph.is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge. + :meth:`~GenericGraph.is_cut_edge` | Check whether the input edge is a cut-edge or a bridge. + :meth:`~GenericGraph.`is_edge_cut` | Check whether the input edges form an edge cut. :meth:`~GenericGraph.is_cut_vertex` | Return ``True`` if the input vertex is a cut-vertex. :meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t` :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): from sage.graphs.connectivity import blocks_and_cut_vertices from sage.graphs.connectivity import blocks_and_cuts_tree from sage.graphs.connectivity import is_cut_edge + from sage.graphs.connectivity import is_edge_cut from sage.graphs.connectivity import is_cut_vertex from sage.graphs.connectivity import edge_connectivity from sage.graphs.connectivity import vertex_connectivity