Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9d1c20d
Change to GAP stable-4.14 in CI
frankiegillis Feb 5, 2025
1ff11a2
Merge branch 'digraphs:main' into main
frankiegillis Feb 25, 2025
00fe48c
Merge branch 'digraphs:main' into main
frankiegillis Feb 26, 2025
7932db5
Merge branch 'main' of github.com:digraphs/Digraphs into HEAD
frankiegillis Feb 26, 2025
4cc0df5
Add HasPseudoSimilarVertices no docs
frankiegillis Mar 2, 2025
f05eaf3
Fixed syntax
frankiegillis Mar 2, 2025
5c5f5e8
fixed syntax
frankiegillis Mar 2, 2025
81a7065
Add HasPSVs
frankiegillis Mar 26, 2025
d3dac89
Is2EdgeTransitive overhaul using Orbit-Stabiliser Theorem
frankiegillis Mar 26, 2025
51a3664
Added support for digraphs with loops
frankiegillis Mar 26, 2025
c9f28ef
Linting
frankiegillis Mar 26, 2025
35033ec
More linting
frankiegillis Mar 26, 2025
02a088c
Edited Documentation
frankiegillis Mar 26, 2025
e1eb03e
Edited Documentation again
frankiegillis Mar 26, 2025
b088fa8
Merge branch 'main' of github.com:digraphs/Digraphs into Is2EdgeTrans…
frankiegillis Apr 7, 2025
4415aa0
Merge branch 'main' of github.com:digraphs/Digraphs into Is2EdgeTrans…
frankiegillis Apr 21, 2025
bc3d557
Once more rewritten Is2EdgeTransitive, this time to avoid looping thr…
frankiegillis Apr 26, 2025
e62fc7e
lint
frankiegillis Apr 26, 2025
1414081
Added more comments to prop.gi
frankiegillis Apr 26, 2025
733cc5f
Fix trailing whitespace
frankiegillis Apr 26, 2025
5ef192f
Added IsTwoEdgeTransitive as synonym for Is2EdgeTransitive
frankiegillis Sep 24, 2025
2964fab
Merge branch 'main' of github.com:digraphs/Digraphs into Is2EdgeTrans…
frankiegillis Sep 24, 2025
851b036
lint
frankiegillis Sep 24, 2025
d3dfdcd
Removed spurious files and added what happens when the argument of Is…
frankiegillis Sep 25, 2025
73afcc5
fixed typo in doc
frankiegillis Sep 25, 2025
6c6ab19
Added new Is2EdgeTransitive draft without bug
frankiegillis Oct 30, 2025
a136f62
back to original
frankiegillis Oct 30, 2025
dfa8fa9
Added DigraphMinimumCut
frankiegillis Oct 30, 2025
4e4b80e
Some nonsense happening in prop
frankiegillis Oct 30, 2025
81e49ee
renamed DigraphMinimumCut to DigraphMinimumCutSet
frankiegillis Oct 31, 2025
272d230
lint
frankiegillis Nov 1, 2025
7e9bd13
Merge branch 'main' of github.com:digraphs/Digraphs into DigraphMinim…
frankiegillis Nov 5, 2025
5e8e5bf
Improved function and updated doc
frankiegillis Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions doc/weights.xml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,34 @@ gap> Sum(flow[1]);
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="DigraphMinimumCut">
<ManSection>
<Attr Name="DigraphMinimumCut" Arg="digraph, s, t"/>
<Returns>A list of lists of integers.</Returns>
<Description>
If <A>digraph</A> is an edge-weighted digraph with distinct vertices <A>s</A> and
<A>t</A>, this returns a list of two lists representing the components of
a minimal <M>s</M>-<M>t</M> cut of <A>digraph</A>. <P/>

An <E><M>s</M>-<M>t</M> cut</E> is a partition of the vertices <M>\{ S, T \}</M> such that <A>s</A> is in <M>S</M> and
<A>t</A> is in <M>T</M>. The <E>capacity</E> of an <M>s</M>-<M>t</M> cut is the sum of the weights of every
edge whose source is in <M>S</M> and whose range is in <M>T</M>. A minimum <M>s</M>-<M>t</M> cut is an <M>s</M>-<M>t</M>
cut whose capacity is minimal.<P/>

This attribute is computed by using <Ref Func="DigraphMaximumFlow"/> and the
max-cut min-flow theorem.<P/>

See <Ref Attr="EdgeWeights" Func="EdgeWeightedDigraph"/>.
<Example><![CDATA[
gap> g := EdgeWeightedDigraph([[2, 2], [3], []], [[3, 2], [1], []]);
<immutable multidigraph with 3 vertices, 3 edges>
gap> DigraphMinimumCut(g, 1, 3);
[ [ 2, 1 ], [3]]
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="RandomUniqueEdgeWeightedDigraph">
<ManSection>
<Oper Name="RandomUniqueEdgeWeightedDigraph" Arg="[filt, ]n[, p]"/>
Expand Down
1 change: 1 addition & 0 deletions doc/z-chap5.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<#Include Label="EdgeWeightedDigraphShortestPaths">
<#Include Label="EdgeWeightedDigraphShortestPath">
<#Include Label="DigraphMaximumFlow">
<#Include Label="DigraphMinimumCut">
<#Include Label="RandomUniqueEdgeWeightedDigraph">
</Section>

Expand Down
4 changes: 3 additions & 1 deletion gap/weights.gd
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_FloydWarshall");
DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_Bellman_Ford");
DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_Dijkstra");

# 5. Maximum Flow
# 5. Maximum Flow and Minimum Cut
DeclareOperation("DigraphMaximumFlow",
[IsDigraph and HasEdgeWeights, IsPosInt, IsPosInt]);
DeclareOperation("DigraphMinimumCut",
[IsDigraph and HasEdgeWeights, IsPosInt, IsPosInt]);

# 6. Random edge weighted digraphs
DeclareOperation("RandomUniqueEdgeWeightedDigraph", [IsPosInt]);
Expand Down
48 changes: 47 additions & 1 deletion gap/weights.gi
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ function(D, source)
end);

#############################################################################
# 5. Maximum Flow
# 5. Maximum Flow and Minimum Cut
#############################################################################

InstallMethod(DigraphMaximumFlow, "for an edge weighted digraph",
Expand Down Expand Up @@ -772,6 +772,52 @@ function(D, start, destination)
return flows;
end);

InstallMethod(DigraphMinimumCut, "for an edge weighted digraph",
[IsDigraph and HasEdgeWeights, IsPosInt, IsPosInt],
function(D, s, t)
local weights, outs, vertices, flow, residuals, u, v, S, T, Q;

# Extract important data
weights := EdgeWeights(D);
outs := OutNeighbours(D);
vertices := DigraphVertices(D);

# Check input
if s < 1 or s > Length(vertices) then
ErrorNoReturn("<s> must be a vertex of <D>,");
elif t < 1 or t > Length(vertices) then
ErrorNoReturn("<t> must be a vertex of <D>,");
elif s = t then
ErrorNoReturn("<s> and <t> must be distinct");
fi;

# Find the residual edge capacities under the maximum flow
flow := DigraphMaximumFlow(D, s, t);
residuals := weights - flow;

# Carry out a BFS to find all the vertices in the residual
# network which are reachable from s. This gives the minimum
# cut by the max-flow min-cut theorem.

S := [s];
Q := [s];
while not IsEmpty(Q) do
u := Q[1];
Remove(Q, 1);
Comment on lines +804 to +806
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
while not IsEmpty(Q) do
u := Q[1];
Remove(Q, 1);
while i <= Length(Q) do
u := Q[i];

Removing an element from the front of a list is an O(n) operation, which gets called every loop. A slight improvement would be to, instead of removing the front element, to just maintain an index i, and at the start of iteration of the while loop to just take the i-th element from Q (constant time), and then increment i at the end of the loop (also constant time). This means that Q will grow to be slightly bigger, but since it wont ever exceed the number of vertices in D, this is not a problem.

Its a rather minor optimization since almost certainly most of the time spent by this function will be taken by the DigraphMaximumFlow function, but still good to not leave performance on the table.

for v in [1 .. Length(outs[u])] do
if residuals[u][v] > 0 then
if not outs[u][v] in S then
Add(Q, outs[u][v]);
Add(S, outs[u][v]);
Comment on lines +809 to +811
Copy link
Collaborator

@reiniscirpons reiniscirpons Nov 24, 2025

Choose a reason for hiding this comment

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

Suggested change
if not outs[u][v] in S then
Add(Q, outs[u][v]);
Add(S, outs[u][v]);
if not seen[outs[u][v]] then
Add(Q, outs[u][v]);
seen[outs[u][v]] = true;

Another minor optimization: you are adding to the list S, which means it will be an unordered list, hence the lookup outs[u][v] in S will take O(n) time.

GAP provides the AddSet function, which adds an element while maintaining the an ordering on the elements of a list. So if you used AddSet(S, outs[u][v]); instead, the lookup time would go down to something like O(log(n)) I think. But in general the AddSet function may be quite slow too (don't know complexity off the top of my head, certainly no more than linear).

However, if you used a boolean list (blist) seen instead, initializing it as seen = BlistList(DigraphVertices(D), []) before entering the while loop, this would create a boolean list consisting of false for every vertex in D, so we would pay a one-time upfront cost of O(n). But for this, we can now check if a vertex has already been visited in constant time (since its just a lookup seen[outs[u][v]] to check if the stored entry is true or false), and you can update the set of seen vertices in constant time (since its just an assignment seen[outs[u][v]] = true in a list without needing to increase the size of the list (since seen was initialized for every vertex already)). See https://docs.gap-system.org/doc/ref/chap22_mj.html for more info on boolean lists.

Same as before, minor optimization but worth making.

fi;
fi;
od;
od;

T := Difference(vertices, S);
return [S, T];
end);

#############################################################################
# 6. Random edge weighted digraphs
#############################################################################
Expand Down
76 changes: 75 additions & 1 deletion tst/standard/weights.tst
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ gap> EdgeWeightedDigraphShortestPath(d, 1, 3);
[ [ 1, 2, 3 ], [ 1, 1 ] ]

#############################################################################
# 5. Maximum Flow
# 5. Maximum Flow and Minimum Cut
#############################################################################

# Maximum flow: empty digraphs
Expand Down Expand Up @@ -368,6 +368,80 @@ gap> gr := EdgeWeightedDigraph([[2], [3, 6], [4], [1, 6], [1, 3], []],
gap> DigraphMaximumFlow(gr, 5, 6);
[ [ 10 ], [ 3, 7 ], [ 7 ], [ 0, 7 ], [ 10, 4 ], [ ] ]

# Minimum cut: empty digraphs
gap> d := EdgeWeightedDigraph([], []);
<immutable empty digraph with 0 vertices>
gap> DigraphMinimumCut(d, 1, 1);
Error, <s> must be a vertex of <D>,

# Minimum cut: single vertex (also empty digraphs)
gap> d := EdgeWeightedDigraph([[]], [[]]);
<immutable empty digraph with 1 vertex>
gap> DigraphMinimumCut(d, 1, 1);
Error, <s> and <t> must be distinct

# Minimum cut: source = dest
gap> d := EdgeWeightedDigraph([[2], []], [[5], []]);
<immutable digraph with 2 vertices, 1 edge>
gap> DigraphMinimumCut(d, 1, 1);
Error, <s> and <t> must be distinct

# Minimum cut: has loop
gap> d := EdgeWeightedDigraph([[1, 2], []], [[5, 10], []]);
<immutable digraph with 2 vertices, 2 edges>
gap> DigraphMinimumCut(d, 1, 2);
[ [ 1 ], [ 2 ] ]

# Minimum cut: invalid source
gap> d := EdgeWeightedDigraph([[1, 2], []], [[5, 10], []]);
<immutable digraph with 2 vertices, 2 edges>
gap> DigraphMinimumCut(d, 5, 2);
Error, <s> must be a vertex of <D>,

# Minimum cut: invalid sink
gap> d := EdgeWeightedDigraph([[1, 2], []], [[5, 10], []]);
<immutable digraph with 2 vertices, 2 edges>
gap> DigraphMinimumCut(d, 1, 5);
Error, <t> must be a vertex of <D>,

# Minimum cut: sink not reachable
gap> d := EdgeWeightedDigraph([[1], []], [[5], []]);
<immutable digraph with 2 vertices, 1 edge>
gap> DigraphMinimumCut(d, 1, 2);
[ [ 1 ], [ 2 ] ]

# Minimum cut: source has in neighbours
gap> d := EdgeWeightedDigraph([[2], [3], []], [[5], [10], []]);
<immutable digraph with 3 vertices, 2 edges>
gap> DigraphMinimumCut(d, 2, 3);
[ [ 2 ], [ 1, 3 ] ]

# Minimum cut: sink has out-neighbours
gap> d := EdgeWeightedDigraph([[2], [3], [2]], [[5], [10], [7]]);
<immutable digraph with 3 vertices, 3 edges>
gap> DigraphMinimumCut(d, 2, 3);
[ [ 2 ], [ 1, 3 ] ]

# Minimum cut: cycle
gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[5], [10], [7]]);
<immutable digraph with 3 vertices, 3 edges>
gap> DigraphMinimumCut(d, 1, 3);
[ [ 1 ], [ 2, 3 ] ]

# Minimum cut: example from Wikipedia
gap> gr := EdgeWeightedDigraph([[3, 4], [], [2, 4], [2]],
> [[10, 5], [], [5, 15], [10]]);;
gap> DigraphMinimumCut(gr, 1, 2);
[ [ 1 ], [ 2, 3, 4 ] ]
gap> DigraphMinimumCut(gr, 3, 2);
[ [ 4, 3 ], [ 1, 2 ] ]

# Minimum cut: example from Wikipedia article on Push-label maximum flow
gap> gr := EdgeWeightedDigraph([[2], [3, 6], [4], [1, 6], [1, 3], []],
> [[12], [3, 7], [10], [5, 10], [15, 4], []]);;
gap> DigraphMinimumCut(gr, 5, 6);
[ [ 1, 2, 5 ], [ 3, 4, 6 ] ]

#############################################################################
# 6. Random edge-weighted digraphs
#############################################################################
Expand Down
Loading