Skip to content

Conversation

@RheyaM
Copy link
Contributor

@RheyaM RheyaM commented Nov 21, 2025

Implemented two methods for calculating the EdgeConnectivity of a Digraph, one using Spanning Trees and one using Dominating Sets, based on the Algorithms detailed in: https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf

Additional functions: DigraphDominatingSet() and DigraphGetNeighbourhood(), for getting a Dominating Set of a digraph and getting the neighbourhood of a subset of vertices in a digraph, respectively.

Copy link
Collaborator

@reiniscirpons reiniscirpons left a comment

Choose a reason for hiding this comment

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

Thanks for the implementation! I had some suggestions for the documentation and for improving the performance of the methods. Mainly more details in the docs and reworking some loops to implement more efficient DFS.

Some other things:

  • The documentation is not currently linked in the manual. You should edit the correct doc/z-chapX.xml file to add this. For example, to get the DigraphDominatingSet documentation to show up you should add the <#Include Label="DigraphDominatingSet"> tag in the correct place of doc/z-chap4.xml. You can test this by seeing if ?DigraphDominatingSet shows up the docs after running DigraphsMakeDoc() in gap. Same applies for the other functions.
  • Functions need to be more careful about symmetric vs non symmetric digraphs.
  • You should re-run the benchmarks after making the performance based changes to the implementation. I think currently the benchmarks are skewed by the time it takes to find the dominating set/spanning tree, but after the reimplementation this time should become negligible, and it will be more about how many vertices need to be checked. So maybe hold-off on removing the methods for now.

Afterwards should be good to merge!

doc/oper.xml Outdated
Comment on lines 1962 to 1964
This creates a dominating set of <A>digraph</A>, which is a subset of the vertices in
<A>digraph</A> such that the neighbourhood of this subset of vertices is equal to all
the vertices in <A>digraph</A>. </P>
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
This creates a dominating set of <A>digraph</A>, which is a subset of the vertices in
<A>digraph</A> such that the neighbourhood of this subset of vertices is equal to all
the vertices in <A>digraph</A>. </P>
This function returns a <E>dominating set</E> of <A>digraph</A>,
which is a subset of vertices whose neighbourhood consists of all
vertices in <A>digraph</A>. <P/>

Some suggestions for rephrasing the sentence a bit. I think generally the first sentence of a function description should concisely describe what the function does in terms of what does the function expect of its arguments and what does the function return. Saying it "creates" something is not bad, but its maybe slightly unclear whether this is created internally and stored somewhere or returned by the function directly etc. Also the new paragraph attribute should be <P/>.

I think you should also clarify what a neighbourhood of a set of vertices is, this is not immediately clear.

doc/oper.xml Outdated
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DigraphGetNeighbourhood">
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
<#GAPDoc Label="DigraphGetNeighbourhood">
<#GAPDoc Label="DigraphOutNeighbourhood">

I think omitting the Get would be better. There is no corresponding DigraphSetNeighbourhood function, so there is no risk of confusion.

Also, since this function is intended for directed graphs, you should specify whether it returns the set of out-neightbours or in-neighbours (and probably implement the in-neighbours version as well).

Alternatively, if the function is only intended for symmetric graphs, you should check for this explicitly in the function definition and also document it accordingly.

doc/weights.xml Outdated
<Attr Name="DigraphEdgeConnectivity" Arg="digraph"/>
<Returns>An integer</Returns>
<Description>
This returns a record representing the edge connectivity of <A>digraph</A>.<P/>
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
This returns a record representing the edge connectivity of <A>digraph</A>.<P/>
This function returns the edge connectivity of <A>digraph</A>.<P/>

This is very sparse on details. I think you should define what the edge-connectivity of a digraph is, it is not at all clear. Most users reading this function will probably be learning about edge connectivity for the first time, you shouldn't assume they are experts in graph theory (and even experts appreciate the reminder/clarification about what exactly we mean by edge connectivity).

As a rule of thumb, when writing, imagine you are writing the doc to explain it to yourself in the past, before you ever undertook this project.

Also, a record is a specific object is gap e.g. rec(a := 2, b:= 3) which stores a set of key-value pairs. This function just returns an integer not a record.

doc/weights.xml Outdated
<Description>
This returns a record representing the edge connectivity of <A>digraph</A>.<P/>

It makes use of <C>DigraphMaximumFlow(<A>digraph</A>)</C>, by constructing an edge-weighted 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
It makes use of <C>DigraphMaximumFlow(<A>digraph</A>)</C>, by constructing an edge-weighted Digraph
It makes use of <C>DigraphMaximumFlow(<A>digraph</A>)</C> function, by constructing an edge-weighted Digraph

It might also be a good idea to link to the doc of the DigraphMaximumFlow documentation, e.g.

See also <Ref Attr="DigraphMaximumFlow"/>.

gap/oper.gi Outdated
Comment on lines 2555 to 2559
neighbourhood := [];
for v in vertices do
neighbourhood := Difference(Union(neighbourhood,
OutNeighbours(digraph)[v]), vertices);
od;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function is correct, but its performance could be improved. In particular, calling Union(X, Y) for two lists $X$ and $Y$ will take about $|X|+|Y|$ steps to compute, this is because, apart from anything, it creates a completely new list consisting of the union of $X$ and $Y$. Furthermore, calling Difference(X, Y) also takes about |X|+|Y| steps for similar reason.

Part of the issue is that the Union function creates a new list every time, instead of modifying the list neighbourhood, which costs a lot of time. Gap provides the UniteSet and IntersectSet functions (see https://docs.gap-system.org/doc/ref/chap21_mj.html#X7B3469CD7EFC1A87 ) which will modify the first argument in place, which would be better since you want to change the set of neighbours anyway. So something like

  for v in vertices do
    UniteSet(neighbourhood, OutNeighbours(digraph)[v]);
    DifferenceSet(neighbourhood, vertices);
  od;

would be a first step to improving the function.

But we can do better! Removing the vertices vertices doesn't have to happen every iteration, indeed we could just do this once at the very end.
Similarly, its not clear you need to maintain neighbourhood to be a set all the while, you could in theory let it have duplicate vertices and just remove them at the end. So, I would suggest something like

Suggested change
neighbourhood := [];
for v in vertices do
neighbourhood := Difference(Union(neighbourhood,
OutNeighbours(digraph)[v]), vertices);
od;
neighbourhood := [];
for v in vertices do
Append(neighbourhood, OutNeighbours(digraph)[v]);
od;
neighbourhood := Difference(Unique(neighbourhood), vertices);

Here the Append function modifies neighbourhood in place, so every loop you would only take about Length( OutNeighbours(digraph)[v]) doing this, instead of also paying the cost of the length of neighbourhood and vertices every time.

And then you can remove duplicate vertices using the Unique function, and then do the difference with vertices once at the end of the function.

gap/weights.gi Outdated
Comment on lines 803 to 809
if DigraphNrVertices(digraph) = 1 then
return 0;
fi;

if DigraphNrStronglyConnectedComponents(digraph) > 1 then
return 0;
fi;
Copy link
Collaborator

@reiniscirpons reiniscirpons Dec 3, 2025

Choose a reason for hiding this comment

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

Suggested change
if DigraphNrVertices(digraph) = 1 then
return 0;
fi;
if DigraphNrStronglyConnectedComponents(digraph) > 1 then
return 0;
fi;
if DigraphNrVertices(digraph) = 1 or
DigraphNrStronglyConnectedComponents(digraph) > 1 then
return 0;
fi;

Its better to join the two checks into one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I was under the impression that the algorithms worked for non symmetric digraphs, which it seems to for the small digraphs I tested on (though admittedly all the larger digraphs I tested on were symmetric anyway since I got them from House of Graphs). I can look at Arc Connectivity when I get some time! Maybe I could swap them out if that's a more useful property to have for digraphs?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hi Rheya, nevermind, I misread the notes, my previous comment was completely wrong, namely it says:

Similarly, the edge–connectivity lambda(G) of a digraph G is
the least cardinality |S| of an arc set S such that G – S is no longer strongly connected or is trivial.

I previously thought that Chapter 2 was exclusively relating to undirected graphs, but after double checking it looks like I had the wrong idea about it. Ill edit my comments to remove these suggestions.

My only hesitation is that Algorithms 4.-7. seem to be written exclusively for undirected graphs, and I'm not sure if the proofs of correctness of these in the literature apply for both directed and undirected graphs.

Looking at the following publication by Esfahanian and Hakirn:

Esfahanian, A.H. and Louis Hakimi, S. (1984), On computing the connectivities of graphs and digraphs. Networks, 14: 355-366. https://doi.org/10.1002/net.3230140211

they separate the computation of edge-connectivity for graphs and digraphs into two sections and seem to say about the spanning tree-based approach in particular that

It is important to note that Lemma 7 does not necessarily hold for vertices in R .
This is why the methods described so far may not work for digraphs.
But there are other spanning trees!

And then they describe a different method for digraphs. So it might be that the spanning tree algorithm does not work as written for digraphs. It might be simplest to restrict the method to symmetric graphs for now. If it turns out that it also words for non-symmetric ones, we can always expand it in the future.

I don't think you need to implement arc connectivity, it seems to be quite different from the method for edge-connectivity.

gap/weights.gi Outdated
added := [v];
notadded := Difference(VerticesED, added);

while (DigraphNrEdges(st) < DigraphNrVertices(EdgeD) - 1) and
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar to the dominating set method, I think this could be made much more efficient by implementing a DFS and using a boolean list to keep track of the vertices that have been seen so far should suffice.

Additionally, you probably don't need to build the full spanning tree graph here, simply keeping track of which vertices were leaves (perhaps in a separate boolean list). This could be done by seeing if all the out-neighbours w of the current vertex v have seen[w] = true.

gap/weights.gi Outdated
min := Minimum(min, Minimum(Minimum(OutDegrees(EdgeD)),
Minimum(InDegrees(EdgeD))));
else
# In the case of spanning trees with only one non-leaf node,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it make sense here to try the dominating set method and only use the simpler all-pairs method if that one fails too? This would make the logic more complicated so just a suggestion.

gap/weights.gi Outdated
b := DigraphMaximumFlow(EdgeD, non_leaf[v], u)[non_leaf[v]];

sum := Minimum(Sum(a), Sum(b));
if (sum < min or min = -1) then
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
if (sum < min or min = -1) then
if sum < min or min = -1 then

Unlike in C++, the brackets are not required for conditionals in if statements.

Comment on lines +955 to +972
else
# If the dominating set of EdgeD is of Length 1,
# the above algorithm will not work
# Revert to iterating through all vertices of the original digraph

u := 1;

for v in [2 .. DigraphNrVertices(EdgeD)] do
a := DigraphMaximumFlow(EdgeD, u, v)[u];
b := DigraphMaximumFlow(EdgeD, v, u)[v];

sum := Minimum(Sum(a), Sum(b));
if (sum < min or min = -1) then
min := sum;
fi;

od;
fi;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This part of code is almost the same across the two EdgeConnectivity functions, so should be factored out into its own helper function. Alternatively, if the EdgeConnectivity or EdgeConnectivityDS functions get merged or one of them gets removed then this gets resolved too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants