-
Notifications
You must be signed in to change notification settings - Fork 61
Implementing DigraphEdgeConnectivity #884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…implemented methods
91c80c5 to
0634eed
Compare
There was a problem hiding this 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.xmlfile to add this. For example, to get theDigraphDominatingSetdocumentation to show up you should add the<#Include Label="DigraphDominatingSet">tag in the correct place ofdoc/z-chap4.xml. You can test this by seeing if?DigraphDominatingSetshows up the docs after runningDigraphsMakeDoc()ingap. 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
| 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <#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/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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
| neighbourhood := []; | ||
| for v in vertices do | ||
| neighbourhood := Difference(Union(neighbourhood, | ||
| OutNeighbours(digraph)[v]), vertices); | ||
| od; |
There was a problem hiding this comment.
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 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
| 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
| if DigraphNrVertices(digraph) = 1 then | ||
| return 0; | ||
| fi; | ||
|
|
||
| if DigraphNrStronglyConnectedComponents(digraph) > 1 then | ||
| return 0; | ||
| fi; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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.
| 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; |
There was a problem hiding this comment.
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.
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.