Skip to content

Conversation

ffloresbrito
Copy link

@ffloresbrito ffloresbrito commented Nov 1, 2017

Edit: ( @reiniscirpons ): the current implementation seems have some bug and it fails on DigraphFromGraph6String("HoStIv{") (this is Graph 33668 on house of graphs). The expected output is 4, currently the method gives 3. My hunch is that the bug may be in the Edmonds-Karp method. It seems PR #584 implements a max-flow method, so once this is merged, we may be able to fix this PR as well.

Hopefully within the next 7 years we will be able to merge this PR!


Original comment:
This pull request adds an attribute called VertexConnectivity which for a graph G = (V, E) computes the least cardinality |S| of a subset S of V, such that G - S is either disconnected, is trivial, or has no vertices. Documentation and tests are included.

Copy link
Collaborator

@wilfwilson wilfwilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more fairly minor comments. I've still not studied the algorithm in detail, to see whether you're implementing it correctly. Not sure whether I will. Maybe @james-d-mitchell knows more about the algorithm and could weigh in?

wilfwilson
wilfwilson previously approved these changes Nov 1, 2017
Copy link
Member

@james-d-mitchell james-d-mitchell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks quite reasonable, apart from some design decisions that are likely rather costly (in terms of time and space). I'd like to see the code in this PR refactored to remove these things.

@james-d-mitchell james-d-mitchell added enhancement A label for PRs that provide enhancements. do not merge A label for PRs that should not be merged for whatever reason. 0.12 and removed 0.11 labels Nov 21, 2017
@wilfwilson wilfwilson added 0.13 and removed 0.12 labels Jan 31, 2018
@wilfwilson wilfwilson added 0.14 and removed 0.13 labels Sep 18, 2018
@wilfwilson wilfwilson added 0.15 and removed 0.14 labels Nov 23, 2018
@wilfwilson
Copy link
Collaborator

@james-d-mitchell Did @ffloresbrito address your changes? Is @ffloresbrito still involved in any GAP-related activity?

@wilfwilson wilfwilson removed the 0.15 label Aug 22, 2019
@wilfwilson wilfwilson changed the title add VertexConnectivity Add VertexConnectivity Mar 3, 2021
@wilfwilson wilfwilson force-pushed the vertex-connectivity branch from de81d16 to b27af13 Compare March 3, 2021 16:55
@wilfwilson
Copy link
Collaborator

@james-d-mitchell I have squashed and rebased this PR on the latest master branch, and fixed the CI errors, and pushed. Assuming that @ffloresbrito is no longer involved with this, we can now decide how to proceed with this PR (or whether just to just close it).

On the whole, I'd say it's better to have a slow implementation than no implementation - it could always be a student project to improve or rewrite it. But that assumes that this implementation is correct, and it's been long enough that I don't remember if it is. So I'll review it again sometime.

@wilfwilson wilfwilson force-pushed the vertex-connectivity branch 2 times, most recently from 7671666 to f26545c Compare March 3, 2021 18:48
@digraphs digraphs deleted a comment from codecov bot Mar 3, 2021
@wilfwilson wilfwilson force-pushed the vertex-connectivity branch 2 times, most recently from 5b55e83 to 7aa5e9d Compare March 10, 2021 07:53
Copy link
Collaborator

@wilfwilson wilfwilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I accidentally marked a comment from James as resolved, but it's not. It basically said:

You should add a citation to the documentation, stating where this algorithm is taken from.

@wilfwilson wilfwilson dismissed james-d-mitchell’s stale review March 10, 2021 13:10

Looks like all of the specific changes that James asked for were made.

@wilfwilson wilfwilson self-assigned this Mar 10, 2021
@wilfwilson wilfwilson force-pushed the vertex-connectivity branch from 7aa5e9d to 7e1f0d7 Compare March 11, 2021 14:43
@wilfwilson wilfwilson changed the title Add VertexConnectivity Add VertexConnectivity Mar 26, 2021
@wilfwilson wilfwilson force-pushed the vertex-connectivity branch from 7e1f0d7 to 1f0981b Compare May 12, 2021 18:03
@wilfwilson wilfwilson force-pushed the vertex-connectivity branch 3 times, most recently from e21ba66 to da6864e Compare May 26, 2021 19:35
@digraphs digraphs deleted a comment from github-actions bot Aug 26, 2024
Copy link
Contributor

@MeikeWeiss MeikeWeiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For us this method would be very helpful. I looked at the algorithm and the implementation and to me it looks like a useful implementation of vertex connectivity.
In my opinion two parts are written a bit awkwardly as you can see below. Is there anything else that needs to be done apart from adding a citation to the documentation?

gap/attr.gi Outdated
mindeg := degs[i];
mindegv := i;
fi;
od;
Copy link
Contributor

@MeikeWeiss MeikeWeiss Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think mindegv:=PositionMinimum(degs) andmindeg:=degs[mindegv] should do the same and is an easier computation and easier to read.

degs[j] := degs[j] + 1;
fi;
od;
od;
Copy link
Contributor

@MeikeWeiss MeikeWeiss Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be also possible to compute degs with OutDegrees and InDegrees such that it is maybe shorter and easier to read.

@james-d-mitchell james-d-mitchell removed the do not merge A label for PRs that should not be merged for whatever reason. label Aug 30, 2024
@wilfwilson
Copy link
Collaborator

Thanks for your review last year @MeikeWeiss. I'll have a look at reviewing and hopefully merging this, once and for all.

For now I'll rebase on main and fix any merge conflicts and CI errors.

@wilfwilson
Copy link
Collaborator

The method currently seems to have a bug:

testing: /tmp/gaproot/pkg/Digraphs/tst/standard/attr.tst
########> Diff in /tmp/gaproot/pkg/Digraphs/tst/standard/attr.tst:
3152
# Input is:
VertexConnectivity(D);
# Expected output:
4
# But found:
3
########
    4903 ms (366 ms GC) and 1.16GB allocated for attr.tst

The correct answer is indeed 4 (as proved by brute force, according to some additional lines that I've since added to the test).

@reiniscirpons
Copy link
Collaborator

reiniscirpons commented Sep 27, 2025

To add my 2 cents:

  1. I think the error is most likely in the edmondskarp method for computing maximum flows. As of Edge weights #5: maximum flow #751, however we have our own implementation of maximum flow computations, so I think it would make sense to just replace the edmondskarp call with calls to DigraphMaximumFlow. Note that due to the differences in output format, the total flow would be given by calling Sum(DigraphMaximumFlow(digraph, start, destination)[start]) as per the docs.

  2. The way that Algorithm 9 from [1] is implemented, a new graph (via the newnetw function) is constructed for every call to edmondskarp. This is how Algorithm 9 is described in [1], but its a bit wasteful, since each constructed graph is almost identical to every other constructed graph. In fact in the book [2] that Algorithm 9 is taken from, only a single augmented graph is constructed, see the proof of Theorem 6.4:

If the original digraph $D = (V, E)$ has no loops or multiple edges, then construct the graph $\overline{D}$ by letting the vertex set $V(\overline{D} ) = V' \cup V''$ where $V'$ and $V''$ are formal copies of vertices in $V$, so that if $v\in V$ then $v' \in V$ and $v''\in V''$ are the corresponding copies of vetices, and let the edge set be given by
$$E(\overline{D}) = \lbrace (v', v'') : v \in V \rbrace \cup \lbrace (u'', v') : (u, v) \in E \rbrace.$$
Essentially each vertex $v$ gets split into 2 vertices an "in" vertex $v'$ and and "out" vertex $v''$ - all in-edges of $v$ go into $v'$ and all out-edges of $v$ leave from $v''$.

It follows from this theorem that, instead of computing the max-flow with source $u$ and target $v$ in the graph newnetw(D, u, v) as is currently done in the algorithm, we can compute the max-flow with source $u''$ and target $v'$ in the graph $\overline{D}$ instead. This means that we only need to compute the single graph $\overline{D}$ instead of the $O(n^2)$ newnetw graphs as it's done currently.

I'll have a crack at making these changes if thats ok with you @wilfwilson

[1] https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
[2] https://www.cambridge.org/core/books/graph-algorithms/8B295BD0845A174FFE6B2CD6D4B2C63F

@wilfwilson
Copy link
Collaborator

Please do, @reiniscirpons! Be my guest. Thank you very much.

What you describe sounds smart.

@reiniscirpons
Copy link
Collaborator

Ok I think I fixed the bug. The new DigraphMaximumFlow function made it quite easy, the actual function is not too long, but I included quite a lot of verbose comments to justify what is going on. Happy to remove later on.

There are still a few questions that should be resolved (currently down as TODOs in the source), namely:

  1. Should we rename the function from VertexConnectivity to DigraphVertexConnectivity? This would be more in line with other digraphs functions.
  2. Should we change the convention for vertex-connectivity of the complete graphs? At the moment we follow the wikipedia definition (https://en.wikipedia.org/wiki/Connectivity_(graph_theory)), see also (https://mathworld.wolfram.com/VertexConnectivity.html), which imply that VertexConnectivity(CompleteDigraph(n)) == n-1. This has the potentially confusing consequence that for the singleton digraph, VertexConnectivity(Digraph([[]])) == 0 but at the same time IsConnectedDigraph(Digraph([[]])) == true. Similarly, because of the convention, VertexConnectivity(CycleDigraph(2)) == 1 and yet IsBiconnectedDigraph(CycleDigraph(2)) == true. The reason we need a convention in the first place is that the simple definition for vertex connectivity - "the least set of vertices whose removal disconnects the digraph" - works for all digraphs except for the complete ones. This is because no set of vertices you remove from a complete digraph disconnects it, unless you count the empty digraph as disconnected (which has its own set of issues). To quote the MathWorld article the convention VertexConnectivity(CompleteDigraph(n)) == n-1 "allows most general results about connectivity to remain valid on complete graphs (West 2001, p. 149)". Other reasonable choices would be VertexConnectivity(CompleteDigraph(n)) == n (this would implicitly make us assume that the empty digraph is not connected) or VertexConnectivity(CompleteDigraph(n)) == infinity (indicating that no set of vertices disconnects the graph, but of course now you need to deal with infinities in your computations). At the moment I take the n-1 convention and just explain the issues with connectivity and biconnectivity for the singleton and 2-cycle in the docs.
  3. To speed up the computation we can remove loops and multiple edges, furthermore we need to symmetrize the graph. At the moment I check if the graph has loops, multiple edges or is not symmetric and if either condition holds then I do all three modifications if any condition holds. In theory I could also test each separately and then perform the change or somehow incorporate all of these modifications when constructing the doubled_digraph digraph. See line 3386 in attr.gi. Might also be premature optimizations, this probably doesn't take too much time when compared to the max-flow computations. Should we optimize these graph modifications?
  4. I need to check adjacency for quite a lot of vertices (we only run a max-flow on non-adjacent vertices), at worst I will need to look at every pair of vertices, I'm currently using DigraphAdjacencyFunction(digraph) for this, is there a better way of checking for adjacency?

@wilfwilson could you give the code a review, when you have the chance? Thanks!

Copy link
Collaborator

@wilfwilson wilfwilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, thank you for working on it.

Perhaps to avoid any weirdness we could only define vertex connectivity for digraphs with at least two vertices? (Or whatever the relevant number would be to avoid the weird cases).

I'm happy with the file having lengthy comments, they're harmless and potentially helpful!

I've got a few suggestions that I've commented directly on the code.

<A>digraph</A>.
<P/>

The weak vertex connectivity for a weakly connected digraph is the largest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The weak vertex connectivity for a weakly connected digraph is the largest
The weak vertex connectivity of a weakly connected digraph is the largest

The weak vertex connectivity for a weakly connected digraph is the largest
number <M>k</M> such that:
<List>
<Item> the digraph has at least <M>k + 1</M> vertices and</Item>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Item> the digraph has at least <M>k + 1</M> vertices and</Item>
<Item> the digraph has at least <M>k + 1</M> vertices, and</Item>


DeclareAttribute("CharacteristicPolynomial", IsDigraph);
DeclareAttribute("NrSpanningTrees", IsDigraph);
# TODO: Should we change the name to DigraphVertexConnectivity?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I say so.

<A>digraph</A>.
<P/>

The weak vertex connectivity for a weakly connected digraph is the largest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, we don't really use/prioritise the term "weakly connected" in the Digraphs package, we rather tend to just use the term "connected". (Please let me know if I'm wrong). So I'd suggest that you change it to this:

Suggested change
The weak vertex connectivity for a weakly connected digraph is the largest
The weak vertex connectivity of a connected digraph (in the sense of `<Ref Prop="IsConnectedDigraph"/>) is the largest

<#Include Label="LaplacianMatrix">
</Section>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +3390 to +3393
digraph := DigraphRemoveAllMultipleEdges(digraph);
digraph := DigraphRemoveLoops(digraph);
digraph := DigraphSymmetricClosure(digraph);
digraph := MakeImmutable(digraph);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
digraph := DigraphRemoveAllMultipleEdges(digraph);
digraph := DigraphRemoveLoops(digraph);
digraph := DigraphSymmetricClosure(digraph);
digraph := MakeImmutable(digraph);
DigraphRemoveAllMultipleEdges(digraph);
DigraphRemoveLoops(digraph);
DigraphSymmetricClosure(digraph);
MakeImmutable(digraph);

Save some bits 😉

Comment on lines +3384 to +3385
# TODO: Do each check separately? Or work this into the doubled digraph
# computation?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to keep this separate from the doubled digraph computation, for the purposes of keeping the code for that computation as understandable as possible.

But if you think merging would give a significant performance benefit, then I wouldn't object.

end);

InstallMethod(VertexConnectivity, "for a digraph", [IsDigraph],
function(digraph)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use D as the variable name, it's what we (at one point) have tried to standardise upon in the library of this the package. (See also #868 where I've made the package more consistent again).

Suggested change
function(digraph)
function(D)

adjacency_function := DigraphAdjacencyFunction(digraph);
kappa_min := fail;
for u in DigraphVertices(digraph) do
if u <> v and not adjacency_function(v, u) then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the u <> v check here mean that you can avoid removing all loops above? I haven't checked.

Also, I think the most efficient thing you can do in general is simply:

Suggested change
if u <> v and not adjacency_function(v, u) then
if u <> v and not u in neighbours_v then

for j in [i + 1 .. Length(neighbours_v)] do
u := neighbours_v[i];
v := neighbours_v[j];
if not adjacency_function(v, u) then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the best you can do here is:

Suggested change
if not adjacency_function(v, u) then
if not IsDigraphEdge(D, v, u) then

Although I'm not totally sure.

But this change would let you get rid of the line

adjacency_function := DigraphAdjacencyFunction(digraph);

above.

@wilfwilson
Copy link
Collaborator

@reiniscirpons In response to your specific questions above, here are my answers:

  1. Yes
  2. I don't mind.
  3. I think it'd be easy enough to do these separately:
multi := IsMultiDigraph(D);
loops := DigraphHasLoops(D);
nsymm := not IsSymmetricDigraph(D);

if multi or loops or nsymm then
  D := DigraphMutableCopy(D);
  if multi then
    DigraphRemoveAllMultipleEdges(D);
  fi;
  if loops then
    DigraphRemoveLoops(D);
  fi;
  if nsymm then
    DigraphSymmetricClosure(D);
  fi;
  MakeImmutable(D);
fi;
  1. I commented about this directly on the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A label for PRs that provide enhancements.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants