diff --git a/Makefile.in b/Makefile.in
index 127623632..3b36c7713 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -26,6 +26,7 @@ KEXT_SOURCES += src/perms.c
KEXT_SOURCES += src/planar.c
KEXT_SOURCES += src/schreier-sims.c
KEXT_SOURCES += src/safemalloc.c
+KEXT_SOURCES += src/dfs.c
ifdef WITH_INCLUDED_BLISS
KEXT_SOURCES += extern/bliss-0.73/defs.cc
diff --git a/doc/oper.xml b/doc/oper.xml
index 8c55db1ac..63d8abfc7 100644
--- a/doc/oper.xml
+++ b/doc/oper.xml
@@ -2489,3 +2489,387 @@ true]]>
gap> DIGRAPHS_FREE_CLIQUES_DATA();
]]>
<#/GAPDoc>
+
+<#GAPDoc Label="NewDFSRecord">
+
+
+
+ A record.
+
+ This record contains four lists (parents, edge, preorder and postorder) with their length
+ equal to the number of vertices in the digraph. Each index i of each list
+ corresponds to vertex i in digraph.
+ These lists store the following (where record is returned by NewDFSRecord):
+
+ parents
+ -
+ At each index, the parent of the vertex is stored.
+
+ Note that when
+ record.config.iterative is true, both record.parents[i]
+ and record.edge[i] for all vertices encountered in DFS
+ search i are updated as successors are pushed to the
+ stack, rather than when i is visited (which occurs when record.config.iterative
+ is false). Once i has been visited,
+ record.parents[i] and record.edge[i]
+ correspond to the true edge and parent i was visited through. When record.config.revisit
+ is true, a vertex may have more than one
+ parent or edge it is visited through.
+
+ edge
+ - At each index i, the index of the edge
+ in OutNeighboursOfVertex(j) of which
+ i was visited through is stored, where j
+ was the parent of i in the DFS.
+ preorder
+ - At each index, the preorder number (order in which the vertex is visited)
+ is stored.
+ postorder
+ - At each index, the postorder number (order in which the vertex is backtracked on)
+ is stored.
+
+
+ The record also stores a further 5 attributes:
+
+ current
+ - The current vertex that is being visited.
+
+ When record.PostOrderFunc is called, record.current
+ refers to the parent of the backtracking vertex.
+
+ Initial: -1
+
+ child
+ -
+ The child of the current vertex.
+
+ When record.PostOrderFunc is called, record.child
+ refers to the backtracking vertex.
+
+ Initial: -1
+
+ graph
+ -
+ The digraph to carry out DFS on.
+
+ stop
+ -
+ Whether to stop the depth first search.
+
+ Initial: false
+
+ config
+ - A configuration for DFS as defined using
+ or . This field should be set
+ by calling NewDFSRecord(graph, config)
+ where config was initially generated by
+ or .
+
+ Default when defined using with
+ no config argument: A record as returned by
+ .
+
+
+
+ When this function is called as NewDFSRecord(record, conf), this function
+ returns a with the
+ record.config field set to conf.
+
+ Initially, the current and child attributes will have -1 values and the lists (parents,
+ preorder, edge and postorder) will have -1 values at all of their indices as no vertex has
+ been visited (if any of the respective record.config.use_preorder
+ flags are set to false, the value of the respective field is fail). The stop attribute will initially be false.
+ This record should be passed into the ExecuteDFS function.
+ See .
+ record := NewDFSRecord(CompleteDigraph(2));;
+gap> record.preorder;
+[ -1, -1 ]
+gap> record.postorder;
+[ -1, -1 ]
+gap> record.stop;
+false
+gap> record.parents;
+[ -1, -1 ]
+gap> record.child;
+-1
+gap> record.current;
+-1
+gap> record.graph;
+
+gap> d := CompleteDigraph(20);;
+gap> flags := NewDFSFlagsLightweight();;
+gap> record := NewDFSRecord(d, flags);;
+gap> flags := NewDFSFlags();;
+gap> flags.iterative := true;;
+gap> record := NewDFSRecord(d, flags);;
+gap> record.config.iterative;
+true
+]]>
+
+
+<#/GAPDoc>
+
+<#GAPDoc Label="NewDFSFlags">
+
+
+ A record.
+
+ This function returns a DFSFlags record to be
+ used as the config field of a DFSRecord.
+ called as NewDFSRecord(record, config) should be used to set
+ config (as shown by the below example), since
+ fields such as record.config.use_postorder
+ change the behaviour of . For
+ example, record.config.use_postorder tells
+ not to create the record.postorder field.
+
+ The fields for both NewDFSFlags and
+ are described as follows, assuming config was returned
+ by or ,
+ and NewDFSRecord(d, config) is
+ called for a digraph d, returning record:
+
+
+ use_preorder
+ -
+ When config.use_preorder is true, record.preorder
+ is fail, and not used during the
procedure
+ (when called with first argument record).
+
+ Default: true
+
+ use_postorder
+ -
+ When config.use_postorder is true, record.postorder
+ is fail, and not used during the
procedure
+ (when called with first argument record).
+
+ Default: true
+
+ use_parents
+ -
+ When config.use_parents is true, record.parents
+ is fail, and not used during the
procedure
+ (when called with first argument record).
+
+ Default: true
+
+ use_edge
+ -
+ When config.use_edge is true, record.edge
+ is fail, and not used during the
procedure
+ (when called with first argument record).
+
+ Default: true
+
+ iterative
+ -
+ Executes the iterative
procedure. Memory usage
+ is generally lower for non iterative DFS. When config.iterative is
+ false (recursive DFS), config.use_parents and config.use_edge
+ must also be true.
+
+ Default: false
+
+ revisit
+ -
+ Allows nodes to be revited during
if they
+ are encountered as a successor, and have been backtracked in the current
+ DFS tree. When config.revisit
+ is true, config.iterative must also be true.
+ Requires config.iterative to be true.
+
+ Default: false
+
+ forest
+ -
+ Ensures all nodes are visited during
.
+ is first ran with a start node of record.start. For each
+ v in DigraphVertices(record.graph),
+ if v has not been visited in any DFS traversals yet (if
+ config.use_preorder then this is the case when
+ record.preorder[v] is -1), a
+ DFS traversal with a start node of v is called until
+ all nodes in DigraphVertices(record.graph)
+ have been visited (traversing all disconnected components). Requires
+ config.forest_specific to be fail.
+
+ Default: false
+
+ forest_specific
+ -
+ config.forest_specific is either fail (in which
+ case this field does not affect
),
+ or a list of vertices in DigraphVertices(record.graph)
+ which are guaranteed to be visited during .
+ This is achieved in the same way as config.forest
+ but instead of ensuring all vertices from DigraphVertices(record.graph)
+ are visited, the same behaviour exists for all vertices
+ from config.forest_specific (if it is not fail).
+ Requires config.forest to be false.
+
+ Default: fail
+
+
+
+ flags := NewDFSFlags();;
+gap> flags;
+rec( forest := false, forest_specific := fail, iterative := false,
+ revisit := false, use_edge := true, use_parents := true,
+ use_postorder := true, use_preorder := true )
+gap> flags.forest := true;;
+gap> flags.use_postorder := false;;
+gap> flags.use_preorder := false;;
+gap> d := CycleDigraph(2);;
+gap> record := NewDFSRecord(d, flags);;
+gap> record.postorder;
+fail
+gap> record.config.use_postorder;
+false
+gap> record.preorder;
+fail
+]]>
+
+
+<#/GAPDoc>
+
+<#GAPDoc Label="NewDFSFlagsLightweight">
+
+
+ A record.
+
+ This function returns a DFSFlags record to be
+ used as the config field of a DFSRecord (see ). It
+ differs to the default config returned by see
+ since all of use_preorder, use_postorder, use_parents and
+ use_edge are set to false.
+ flags := NewDFSFlagsLightweight();;
+gap> flags.use_preorder;
+false
+gap> flags.use_postorder;
+false
+gap> flags.use_edge;
+false
+gap> flags.use_parents;
+false
+gap> flags.iterative := true;
+true
+gap> d := BinaryTree(2);;
+gap> record := NewDFSRecord(d, flags);;
+gap> record.config.use_parents;
+false
+gap> record.preorder;
+fail
+gap> record.postorder;
+fail
+gap> record.edge;
+fail
+gap> record.parents;
+fail
+]]>
+
+
+<#/GAPDoc>
+
+<#GAPDoc Label="ExecuteDFS">
+
+
+
+ This performs a full depth first search from the start vertex (where start is a vertex within the graph).
+ The depth first search can be terminated by changing the record.stop attribute to true in the
+ PreOrderFunc, PostOrderFunc, AncestorFunc or CrossFunc functions.
+ ExecuteDFS takes 7 arguments:
+
+ record
+ - The depth first search record (created using
).
+ data
+ - An object that you want to manipulate in the functions passed.
+ start
+ - The vertex where the depth first search begins.
+ PreOrderFunc
+ - This function is called when a vertex is first visited. This vertex
+ is stored in record.current. A vertex can be backtracked
+ more than once when record.config.revisit is true.
+ PostOrderFunc
+ - This function is called when a vertex has no more unvisited children
+ causing us to backtrack. This vertex is stored in record.child and its parent is stored
+ in record.current. A vertex can be visited
+ more than once when record.config.revisit is true.
+
+ When record.PostOrderFunc is not fail, then
+ record.config.use_parents
+ must be true (recursive DFS requires record.config.use_parents
+ in general).
+
+ AncestorFunc
+ - This function is called when [record.current,
+ record.child] is an edge and record.child is an ancestor of record.current. An ancestor here means that
+ record.child is on the same branch as
+ record.current but was visited prior to
+ record.current (record.current has been backtracked
+ on). Equivalently, [record.current,
+ record.child] is a back edge.
+ CrossFunc
+ - This function is called when [record.current,
+ record.child] is an edge and record.child has been visited before record.current
+ and it is not an ancestor of record.current. Equivalently, [record.current,
+ record.child] is a cross edge.
+
+ When record.CrossFunc is not fail, record.config.use_preorder
+ must be true (to determine visit order).
+
+
+ PreOrderFunc, PostOrderFunc, AncestorFunc and CrossFunc
+ should be set to fail unless a function is specified.
+
+ Note that this function only performs a depth first search on the vertices reachable from start
+ unless record.config.forest is true or record.config.forest_specific
+ is a list of vertices to visit.
+ It is also important to note that all functions passed need to accept arguments record and data.
+ Finally, for the start vertex, its parent is itself and the PreOrderFunc
+ will be called on it. This is also the case for the start vertex of any additional
+ searches required when record.config.forest is true or
+ record.config.forest_specific is a list of vertices (not fail).
+
+ See for more details on record.
+ record := NewDFSRecord(CycleDigraph(10));;
+gap> ExecuteDFS(record, [], 1, fail,
+> fail, fail, fail);
+gap> record.preorder;
+[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
+gap> record := NewDFSRecord(CompleteDigraph(10));;
+gap> data := rec(cycle_vertex := 0);;
+gap> AncestorFunc := function(record, data)
+> record.stop := true;
+> data.cycle_vertex := record.child;
+> end;;
+gap> ExecuteDFS(record, data, 1, fail,
+> fail, AncestorFunc, fail);
+gap> record.stop;
+true
+gap> data.cycle_vertex;
+1
+gap> record.preorder;
+[ 1, 2, -1, -1, -1, -1, -1, -1, -1, -1 ]
+gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));;
+gap> CrossFunc := function(record, data)
+> record.stop := true;
+> Add(data, record.child);
+> end;;
+gap> data := [];;
+gap> ExecuteDFS(record, data, 1, fail,
+> fail, fail, CrossFunc);
+gap> record.stop;
+true
+gap> data;
+[ 4 ]
+]]>
+
+
+<#/GAPDoc>
diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml
index 4a0feb10b..c86cdce3f 100644
--- a/doc/z-chap4.xml
+++ b/doc/z-chap4.xml
@@ -20,6 +20,10 @@
<#Include Label="IsMatching">
<#Include Label="DigraphMaximalMatching">
<#Include Label="DigraphMaximumMatching">
+ <#Include Label="NewDFSRecord">
+ <#Include Label="NewDFSFlags">
+ <#Include Label="NewDFSFlagsLightweight">
+ <#Include Label="ExecuteDFS">
Neighbours and degree
diff --git a/gap/attr.gi b/gap/attr.gi
index 345a24620..24523a6af 100644
--- a/gap/attr.gi
+++ b/gap/attr.gi
@@ -28,8 +28,8 @@ InstallGlobalFunction(OutNeighbors, OutNeighbours);
BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation",
function(D)
- local N, copy, articulation_points, bridges, orientation, nbs, counter, pre,
- low, nr_children, stack, u, v, i, w, connected;
+ local N, copy, PostOrderFunc, PreOrderFunc, AncestorCrossFunc, data, record,
+ connected, parent, flags;
N := DigraphNrVertices(D);
@@ -41,115 +41,116 @@ function(D)
# the graph disconnected), no bridges, strong orientation (since
# the digraph with 0 nodes is strongly connected).
return [true, [], [], D];
- elif not IsSymmetricDigraph(D) then
- copy := DigraphSymmetricClosure(DigraphMutableCopyIfMutable(D));
+ elif not IsSymmetricDigraph(D) or IsMultiDigraph(D) then
+ copy := DigraphSymmetricClosure(DigraphMutableCopy(D));
+ copy := DigraphRemoveAllMultipleEdges(copy);
MakeImmutable(copy);
+ # No forward edges or cross edges now exist
else
copy := D;
fi;
- # outputs
- articulation_points := [];
- bridges := [];
- orientation := List([1 .. N], x -> BlistList([1 .. N], []));
+ flags := NewDFSFlagsLightweight();
+ flags.use_preorder := true;
+ flags.use_edge := true;
+ flags.use_parents := true;
+
+ PostOrderFunc := function(record, data)
+ local child, current;
+ child := record.child;
+ current := record.parents[child];
+ # record.preorder[child] > record.preorder[current] then
+ if current <> child then
+ # stops the duplication of articulation_points
+ if current <> 1 and data.low[child] >= record.preorder[current] then
+ Add(data.articulation_points, current);
+ fi;
+ if data.low[child] = record.preorder[child] then
+ Add(data.bridges, [current, child]);
+ fi;
+ if data.low[child] < data.low[current] then
+ data.low[current] := data.low[child];
+ fi;
+ fi;
+ end;
- # Get out-neighbours once, to avoid repeated copying for mutable digraphs.
- nbs := OutNeighbours(copy);
+ PreOrderFunc := function(record, data)
+ local current, parents;
+ current := record.current;
+ if current <> 1 then
+ parent := record.parents[current];
+ if parent = 1 then
+ data.nr_children := data.nr_children + 1;
+ fi;
+ data.orientation[parent][current] := true;
+ fi;
+ data.counter := data.counter + 1;
+ data.low[current] := data.counter;
+ end;
- # number of nodes encountered in the search so far
- counter := 0;
+ AncestorCrossFunc := function(record, data)
+ local current, child, parent;
+ current := record.current;
+ child := record.child;
+ parent := record.parents[current];
+ # current -> child is a back edge or cross edge
+ if child <> parent and record.preorder[child] < data.low[current] then
+ data.low[current] := record.preorder[child];
+ fi;
+ data.orientation[current][child] := not data.orientation[child][current];
+ end;
- # the order in which the nodes are visited, -1 indicates "not yet visited".
- pre := ListWithIdenticalEntries(N, -1);
+ data := rec();
+
+ # outputs
+ data.articulation_points := [];
+ data.bridges := [];
+ data.orientation := List([1 .. N], x -> BlistList([1 .. N], []));
# low[i] is the lowest value in pre currently reachable from node i.
- low := [];
+ data.low := [];
+
+ # number of nodes encountered in the search so far
+ data.counter := 0;
# nr_children of node 1, for articulation points the root node (1) is an
# articulation point if and only if it has at least 2 children.
- nr_children := 0;
-
- stack := Stack();
- u := 1;
- v := 1;
- i := 0;
-
- repeat
- if pre[v] <> -1 then
- # backtracking
- i := Pop(stack);
- v := Pop(stack);
- u := Pop(stack);
- w := nbs[v][i];
-
- if v <> 1 and low[w] >= pre[v] then
- Add(articulation_points, v);
- fi;
- if low[w] = pre[w] then
- Add(bridges, [v, w]);
- fi;
- if low[w] < low[v] then
- low[v] := low[w];
- fi;
- else
- # diving - part 1
- counter := counter + 1;
- pre[v] := counter;
- low[v] := counter;
- fi;
- i := PositionProperty(nbs[v], w -> w <> v, i);
- while i <> fail do
- w := nbs[v][i];
- if pre[w] <> -1 then
- # v -> w is a back edge
- if w <> u and pre[w] < low[v] then
- low[v] := pre[w];
- fi;
- orientation[v][w] := not orientation[w][v];
- i := PositionProperty(nbs[v], w -> w <> v, i);
- else
- # diving - part 2
- if v = 1 then
- nr_children := nr_children + 1;
- fi;
- orientation[v][w] := true;
- Push(stack, u);
- Push(stack, v);
- Push(stack, i);
- u := v;
- v := w;
- i := 0;
- break;
- fi;
- od;
- until Size(stack) = 0;
-
- if counter = DigraphNrVertices(D) then
+ data.nr_children := 0;
+
+ record := NewDFSRecord(copy, flags);
+ ExecuteDFS(record,
+ data,
+ 1,
+ PreOrderFunc,
+ PostOrderFunc,
+ AncestorCrossFunc,
+ AncestorCrossFunc);
+ if data.counter = DigraphNrVertices(D) then
connected := true;
- if nr_children > 1 then
- Add(articulation_points, 1);
+ if data.nr_children > 1 then
+ Add(data.articulation_points, 1);
fi;
- if not IsEmpty(bridges) then
- orientation := fail;
+ if not IsEmpty(data.bridges) then
+ data.orientation := fail;
else
- orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D),
- orientation);
+ data.orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D),
+ data.orientation);
fi;
else
- connected := false;
- articulation_points := [];
- bridges := [];
- orientation := fail;
+ connected := false;
+ data.articulation_points := [];
+ data.bridges := [];
+ data.orientation := fail;
fi;
if IsImmutableDigraph(D) then
SetIsConnectedDigraph(D, connected);
- SetArticulationPoints(D, articulation_points);
- SetBridges(D, bridges);
+ SetArticulationPoints(D, data.articulation_points);
+ SetBridges(D, data.bridges);
if IsSymmetricDigraph(D) then
- SetStrongOrientationAttr(D, orientation);
+ SetStrongOrientationAttr(D, data.orientation);
fi;
fi;
- return [connected, articulation_points, bridges, orientation];
+ return [connected, data.articulation_points, data.bridges, data.orientation];
end);
InstallMethod(ArticulationPoints, "for a digraph by out-neighbours",
@@ -1064,7 +1065,47 @@ end);
InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
-D -> DIGRAPH_TOPO_SORT(OutNeighbours(D)));
+function(D)
+ local N, record, count, out, PostOrderFunc, AncestorFunc, flags;
+
+ N := DigraphNrVertices(D);
+ if N = 0 then
+ return [];
+ fi;
+
+ flags := NewDFSFlagsLightweight();
+ flags.use_parents := true;
+ flags.use_edge := true;
+
+ record := NewDFSRecord(D, flags);
+
+ count := 0;
+ out := ListWithIdenticalEntries(N, -1);
+ PostOrderFunc := function(record, _)
+ count := count + 1;
+ out[count] := record.child;
+ end;
+ AncestorFunc := function(record, _)
+ if record.current <> record.child then
+ record.stop := true;
+ fi;
+ end;
+
+ record.config.forest := true;
+
+ ExecuteDFS(record,
+ fail,
+ 1,
+ fail,
+ PostOrderFunc,
+ AncestorFunc,
+ fail);
+ if record.stop then
+ return fail;
+ fi;
+
+ return out;
+end);
InstallMethod(DigraphStronglyConnectedComponents,
"for a digraph by out-neighbours",
@@ -2996,38 +3037,52 @@ InstallMethod(DigraphReflexiveTransitiveReductionAttr,
"for an immutable digraph",
[IsImmutableDigraph], DigraphReflexiveTransitiveReduction);
-InstallMethod(UndirectedSpanningForest,
-"for a mutable digraph by out-neighbours",
-[IsMutableDigraph and IsDigraphByOutNeighboursRep],
+InstallMethod(UndirectedSpanningForest, "for a digraph by out-neighbours",
+[IsDigraphByOutNeighboursRep],
function(D)
- if DigraphHasNoVertices(D) then
+ local C, record, conf, data, PreOrderFunc;
+ if DigraphNrVertices(D) = 0 then
return fail;
fi;
- MaximalSymmetricSubdigraph(D);
- D!.OutNeighbours := DIGRAPH_SYMMETRIC_SPANNING_FOREST(D!.OutNeighbours);
- ClearDigraphEdgeLabels(D);
- return D;
-end);
+ C := MaximalSymmetricSubdigraph(D);
+ data := List(DigraphVertices(C), x -> []);
-InstallMethod(UndirectedSpanningForest, "for an immutable digraph",
-[IsImmutableDigraph], UndirectedSpanningForestAttr);
+ PreOrderFunc := function(record, data)
+ if record.parents[record.current] <> record.current then
+ Add(data[record.parents[record.current]], record.current);
+ Add(data[record.current], record.parents[record.current]);
+ fi;
+ end;
-InstallMethod(UndirectedSpanningForestAttr, "for an immutable digraph",
-[IsImmutableDigraph and IsDigraphByOutNeighboursRep],
-function(D)
- local C;
- if DigraphHasNoVertices(D) then
- return fail;
+ conf := NewDFSFlagsLightweight();
+ conf.use_parents := true;
+ conf.iterative := true;
+ conf.forest := true;
+
+ record := NewDFSRecord(C, conf);
+
+ ExecuteDFS(record, data, 1, PreOrderFunc, fail,
+ fail, fail);
+
+ if IsMutableDigraph(D) then
+ D!.OutNeighbours := data;
+ ClearDigraphEdgeLabels(D);
+ return D;
fi;
- C := MaximalSymmetricSubdigraph(D);
- C := DIGRAPH_SYMMETRIC_SPANNING_FOREST(C!.OutNeighbours);
- C := ConvertToImmutableDigraphNC(C);
+ C := ConvertToImmutableDigraphNC(data);
+ SetUndirectedSpanningForestAttr(D, C);
SetIsUndirectedForest(C, true);
SetIsMultiDigraph(C, false);
SetDigraphHasLoops(C, false);
return C;
end);
+InstallMethod(UndirectedSpanningForest, "for an immutable digraph",
+[IsImmutableDigraph], UndirectedSpanningForestAttr);
+
+InstallMethod(UndirectedSpanningForestAttr, "for an immutable digraph",
+[IsImmutableDigraph and IsDigraphByOutNeighboursRep], UndirectedSpanningForest);
+
InstallMethod(UndirectedSpanningTree, "for a mutable digraph",
[IsMutableDigraph],
function(D)
diff --git a/gap/oper.gd b/gap/oper.gd
index fbfe4c554..1b64acbd9 100644
--- a/gap/oper.gd
+++ b/gap/oper.gd
@@ -154,3 +154,10 @@ DeclareOperation("PartialOrderDigraphJoinOfVertices",
[IsDigraph, IsPosInt, IsPosInt]);
DeclareOperation("PartialOrderDigraphMeetOfVertices",
[IsDigraph, IsPosInt, IsPosInt]);
+
+# 11. DFS
+DeclareOperation("NewDFSRecord", [IsDigraph]);
+DeclareOperation("NewDFSRecord", [IsDigraph, IsRecord]);
+DeclareOperation("NewDFSFlags", []);
+DeclareOperation("NewDFSFlagsLightweight", []);
+DeclareGlobalFunction("ExecuteDFS");
diff --git a/gap/oper.gi b/gap/oper.gi
index d934269b6..08dab8f1c 100644
--- a/gap/oper.gi
+++ b/gap/oper.gi
@@ -1665,10 +1665,11 @@ end);
InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints",
[IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt],
function(D, u, v)
- local verts;
+ local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents,
+ flags;
- verts := DigraphVertices(D);
- if not (u in verts and v in verts) then
+ N := DigraphNrVertices(D);
+ if u > N or v > N then
ErrorNoReturn("the 2nd and 3rd arguments and must be ",
"vertices of the 1st argument ,");
elif IsDigraphEdge(D, u, v) then
@@ -1681,8 +1682,66 @@ function(D, u, v)
and DigraphConnectedComponents(D).id[u] <>
DigraphConnectedComponents(D).id[v] then
return fail;
+ elif OutDegreeOfVertex(D, u) = 0
+ or (HasInNeighbours(D) and InDegreeOfVertex(D, v) = 0) then
+ return fail;
+ fi;
+
+ flags := NewDFSFlagsLightweight();
+
+ flags.use_edge := true;
+ flags.use_parents := true;
+
+ record := NewDFSRecord(D, flags);
+
+ if u <> v then
+ # if v is reachable from u, then u is an ancestor of v, and so at some
+ # point v will be encountered for the first time, and PreOrderFunc will be
+ # called.
+ PreOrderFunc := function(record, _)
+ if record.current = v then
+ record.stop := true;
+ fi;
+ end;
+ AncestorFunc := fail;
+ else
+ # if u is reachable from u, then u will be encountered as an ancestor of
+ # itself, but PreOrderFunc won't be called (because u has already been
+ # discovered).
+ PreOrderFunc := fail;
+ AncestorFunc := function(record, _)
+ if record.child = v then
+ record.stop := true;
+ fi;
+ end;
fi;
- return DIGRAPH_PATH(OutNeighbours(D), u, v);
+
+ ExecuteDFS(record,
+ fail,
+ u,
+ PreOrderFunc,
+ fail,
+ AncestorFunc,
+ fail);
+ if not record.stop then
+ return fail;
+ fi;
+ nodes := [v];
+ edges := [];
+ current := v;
+ if u = v then
+ # Go one back from v to the last node in the tree
+ current := record.current;
+ Add(nodes, current);
+ Add(edges, Position(OutNeighboursOfVertex(D, current), u));
+ fi;
+ # Follow the path from current (which is a descendant of u) back to u
+ while current <> u do
+ Add(edges, record.edge[current]);
+ current := record.parents[current];
+ Add(nodes, current);
+ od;
+ return [Reversed(nodes), Reversed(edges)];
end);
InstallMethod(IsDigraphPath, "for a digraph and list",
@@ -1977,17 +2036,47 @@ end);
InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int",
[IsDigraphByOutNeighboursRep, IsPosInt],
function(D, v)
- local dist;
+ local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags;
if not v in DigraphVertices(D) then
ErrorNoReturn("the 2nd argument must be a vertex of the 1st ",
"argument ,");
fi;
- dist := DIGRAPH_LONGEST_DIST_VERTEX(OutNeighbours(D), v);
- if dist = -2 then
+
+ flags := NewDFSFlagsLightweight();
+ flags.iterative := true; # revisit DFS must be iterative
+ flags.use_parents := true;
+ flags.revisit := true; # If found another edge to an already
+ # visited and backtracked on node,
+ # set to unvisited, and visit it
+
+ record := NewDFSRecord(D, flags);
+
+ data := rec(prev := -1, best := 0);
+
+ AncestorFunc := function(record, _)
+ record.stop := true;
+ end;
+
+ PostOrderFunc := function(_, data)
+ data.prev := data.prev - 1;
+ end;
+
+ PreOrderFunc := function(_, data)
+ data.prev := data.prev + 1;
+ if data.prev > data.best then
+ data.best := data.prev;
+ fi;
+ end;
+
+ ExecuteDFS(record, data, v,
+ PreOrderFunc, PostOrderFunc,
+ AncestorFunc, fail);
+ if record.stop then
return infinity;
fi;
- return dist;
+ return data.best;
+
end);
InstallMethod(DigraphRandomWalk,
@@ -2271,56 +2360,93 @@ end);
InstallMethod(VerticesReachableFrom, "for a digraph and a vertex",
[IsDigraph, IsPosInt],
function(D, root)
- local N;
- N := DigraphNrVertices(D);
+ local N, record, conf, data, AncestorFunc, PreOrderFunc;
+ N := DigraphNrVertices(D);
if 0 = root or root > N then
ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ",
"argument (a digraph)");
fi;
- return VerticesReachableFrom(D, [root]);
+ conf := NewDFSFlagsLightweight();
+
+ conf.use_edge := true;
+ conf.use_parents := true;
+
+ record := NewDFSRecord(D, conf);
+ data := rec(result := [], root_reached := false);
+
+ PreOrderFunc := function(record, data)
+ if record.current <> root then
+ Add(data.result, record.current);
+ fi;
+ end;
+
+ AncestorFunc := function(record, data)
+ if record.child = root and not data.root_reached then
+ data.root_reached := true;
+ Add(data.result, root);
+ fi;
+ end;
+
+ ExecuteDFS(record,
+ data,
+ root,
+ PreOrderFunc,
+ fail,
+ AncestorFunc,
+ fail);
+ Sort(data.result);
+ return data.result;
end);
InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices",
[IsDigraph, IsList],
function(D, roots)
- local N, index, visited, queue_tail, queue,
- root, element, neighbour, graph_out_neighbors, node_neighbours;
+ local record, flags, N, PreOrderFunc, AncestorFunc,
+ data;
+
+ if (roots = []) then
+ return [];
+ fi;
N := DigraphNrVertices(D);
- for root in roots do
- if not IsPosInt(N) or 0 = root or root > N then
- ErrorNoReturn("an element of the 2nd argument ",
- "(roots) is not a vertex of the 1st ",
- "argument (a digraph)");
+ if (ForAny(roots, v -> v <= 0 or v > N)) then
+ ErrorNoReturn("an element of the 2nd argument ",
+ "(roots) is not a vertex of the 1st ",
+ "argument (a digraph)");
+ fi;
+
+ data := rec(result := BlistList([1 .. N], []));
+
+ PreOrderFunc := function(record, data)
+ if record.parents[record.current] <> record.current then
+ data.result[record.current] := true;
fi;
- od;
+ end;
- visited := BlistList([1 .. N], []);
+ AncestorFunc := function(record, data)
+ data.result[record.child] := true;
+ end;
- graph_out_neighbors := OutNeighbors(D);
- queue := EmptyPlist(N);
- Append(queue, roots);
+ flags := NewDFSFlagsLightweight();
+ flags.use_edge := true;
+ flags.use_parents := true;
- queue_tail := Length(roots);
+ flags.forest_specific := roots;
- index := 1;
- while IsBound(queue[index]) do
- element := queue[index];
- node_neighbours := graph_out_neighbors[element];
- for neighbour in node_neighbours do
- if not visited[neighbour] then;
- visited[neighbour] := true;
- queue_tail := queue_tail + 1;
- queue[queue_tail] := neighbour;
- fi;
- od;
- index := index + 1;
- od;
+ record := NewDFSRecord(D, flags);
- return ListBlist([1 .. N], visited);
+ ExecuteDFS(record,
+ data,
+ roots[1],
+ PreOrderFunc,
+ fail,
+ AncestorFunc,
+ fail);
+
+ return ListBlist([1 .. N], data.result);
end);
InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices",
@@ -2349,9 +2475,10 @@ InstallMethod(IsOrderFilter, "for a digraph and a list of vertices",
InstallMethod(DominatorTree, "for a digraph and a vertex",
[IsDigraph, IsPosInt],
function(D, root)
- local M, node_to_preorder_num, preorder_num_to_node, parent, index, next,
- current, succ, prev, n, semi, lastlinked, label, bucket, idom,
- compress, eval, pred, N, w, y, x, i, v;
+ local M, preorder_num_to_node, PreOrderFunc, record, parents,
+ node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval,
+ pred, N, w, y, x, i, v, flags;
+
M := DigraphNrVertices(D);
if 0 = root or root > M then
@@ -2359,36 +2486,32 @@ function(D, root)
"argument (a digraph)");
fi;
- node_to_preorder_num := [];
- node_to_preorder_num[root] := 1;
- preorder_num_to_node := [root];
+ preorder_num_to_node := [];
- parent := [];
- parent[root] := fail;
+ PreOrderFunc := function(record, data)
+ Add(data, record.current);
+ end;
- index := ListWithIdenticalEntries(M, 1);
+ flags := NewDFSFlagsLightweight();
+ flags.use_preorder := true;
+ flags.use_parents := true;
+ flags.use_edge := true;
+
+ record := NewDFSRecord(D, flags);
+
+ ExecuteDFS(record,
+ preorder_num_to_node,
+ root,
+ PreOrderFunc,
+ fail,
+ fail,
+ fail);
+
+ parents := record.parents;
+ node_to_preorder_num := record.preorder;
+
+ parents[root] := -1;
- next := 2;
- current := root;
- succ := OutNeighbours(D);
- repeat
- prev := current;
- for i in [index[current] .. Length(succ[current])] do
- n := succ[current][i];
- if not IsBound(node_to_preorder_num[n]) then
- Add(preorder_num_to_node, n);
- parent[n] := current;
- index[current] := i + 1;
- node_to_preorder_num[n] := next;
- next := next + 1;
- current := n;
- break;
- fi;
- od;
- if prev = current then
- current := parent[current];
- fi;
- until current = fail;
semi := [1 .. M];
lastlinked := M + 1;
label := [];
@@ -2398,7 +2521,7 @@ function(D, root)
compress := function(v)
local u;
- u := parent[v];
+ u := parents[v];
if u <> fail and lastlinked <= M and node_to_preorder_num[u] >=
node_to_preorder_num[lastlinked] then
compress(u);
@@ -2406,7 +2529,7 @@ function(D, root)
< node_to_preorder_num[semi[label[v]]] then
label[v] := label[u];
fi;
- parent[v] := parent[u];
+ parents[v] := parents[u];
fi;
end;
@@ -2426,7 +2549,8 @@ function(D, root)
w := preorder_num_to_node[i];
for v in bucket[w] do
y := eval(v);
- if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then
+ if node_to_preorder_num[semi[y]] <
+ node_to_preorder_num[w] then
idom[v] := y;
else
idom[v] := w;
@@ -2434,15 +2558,16 @@ function(D, root)
od;
bucket[w] := [];
for v in pred[w] do
- if IsBound(node_to_preorder_num[v]) then
+ if node_to_preorder_num[v] <> -1 then
x := eval(v);
- if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then
+ if node_to_preorder_num[semi[x]] <
+ node_to_preorder_num[semi[w]] then
semi[w] := semi[x];
fi;
fi;
od;
- if parent[w] = semi[w] then
- idom[w] := parent[w];
+ if parents[w] = semi[w] then
+ idom[w] := parents[w];
else
Add(bucket[semi[w]], w);
fi;
@@ -2683,3 +2808,178 @@ function(D, i, j)
return fail;
end);
+
+#############################################################################
+# 11. DFS
+#############################################################################
+
+DIGRAPHS_DFSRecNames := function()
+ return ["stop", "graph", "child", "parents", "preorder", "postorder",
+ "current", "edge"];
+ end;
+
+DIGRAPHS_DFSFlagNames := function()
+ return ["iterative", "forest", "revisit", "use_parents", "use_edge",
+ "use_postorder", "use_preorder", "forest_specific"];
+ end;
+
+DIGRAPHS_DFSError := function()
+ ErrorNoReturn("the 1st argument must be created with ",
+ "NewDFSRecord,");
+end;
+
+DIGRAPHS_DFSFlagsBoolErr := function(flags, field)
+ if not IsBool(flags.(field)) then
+ ErrorNoReturn("the 2nd argument (a record) should have a Bool ",
+ "value for field ,", field);
+ fi;
+end;
+
+DIGRAPHS_DFS_CheckFlags := function(flags, graph)
+ # Already confirmed expected fields are bound
+ local bool_flag;
+
+ for bool_flag in ["iterative", "forest", "revisit", "use_parents", "use_edge",
+ "use_postorder", "use_preorder"] do
+ DIGRAPHS_DFSFlagsBoolErr(flags, bool_flag);
+ od;
+
+ if (flags.forest_specific <> fail) and
+ (not (IsDenseList(flags.forest_specific) and
+ (IsEmpty(flags.forest_specific)
+ or IsPosInt(flags.forest_specific[1])))) then
+ ErrorNoReturn("the 2nd argument (a record) should have a Bool ",
+ "value for field .");
+ fi;
+
+ if flags.forest_specific <> fail and
+ (ForAny(flags.forest_specific, n -> n < 1
+ or n > DigraphNrVertices(graph))) then
+ ErrorNoReturn("the 2nd argument (a record) has elements in ",
+ ".forest_specific that are not vertices in the ",
+ "second argument .");
+ fi;
+end;
+
+DIGRAPHS_ExecuteDFSCheck := function(record)
+ local record_names, config_names;
+
+ record_names := DIGRAPHS_DFSRecNames();
+ config_names := DIGRAPHS_DFSFlagNames();
+
+ if not IsBound(record.config) then
+ DIGRAPHS_DFSError();
+ elif ForAny(config_names, n -> not IsBound(record.config.(n))) then
+ DIGRAPHS_DFSError();
+ elif ForAny(record_names, n -> not IsBound(record.(n))) then
+ DIGRAPHS_DFSError();
+ elif record.config.forest_specific <> fail and
+ not IsDenseList(record.config.forest_specific) then
+ ErrorNoReturn("the 1st argument has a value of",
+ " .config.forest_specific",
+ " that is not fail or a dense list,");
+ elif ForAny(["parents", "preorder", "postorder", "edge"],
+ n -> record.(n) <> fail and not IsDenseList(record.(n))) then
+ DIGRAPHS_DFSError();
+ fi;
+end;
+
+InstallMethod(NewDFSRecord,
+"for a digraph", [IsDigraph],
+Graph -> NewDFSRecord(Graph, NewDFSFlags()));
+
+InstallMethod(NewDFSRecord,
+"for a digraph and a record", [IsDigraph, IsRecord],
+function(graph, conf)
+ local record, config_names, N;
+
+ N := DigraphNrVertices(graph);
+
+ config_names := DIGRAPHS_DFSFlagNames();
+
+ record := rec();
+
+ if ForAny(config_names, n -> not IsBound(conf.(n))) then
+ DIGRAPHS_DFSError();
+ fi;
+
+ DIGRAPHS_DFS_CheckFlags(conf, graph);
+
+ record.graph := graph;
+ record.child := -1;
+ record.current := -1;
+ record.stop := false;
+
+ if conf.use_preorder then
+ record.preorder := ListWithIdenticalEntries(N, -1);
+ else
+ record.preorder := fail;
+ fi;
+ if conf.use_parents then
+ record.parents := ListWithIdenticalEntries(N, -1);
+ else
+ record.parents := fail;
+ fi;
+ if conf.use_postorder then
+ record.postorder := ListWithIdenticalEntries(N, -1);
+ else
+ record.postorder := fail;
+ fi;
+ if conf.use_edge then
+ record.edge := ListWithIdenticalEntries(N, -1);
+ else
+ record.edge := fail;
+ fi;
+
+ record.config := conf;
+ return record;
+end);
+
+InstallMethod(NewDFSFlags,
+"", [],
+function()
+ local config;
+ config := rec();
+ config.forest := false; # Visit all vertices (connected components)
+ config.forest_specific := fail; # Visit specific vertices
+ config.revisit := false; # Use for revisiting nodes (requires iter)
+ config.iterative := false;
+ config.use_edge := true; # Whether these record fields are necessary
+ config.use_postorder := true;
+ config.use_preorder := true;
+ config.use_parents := true;
+ return config;
+end);
+
+InstallMethod(NewDFSFlagsLightweight,
+"", [],
+function()
+ local config;
+ config := NewDFSFlags();
+ config.iterative := false;
+ config.use_postorder := false;
+ config.use_preorder := false;
+ config.use_parents := false;
+ config.use_edge := false;
+ return config;
+end);
+
+# * PreOrderFunc is called with (record, data) when a vertex is popped from the
+# stack for the first time.
+# * PostOrderFunc is called with (record, data) when all of record.child's
+# children have been visited (i.e. when we backtrack from record.child to
+# record.parent[record.child]).
+# * AncestorFunc is called with (record, data) when (record.current,
+# record.child) is an edge and record.child is an ancestor of record.current.
+# * CrossFunc is called with (record, data) when (record.current, record.child)
+# is an edge, the preorder value of record.current is greater than the
+# preorder value of child, and record.current and child are unrelated
+# by ancestry.
+
+InstallGlobalFunction(ExecuteDFS,
+function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc,
+ CrossFunc)
+ DIGRAPHS_ExecuteDFSCheck(record);
+ ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc,
+ AncestorFunc, CrossFunc);
+end);
diff --git a/gap/prop.gi b/gap/prop.gi
index e519535b2..b97fd2125 100644
--- a/gap/prop.gi
+++ b/gap/prop.gi
@@ -239,22 +239,31 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D));
InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
function(D)
- local n;
+ local n, record, AncestorFunc, flags;
n := DigraphNrVertices(D);
if n = 0 then
return true;
- elif HasDigraphTopologicalSort(D) and
- DigraphTopologicalSort(D) = fail then
- return false;
- elif HasDigraphHasLoops(D) and DigraphHasLoops(D) then
- return false;
- elif HasDigraphStronglyConnectedComponents(D) then
- if DigraphNrStronglyConnectedComponents(D) = n then
- return not DigraphHasLoops(D);
- fi;
+ fi;
+
+ flags := NewDFSFlagsLightweight();
+ flags.iterative := true;
+
+ record := NewDFSRecord(D, flags);
+
+ # A Digraph is acyclic if it has no back edges
+ AncestorFunc := function(record, _)
+ record.stop := true;
+ end;
+
+ record.config.forest := true;
+ ExecuteDFS(record, fail, 1, fail,
+ fail, AncestorFunc, fail);
+
+ if record.stop then
return false;
fi;
- return IS_ACYCLIC_DIGRAPH(OutNeighbours(D));
+
+ return true;
end);
# Complexity O(number of edges)
@@ -378,7 +387,43 @@ D -> DigraphPeriod(D) = 1);
InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
-D -> IS_ANTISYMMETRIC_DIGRAPH(OutNeighbours(D)));
+function(D)
+ local record, AncestorFunc, flags;
+
+ if DigraphNrVertices(D) <= 1 then
+ return true;
+ fi;
+
+ flags := NewDFSFlagsLightweight();
+ flags.iterative := true;
+
+ record := NewDFSRecord(D, flags);
+ record.config.forest := true;
+
+ AncestorFunc := function(record, _)
+ local pos, neighbours;
+ if record.child = record.current then
+ return;
+ fi;
+
+ # back edge record.current -> record.child
+ # checks if the child has a symmetric edge with current node
+ neighbours := OutNeighboursOfVertex(D, record.child);
+ pos := Position(neighbours, record.current);
+ if pos <> fail then
+ record.stop := true;
+ fi;
+ end;
+
+ ExecuteDFS(record, [], 1, fail, fail,
+ AncestorFunc, fail);
+
+ if record.stop then
+ return false;
+ fi;
+
+ return true;
+end);
InstallMethod(IsTransitiveDigraph, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
diff --git a/src/dfs.c b/src/dfs.c
new file mode 100644
index 000000000..c4153e0de
--- /dev/null
+++ b/src/dfs.c
@@ -0,0 +1,481 @@
+/*******************************************************************************
+**
+*A dfs.c GAP package Digraphs Lea Racine
+** James Mitchell
+**
+** Copyright (C) 2014-21 - Julius Jonusas, James Mitchell, Michael Torpey,
+** Wilf A. Wilson et al.
+**
+** This file is free software, see the digraphs/LICENSE.
+**
+*******************************************************************************/
+
+#include "dfs.h"
+
+#include // for uint64_t
+#include // for NULL, free
+#include "digraphs-debug.h"
+#include "safemalloc.h" // for safe_malloc
+
+Int DigraphNrVertices(Obj);
+Obj FuncOutNeighbours(Obj, Obj);
+
+// Macros used for both recursive and iterative
+
+#define CALL_CHECK_STOP(f, RNamStop, record, data) \
+ CALL_2ARGS(f, record, data); \
+ if (ElmPRec(record, RNamStop) == True) { \
+ CHANGED_BAG(record); \
+ return false; \
+ }
+
+#define GET_PREORDER(args, idx) \
+ args->dfs_conf->use_preorder ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) \
+ : args->preorder_partial[idx]
+
+#define SET_PREORDER(args, idx) \
+ if (args->dfs_conf->use_preorder) { \
+ ASS_LIST(args->preorder, idx, INTOBJ_INT(++(*args->preorder_num))); \
+ } else { \
+ args->preorder_partial[idx] = true; \
+ }
+
+#define UNSET_PREORDER(args, idx) \
+ if (args->dfs_conf->use_preorder) { \
+ ASS_LIST(args->preorder, idx, INTOBJ_INT(-1)); \
+ } else { \
+ args->preorder_partial[idx] = false; \
+ }
+
+#define IS_VISITED(args, idx) \
+ args->dfs_conf->use_preorder \
+ ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) != -1 \
+ : args->preorder_partial[idx]
+
+#define GET_POSTORDER(args, idx) \
+ args->dfs_conf->use_postorder ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) \
+ : args->postorder_partial[idx]
+
+#define SET_POSTORDER(args, idx) \
+ if (args->dfs_conf->use_postorder) { \
+ ASS_LIST(args->postorder, idx, INTOBJ_INT(++(*args->postorder_num))); \
+ } else if (args->dfs_conf->partial_postorder) { \
+ args->postorder_partial[idx] = true; \
+ }
+
+#define IS_BACKTRACKED(args, idx) \
+ args->dfs_conf->use_postorder \
+ ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) != -1 \
+ : args->postorder_partial[idx]
+
+#define ON_PREORDER(current, args) \
+ SET_PREORDER(args, current); \
+ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \
+ CHANGED_BAG(args->record); \
+ if (args->CallPreorder) { \
+ CALL_CHECK_STOP( \
+ args->PreorderFunc, args->RNamStop, args->record, args->data) \
+ }
+
+#define ANCESTOR_CROSS(current, v, backtracked, args) \
+ if (args->CallAncestor || args->CallCross) { \
+ AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \
+ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \
+ CHANGED_BAG(args->record); \
+ if (args->CallAncestor && !backtracked) { \
+ CALL_CHECK_STOP( \
+ args->AncestorFunc, args->RNamStop, args->record, args->data) \
+ } else if (args->CallCross \
+ && (backtracked \
+ && ((GET_PREORDER(args, v)) \
+ < (GET_PREORDER(args, current))))) { \
+ CALL_CHECK_STOP( \
+ args->CrossFunc, args->RNamStop, args->record, args->data) \
+ } \
+ CHANGED_BAG(args->record); \
+ }
+
+#define ON_BACKTRACK(current, parent, args) \
+ if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) { \
+ SET_POSTORDER(args, current); \
+ CHANGED_BAG(args->record); \
+ } \
+ if (args->CallPostorder) { \
+ /* When CallPostorder is true, use_parents must be true, parent is valid \
+ */ \
+ AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \
+ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \
+ CHANGED_BAG(args->record); \
+ CALL_CHECK_STOP( \
+ args->PostorderFunc, args->RNamStop, args->record, args->data) \
+ CHANGED_BAG(args->record); \
+ }
+
+#define ON_ADD_SUCC(current, succ, idx, args) \
+ if (args->dfs_conf->use_parents) { \
+ AssPlist(args->parents, succ, INTOBJ_INT(current)); \
+ } \
+ if (args->dfs_conf->use_edge) { \
+ AssPlist(args->edge, succ, INTOBJ_INT(idx)); \
+ } \
+ CHANGED_BAG(args->record);
+
+#define STACK_PUSH(stack, size, val) \
+ AssPlist(stack, ++size, val)
+
+#define STACK_POP(stack, size) \
+ ELM_PLIST(stack, size--)
+
+#define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to
+ // visit the current node)
+
+#define RECURSE_FOREST(dfs_args, v) \
+ bool visited = IS_VISITED(dfs_args, v); \
+ if (!visited) { \
+ if (dfs_args -> dfs_conf -> use_parents) { \
+ AssPlist(dfs_args->parents, v, INTOBJ_INT(v)); \
+ CHANGED_BAG(record); \
+ } \
+ if (!ExecuteDFSRec(v, v, PREORDER_IDX, dfs_args)) { \
+ CHANGED_BAG(record); \
+ recordCleanup(dfs_args); \
+ return record; \
+ } \
+ }
+
+#define ITER_FOREST(args, stack, v) \
+ bool visited = IS_VISITED(args, v); \
+ \
+ if (!visited) { \
+ if (args->dfs_conf->use_parents) { \
+ AssPlist(args->parents, v, INTOBJ_INT(v)); \
+ } \
+ CHANGED_BAG(args->record); \
+ AssPlist(stack, 1, INTOBJ_INT(v)); \
+ \
+ if (!iter_loop(stack, 1, args)) \
+ return false; \
+ }
+
+void recordCleanup(struct dfs_args* args) {
+ struct dfs_config* dfs_conf = args -> dfs_conf;
+
+ if (!dfs_conf -> use_preorder) {
+ free(args -> preorder_partial);
+ }
+
+ if (dfs_conf -> partial_postorder) {
+ free(args -> postorder_partial);
+ }
+}
+
+void parseConfig(struct dfs_args* args, Obj conf_record) {
+ struct dfs_config* conf = args -> dfs_conf;
+ conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True;
+ conf -> revisit = ElmPRec(conf_record, RNamName("revisit")) == True;
+ conf -> forest = ElmPRec(conf_record, RNamName("forest")) == True;
+ conf -> use_preorder =
+ ElmPRec(conf_record, RNamName("use_preorder")) == True;
+ conf -> use_postorder =
+ ElmPRec(conf_record, RNamName("use_postorder")) == True;
+ conf -> use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True;
+ conf -> use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True;
+ conf -> forest_specific = ElmPRec(conf_record, RNamName("forest_specific"));
+ conf -> partial_postorder = false;
+
+ if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) {
+ ErrorQuit(
+ "In a DFSRecord where the config flag iterative is false, use_edge and "
+ "use_parents must be true", 0L, 0L);
+ }
+
+ if (conf -> revisit && !conf -> iter) {
+ ErrorQuit(
+ "In a DFSRecord where the config flag revisit is true, iterative "
+ "must also be true", 0L, 0L);
+ }
+
+ if ((args -> CallAncestor || args -> CallCross || conf -> revisit) &&
+ !conf -> use_postorder) {
+ conf -> partial_postorder = true;
+ }
+
+ if ((args -> CallPostorder && conf -> iter) && !conf -> use_parents) {
+ // use_parents is always required for recursive (only check for iter)
+ ErrorQuit(
+ "In a DFSRecord where a PostorderFunc exists, where the config flag "
+ "iter is true, the flag use_parents must also be true", 0L, 0L);
+ }
+
+ if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) {
+ conf -> partial_postorder = true;
+ }
+
+ if (conf -> forest_specific != Fail && conf -> forest) {
+ ErrorQuit(
+ "In a DFSRecord where the config flag forest_specific is not fail, "
+ "forest cannot also be true", 0L, 0L);
+ }
+
+ if (args -> CallCross && (!conf -> use_preorder)) {
+ ErrorQuit(
+ "In a DFSRecord where there is a CrossFunc, the config flag "
+ "use_preorder must be true", 0L, 0L);
+ }
+
+}
+
+// Extreme examples are on the pull request #459
+
+/* Iterative DFS (used for revisiting vertices)
+ Necessary record elements: none
+ If CallPostorder, then parents is necessary
+
+ Differences with recursive : edge and parents are updated
+ when successors are pushed to the stack, whereas with
+ recursive they are updated before visiting
+*/
+
+bool ExecuteDFSIter(Int start, struct dfs_args* args) {
+ Int N = LEN_LIST(args -> neighbors);
+ Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2);
+
+ Int stack_size = 1;
+
+ AssPlist(stack, 1, INTOBJ_INT(start));
+
+ if (!iter_loop(stack, stack_size, args)) return false;
+
+ if (args -> dfs_conf -> forest) {
+ for (Int i = 1; i <= LEN_LIST(args -> neighbors); i++) {
+ ITER_FOREST(args, stack, i);
+ }
+ } else if (args -> dfs_conf -> forest_specific != Fail) {
+ Int len = LEN_LIST(args -> dfs_conf -> forest_specific);
+ for (Int i = 1; i <= len; i++) {
+ ITER_FOREST(args, stack, i);
+ }
+ }
+ return true;
+}
+
+/*
+ Main loop for iterative DFS called by ITER_FOREST and ExecuteDFSIter.
+
+ ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function
+ if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc,
+ CrossFunc, or AncestorFunc.
+*/
+bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) {
+ while (stack_size > 0) {
+ Int current = INT_INTOBJ(STACK_POP(stack, stack_size));
+
+ if (current < 0) {
+ Int bt_on = current * -1;
+ Int parent = !args -> dfs_conf -> use_parents ? -1 :
+ INT_INTOBJ(ELM_LIST(args -> parents, bt_on));
+ ON_BACKTRACK(bt_on, parent, args);
+ continue;
+ } else if (IS_VISITED(args, current)) {
+ continue;
+ }
+
+ ON_PREORDER(current, args); // and push backtrack node
+ if (args -> dfs_conf -> use_postorder
+ || args -> dfs_conf -> partial_postorder
+ || args -> CallPostorder) {
+ STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1));
+ }
+
+ Obj succ = ELM_LIST(args -> neighbors, current);
+
+ for (Int i = LEN_LIST(succ); i > 0; i--) {
+ Int v = INT_INTOBJ(ELM_LIST(succ, i));
+
+ bool backtracked = (args -> dfs_conf -> use_postorder
+ || args -> dfs_conf -> partial_postorder) &&
+ (IS_BACKTRACKED(args, v));
+ bool revisit = (args -> dfs_conf -> revisit && backtracked);
+ if (revisit) {
+ UNSET_PREORDER(args, v);
+ CHANGED_BAG(args->record);
+ }
+ bool visited = IS_VISITED(args, v);
+
+ if (!visited) {
+ ON_ADD_SUCC(current, v, i, args);
+ STACK_PUSH(stack, stack_size, INTOBJ_INT(v));
+ } else {
+ ANCESTOR_CROSS(current, v, backtracked, args);
+ }
+ }
+ }
+ return true;
+}
+
+/* Recursive DFS
+ Necessary record elements: edge, parents
+ goto used to force tail call optimization
+
+ ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function
+ if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc,
+ CrossFunc, or AncestorFunc.
+*/
+
+bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) {
+rec:
+ if (idx == PREORDER_IDX) { // visit current
+ ON_PREORDER(current, args);
+
+ // Start recursing on successors of vertex , with parent
+ idx += 1;
+ goto rec;
+ }
+
+ Obj succ = ELM_LIST(args->neighbors, current);
+
+ if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored)
+ ON_BACKTRACK(current, parent, args);
+
+ Int prev_idx = INT_INTOBJ(ELM_LIST(args->edge, current));
+ Int parents_parent = INT_INTOBJ(ELM_LIST(args->parents, parent));
+
+ if (parent == current) {
+ return true; // At root
+ }
+
+ // Continue exploration of 's successors
+
+ current = parent; // Backtrack to parent of vertex
+ parent = parents_parent; // The parent is now the new vertex's
+ // previously assigned parent
+ idx = prev_idx + 1; // Index is the next successor to visit
+ // continuing previous exploration of
+ // 's successors
+ goto rec;
+ } else {
+ Int v = INT_INTOBJ(ELM_LIST(succ, idx));
+ bool visited = IS_VISITED(args, v);
+
+ if (!visited) {
+ ON_ADD_SUCC(current, v, idx, args);
+
+ parent = current; // Explore successor v with parent
+ current = v;
+ idx = PREORDER_IDX; // Initial index to indicate v is being visited
+
+ goto rec;
+
+ } else {
+ bool backtracked =
+ (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder)
+ && (IS_BACKTRACKED(args, v));
+ ANCESTOR_CROSS(current, v, backtracked, args);
+
+ idx += 1; // Skip this successor of
+ // since it has already been visited
+ goto rec;
+ }
+ }
+}
+
+Obj FuncExecuteDFS_C(Obj self, Obj args) {
+ DIGRAPHS_ASSERT(LEN_PLIST(args) == 7);
+ Obj record = ELM_PLIST(args, 1);
+ Obj config = ElmPRec(record, RNamName("config"));
+ Obj data = ELM_PLIST(args, 2);
+ Obj start = ELM_PLIST(args, 3);
+ Obj PreorderFunc = ELM_PLIST(args, 4);
+ Obj PostorderFunc = ELM_PLIST(args, 5);
+ Obj AncestorFunc = ELM_PLIST(args, 6);
+ Obj CrossFunc = ELM_PLIST(args, 7);
+
+ DIGRAPHS_ASSERT(IS_PREC(record));
+ DIGRAPHS_ASSERT(IS_INTOBJ(start));
+
+ PreorderFunc = !IS_FUNC(PreorderFunc) ? Fail : PreorderFunc;
+ PostorderFunc = !IS_FUNC(PostorderFunc) ? Fail : PostorderFunc;
+ AncestorFunc = !IS_FUNC(AncestorFunc) ? Fail : AncestorFunc;
+ CrossFunc = !IS_FUNC(CrossFunc) ? Fail : CrossFunc;
+
+ Obj D = ElmPRec(record, RNamName("graph"));
+ Obj outNeighbours = FuncOutNeighbours(self, D);
+ Int N = DigraphNrVertices(D);
+
+ if (INT_INTOBJ(start) > N) {
+ ErrorQuit(
+ "the third argument must be a vertex in your graph,", 0L, 0L);
+ }
+
+ Int RNamStop = RNamName("stop");
+
+ if (ElmPRec(record, RNamStop) == True) return record;
+
+ Int preorder_num = 0;
+ Int postorder_num = 0;
+
+ struct dfs_config dfs_conf = {0};
+
+ struct dfs_args dfs_args_ = {
+ .dfs_conf = &dfs_conf,
+ .record = record,
+ .preorder_num = &preorder_num,
+ .postorder_num = &postorder_num,
+ .parents = ElmPRec(record, RNamName("parents")),
+ .postorder = ElmPRec(record, RNamName("postorder")),
+ .preorder = ElmPRec(record, RNamName("preorder")),
+ .edge = ElmPRec(record, RNamName("edge")),
+ .neighbors = outNeighbours, .data = data,
+ .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc,
+ .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc,
+ .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"),
+ .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail,
+ .CallPostorder = PostorderFunc != Fail,
+ .CallAncestor = AncestorFunc != Fail, .CallCross = CrossFunc != Fail};
+
+ parseConfig(&dfs_args_, config);
+
+ if (!dfs_conf.use_preorder) {
+ dfs_args_.preorder_partial = (bool*) safe_malloc((N + 1) * sizeof(bool));
+ memset(dfs_args_.preorder_partial, false, (N + 1) * sizeof(bool));
+ }
+
+ if (dfs_conf.partial_postorder) {
+ dfs_args_.postorder_partial = (bool*) safe_malloc((N + 1) * sizeof(bool));
+ memset(dfs_args_.postorder_partial, false, (N + 1) * sizeof(bool));
+ }
+
+ if (dfs_conf.use_parents) {
+ AssPlist(dfs_args_.parents, INT_INTOBJ(start), start);
+ CHANGED_BAG(record);
+ }
+
+ Int current = INT_INTOBJ(start);
+
+ if (dfs_conf.iter || dfs_conf.revisit) {
+ ExecuteDFSIter(current, &dfs_args_);
+ } else {
+ if (dfs_conf.forest || (dfs_conf.forest_specific != Fail)) {
+ // Initial DFS with specified start index
+ if (ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_)) {
+ if (dfs_conf.forest) {
+ for (Int i = 1; i <= N; i++) {
+ RECURSE_FOREST((&dfs_args_), i); // Returns
+ }
+ } else if (dfs_conf.forest_specific != Fail) {
+ for (Int i = 1; i <= LEN_LIST(dfs_conf.forest_specific); i++) {
+ RECURSE_FOREST((&dfs_args_), // Returns
+ INT_INTOBJ(ELM_LIST(dfs_conf.forest_specific, i)));
+ }
+ }
+ }
+ } else {
+ ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_);
+ }
+ }
+
+ recordCleanup(&dfs_args_);
+
+ CHANGED_BAG(record);
+ return record;
+}
diff --git a/src/dfs.h b/src/dfs.h
new file mode 100644
index 000000000..9ca3c6e91
--- /dev/null
+++ b/src/dfs.h
@@ -0,0 +1,82 @@
+/*******************************************************************************
+**
+*A dfs.h GAP package Digraphs Lea Racine
+** James Mitchell
+**
+** Copyright (C) 2014-21 - Julius Jonusas, James Mitchell, Michael Torpey,
+** Wilf A. Wilson et al.
+**
+** This file is free software, see the digraphs/LICENSE.
+**
+*******************************************************************************/
+
+#ifndef DIGRAPHS_SRC_DFS_H_
+#define DIGRAPHS_SRC_DFS_H_
+
+#include // for false, true, bool
+// GAP headers
+#include "gap-includes.h" // for Obj, Int
+
+bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data);
+
+struct dfs_args {
+ struct dfs_config* dfs_conf;
+
+ Obj record;
+ Int* preorder_num;
+ Int* postorder_num;
+
+ Obj parents;
+
+ union {
+ Obj postorder;
+ bool* postorder_partial;
+ };
+
+ union {
+ Obj preorder;
+ bool* preorder_partial;
+ };
+ Obj edge;
+
+ Obj neighbors;
+
+ Obj data;
+ Obj PreorderFunc;
+ Obj PostorderFunc;
+ Obj AncestorFunc;
+ Obj CrossFunc;
+
+ Int RNamChild;
+ Int RNamCurrent;
+ Int RNamStop;
+
+ bool CallPreorder;
+ bool CallPostorder;
+ bool CallAncestor;
+ bool CallCross;
+};
+
+struct dfs_config {
+ bool revisit;
+ bool iter;
+ bool forest;
+ Obj forest_specific;
+ bool use_preorder;
+ bool use_postorder;
+ bool partial_postorder;
+ bool use_parents;
+ bool use_edge;
+};
+
+
+
+bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args);
+bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args);
+bool ExecuteDFSIter(Int start, struct dfs_args* args);
+Obj FuncExecuteDFS_C(Obj self, Obj args);
+
+void parseConfig(struct dfs_args*, Obj conf_record);
+void recordCleanup(struct dfs_args* args);
+
+#endif // DIGRAPHS_SRC_DFS_H_
diff --git a/src/digraphs.c b/src/digraphs.c
index ba0011b31..e7e0dabcd 100644
--- a/src/digraphs.c
+++ b/src/digraphs.c
@@ -26,6 +26,7 @@
#include "homos.h" // for FuncHomomorphismDigraphsFinder
#include "planar.h" // for FUNC_IS_PLANAR, . . .
#include "safemalloc.h" // for safe_malloc
+#include "dfs.h" // for generic DFS
#undef PACKAGE
#undef PACKAGE_BUGREPORT
@@ -2223,6 +2224,10 @@ static StructGVarFunc GVarFuncs[] = {
GVAR_FUNC(SUBGRAPH_HOMEOMORPHIC_TO_K4, 1, "digraph"),
GVAR_FUNC(DIGRAPHS_FREE_HOMOS_DATA, 0, ""),
GVAR_FUNC(DIGRAPHS_FREE_CLIQUES_DATA, 0, ""),
+ GVAR_FUNC(ExecuteDFS_C,
+ -1,
+ "record, data, start, PreOrderFunc, PostOrderFunc, "
+ "AncestorFunc, CrossFunc"),
{0, 0, 0, 0, 0} /* Finish with an empty entry */
};
@@ -2256,6 +2261,7 @@ static Int InitKernel(StructInitInfo* module) {
ImportGVarFromLibrary("Group", &Group);
ImportGVarFromLibrary("ClosureGroup", &ClosureGroup);
ImportGVarFromLibrary("InfoWarning", &InfoWarning);
+
/* return success */
return 0;
}
diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst
index e8460bc84..a90854b56 100644
--- a/tst/standard/oper.tst
+++ b/tst/standard/oper.tst
@@ -1342,6 +1342,11 @@ ent ,
gap> DigraphPath(gr, 11, 11);
Error, the 2nd and 3rd arguments and must be vertices of the 1st argum\
ent ,
+gap> gr := Digraph([[1, 2], [3], [1]]);;
+gap> path := DigraphPath(gr, 1, 1);
+[ [ 1, 1 ], [ 1 ] ]
+gap> IsDigraphPath(gr, path);
+true
# IteratorOfPaths
gap> gr := CompleteDigraph(5);;
@@ -1446,6 +1451,12 @@ gap> DigraphLongestDistanceFromVertex(gr, 15);
infinity
gap> DigraphLongestDistanceFromVertex(gr, 16);
Error, the 2nd argument must be a vertex of the 1st argument ,
+gap> D := Digraph([[2, 4], [3, 4], [5], [], []]);;
+gap> DigraphLongestDistanceFromVertex(D, 1);
+3
+gap> D := Digraph([[2, 3], [], [2]]);;
+gap> DigraphLongestDistanceFromVertex(D, 1);
+2
# DigraphRandomWalk
gap> gr := CompleteDigraph(5);
@@ -3234,6 +3245,215 @@ gap> DigraphEdges(D);
gap> DigraphVertexLabels(D);
[ 1, 2, 3, 6, [ 4, 5 ] ]
+# DFS
+
+# NewDFSRecord TODO uncomment when know what record will be
+# gap> NewDFSRecord(ChainDigraph(10));
+# rec( child := -1, current := -1, edge := HashMap([]),
+# graph := , parents := HashMap([]),
+# postorder := HashMap([]), preorder := HashMap([]), stop := false )
+# gap> NewDFSRecord(CompleteDigraph(2));
+# rec( child := -1, current := -1, edge := HashMap([]),
+# graph := ,
+# parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]),
+# stop := false )
+# gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]]));
+# rec( child := -1, current := -1, edge := HashMap([]),
+# graph := ,
+# parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]),
+# stop := false )
+
+# ExecuteDFS - Error Checking
+gap> ExecuteDFS(rec(), [], 1, fail,
+> fail, fail, fail);
+Error, the 1st argument must be created with NewDFSRecord,
+gap> D := ChainDigraph(1);;
+gap> ExecuteDFS(NewDFSRecord(D), [], 3, fail, fail, fail,
+> fail);
+Error, the third argument must be a vertex in your graph,
+gap> d := BinaryTree(5);;
+gap> record := NewDFSRecord(d);;
+gap> record.config.forest_specific := [1,, 3];;
+gap> ExecuteDFS(record, [], 3, fail, fail, fail,
+> fail);
+Error, the 1st argument has a value of .config.forest_specifi\
+c that is not fail or a dense list,
+
+# ExecuteDFS - Correctness
+gap> record := NewDFSRecord(CompleteDigraph(10));;
+gap> ExecuteDFS(record, [], 2, fail,
+> fail, fail, fail);
+gap> preorder_list := [];;
+gap> record.preorder;
+[ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ]
+gap> record := NewDFSRecord(CompleteDigraph(15));;
+gap> data := rec(cycle_vertex := 0);;
+gap> AncestorFunc := function(record, data)
+> record.stop := true;
+> data.cycle_vertex := record.child;
+> end;;
+gap> ExecuteDFS(record, data, 1, fail,
+> fail, AncestorFunc, fail);
+gap> record.stop;
+true
+gap> data.cycle_vertex;
+1
+gap> record.preorder;
+[ 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ]
+gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));;
+gap> CrossFunc := function(record, data)
+> record.stop := true;
+> Add(data, record.child);
+> end;;
+gap> data := [];;
+gap> ExecuteDFS(record, data, 1, fail,
+> fail, fail, CrossFunc);
+gap> record.stop;
+true
+gap> data;
+[ 4 ]
+gap> AncestorFunc := function(record, data)
+> Add(data.back_edges, [record.current, record.child]);
+> end;;
+gap> CrossFunc := function(record, data)
+> Add(data.cross_edges, [record.current, record.child]);
+> end;;
+gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));;
+gap> data := rec(back_edges := [], cross_edges := []);;
+gap> ExecuteDFS(record, data, 1, fail,
+> fail, AncestorFunc, CrossFunc);;
+gap> data;
+rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] )
+gap> record := NewDFSRecord(Digraph([[2, 3], [], [2]]));;
+gap> data := rec(back_edges := [], cross_edges := []);;
+gap> ExecuteDFS(record, data, 1, fail, fail, AncestorFunc,
+> CrossFunc);
+gap> record.preorder;
+[ 1, 2, 3 ]
+gap> record.postorder;
+[ 3, 1, 2 ]
+gap> data;
+rec( back_edges := [ ], cross_edges := [ [ 3, 2 ] ] )
+gap> record := NewDFSRecord(Digraph([[2, 3], [3], []]));;
+gap> data := rec(explored_edges := []);;
+gap> PreorderFunc := function(record, data)
+> if record.current <> record.parents[record.current] then
+> Add(data.explored_edges, [record.parents[record.current], record.current]);
+> fi;
+> end;;
+gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail,
+> fail);
+gap> data.explored_edges;
+[ [ 1, 2 ], [ 2, 3 ] ]
+gap> gr := Digraph(List([1 .. 5], x -> [1 .. 5]));;
+gap> record := NewDFSRecord(gr);;
+gap> ExecuteDFS(record, data, 1, fail, fail, fail,
+> fail);
+
+# Stopping ExecuteDFS
+gap> gr := CompleteDigraph(10000);;
+gap> record := NewDFSRecord(gr);;
+gap> data := rec(count := 0);;
+gap> PreorderFunc := function(record, data)
+> data.count := data.count + 1;
+> if record.current = 5000 then
+> record.stop := true;
+> fi;
+> end;;
+gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail,
+> fail);
+gap> data.count;
+5000
+gap> record_iter := NewDFSRecord(gr);;
+gap> record_iter.config.iterative := true;;
+gap> data := rec(count := 0);;
+gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail,
+> fail);
+gap> data.count;
+5000
+gap> data := ["postorder", "preorder"];;
+gap> ForAll(data, x -> record_iter.(x) = record.(x));
+true
+gap> record := NewDFSRecord(gr);;
+gap> record.config.revisit := true;;
+gap> record.config.iterative := true;;
+gap> record.config.use_postorder := true;;
+gap> data := rec(count := 0);;
+gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail,
+> fail);
+gap> data.count;
+5000
+
+# Checking results are consistent for different options - BinaryTree(5)
+gap> gr := BinaryTree(5);;
+gap> record := NewDFSRecord(gr);;
+gap> configs := [];;
+gap> temp_conf := ShallowCopy(record.config);; # forest, recursive
+gap> temp_conf.forest := true;;
+gap> Add(configs, temp_conf);;
+gap> temp_conf := ShallowCopy(record.config);; # forest_specific, recursive
+gap> temp_conf.forest_specific := DigraphVertices(gr);;
+gap> Add(configs, temp_conf);;
+gap> temp_conf := ShallowCopy(configs[1]);; # forest, iterative
+gap> temp_conf.iterative := true;;
+gap> Add(configs, temp_conf);;
+gap> temp_conf := ShallowCopy(configs[2]);; # forest_specific, iterative
+gap> temp_conf.iterative := true;;
+gap> Add(configs, temp_conf);;
+gap> records := [];;
+gap> for conf in configs do
+> record := NewDFSRecord(gr, conf);;
+> ExecuteDFS(record, fail, 1, fail, fail, fail, fail);;
+> Add(records, record);;
+> od;
+gap> ForAll(records, r ->
+> ((r.edge = records[1].edge) and
+> (r.parents = records[1].parents) and
+> (r.preorder = records[1].preorder) and
+> (r.postorder = records[1].postorder) and
+> (IsSet(r.postorder) and IsSet(r.preorder)))
+> );
+true
+
+# IsDigraphPath
+gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset);
+
+gap> DigraphReflexiveTransitiveReduction(D);
+
+gap> MakeImmutable(D);
+
+gap> IsDigraphPath(D, [1, 2, 3], []);
+Error, the 2nd and 3rd arguments (lists) are incompatible, expected 3rd argume\
+nt of length 2, got 0
+gap> IsDigraphPath(D, [1], []);
+true
+gap> IsDigraphPath(D, [1, 2], [5]);
+false
+gap> IsDigraphPath(D, [32, 31, 33], [1, 1]);
+false
+gap> IsDigraphPath(D, [32, 33, 31], [1, 1]);
+false
+gap> IsDigraphPath(D, [6, 9, 16, 17], [3, 3, 2]);
+true
+gap> IsDigraphPath(D, [33, 9, 16, 17], [3, 3, 2]);
+false
+gap> IsDigraphPath(D, [6, 9, 18, 1], [9, 10, 2]);
+false
+
+# IsDigraphPath
+gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset);
+
+gap> DigraphReflexiveTransitiveReduction(D);
+
+gap> MakeImmutable(D);
+
+gap> IsDigraphPath(D, DigraphPath(D, 6, 1));
+true
+gap> ForAll(List(IteratorOfPaths(D, 6, 1)), x -> IsDigraphPath(D, x));
+true
+gap> IsDigraphPath(D, []);
+Error, the 2nd argument (a list) must have length 2, but found length 0
+
# DIGRAPHS_UnbindVariables
gap> Unbind(C);
gap> Unbind(D);
@@ -3295,6 +3515,15 @@ gap> Unbind(u1);
gap> Unbind(u2);
gap> Unbind(x);
gap> Unbind(TestPartialOrderDigraph);
+gap> Unbind(PreorderFunc);
+gap> Unbind(AncestorFunc);
+gap> Unbind(CrossFunc);
+gap> Unbind(record);
+gap> Unbind(data);
+gap> Unbind(parents);
+gap> Unbind(edge);
+gap> Unbind(postorder);
+gap> Unbind(preorder);
#
gap> DIGRAPHS_StopTest();