@@ -26,6 +26,7 @@ Here is what the module can do:
2626 :meth:`is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
2727 :meth:`is_edge_cut` | Check whether the input edges form an edge cut.
2828 :meth:`is_cut_vertex` | Check whether the input vertex is a cut-vertex.
29+ :meth:`is_vertex_cut` | Check whether the input vertices form a vertex cut.
2930 :meth:`edge_connectivity` | Return the edge connectivity of the graph.
3031 :meth:`vertex_connectivity` | Return the vertex connectivity of the graph.
3132
@@ -971,58 +972,64 @@ def is_cut_edge(G, u, v=None, label=None):
971972 return sol
972973
973974
974- def is_cut_vertex (G , u , weak = False ):
975+ def is_vertex_cut (G , cut , weak = False ):
975976 r """
976- Check whether the input vertex is a cut- vertex.
977+ Check whether the input vertices form a vertex cut .
977978
978- A vertex is a cut-vertex if its removal from the ( di) graph increases the
979- number of ( strongly) connected components. Isolated vertices or leafs are
980- not cut-vertices. This function works with simple graphs as well as graphs
981- with loops and multiple edges.
979+ A set of vertices is a vertex cut if its removal from the ( di) graph
980+ increases the number of ( strongly) connected components. This function works
981+ with simple graphs as well as graphs with loops and multiple edges.
982982
983983 INPUT:
984984
985985 - ``G`` -- a Sage ( Di) Graph
986986
987- - ``u `` -- a vertex
987+ - ``cut `` -- a set of vertices
988988
989989 - ``weak`` -- boolean ( default: ``False``) ; whether the connectivity of
990990 directed graphs is to be taken in the weak sense, that is ignoring edges
991991 orientations
992992
993- OUTPUT:
994-
995- Return ``True`` if ``u`` is a cut-vertex, and ``False`` otherwise.
996-
997993 EXAMPLES:
998994
999- Giving a LollipopGraph( 4,2) , that is a complete graph with 4 vertices with a
1000- pending edge::
995+ Giving a cycle graph of order 4::
1001996
1002- sage: from sage. graphs. connectivity import is_cut_vertex
1003- sage: G = graphs. LollipopGraph ( 4, 2 )
1004- sage: is_cut_vertex ( G, 0 )
997+ sage: from sage. graphs. connectivity import is_vertex_cut
998+ sage: G = graphs. CycleGraph ( 4 )
999+ sage: is_vertex_cut ( G, [ 0, 1 ] )
10051000 False
1006- sage: is_cut_vertex ( G, 3 )
1001+ sage: is_vertex_cut ( G, [ 0, 2 ] )
10071002 True
1008- sage: G. is_cut_vertex( 3)
1003+
1004+ Giving a disconnected graph::
1005+
1006+ sage: from sage. graphs. connectivity import is_vertex_cut
1007+ sage: G = graphs. CycleGraph( 4) * 2
1008+ sage: G. connected_components( )
1009+ [[0, 1, 2, 3 ], [4, 5, 6, 7 ]]
1010+ sage: is_vertex_cut( G, [0, 2 ])
1011+ True
1012+ sage: is_vertex_cut( G, [4, 6 ])
1013+ True
1014+ sage: is_vertex_cut( G, [0, 6 ])
1015+ False
1016+ sage: is_vertex_cut( G, [0, 4, 6 ])
10091017 True
10101018
10111019 Comparing the weak and strong connectivity of a digraph::
10121020
1013- sage: from sage. graphs. connectivity import is_strongly_connected
10141021 sage: D = digraphs. Circuit( 6)
1015- sage: is_strongly_connected( D )
1022+ sage: D . is_strongly_connected( )
10161023 True
1017- sage: is_cut_vertex ( D, 2 )
1024+ sage: is_vertex_cut ( D, [ 2 ] )
10181025 True
1019- sage: is_cut_vertex ( D, 2 , weak=True)
1026+ sage: is_vertex_cut ( D, [ 2 ] , weak=True)
10201027 False
10211028
10221029 Giving a vertex that is not in the graph::
10231030
10241031 sage: G = graphs. CompleteGraph( 4)
1025- sage: is_cut_vertex ( G, 7 )
1032+ sage: is_vertex_cut ( G, [ 7 ] )
10261033 Traceback ( most recent call last) :
10271034 ...
10281035 ValueError: vertex ( 7) is not a vertex of the graph
@@ -1031,7 +1038,7 @@ def is_cut_vertex(G, u, weak=False):
10311038
10321039 If ``G`` is not a Sage graph, an error is raised::
10331040
1034- sage: is_cut_vertex ( 'I am not a graph', 0 )
1041+ sage: is_vertex_cut ( 'I am not a graph', [ 0 ] )
10351042 Traceback ( most recent call last) :
10361043 ...
10371044 TypeError: the input must be a Sage graph
@@ -1040,68 +1047,144 @@ def is_cut_vertex(G, u, weak=False):
10401047 if not isinstance (G, GenericGraph):
10411048 raise TypeError (" the input must be a Sage graph" )
10421049
1043- if u not in G:
1044- raise ValueError (" vertex ({0}) is not a vertex of the graph" .format(repr (u)))
1045-
1046- # Initialization
1047- cdef set CC
1048- cdef list neighbors_func
1049- if not G.is_directed() or weak:
1050- # Weak connectivity
1050+ cdef set cutset = set (cut)
1051+ for u in cutset:
1052+ if u not in G:
1053+ raise ValueError (" vertex ({0}) is not a vertex of the graph" .format(repr (u)))
10511054
1052- if G.degree(u) < 2 :
1053- # An isolated or a leaf vertex is not a cut vertex
1054- return False
1055-
1056- neighbors_func = [G.neighbor_iterator]
1057- start = next(G.neighbor_iterator(u))
1058- CC = set (G)
1055+ if len (cutset) >= G.order() - 1 :
1056+ # A vertex cut must be of size at most n - 2
1057+ return False
10591058
1059+ # We deal with graphs with multiple (strongly) connected components
1060+ cdef list CC
1061+ if G.is_directed() and not weak:
1062+ CC = G.strongly_connected_components()
10601063 else :
1061- # Strong connectivity for digraphs
1062-
1063- if not G.out_degree(u) or not G.in_degree(u):
1064- # A vertex without in or out neighbors is not a cut vertex
1065- return False
1064+ CC = G.connected_components(sort = False )
1065+ if len (CC) > 1 :
1066+ for comp in CC:
1067+ subcut = cutset.intersection(comp)
1068+ if subcut and is_vertex_cut(G.subgraph(comp), subcut, weak = weak):
1069+ return True
1070+ return False
10661071
1067- # We consider only the strongly connected component containing u
1068- CC = set (strongly_connected_component_containing_vertex(G, u))
1072+ cdef list boundary = G.vertex_boundary(cutset)
1073+ if not boundary:
1074+ # We need at least 1 vertex in the boundary of the cut
1075+ return False
10691076
1070- # We perform two DFS starting from an out neighbor of u and avoiding
1071- # u. The first DFS follows the edges directions, and the second is
1072- # in the reverse order. If both allow to reach all neighbors of u,
1073- # then u is not a cut vertex
1074- neighbors_func = [G.neighbor_out_iterator, G.neighbor_in_iterator]
1075- start = next(G.neighbor_out_iterator(u))
1077+ cdef list cases = [(G.neighbor_iterator, boundary)]
1078+ if not weak and G.is_directed():
1079+ # Strong connectivity for digraphs.
1080+ # We perform two DFS starting from an out neighbor of cut and avoiding
1081+ # cut. The first DFS follows the edges directions, and the second is
1082+ # in the reverse order. If both allow to reach all neighbors of cut,
1083+ # then it is not a vertex cut.
1084+ # We set data for the reverse order
1085+ in_boundary = set ()
1086+ for u in cutset:
1087+ in_boundary.update(G.neighbor_in_iterator(u))
1088+ in_boundary.difference_update(cutset)
1089+ if not in_boundary:
1090+ return False
1091+ cases.append((G.neighbor_in_iterator, list (in_boundary)))
10761092
1077- CC.discard(u)
1078- CC.discard(start)
10791093 cdef list queue
10801094 cdef set seen
10811095 cdef set targets
1096+ start = boundary[0 ]
10821097
1083- for neighbors in neighbors_func :
1098+ for neighbors, this_boundary in cases :
10841099
1085- # We perform a DFS starting from a neighbor of u and avoiding u
1100+ # We perform a DFS starting from start and avoiding cut
10861101 queue = [start]
1087- seen = set (queue)
1088- targets = CC.intersection(G.neighbor_iterator(u))
1102+ seen = set (cutset)
1103+ seen.add(start)
1104+ targets = set (this_boundary)
10891105 targets.discard(start)
1090- while queue and targets :
1106+ while queue:
10911107 v = queue.pop()
10921108 for w in neighbors(v):
1093- if w not in seen and w in CC :
1109+ if w not in seen:
10941110 seen.add(w)
10951111 queue.append(w)
10961112 targets.discard(w)
10971113
1098- # If some neighbors cannot be reached, u is a cut vertex.
1114+ # If some neighbors cannot be reached, we have a vertex cut
10991115 if targets:
11001116 return True
11011117
11021118 return False
11031119
11041120
1121+ def is_cut_vertex (G , u , weak = False ):
1122+ r """
1123+ Check whether the input vertex is a cut-vertex.
1124+
1125+ A vertex is a cut-vertex if its removal from the ( di) graph increases the
1126+ number of ( strongly) connected components. Isolated vertices or leaves are
1127+ not cut-vertices. This function works with simple graphs as well as graphs
1128+ with loops and multiple edges.
1129+
1130+ INPUT:
1131+
1132+ - ``G`` -- a Sage ( Di) Graph
1133+
1134+ - ``u`` -- a vertex
1135+
1136+ - ``weak`` -- boolean ( default: ``False``) ; whether the connectivity of
1137+ directed graphs is to be taken in the weak sense, that is ignoring edges
1138+ orientations
1139+
1140+ OUTPUT:
1141+
1142+ Return ``True`` if ``u`` is a cut-vertex, and ``False`` otherwise.
1143+
1144+ EXAMPLES:
1145+
1146+ Giving a LollipopGraph( 4,2) , that is a complete graph with 4 vertices with a
1147+ pending edge::
1148+
1149+ sage: from sage. graphs. connectivity import is_cut_vertex
1150+ sage: G = graphs. LollipopGraph( 4, 2)
1151+ sage: is_cut_vertex( G, 0)
1152+ False
1153+ sage: is_cut_vertex( G, 3)
1154+ True
1155+ sage: G. is_cut_vertex( 3)
1156+ True
1157+
1158+ Comparing the weak and strong connectivity of a digraph::
1159+
1160+ sage: D = digraphs. Circuit( 6)
1161+ sage: D. is_strongly_connected( )
1162+ True
1163+ sage: is_cut_vertex( D, 2)
1164+ True
1165+ sage: is_cut_vertex( D, 2, weak=True)
1166+ False
1167+
1168+ Giving a vertex that is not in the graph::
1169+
1170+ sage: G = graphs. CompleteGraph( 4)
1171+ sage: is_cut_vertex( G, 7)
1172+ Traceback ( most recent call last) :
1173+ ...
1174+ ValueError: vertex ( 7) is not a vertex of the graph
1175+
1176+ TESTS:
1177+
1178+ If ``G`` is not a Sage graph, an error is raised::
1179+
1180+ sage: is_cut_vertex( 'I am not a graph', 0)
1181+ Traceback ( most recent call last) :
1182+ ...
1183+ TypeError: the input must be a Sage graph
1184+ """
1185+ return is_vertex_cut(G, [u], weak = weak)
1186+
1187+
11051188def edge_connectivity (G ,
11061189 value_only = True ,
11071190 implementation = None ,
0 commit comments