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?");