@@ -3358,148 +3358,150 @@ function(D)
33583358 return Union(M, DIGRAPHS_MateToMatching(D, mateD));
33593359end );
33603360
3361- InstallMethod(VertexConnectivity, " for a digraph" , [ IsDigraph] ,
3362- function (digraph )
3363- local kappas, newnetw, edmondskarp, mat, degs, mindegv, mindeg, Nv, outn, k,
3364- i, j, x, y;
3365-
3366- if DigraphNrVertices(digraph) <= 1 or not IsConnectedDigraph(digraph) then
3361+ InstallMethod(DigraphVertexConnectivity, " for a digraph" , [ IsDigraph] ,
3362+ function (D )
3363+ local doubled_D_adj, doubled_D, max_flow, u, v, i, j,
3364+ neighbours_v, kappa, kappa_min, is_multi, has_loops, is_nonsymm;
3365+
3366+ # As per Wikipedia:
3367+ # "A graph is said to be k-vertex-connected if it contains at least k + 1
3368+ # vertices, but does not contain a set of k − 1 vertices whose removal
3369+ # disconnects the graph."
3370+ # https://en.wikipedia.org/wiki/Connectivity_(graph_theory)
3371+ # The knock-on effect is that the singleton graph has vertex connectivity 0.
3372+ # This is discussed in the documentation in more detail.
3373+ if DigraphNrVertices(D) <= 1 or not IsConnectedDigraph(D) then
33673374 return 0 ;
33683375 fi ;
33693376
3370- if IsMultiDigraph(digraph) then
3371- digraph := DigraphRemoveAllMultipleEdges(digraph);
3377+ # Remove multiple edges, loops and symmetrize, if necessary
3378+ is_multi := IsMultiDigraph(D);
3379+ has_loops := DigraphHasLoops(D);
3380+ is_nonsymm := not IsSymmetricDigraph(D);
3381+ if is_multi or has_loops or is_nonsymm then
3382+ D := DigraphMutableCopy(D);
3383+ if is_multi then
3384+ DigraphRemoveAllMultipleEdges(D);
3385+ fi ;
3386+ if has_loops then
3387+ DigraphRemoveLoops(D);
3388+ fi ;
3389+ if is_nonsymm then
3390+ DigraphSymmetricClosure(D);
3391+ fi ;
3392+ # NOTE: D is mutable following this operation. This should not cause issues
3393+ # or slow down the computations.
33723394 fi ;
33733395
3374- kappas := [ DigraphNrVertices(digraph) - 1 ] ;
3375-
3376- # The function newnetw is an implementation of Algorithm Nine from
3377- # Abdol-Hossein Esfahanian's ``Connectivity Algorithms'' which can be found at
3378- # https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
3379- newnetw := function (digraph, source, sink )
3380- local n, mat, outn, x, y;
3381- n := DigraphNrVertices(digraph);
3382- mat := List([ 1 .. 2 * n] , x -> BlistList([ 1 .. 2 * n] , [] ));
3383- outn := OutNeighbours(digraph);
3384- for x in [ 1 .. DigraphNrVertices(digraph)] do
3385- if x <> source and x <> sink then
3386- mat[ x + n][ x] := true ;
3387- fi ;
3388- for y in outn[ x] do
3389- if x = source or x = sink then
3390- mat[ x][ y + n] := true ;
3391- mat[ y][ x] := true ;
3392- elif y = source or y = sink then
3393- mat[ y][ x + n] := true ;
3394- mat[ x][ y] := true ;
3395- else
3396- mat[ y][ x + n] := true ;
3397- mat[ x][ y + n] := true ;
3398- fi ;
3399- od ;
3400- od ;
3401- return List(mat, x -> ListBlist([ 1 .. 2 * n] , x));
3402- end ;
3403-
3404- # The following function is an implementation of the Edmonds-Karp algorithm
3405- # with some minor adjustments that take into account the fact that the
3406- # capacity of all edges is 1.
3407- edmondskarp := function (netw, source, sink )
3408- local flow, capacity, queue, m, predecessor, edgeindex, stop, current, n, v;
3409-
3410- flow := 0 ;
3411- capacity := List(netw, x -> BlistList(x, x));
3412- # nredges := Sum(List(netw, Length));
3413-
3414- while true do
3415- queue := [ source] ;
3416- m := 1 ;
3417- predecessor := List(netw, x -> 0 );
3418- edgeindex := List(netw, x -> 0 );
3419- stop := false ;
3420- while m <= Size(queue) and not stop do
3421- current := queue[ m] ;
3422- n := 0 ;
3423- for v in netw[ current] do
3424- n := n + 1 ;
3425- if predecessor[ v] = 0 and v <> source and capacity[ current][ n] then
3426- predecessor[ v] := current;
3427- edgeindex[ v] := n;
3428- Add(queue, v);
3429- fi ;
3430- if v = sink then
3431- stop := true ;
3432- break ;
3433- fi ;
3434- od ;
3435- m := m + 1 ;
3436- od ;
3437-
3438- if predecessor[ sink] <> 0 then
3439- v := predecessor[ sink] ;
3440- n := edgeindex[ sink] ;
3441- while v <> 0 do
3442- capacity[ v][ n] := false ;
3443- n := edgeindex[ v] ;
3444- v := predecessor[ v] ;
3445- od ;
3446- flow := flow + 1 ;
3447- else
3448- return flow;
3449- fi ;
3450- od ;
3451- end ;
3452-
3453- # Referring once again to Abdol-Hossein Esfahanian's paper
3454- # (see newnetw, above).
3455- # The following lines implement Algorithm Eleven of that paper.
3456- mat := BooleanAdjacencyMatrix(digraph);
3457- degs := ListWithIdenticalEntries(DigraphNrVertices(digraph), 0 );
3458- for i in DigraphVertices(digraph) do
3459- for j in [ i + 1 .. DigraphNrVertices(digraph)] do
3460- if mat[ i][ j] or mat[ j][ i] then
3461- degs[ i] := degs[ i] + 1 ;
3462- degs[ j] := degs[ j] + 1 ;
3463- fi ;
3396+ # Special case complete digraph since no set of vertices disconnects it.
3397+ if IsCompleteDigraph(D) then
3398+ return DigraphNrVertices(D) - 1 ;
3399+ fi ;
3400+
3401+ # Construct "doubled" digraph as per Theorem 6.4 of
3402+ # Even S. Applications of Network Flow Techniques.
3403+ # In: Even G, ed. Graph Algorithms.
3404+ # Cambridge University Press; 2011:117-145.
3405+ # https://doi.org/10.1017/CBO9781139015165
3406+ # Doubles the vertices of the digraph `D`. For every vertex v, 2*v-1 is
3407+ # called the "in"-vertex and 2*v is called the "out"-vertex. For every edge
3408+ # (v, u) in `D`, the doubled digraph contains an edge (2*v, 2*u-1) from
3409+ # the out-vertex of `v` to the in-vertex of `u`. Additionally, there is an
3410+ # edge (2*v-1, 2*v) for every vertex v in `D`.
3411+ doubled_D_adj := List([ 1 .. 2 * DigraphNrVertices(D)] , x -> [] );
3412+ for v in DigraphVertices(D) do
3413+ for u in OutNeighborsOfVertex(D, v) do
3414+ Add(doubled_D_adj[ 2 * v] , 2 * u - 1 );
34643415 od ;
3416+ Add(doubled_D_adj[ 2 * v - 1 ] , 2 * v);
34653417 od ;
3466-
3467- mindegv := 0 ;
3468- mindeg := DigraphNrVertices(digraph) + 1 ;
3469- for i in DigraphVertices(digraph) do
3470- if degs[ i] < mindeg then
3471- mindeg := degs[ i] ;
3472- mindegv := i;
3473- fi ;
3474- od ;
3475-
3476- Nv := OutNeighboursOfVertex(digraph, mindegv);
3477- outn := OutNeighbours(digraph);
3478-
3479- for x in DigraphVertices(digraph) do
3480- if x <> mindegv and not mat[ x][ mindegv] and not mat[ mindegv][ x] then
3481- k := edmondskarp(newnetw(digraph, mindegv, x), mindegv, x);
3482- if k = 0 then
3483- return 0 ;
3484- else
3485- AddSet(kappas, k);
3418+ doubled_D := EdgeWeightedDigraph(
3419+ doubled_D_adj,
3420+ List(doubled_D_adj, x -> ListWithIdenticalEntries(Length(x), 1 )));
3421+
3422+ # The resulting graph, `doubled_D` is bipartite, and, additionally,
3423+ # there is a correspondence between paths in `D` and `doubled_D`
3424+ # given by mapping the path (v_1, v_2, ... v_n) in `D` to the path
3425+ # (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) in
3426+ # `doubled_D`. An conversely, any path starting with an even vertex and
3427+ # ending with an odd vertex in `doubled_D` is of the form
3428+ # (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) for
3429+ # some path (v_1, v_2, ... v_n) in `D`.
3430+
3431+ # The local vertex connectivity for a pair of vertices u, v is the
3432+ # size of the least set S that contain u and v
3433+ # such that any (u, v)-path (that is, a path with source u and target v)
3434+ # passes through S. If two vertices are adjacent, then the local connectivity
3435+ # is infinity by convention. The minimum cut with source u and target v is
3436+ # the least number of edges that need to be removed so that there is no
3437+ # longer a (u, v)-path.
3438+
3439+ # Let u and v be non-adjacent vertices in `D`. Because of the
3440+ # correspondence of paths in `D` and `doubled_D`, any such set S
3441+ # for `D` corresponds to a set of edges E_S (obtained by replacing the
3442+ # vertex w by the edge (2*w-1, 2*w)) in `doubled_D` whose removal
3443+ # disconnects 2*u from 2*v-1.
3444+ # Conversely, it can be shown that the smallest set of edges disconnecting
3445+ # 2*u from 2*v-1 has the same cardinality as E_S. This is because, whenever
3446+ # we remove any edge (2*s, 2*t-1) from `doubled_D` with the goal of
3447+ # disconnecting 2*u from 2*v-1, it is always just as
3448+ # efficient or more efficient to remove the edge (2*s-1, 2*s) instead, since
3449+ # any path from 2*u to 2*v-1 utilizing (2*s, 2*t-1) in `doubled_D` must
3450+ # pass through (2*s-1, 2*s) by construction. Note that u and v are
3451+ # non-adjacent by assumption, so it cannot be the case that
3452+ # (2*s, 2*t-1) = (2*u, 2*v-1).
3453+ # It follows that, for non-adjacent vertices, the local connectivity of u and
3454+ # v in `D` is equal to the minimum cut with source 2*u-1 and target 2*v
3455+ # in `doubled_D`.
3456+
3457+ # By the max-flow min-cut theorem, the size of the minimum cut with source u
3458+ # and target v equals the maximum flow between u and v see Wikipedia below:
3459+ # https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem
3460+ # Hence we can compute local vertex connectivity by repeated calls to the
3461+ # max_flow function below:
3462+ max_flow := { digraph, source, target} ->
3463+ Sum(DigraphMaximumFlow(digraph, source, target)[ source] );
3464+
3465+ # The vertex connectivity is the minimum of the local vertex connectivity
3466+ # over all pairs of vertices. However, it can be computed a bit more
3467+ # cleverly by utilizing some theory to reduce the number of pairs considered.
3468+ # In this function we implement Algorithm 11 from Abdol-Hossein
3469+ # Esfahanian's ``Connectivity Algorithms'' which can be found at
3470+ # https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
3471+ # In particular, we reduce the number of local vertex connectivity
3472+ # computations to n-d-1 + d*(d-1)/2 where n is the total number of vertices
3473+ # and d is the minimum degree of any vertex.
3474+ v := PositionMinimum(OutDegrees(D));
3475+ neighbours_v := OutNeighboursOfVertex(D, v);
3476+ kappa_min := fail ;
3477+ for u in DigraphVertices(D) do
3478+ if u <> v and not IsDigraphEdge(D, v, u) then
3479+ kappa := max_flow(doubled_D, 2 * u, 2 * v - 1 );
3480+ if kappa_min = fail or kappa < kappa_min then
3481+ kappa_min := kappa;
34863482 fi ;
34873483 fi ;
34883484 od ;
34893485
3490- for x in [ 1 .. Size(Nv) - 1 ] do
3491- for y in [ x + 1 .. Size(Nv )] do
3492- if not mat [ Nv [ x ]][ Nv [ y ]] and not mat [ Nv [ y ]][ Nv [ x ]] then
3493- k := edmondskarp(newnetw(digraph, Nv [ x ] , Nv [ y ] ), Nv [ x ] , Nv [ y ] ) ;
3494- if k = 0 then
3495- return 0 ;
3496- else
3497- AddSet(kappas, k) ;
3486+ for i in [ 1 .. Length(neighbours_v) ] do
3487+ for j in [ i + 1 .. Length(neighbours_v )] do
3488+ u := neighbours_v [ i ] ;
3489+ v := neighbours_v [ j ] ;
3490+ if not IsDigraphEdge(D, v, u) then
3491+ kappa := max_flow(doubled_D, 2 * u, 2 * v - 1 ) ;
3492+ if kappa_min = fail or kappa < kappa_min then
3493+ kappa_min := kappa ;
34983494 fi ;
34993495 fi ;
35003496 od ;
35013497 od ;
3502- return kappas[ 1 ] ;
3498+
3499+ # We can only be here if every vertex is adjacent to a vertex of minimum
3500+ # degree u and every pair of vertices v, w adjacent to u are also adjacent
3501+ # to each other. In other words, `D` is a complete graph, but we
3502+ # deal with these at the start. So this assert should pass.
3503+ Assert (1 , kappa_min <> fail );
3504+ return kappa_min;
35033505end );
35043506
35053507# The following function is a transliteration from python to GAP of
0 commit comments