diff --git a/doc/attr.xml b/doc/attr.xml index 0684df880..40be0005d 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -2680,6 +2680,81 @@ gap> Length(M); <#/GAPDoc> +<#GAPDoc Label="DigraphVertexConnectivity"> + + + An non-negative integer. + + This function returns the vertex connectivity of the digraph + digraph. This is also sometimes called the weak vertex connectivity. +

+ + The vertex connectivity of a connected digraph (in the sense of + ) is the largest number k such that: + + the digraph has at least k + 1 vertices, and + the digraph remains connected after the removal of any set of at + most k - 1 vertices. + + If the digraph is not connected, then its vertex connectivity is 0. + For a non-empty digraph whose symmetric closure is not complete, the vertex + connectivity is equal to the size of the smallest set of vertices whose + removal would disconnect the digraph. +

+ + Vertex connectivity of small digraphs may be counterintuitive + with respect to the notions of connectivity, as defined by + , and biconnecivity, as defined by + . Namely + + the empty digraph is connected, but its vertex connectivity is 0; + + the singleton digraph is connected, but its vertex connectivity is + 0; + the 2-cycle digraph is biconnected, but its vertex connectivity is + only 1. + + However, for a digraph with at least 3 vertices, having vertex connectivity + greater than or equal to 1 is equivalent to being connected, and having + vertex connectivity greater than or equal to 2 is equivalent to being + biconnected. +

+ + The algorithm makes n - d - 1 + d \cdot (d - 1) / 2 calls to the + maximum flow algorithm, where n + is the number of vertices of digraph, and d is the + minimum degree of the symmetric closure of digraph, + see . + + D := CompleteBipartiteDigraph(4, 5); + +gap> DigraphVertexConnectivity(D); +4 +gap> DigraphVertexConnectivity(PancakeGraph(4)); +3 +gap> D := Digraph([[2, 4, 5], [1, 4], [4, 7], [1, 2, 3, 5, 6, 7], +> [1, 4], [4, 7], [3, 4, 6]]); + +gap> DigraphVertexConnectivity(D); +1 +gap> J := JohnsonDigraph(9, 2); + +gap> DigraphVertexConnectivity(J); +14 +gap> DigraphVertexConnectivity(Digraph([])); +0 +gap> DigraphVertexConnectivity(Digraph([[1]])); +0 +gap> DigraphVertexConnectivity(CycleDigraph(2)); +1 +gap> DigraphVertexConnectivity(CompleteDigraph(5)); +4 +]]> + + +<#/GAPDoc> + <#GAPDoc Label="NonUpperSemimodularPair"> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 999d01486..059e10eea 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -40,7 +40,7 @@ <#Include Label="DegreeMatrix"> <#Include Label="LaplacianMatrix"> - +

Orders <#Include Label="PartialOrderDigraphMeetOfVertices"> <#Include Label="NonUpperSemimodularPair"> @@ -88,6 +88,7 @@ <#Include Label="HamiltonianPath"> <#Include Label="NrSpanningTrees"> <#Include Label="DigraphDijkstra"> + <#Include Label="DigraphVertexConnectivity"> <#Include Label="DigraphCycleBasis"> <#Include Label="DigraphIsKing"> <#Include Label="DigraphKings"> diff --git a/gap/attr.gd b/gap/attr.gd index 7afe2359e..ee9710f25 100644 --- a/gap/attr.gd +++ b/gap/attr.gd @@ -77,6 +77,7 @@ DeclareAttribute("DigraphCore", IsDigraph); DeclareAttribute("CharacteristicPolynomial", IsDigraph); DeclareAttribute("NrSpanningTrees", IsDigraph); +DeclareAttribute("DigraphVertexConnectivity", IsDigraph); # AsGraph must be mutable for grape to function properly DeclareAttribute("AsGraph", IsDigraph, "mutable"); diff --git a/gap/attr.gi b/gap/attr.gi index ed325892c..2416159a5 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -3358,6 +3358,152 @@ function(D) return Union(M, DIGRAPHS_MateToMatching(D, mateD)); end); +InstallMethod(DigraphVertexConnectivity, "for a digraph", [IsDigraph], +function(D) + local doubled_D_adj, doubled_D, max_flow, u, v, i, j, + neighbours_v, kappa, kappa_min, is_multi, has_loops, is_nonsymm; + + # As per Wikipedia: + # "A graph is said to be k-vertex-connected if it contains at least k + 1 + # vertices, but does not contain a set of k − 1 vertices whose removal + # disconnects the graph." + # https://en.wikipedia.org/wiki/Connectivity_(graph_theory) + # The knock-on effect is that the singleton graph has vertex connectivity 0. + # This is discussed in the documentation in more detail. + if DigraphNrVertices(D) <= 1 or not IsConnectedDigraph(D) then + return 0; + fi; + + # Remove multiple edges, loops and symmetrize, if necessary + is_multi := IsMultiDigraph(D); + has_loops := DigraphHasLoops(D); + is_nonsymm := not IsSymmetricDigraph(D); + if is_multi or has_loops or is_nonsymm then + D := DigraphMutableCopy(D); + if is_multi then + DigraphRemoveAllMultipleEdges(D); + fi; + if has_loops then + DigraphRemoveLoops(D); + fi; + if is_nonsymm then + DigraphSymmetricClosure(D); + fi; + # NOTE: D is mutable following this operation. This should not cause issues + # or slow down the computations. + fi; + + # Special case complete digraph since no set of vertices disconnects it. + if IsCompleteDigraph(D) then + return DigraphNrVertices(D) - 1; + fi; + + # Construct "doubled" digraph as per Theorem 6.4 of + # Even S. Applications of Network Flow Techniques. + # In: Even G, ed. Graph Algorithms. + # Cambridge University Press; 2011:117-145. + # https://doi.org/10.1017/CBO9781139015165 + # Doubles the vertices of the digraph `D`. For every vertex v, 2*v-1 is + # called the "in"-vertex and 2*v is called the "out"-vertex. For every edge + # (v, u) in `D`, the doubled digraph contains an edge (2*v, 2*u-1) from + # the out-vertex of `v` to the in-vertex of `u`. Additionally, there is an + # edge (2*v-1, 2*v) for every vertex v in `D`. + doubled_D_adj := List([1 .. 2 * DigraphNrVertices(D)], x -> []); + for v in DigraphVertices(D) do + for u in OutNeighborsOfVertex(D, v) do + Add(doubled_D_adj[2 * v], 2 * u - 1); + od; + Add(doubled_D_adj[2 * v - 1], 2 * v); + od; + doubled_D := EdgeWeightedDigraph( + doubled_D_adj, + List(doubled_D_adj, x -> ListWithIdenticalEntries(Length(x), 1))); + + # The resulting graph, `doubled_D` is bipartite, and, additionally, + # there is a correspondence between paths in `D` and `doubled_D` + # given by mapping the path (v_1, v_2, ... v_n) in `D` to the path + # (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) in + # `doubled_D`. An conversely, any path starting with an even vertex and + # ending with an odd vertex in `doubled_D` is of the form + # (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) for + # some path (v_1, v_2, ... v_n) in `D`. + + # The local vertex connectivity for a pair of vertices u, v is the + # size of the least set S that contain u and v + # such that any (u, v)-path (that is, a path with source u and target v) + # passes through S. If two vertices are adjacent, then the local connectivity + # is infinity by convention. The minimum cut with source u and target v is + # the least number of edges that need to be removed so that there is no + # longer a (u, v)-path. + + # Let u and v be non-adjacent vertices in `D`. Because of the + # correspondence of paths in `D` and `doubled_D`, any such set S + # for `D` corresponds to a set of edges E_S (obtained by replacing the + # vertex w by the edge (2*w-1, 2*w)) in `doubled_D` whose removal + # disconnects 2*u from 2*v-1. + # Conversely, it can be shown that the smallest set of edges disconnecting + # 2*u from 2*v-1 has the same cardinality as E_S. This is because, whenever + # we remove any edge (2*s, 2*t-1) from `doubled_D` with the goal of + # disconnecting 2*u from 2*v-1, it is always just as + # efficient or more efficient to remove the edge (2*s-1, 2*s) instead, since + # any path from 2*u to 2*v-1 utilizing (2*s, 2*t-1) in `doubled_D` must + # pass through (2*s-1, 2*s) by construction. Note that u and v are + # non-adjacent by assumption, so it cannot be the case that + # (2*s, 2*t-1) = (2*u, 2*v-1). + # It follows that, for non-adjacent vertices, the local connectivity of u and + # v in `D` is equal to the minimum cut with source 2*u-1 and target 2*v + # in `doubled_D`. + + # By the max-flow min-cut theorem, the size of the minimum cut with source u + # and target v equals the maximum flow between u and v see Wikipedia below: + # https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem + # Hence we can compute local vertex connectivity by repeated calls to the + # max_flow function below: + max_flow := {digraph, source, target} -> + Sum(DigraphMaximumFlow(digraph, source, target)[source]); + + # The vertex connectivity is the minimum of the local vertex connectivity + # over all pairs of vertices. However, it can be computed a bit more + # cleverly by utilizing some theory to reduce the number of pairs considered. + # In this function we implement Algorithm 11 from Abdol-Hossein + # Esfahanian's ``Connectivity Algorithms'' which can be found at + # https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + # In particular, we reduce the number of local vertex connectivity + # computations to n-d-1 + d*(d-1)/2 where n is the total number of vertices + # and d is the minimum degree of any vertex. + v := PositionMinimum(OutDegrees(D)); + neighbours_v := OutNeighboursOfVertex(D, v); + kappa_min := fail; + for u in DigraphVertices(D) do + if u <> v and not IsDigraphEdge(D, v, u) then + kappa := max_flow(doubled_D, 2 * u, 2 * v - 1); + if kappa_min = fail or kappa < kappa_min then + kappa_min := kappa; + fi; + fi; + od; + + for i in [1 .. Length(neighbours_v)] do + for j in [i + 1 .. Length(neighbours_v)] do + u := neighbours_v[i]; + v := neighbours_v[j]; + if not IsDigraphEdge(D, v, u) then + kappa := max_flow(doubled_D, 2 * u, 2 * v - 1); + if kappa_min = fail or kappa < kappa_min then + kappa_min := kappa; + fi; + fi; + od; + od; + + # We can only be here if every vertex is adjacent to a vertex of minimum + # degree u and every pair of vertices v, w adjacent to u are also adjacent + # to each other. In other words, `D` is a complete graph, but we + # deal with these at the start. So this assert should pass. + Assert(1, kappa_min <> fail); + return kappa_min; +end); + # The following function is a transliteration from python to GAP of # the function find_nonsemimodular_pair # in sage/src/sage/combinat/posets/hasse_diagram.py diff --git a/gap/examples.gi b/gap/examples.gi index 67e3921bc..84096fd78 100644 --- a/gap/examples.gi +++ b/gap/examples.gi @@ -296,6 +296,9 @@ function(_, n, k) D := MakeImmutable(JohnsonDigraphCons(IsMutableDigraph, n, k)); SetIsMultiDigraph(D, false); SetIsSymmetricDigraph(D, true); + if k <= n then + SetDigraphVertexConnectivity(D, (n - k) * k); + fi; return D; end); diff --git a/tst/extreme/attr.tst b/tst/extreme/attr.tst index 6a7f34303..ce0346e07 100644 --- a/tst/extreme/attr.tst +++ b/tst/extreme/attr.tst @@ -70,6 +70,28 @@ gap> gr := DigraphFromGraph6String(str);; gap> ChromaticNumber(gr); 6 +# DigraphVertexConnectivity +gap> str := Concatenation("""{???????????_?O?@??G??_?@??@???_??G??@?@???A??""", +> """?_?O?O?_??C?G??_C?A?C??C?_??C?G??A?O???G?O??@?G?@???G?A??@????????????""", +> """??_???????????@?????????????G???F_??????WW?????Bo???o???_???E??A?_?E??""", +> """?C????B??C@???K??@???@_??@?M?????????B??????C?o??????P_????????W??????""", +> """?KB???????I??????W????????CG??_?????B??B""");; # House of Graphs 1357 +gap> gr := DigraphFromGraph6String(str);; +gap> DigraphVertexConnectivity(gr); +3 +gap> gr := CompleteMultipartiteDigraph([20, 20, 20]);; +gap> DigraphVertexConnectivity(gr); +40 +gap> str := Concatenation("""~?@A}rEEB?oE?W?o?o?W?E??o?B??E??E??B???o??E???""", +> """W???n~~~n~~~V~~~d~~~wn~~~A~~~{D~~~wD~~~wA~~~{?n~~~?D~~~w?V~~~_?n~~~??n""", +> """~~~??V~~~_?D~~~w??n~~~??A~~~{??D~~~w??D~~~w??A~~~{???^~~~~~~z~~~~~~~N~""", +> """~~~~~{^~~~~~~w^~~~~~~wN~~~~~~{B~~~~~~~?^~~~~~~w@~~~~~~~_B~~~~~~~?B~~~~""", +> """~~~?@~~~~~~~_?^~~~~~~w?B~~~~~~~??N~~~~~~{??^~~~~~~w??^~~~~~~w??N~~~~~~""", +> """{??B~~~~~~~???^~~~~~~w??@~~~~~~~_???""");; # House of Graphs 52267 +gap> gr := DigraphFromGraph6String(str);; +gap> DigraphVertexConnectivity(gr); +44 + # gap> DIGRAPHS_StopTest(); gap> STOP_TEST("Digraphs package: extreme/attr.tst", 0); diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst index 55f047434..32847ee3f 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -3114,6 +3114,146 @@ gap> D := DigraphRemoveEdge(D, 1, 3); gap> D := DigraphRemoveEdge(D, 1, 3); +# DigraphVertexConnectivity +gap> D := Digraph([[2, 3, 4], [3, 4], [4], []]); + +gap> DigraphVertexConnectivity(D); +3 +gap> D := Digraph(IsMutableDigraph, [[2, 3, 4], [3, 4], [4], []]); + +gap> DigraphVertexConnectivity(D); +3 +gap> D = Digraph(IsMutableDigraph, [[2, 3, 4], [3, 4], [4], []]); +true +gap> D := Digraph([[1, 2, 3, 4], [3, 4], [4], [4]]);; +gap> DigraphVertexConnectivity(D); +3 +gap> D := Digraph([[2, 2, 3, 4], [3, 3, 3, 3, 4, 4, 4, 4], [4, 4, 4], []]);; +gap> DigraphVertexConnectivity(D); +3 +gap> D := CompleteDigraph(10); + +gap> DigraphVertexConnectivity(D); +9 +gap> ForAny(Combinations(DigraphVertices(D), 8), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> D := JohnsonDigraph(9, 2); + +gap> DigraphVertexConnectivity(D); +14 +gap> D := EmptyDigraph(0); + +gap> DigraphVertexConnectivity(D); +0 +gap> D := EmptyDigraph(1); + +gap> DigraphVertexConnectivity(D); +0 +gap> D := Digraph([[2, 4, 5], [1, 4], [4, 7], [1, 2, 3, 5, 6, 7], +> [1, 4], [4, 7], [3, 4, 6]]); + +gap> DigraphVertexConnectivity(D); +1 +gap> not IsConnectedDigraph(D); +false +gap> ForAny(Combinations(DigraphVertices(D), 1), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := Digraph([[2, 4, 5], [1, 3, 4], [4, 7], [1, 2, 3, 5, 6, 7], +> [1, 4], [4, 7], [3, 4, 6]]); + +gap> DigraphVertexConnectivity(D); +2 +gap> ForAny(Combinations(DigraphVertices(D), 1), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> ForAny(Combinations(DigraphVertices(D), 2), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := Digraph([[2, 3], [3, 5], [1, 2, 4], [2, 3], [3]]); + +gap> DigraphVertexConnectivity(D); +2 +gap> ForAny(Combinations(DigraphVertices(D), 1), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> ForAny(Combinations(DigraphVertices(D), 2), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := DigraphFromGraph6String("NoCQ@?EAS_C`QA?c_Kg");; +gap> DigraphVertexConnectivity(D); +3 +gap> ForAny(Combinations(DigraphVertices(D), 2), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> ForAny(Combinations(DigraphVertices(D), 3), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := DigraphFromGraph6String("HoStIv{");; +gap> DigraphVertexConnectivity(D); +4 +gap> ForAny(Combinations(DigraphVertices(D), 3), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> ForAny(Combinations(DigraphVertices(D), 4), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := PancakeGraph(4);; +gap> ForAny(Combinations(DigraphVertices(D), 2), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> ForAny(Combinations(DigraphVertices(D), 3), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +true +gap> D := DigraphFromGraph6String( +> "Os_??L@GOS`SEKT@E`BK?");; # House of Graphs 44091 +gap> DigraphVertexConnectivity(D); +4 +gap> D := DigraphFromGraph6String( +> "]s_??CD@?C_O@@?S?C_@O?O_E??_AgO@X?@?G?CI??OC?C@CA?GA?_@AA?A?OG?OG???d???@g" +> );; # House of Graphs 49360 +gap> DigraphVertexConnectivity(D); +4 +gap> D := CirculantGraph(14, [1, 4, 7]);; # House of Graphs 53516 +gap> DigraphVertexConnectivity(D); +5 +gap> D := CirculantGraph(16, [1, 3, 8]);; # House of Graphs 53524 +gap> DigraphVertexConnectivity(D); +5 +gap> D := CirculantGraph(17, [1, 3, 5]);; # House of Graphs 53527 +gap> DigraphVertexConnectivity(D); +6 +gap> D := CirculantGraph(16, [1, 4, 7]);; # House of Graphs 53528 +gap> DigraphVertexConnectivity(D); +6 +gap> D := CirculantGraph(19, [3, 4, 5]);; # House of Graphs 53529 +gap> DigraphVertexConnectivity(D); +6 +gap> D := CirculantGraph(20, [1, 5, 8]);; # House of Graphs 53696 +gap> DigraphVertexConnectivity(D); +6 +gap> D := CirculantGraph(19, [1, 5, 8]);; # House of Graphs 53697 +gap> DigraphVertexConnectivity(D); +6 +gap> D := DigraphFromGraph6String( +> "[~yCKMF`{~r}????`?WOFA?{OBy?VwoFL_B|Y?}r_FyM@jkH{?MF{__M}_?ZNw?E" +> );; # House of Graphs 33964 +gap> DigraphVertexConnectivity(D); +7 + # Semimodular lattices gap> D := DigraphFromDigraph6String("&C[o?");