From f2118f09ab1dcb879f7e21b1d4f27d09f02f755e Mon Sep 17 00:00:00 2001 From: Fernando Flores Brito Date: Wed, 1 Nov 2017 14:20:29 +0000 Subject: [PATCH 1/6] attr: add VertexConnectivity --- doc/attr.xml | 41 ++++++++++++ doc/z-chap4.xml | 3 +- gap/attr.gd | 1 + gap/attr.gi | 144 ++++++++++++++++++++++++++++++++++++++++++ gap/examples.gi | 5 ++ tst/standard/attr.tst | 89 ++++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 1 deletion(-) diff --git a/doc/attr.xml b/doc/attr.xml index 0684df880..a9e818928 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -2680,6 +2680,47 @@ gap> Length(M); <#/GAPDoc> +<#GAPDoc Label="VertexConnectivity"> + + + An non-negative integer. + + For a digraph digraph with set of vertices V, the attribute + VertexConnectivity(digraph) returns the least cardinality + |S| of a subset S of V such that the induced subdigraph + of digraph on V \ S is disconnected, or has at most one + vertex.

+ + Note, in particular, that empty digraphs, and disconnected digraphs, have + vertex connectivity zero.

+ + The algorithm makes n - d - 1 + d * (d - 1) / 2 calls to a max-flow + algorithm which itself has complexity O((n ^ 2) * e), where n + is the number of vertices of digraph, and e, d are the number + of edges and the minimum degree (respectively) of the underlying undirected + graph of digraph. + + J := JohnsonDigraph(9, 2); + +gap> VertexConnectivity(J); +14 +gap> D := CompleteBipartiteDigraph(4, 5); + +gap> VertexConnectivity(D); +4 +gap> VertexConnectivity(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> VertexConnectivity(D); +1 +]]> + + +<#/GAPDoc> + <#GAPDoc Label="NonUpperSemimodularPair"> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 999d01486..30b6b8492 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="VertexConnectivity"> <#Include Label="DigraphCycleBasis"> <#Include Label="DigraphIsKing"> <#Include Label="DigraphKings"> diff --git a/gap/attr.gd b/gap/attr.gd index 7afe2359e..5663ed7d0 100644 --- a/gap/attr.gd +++ b/gap/attr.gd @@ -77,6 +77,7 @@ DeclareAttribute("DigraphCore", IsDigraph); DeclareAttribute("CharacteristicPolynomial", IsDigraph); DeclareAttribute("NrSpanningTrees", IsDigraph); +DeclareAttribute("VertexConnectivity", 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..b64e20558 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -3358,6 +3358,150 @@ function(D) return Union(M, DIGRAPHS_MateToMatching(D, mateD)); end); +InstallMethod(VertexConnectivity, "for a digraph", [IsDigraph], +function(digraph) + local kappas, newnetw, edmondskarp, mat, degs, mindegv, mindeg, Nv, outn, k, + i, j, x, y; + + if DigraphNrVertices(digraph) <= 1 or not IsConnectedDigraph(digraph) then + return 0; + fi; + + if IsMultiDigraph(digraph) then + digraph := DigraphRemoveAllMultipleEdges(digraph); + fi; + + kappas := [DigraphNrVertices(digraph) - 1]; + + # The function newnetw is an implementation of Algorithm Nine from + # Abdol-Hossein Esfahanian's ``Connectivity Algorithms'' which can be found at + # https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + newnetw := function(digraph, source, sink) + local n, mat, outn, x, y; + n := DigraphNrVertices(digraph); + mat := List([1 .. 2 * n], x -> BlistList([1 .. 2 * n], [])); + outn := OutNeighbours(digraph); + for x in [1 .. DigraphNrVertices(digraph)] do + if x <> source and x <> sink then + mat[x + n][x] := true; + fi; + for y in outn[x] do + if x = source or x = sink then + mat[x][y + n] := true; + mat[y][x] := true; + elif y = source or y = sink then + mat[y][x + n] := true; + mat[x][y] := true; + else + mat[y][x + n] := true; + mat[x][y + n] := true; + fi; + od; + od; + return List(mat, x -> ListBlist([1 .. 2 * n], x)); + end; + + # The following function is an implementation of the Edmonds-Karp algorithm + # with some minor adjustments that take into account the fact that the + # capacity of all edges is 1. + edmondskarp := function(netw, source, sink) + local flow, capacity, queue, m, predecessor, edgeindex, stop, current, n, v; + + flow := 0; + capacity := List(netw, x -> BlistList(x, x)); + # nredges := Sum(List(netw, Length)); + + while true do + queue := [source]; + m := 1; + predecessor := List(netw, x -> 0); + edgeindex := List(netw, x -> 0); + stop := false; + while m <= Size(queue) and not stop do + current := queue[m]; + n := 0; + for v in netw[current] do + n := n + 1; + if predecessor[v] = 0 and v <> source and capacity[current][n] then + predecessor[v] := current; + edgeindex[v] := n; + Add(queue, v); + fi; + if v = sink then + stop := true; + break; + fi; + od; + m := m + 1; + od; + + if predecessor[sink] <> 0 then + v := predecessor[sink]; + n := edgeindex[sink]; + while v <> 0 do + capacity[v][n] := false; + n := edgeindex[v]; + v := predecessor[v]; + od; + flow := flow + 1; + else + return flow; + fi; + od; + end; + + # Referring once again to Abdol-Hossein Esfahanian's paper + # (see newnetw, above). + # The following lines implement Algorithm Eleven of that paper. + mat := BooleanAdjacencyMatrix(digraph); + degs := ListWithIdenticalEntries(DigraphNrVertices(digraph), 0); + for i in DigraphVertices(digraph) do + for j in [i + 1 .. DigraphNrVertices(digraph)] do + if mat[i][j] or mat[j][i] then + degs[i] := degs[i] + 1; + degs[j] := degs[j] + 1; + fi; + od; + od; + + mindegv := 0; + mindeg := DigraphNrVertices(digraph) + 1; + for i in DigraphVertices(digraph) do + if degs[i] < mindeg then + mindeg := degs[i]; + mindegv := i; + fi; + od; + + Nv := OutNeighboursOfVertex(digraph, mindegv); + outn := OutNeighbours(digraph); + + for x in DigraphVertices(digraph) do + if x <> mindegv and not mat[x][mindegv] and not mat[mindegv][x] then + k := edmondskarp(newnetw(digraph, mindegv, x), mindegv, x); + if k = 0 then + return 0; + else + AddSet(kappas, k); + fi; + fi; + od; + + for x in [1 .. Size(Nv) - 1] do + for y in [x + 1 .. Size(Nv)] do + if not mat[Nv[x]][Nv[y]] and not mat[Nv[y]][Nv[x]] then + k := edmondskarp(newnetw(digraph, Nv[x], Nv[y]), Nv[x], Nv[y]); + if k = 0 then + return 0; + else + AddSet(kappas, k); + fi; + fi; + od; + od; + return kappas[1]; +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..07c3ea6b0 100644 --- a/gap/examples.gi +++ b/gap/examples.gi @@ -296,6 +296,11 @@ function(_, n, k) D := MakeImmutable(JohnsonDigraphCons(IsMutableDigraph, n, k)); SetIsMultiDigraph(D, false); SetIsSymmetricDigraph(D, true); + if k > n then + SetVertexConnectivity(D, 0); + else + SetVertexConnectivity(D, (n - k) * k); + fi; return D; end); diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst index 55f047434..505f1d4ff 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -3114,6 +3114,95 @@ gap> D := DigraphRemoveEdge(D, 1, 3); gap> D := DigraphRemoveEdge(D, 1, 3); +# VertexConnectivity +gap> D := CompleteDigraph(10); + +gap> VertexConnectivity(D); +9 +gap> ForAny(Combinations(DigraphVertices(D), 8), +> x -> not IsConnectedDigraph(InducedSubdigraph(D, +> Difference(DigraphVertices(D), x)))); +false +gap> D := JohnsonDigraph(9, 2); + +gap> VertexConnectivity(D); +14 +gap> D := EmptyDigraph(0); + +gap> VertexConnectivity(D); +0 +gap> D := EmptyDigraph(1); + +gap> VertexConnectivity(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> VertexConnectivity(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> VertexConnectivity(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> VertexConnectivity(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> VertexConnectivity(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> VertexConnectivity(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 + # Semimodular lattices gap> D := DigraphFromDigraph6String("&C[o?"); From 3fddbcc728481525705258e39b3eda124f6073d9 Mon Sep 17 00:00:00 2001 From: reiniscirpons Date: Mon, 29 Sep 2025 22:25:08 +0100 Subject: [PATCH 2/6] Reimplement method with maximum flow + single doubled digraph computation --- gap/attr.gi | 259 +++++++++++++++++++++++++++------------------------- 1 file changed, 134 insertions(+), 125 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index b64e20558..1bce0efc6 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -3360,146 +3360,155 @@ end); InstallMethod(VertexConnectivity, "for a digraph", [IsDigraph], function(digraph) - local kappas, newnetw, edmondskarp, mat, degs, mindegv, mindeg, Nv, outn, k, - i, j, x, y; - + local doubled_digraph_adj, doubled_digraph, max_flow, u, v, i, j, + neighbours_v, kappa, kappa_min, adjacency_function; + + # 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 is not 1-connected, which + # is slightly confusing. + # TODO: Maybe change this convention, because at the moment e.g. + # The graph on 1 vertex is connected according to IsConnectedDigraph, but is + # not 1-connected according to this function. + # Similarly CycleDigraph(2) is biconnected according to IsBiconnectDigraph, + # but it is not 2-connected according to this function, which is a bit + # frustrating. if DigraphNrVertices(digraph) <= 1 or not IsConnectedDigraph(digraph) then return 0; fi; - if IsMultiDigraph(digraph) then + # Remove multiple edges + # TODO: Do each check separately? Or work this into the doubled digraph + # computation? + if IsMultiDigraph(digraph) or + DigraphHasLoops(digraph) or + not IsSymmetricDigraph(digraph) then + digraph := DigraphMutableCopy(digraph); digraph := DigraphRemoveAllMultipleEdges(digraph); - fi; - - kappas := [DigraphNrVertices(digraph) - 1]; - - # The function newnetw is an implementation of Algorithm Nine from - # Abdol-Hossein Esfahanian's ``Connectivity Algorithms'' which can be found at - # https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf - newnetw := function(digraph, source, sink) - local n, mat, outn, x, y; - n := DigraphNrVertices(digraph); - mat := List([1 .. 2 * n], x -> BlistList([1 .. 2 * n], [])); - outn := OutNeighbours(digraph); - for x in [1 .. DigraphNrVertices(digraph)] do - if x <> source and x <> sink then - mat[x + n][x] := true; - fi; - for y in outn[x] do - if x = source or x = sink then - mat[x][y + n] := true; - mat[y][x] := true; - elif y = source or y = sink then - mat[y][x + n] := true; - mat[x][y] := true; - else - mat[y][x + n] := true; - mat[x][y + n] := true; - fi; - od; - od; - return List(mat, x -> ListBlist([1 .. 2 * n], x)); - end; - - # The following function is an implementation of the Edmonds-Karp algorithm - # with some minor adjustments that take into account the fact that the - # capacity of all edges is 1. - edmondskarp := function(netw, source, sink) - local flow, capacity, queue, m, predecessor, edgeindex, stop, current, n, v; - - flow := 0; - capacity := List(netw, x -> BlistList(x, x)); - # nredges := Sum(List(netw, Length)); - - while true do - queue := [source]; - m := 1; - predecessor := List(netw, x -> 0); - edgeindex := List(netw, x -> 0); - stop := false; - while m <= Size(queue) and not stop do - current := queue[m]; - n := 0; - for v in netw[current] do - n := n + 1; - if predecessor[v] = 0 and v <> source and capacity[current][n] then - predecessor[v] := current; - edgeindex[v] := n; - Add(queue, v); - fi; - if v = sink then - stop := true; - break; - fi; - od; - m := m + 1; - od; - - if predecessor[sink] <> 0 then - v := predecessor[sink]; - n := edgeindex[sink]; - while v <> 0 do - capacity[v][n] := false; - n := edgeindex[v]; - v := predecessor[v]; - od; - flow := flow + 1; - else - return flow; - fi; - od; - end; - - # Referring once again to Abdol-Hossein Esfahanian's paper - # (see newnetw, above). - # The following lines implement Algorithm Eleven of that paper. - mat := BooleanAdjacencyMatrix(digraph); - degs := ListWithIdenticalEntries(DigraphNrVertices(digraph), 0); - for i in DigraphVertices(digraph) do - for j in [i + 1 .. DigraphNrVertices(digraph)] do - if mat[i][j] or mat[j][i] then - degs[i] := degs[i] + 1; - degs[j] := degs[j] + 1; - fi; + digraph := DigraphRemoveLoops(digraph); + digraph := DigraphSymmetricClosure(digraph); + digraph := MakeImmutable(digraph); + fi; + + # Special case complete digraph since technically no set of vertices + # disconnects it. + # TODO: if we decide to change the convention above then we should change it + # here too. + if IsCompleteDigraph(digraph) then + return DigraphNrVertices(digraph) - 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 `digraph`. 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 + # `digraph`, 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 `digraph`. + doubled_digraph_adj := List([1 .. 2 * DigraphNrVertices(digraph)], x -> []); + for v in DigraphVertices(digraph) do + for u in OutNeighborsOfVertex(digraph, v) do + Add(doubled_digraph_adj[2 * v], 2 * u - 1); od; + Add(doubled_digraph_adj[2 * v - 1], 2 * v); od; - - mindegv := 0; - mindeg := DigraphNrVertices(digraph) + 1; - for i in DigraphVertices(digraph) do - if degs[i] < mindeg then - mindeg := degs[i]; - mindegv := i; - fi; - od; - - Nv := OutNeighboursOfVertex(digraph, mindegv); - outn := OutNeighbours(digraph); - - for x in DigraphVertices(digraph) do - if x <> mindegv and not mat[x][mindegv] and not mat[mindegv][x] then - k := edmondskarp(newnetw(digraph, mindegv, x), mindegv, x); - if k = 0 then - return 0; - else - AddSet(kappas, k); + doubled_digraph := EdgeWeightedDigraph( + doubled_digraph_adj, + List(doubled_digraph_adj, x -> ListWithIdenticalEntries(Length(x), 1))); + + # The resulting graph, `doubled_digraph` is bipartite, and, additionally, + # there is a correspondence between paths in `digraph` and `doubled_digraph` + # given by mapping the path (v_1, v_2, ... v_n) in `digraph` 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_digraph`. An conversely, any path starting with an even vertex and + # ending with an odd vertex in `doubled_digraph` 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 `digraph`. + + # 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 `digraph`. Because of the + # correspondence of paths in `digraph` and `doubled_digraph`, any such set S + # for `digraph` corresponds to a set of edges E_S (obtained by replacing the + # vertex w by the edge (2*w-1, 2*w)) in `doubled_digraph` 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_digraph` 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_digraph` 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 `digraph` is equal to the minimum cut with source 2*u-1 and target 2*v + # in `doubled_digraph`. + + # 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 := {D, source, target} -> + Sum(DigraphMaximumFlow(D, 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(digraph)); + neighbours_v := OutNeighboursOfVertex(digraph, v); + # TODO: is adjacency function the best way to check adjacency? + adjacency_function := DigraphAdjacencyFunction(digraph); + kappa_min := -1; + for u in DigraphVertices(digraph) do + if u <> v and not adjacency_function(v, u) then + kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); + if kappa_min = -1 or kappa < kappa_min then + kappa_min := kappa; fi; fi; od; - for x in [1 .. Size(Nv) - 1] do - for y in [x + 1 .. Size(Nv)] do - if not mat[Nv[x]][Nv[y]] and not mat[Nv[y]][Nv[x]] then - k := edmondskarp(newnetw(digraph, Nv[x], Nv[y]), Nv[x], Nv[y]); - if k = 0 then - return 0; - else - AddSet(kappas, k); + 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 adjacency_function(v, u) then + kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); + if kappa_min = -1 or kappa < kappa_min then + kappa_min := kappa; fi; fi; od; od; - return kappas[1]; + + if kappa_min = -1 then + # 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, `digraph` is a complete graph, but we + # deal with these at the start. So this return should be unreachable. + return fail; + fi; + return kappa_min; end); # The following function is a transliteration from python to GAP of From e52f20564abca18d049a1c22080cc8b7f0594584 Mon Sep 17 00:00:00 2001 From: reiniscirpons Date: Tue, 30 Sep 2025 13:02:27 +0100 Subject: [PATCH 3/6] Add more tests --- tst/extreme/attr.tst | 22 ++++++++++++++++++++++ tst/standard/attr.tst | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tst/extreme/attr.tst b/tst/extreme/attr.tst index 6a7f34303..ee2d41184 100644 --- a/tst/extreme/attr.tst +++ b/tst/extreme/attr.tst @@ -70,6 +70,28 @@ gap> gr := DigraphFromGraph6String(str);; gap> ChromaticNumber(gr); 6 +# VertexConnectivity +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> VertexConnectivity(gr); +3 +gap> gr := CompleteMultipartiteDigraph([20, 20, 20]);; +gap> VertexConnectivity(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> VertexConnectivity(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 505f1d4ff..c8ff58e49 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -3193,7 +3193,7 @@ gap> ForAny(Combinations(DigraphVertices(D), 4), > x -> not IsConnectedDigraph(InducedSubdigraph(D, > Difference(DigraphVertices(D), x)))); true -gap> D := PancakeGraph(4); +gap> D := PancakeGraph(4);; gap> ForAny(Combinations(DigraphVertices(D), 2), > x -> not IsConnectedDigraph(InducedSubdigraph(D, > Difference(DigraphVertices(D), x)))); @@ -3202,6 +3202,41 @@ 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> VertexConnectivity(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> VertexConnectivity(D); +4 +gap> D := CirculantGraph(14, [1, 4, 7]);; # House of Graphs 53516 +gap> VertexConnectivity(D); +5 +gap> D := CirculantGraph(16, [1, 3, 8]);; # House of Graphs 53524 +gap> VertexConnectivity(D); +5 +gap> D := CirculantGraph(17, [1, 3, 5]);; # House of Graphs 53527 +gap> VertexConnectivity(D); +6 +gap> D := CirculantGraph(16, [1, 4, 7]);; # House of Graphs 53528 +gap> VertexConnectivity(D); +6 +gap> D := CirculantGraph(19, [3, 4, 5]);; # House of Graphs 53529 +gap> VertexConnectivity(D); +6 +gap> D := CirculantGraph(20, [1, 5, 8]);; # House of Graphs 53696 +gap> VertexConnectivity(D); +6 +gap> D := CirculantGraph(19, [1, 5, 8]);; # House of Graphs 53697 +gap> VertexConnectivity(D); +6 +gap> D := DigraphFromGraph6String( +> "[~yCKMF`{~r}????`?WOFA?{OBy?VwoFL_B|Y?}r_FyM@jkH{?MF{__M}_?ZNw?E" +> );; # House of Graphs 33964 +gap> VertexConnectivity(D); +7 # Semimodular lattices gap> D := DigraphFromDigraph6String("&C[o?"); From 8e92704350a8778d4c5f9100c2caab8f519f86d7 Mon Sep 17 00:00:00 2001 From: reiniscirpons Date: Tue, 30 Sep 2025 13:46:08 +0100 Subject: [PATCH 4/6] Update doc --- doc/attr.xml | 56 +++++++++++++++++++++++++++++++++++++--------------- gap/attr.gd | 1 + 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/doc/attr.xml b/doc/attr.xml index a9e818928..ac960a044 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -2685,26 +2685,46 @@ gap> Length(M); An non-negative integer. - For a digraph digraph with set of vertices V, the attribute - VertexConnectivity(digraph) returns the least cardinality - |S| of a subset S of V such that the induced subdigraph - of digraph on V \ S is disconnected, or has at most one - vertex.

+ This function returns the weak vertex connectivity of the digraph + digraph. +

- Note, in particular, that empty digraphs, and disconnected digraphs, have - vertex connectivity zero.

+ The weak vertex connectivity for a weakly connected digraph is the largest + number k such that: + + the digraph has at least k + 1 vertices and + the digraph remains weakly connected after the removal of any set + of at most k - 1 vertices. + + If the digraph is not weakly 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 (weakly) disconnect the digraph. +

- The algorithm makes n - d - 1 + d * (d - 1) / 2 calls to a max-flow - algorithm which itself has complexity O((n ^ 2) * e), where n - is the number of vertices of digraph, and e, d are the number - of edges and the minimum degree (respectively) of the underlying undirected - graph of digraph. + By convention the vertex connectivity of the empty digraph is + 0, and the vertex connectivity of a complete graph on n vertices is + n-1. In particular the singleton graph has vertex connectivity 0 and + the 2-cycle graph has vertex connectivity 1. This means that weak + connectivity (see ) does not imply weak + vertex 1-connectivity and biconnectivity + (see ) does not imply weak vertex + 2-connectivity. +

+ + 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 . J := JohnsonDigraph(9, 2); - -gap> VertexConnectivity(J); -14 +gap> VertexConnectivity(Digraph([])); +0 +gap> VertexConnectivity(Digraph([[]])); +0 +gap> VertexConnectivity(CycleDigraph(2)); +1 gap> D := CompleteBipartiteDigraph(4, 5); gap> VertexConnectivity(D); @@ -2716,6 +2736,10 @@ gap> D := Digraph([[2, 4, 5], [1, 4], [4, 7], [1, 2, 3, 5, 6, 7], gap> VertexConnectivity(D); 1 +gap> J := JohnsonDigraph(9, 2); + +gap> VertexConnectivity(J); +14 ]]> diff --git a/gap/attr.gd b/gap/attr.gd index 5663ed7d0..44f8ec8e0 100644 --- a/gap/attr.gd +++ b/gap/attr.gd @@ -77,6 +77,7 @@ DeclareAttribute("DigraphCore", IsDigraph); DeclareAttribute("CharacteristicPolynomial", IsDigraph); DeclareAttribute("NrSpanningTrees", IsDigraph); +# TODO: Should we change the name to DigraphVertexConnectivity? DeclareAttribute("VertexConnectivity", IsDigraph); # AsGraph must be mutable for grape to function properly From 10beabb2eb5118ccd5afe670a9fd1767232cc546 Mon Sep 17 00:00:00 2001 From: reiniscirpons Date: Tue, 30 Sep 2025 14:06:45 +0100 Subject: [PATCH 5/6] Modify code to have an assert instead of unreachable if --- gap/attr.gi | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 1bce0efc6..cf1048f35 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -3478,11 +3478,11 @@ function(digraph) neighbours_v := OutNeighboursOfVertex(digraph, v); # TODO: is adjacency function the best way to check adjacency? adjacency_function := DigraphAdjacencyFunction(digraph); - kappa_min := -1; + kappa_min := fail; for u in DigraphVertices(digraph) do if u <> v and not adjacency_function(v, u) then kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); - if kappa_min = -1 or kappa < kappa_min then + if kappa_min = fail or kappa < kappa_min then kappa_min := kappa; fi; fi; @@ -3494,20 +3494,18 @@ function(digraph) v := neighbours_v[j]; if not adjacency_function(v, u) then kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); - if kappa_min = -1 or kappa < kappa_min then + if kappa_min = fail or kappa < kappa_min then kappa_min := kappa; fi; fi; od; od; - if kappa_min = -1 then - # 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, `digraph` is a complete graph, but we - # deal with these at the start. So this return should be unreachable. - return fail; - fi; + # 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, `digraph` is a complete graph, but we + # deal with these at the start. So this return should be unreachable. + Assert(1, kappa_min <> fail); return kappa_min; end); From 949d62ed11b93e19fe01cd5a894ac1b3ada14196 Mon Sep 17 00:00:00 2001 From: reiniscirpons Date: Fri, 3 Oct 2025 00:23:16 +0100 Subject: [PATCH 6/6] Incorporate feedback from code review and add some more tests. --- doc/attr.xml | 68 ++++++++++++--------- doc/z-chap4.xml | 2 +- gap/attr.gd | 3 +- gap/attr.gi | 135 ++++++++++++++++++++---------------------- gap/examples.gi | 6 +- tst/extreme/attr.tst | 8 +-- tst/standard/attr.tst | 56 +++++++++++------- 7 files changed, 148 insertions(+), 130 deletions(-) diff --git a/doc/attr.xml b/doc/attr.xml index ac960a044..40be0005d 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -2680,36 +2680,44 @@ gap> Length(M); <#/GAPDoc> -<#GAPDoc Label="VertexConnectivity"> +<#GAPDoc Label="DigraphVertexConnectivity"> - + An non-negative integer. - This function returns the weak vertex connectivity of the digraph - digraph. + This function returns the vertex connectivity of the digraph + digraph. This is also sometimes called the weak vertex connectivity.

- The weak vertex connectivity for a weakly connected digraph is the largest - number k such that: + 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 weakly connected after the removal of any set - of at most k - 1 vertices. + 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 weakly connected, then its vertex connectivity is 0. + 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 (weakly) disconnect the digraph. + removal would disconnect the digraph.

- By convention the vertex connectivity of the empty digraph is - 0, and the vertex connectivity of a complete graph on n vertices is - n-1. In particular the singleton graph has vertex connectivity 0 and - the 2-cycle graph has vertex connectivity 1. This means that weak - connectivity (see ) does not imply weak - vertex 1-connectivity and biconnectivity - (see ) does not imply weak vertex - 2-connectivity. + 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 @@ -2719,27 +2727,29 @@ gap> Length(M); see . VertexConnectivity(Digraph([])); -0 -gap> VertexConnectivity(Digraph([[]])); -0 -gap> VertexConnectivity(CycleDigraph(2)); -1 gap> D := CompleteBipartiteDigraph(4, 5); -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 4 -gap> VertexConnectivity(PancakeGraph(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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 1 gap> J := JohnsonDigraph(9, 2); -gap> VertexConnectivity(J); +gap> DigraphVertexConnectivity(J); 14 +gap> DigraphVertexConnectivity(Digraph([])); +0 +gap> DigraphVertexConnectivity(Digraph([[1]])); +0 +gap> DigraphVertexConnectivity(CycleDigraph(2)); +1 +gap> DigraphVertexConnectivity(CompleteDigraph(5)); +4 ]]> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 30b6b8492..059e10eea 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -88,7 +88,7 @@ <#Include Label="HamiltonianPath"> <#Include Label="NrSpanningTrees"> <#Include Label="DigraphDijkstra"> - <#Include Label="VertexConnectivity"> + <#Include Label="DigraphVertexConnectivity"> <#Include Label="DigraphCycleBasis"> <#Include Label="DigraphIsKing"> <#Include Label="DigraphKings"> diff --git a/gap/attr.gd b/gap/attr.gd index 44f8ec8e0..ee9710f25 100644 --- a/gap/attr.gd +++ b/gap/attr.gd @@ -77,8 +77,7 @@ DeclareAttribute("DigraphCore", IsDigraph); DeclareAttribute("CharacteristicPolynomial", IsDigraph); DeclareAttribute("NrSpanningTrees", IsDigraph); -# TODO: Should we change the name to DigraphVertexConnectivity? -DeclareAttribute("VertexConnectivity", 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 cf1048f35..2416159a5 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -3358,47 +3358,44 @@ function(D) return Union(M, DIGRAPHS_MateToMatching(D, mateD)); end); -InstallMethod(VertexConnectivity, "for a digraph", [IsDigraph], -function(digraph) - local doubled_digraph_adj, doubled_digraph, max_flow, u, v, i, j, - neighbours_v, kappa, kappa_min, adjacency_function; +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 is not 1-connected, which - # is slightly confusing. - # TODO: Maybe change this convention, because at the moment e.g. - # The graph on 1 vertex is connected according to IsConnectedDigraph, but is - # not 1-connected according to this function. - # Similarly CycleDigraph(2) is biconnected according to IsBiconnectDigraph, - # but it is not 2-connected according to this function, which is a bit - # frustrating. - if DigraphNrVertices(digraph) <= 1 or not IsConnectedDigraph(digraph) then + # 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 - # TODO: Do each check separately? Or work this into the doubled digraph - # computation? - if IsMultiDigraph(digraph) or - DigraphHasLoops(digraph) or - not IsSymmetricDigraph(digraph) then - digraph := DigraphMutableCopy(digraph); - digraph := DigraphRemoveAllMultipleEdges(digraph); - digraph := DigraphRemoveLoops(digraph); - digraph := DigraphSymmetricClosure(digraph); - digraph := MakeImmutable(digraph); + # 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 technically no set of vertices - # disconnects it. - # TODO: if we decide to change the convention above then we should change it - # here too. - if IsCompleteDigraph(digraph) then - return DigraphNrVertices(digraph) - 1; + # 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 @@ -3406,30 +3403,30 @@ function(digraph) # In: Even G, ed. Graph Algorithms. # Cambridge University Press; 2011:117-145. # https://doi.org/10.1017/CBO9781139015165 - # Doubles the vertices of `digraph`. 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 - # `digraph`, 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 `digraph`. - doubled_digraph_adj := List([1 .. 2 * DigraphNrVertices(digraph)], x -> []); - for v in DigraphVertices(digraph) do - for u in OutNeighborsOfVertex(digraph, v) do - Add(doubled_digraph_adj[2 * v], 2 * u - 1); + # 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_digraph_adj[2 * v - 1], 2 * v); + Add(doubled_D_adj[2 * v - 1], 2 * v); od; - doubled_digraph := EdgeWeightedDigraph( - doubled_digraph_adj, - List(doubled_digraph_adj, x -> ListWithIdenticalEntries(Length(x), 1))); + doubled_D := EdgeWeightedDigraph( + doubled_D_adj, + List(doubled_D_adj, x -> ListWithIdenticalEntries(Length(x), 1))); - # The resulting graph, `doubled_digraph` is bipartite, and, additionally, - # there is a correspondence between paths in `digraph` and `doubled_digraph` - # given by mapping the path (v_1, v_2, ... v_n) in `digraph` to the path + # 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_digraph`. An conversely, any path starting with an even vertex and - # ending with an odd vertex in `doubled_digraph` is of the form + # `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 `digraph`. + # 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 @@ -3439,31 +3436,31 @@ function(digraph) # 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 `digraph`. Because of the - # correspondence of paths in `digraph` and `doubled_digraph`, any such set S - # for `digraph` corresponds to a set of edges E_S (obtained by replacing the - # vertex w by the edge (2*w-1, 2*w)) in `doubled_digraph` whose removal + # 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_digraph` with the goal of + # 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_digraph` must + # 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 `digraph` is equal to the minimum cut with source 2*u-1 and target 2*v - # in `doubled_digraph`. + # 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 := {D, source, target} -> - Sum(DigraphMaximumFlow(D, source, target)[source]); + 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 @@ -3474,14 +3471,12 @@ function(digraph) # 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(digraph)); - neighbours_v := OutNeighboursOfVertex(digraph, v); - # TODO: is adjacency function the best way to check adjacency? - adjacency_function := DigraphAdjacencyFunction(digraph); + v := PositionMinimum(OutDegrees(D)); + neighbours_v := OutNeighboursOfVertex(D, v); kappa_min := fail; - for u in DigraphVertices(digraph) do - if u <> v and not adjacency_function(v, u) then - kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); + 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; @@ -3492,8 +3487,8 @@ function(digraph) for j in [i + 1 .. Length(neighbours_v)] do u := neighbours_v[i]; v := neighbours_v[j]; - if not adjacency_function(v, u) then - kappa := max_flow(doubled_digraph, 2 * u, 2 * v - 1); + 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; @@ -3503,8 +3498,8 @@ function(digraph) # 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, `digraph` is a complete graph, but we - # deal with these at the start. So this return should be unreachable. + # 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); diff --git a/gap/examples.gi b/gap/examples.gi index 07c3ea6b0..84096fd78 100644 --- a/gap/examples.gi +++ b/gap/examples.gi @@ -296,10 +296,8 @@ function(_, n, k) D := MakeImmutable(JohnsonDigraphCons(IsMutableDigraph, n, k)); SetIsMultiDigraph(D, false); SetIsSymmetricDigraph(D, true); - if k > n then - SetVertexConnectivity(D, 0); - else - SetVertexConnectivity(D, (n - k) * k); + 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 ee2d41184..ce0346e07 100644 --- a/tst/extreme/attr.tst +++ b/tst/extreme/attr.tst @@ -70,17 +70,17 @@ gap> gr := DigraphFromGraph6String(str);; gap> ChromaticNumber(gr); 6 -# VertexConnectivity +# 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> VertexConnectivity(gr); +gap> DigraphVertexConnectivity(gr); 3 gap> gr := CompleteMultipartiteDigraph([20, 20, 20]);; -gap> VertexConnectivity(gr); +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""", @@ -89,7 +89,7 @@ gap> str := Concatenation("""~?@A}rEEB?oE?W?o?o?W?E??o?B??E??E??B???o??E???""", > """~~~?@~~~~~~~_?^~~~~~~w?B~~~~~~~??N~~~~~~{??^~~~~~~w??^~~~~~~w??N~~~~~~""", > """{??B~~~~~~~???^~~~~~~w??@~~~~~~~_???""");; # House of Graphs 52267 gap> gr := DigraphFromGraph6String(str);; -gap> VertexConnectivity(gr); +gap> DigraphVertexConnectivity(gr); 44 # diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst index c8ff58e49..32847ee3f 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -3114,10 +3114,26 @@ gap> D := DigraphRemoveEdge(D, 1, 3); gap> D := DigraphRemoveEdge(D, 1, 3); -# VertexConnectivity +# 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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 9 gap> ForAny(Combinations(DigraphVertices(D), 8), > x -> not IsConnectedDigraph(InducedSubdigraph(D, @@ -3125,20 +3141,20 @@ gap> ForAny(Combinations(DigraphVertices(D), 8), false gap> D := JohnsonDigraph(9, 2); -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 14 gap> D := EmptyDigraph(0); -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 0 gap> D := EmptyDigraph(1); -gap> VertexConnectivity(D); +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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 1 gap> not IsConnectedDigraph(D); false @@ -3149,7 +3165,7 @@ 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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 2 gap> ForAny(Combinations(DigraphVertices(D), 1), > x -> not IsConnectedDigraph(InducedSubdigraph(D, @@ -3161,7 +3177,7 @@ gap> ForAny(Combinations(DigraphVertices(D), 2), true gap> D := Digraph([[2, 3], [3, 5], [1, 2, 4], [2, 3], [3]]); -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 2 gap> ForAny(Combinations(DigraphVertices(D), 1), > x -> not IsConnectedDigraph(InducedSubdigraph(D, @@ -3172,7 +3188,7 @@ gap> ForAny(Combinations(DigraphVertices(D), 2), > Difference(DigraphVertices(D), x)))); true gap> D := DigraphFromGraph6String("NoCQ@?EAS_C`QA?c_Kg");; -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 3 gap> ForAny(Combinations(DigraphVertices(D), 2), > x -> not IsConnectedDigraph(InducedSubdigraph(D, @@ -3183,7 +3199,7 @@ gap> ForAny(Combinations(DigraphVertices(D), 3), > Difference(DigraphVertices(D), x)))); true gap> D := DigraphFromGraph6String("HoStIv{");; -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 4 gap> ForAny(Combinations(DigraphVertices(D), 3), > x -> not IsConnectedDigraph(InducedSubdigraph(D, @@ -3204,38 +3220,38 @@ gap> ForAny(Combinations(DigraphVertices(D), 3), true gap> D := DigraphFromGraph6String( > "Os_??L@GOS`SEKT@E`BK?");; # House of Graphs 44091 -gap> VertexConnectivity(D); +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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 4 gap> D := CirculantGraph(14, [1, 4, 7]);; # House of Graphs 53516 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 5 gap> D := CirculantGraph(16, [1, 3, 8]);; # House of Graphs 53524 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 5 gap> D := CirculantGraph(17, [1, 3, 5]);; # House of Graphs 53527 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 6 gap> D := CirculantGraph(16, [1, 4, 7]);; # House of Graphs 53528 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 6 gap> D := CirculantGraph(19, [3, 4, 5]);; # House of Graphs 53529 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 6 gap> D := CirculantGraph(20, [1, 5, 8]);; # House of Graphs 53696 -gap> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 6 gap> D := CirculantGraph(19, [1, 5, 8]);; # House of Graphs 53697 -gap> VertexConnectivity(D); +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> VertexConnectivity(D); +gap> DigraphVertexConnectivity(D); 7 # Semimodular lattices