Skip to content

Attempt to add ExecuteDFS to main (Generic depth first search) #737

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

Open
wants to merge 54 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4c1a7e4
Initial DFS Generic, stack now fixed size
saffronmciver Mar 2, 2025
30b38c5
Merge branch 'dfs' of github.com:saffronmciver/Digraphs into dfs
saffronmciver Mar 2, 2025
2703528
Fix bit array usage
saffronmciver Mar 2, 2025
b0a1fe7
Fix last commit
saffronmciver Mar 2, 2025
12f4013
Add back vertices reachable from
saffronmciver Mar 3, 2025
5c7e7b4
Fix ExecuteDFS and add back some uses
saffronmciver Mar 3, 2025
31cd038
Investigate recursive solution to save stack additions
saffronmciver Mar 3, 2025
d8be420
Before converting to HashMap
saffronmciver Mar 3, 2025
d7c2d3b
Add recursive DFS with HashMaps, fix issue with cross / back edges
saffronmciver Mar 4, 2025
f367047
Fix lint errors
saffronmciver Mar 4, 2025
99955c6
Fix wrong result from DigraphLongestDistanceFromVertex and CrossFunc …
saffronmciver Mar 4, 2025
bf7eb4f
Fix missed gaplint errors
saffronmciver Mar 4, 2025
97d981d
Fix compile warnings
saffronmciver Mar 4, 2025
091a150
Fix include order
saffronmciver Mar 4, 2025
5febb4f
Fix compile warnings (2)
saffronmciver Mar 4, 2025
c9c2da4
Add back DominatorTree
saffronmciver Mar 5, 2025
8bdf438
Change TopologicalSort to use DFS forest flag, and reserving hashmaps…
saffronmciver Mar 12, 2025
a81fe2f
Change IsNotEqualSet to checking IsBound in ExecuteDFS to improve per…
saffronmciver Mar 18, 2025
6ebbf44
Remove file
saffronmciver Mar 18, 2025
7e8d627
Add visited / backtrack bitarrays to reduce hash lookups. Add revisit…
saffronmciver Mar 25, 2025
0ea0274
Remove reserve for hashmaps
saffronmciver Mar 25, 2025
9efd4b3
List version with bit arrays
saffronmciver Mar 25, 2025
09c9009
List version without bit arrays
saffronmciver Mar 25, 2025
4bb12ef
Refactor dfs function for tail call optimisation on deeper trees (e.g…
saffronmciver Mar 28, 2025
6856b6d
Add an iterative version for revisiting nodes (DigraphLongestDistance…
saffronmciver Apr 2, 2025
3ecf4de
Add back (DFS Versions of) IsAntiSymmetricDigraph and UndirectedSpann…
saffronmciver Apr 9, 2025
e659124
Change args used for ExecuteDFS instances to remove use of unnecessar…
saffronmciver Apr 9, 2025
c5110d3
Attempt 1 to make GAP 4.11 pass (changed all uses to iterative
saffronmciver Apr 9, 2025
30ab115
Attempt 2, added CHANGED_BAG
saffronmciver Apr 9, 2025
4a9a350
Attempt 3, no PLIST usage for out neighbors
saffronmciver Apr 9, 2025
dd002e4
Add forest specific (specific vertex start points for forest DFS), mo…
saffronmciver Apr 16, 2025
c0fd0ed
Fix lint error
saffronmciver Apr 16, 2025
83b6c4b
Merge branch 'digraphs:main' into dfs
saffronmciver Apr 16, 2025
d67d5e6
Correct error on VerticesReachableFrom
saffronmciver Apr 16, 2025
9487572
Merge branch 'dfs' of github.com:saffronmciver/Digraphs into dfs
saffronmciver Apr 16, 2025
cbdc208
Add configuration to remove use of record elements (when use_preorder…
saffronmciver Apr 23, 2025
7b04099
Change uses of DFS to remove unnecessary record element usage
saffronmciver Apr 23, 2025
1e16a66
Add documentation, input checking for DFSFlags, correct error message
saffronmciver Apr 30, 2025
6af3e1b
Fix documentation spelling
saffronmciver Apr 30, 2025
f262e5e
Fix valgrind error with record cleanup for forest DFS
saffronmciver Apr 30, 2025
a40ca2a
Correct documentation errors
saffronmciver Apr 30, 2025
6b49250
Fix usage of PList for forest_specific flag, add tests for consistenc…
saffronmciver Jun 7, 2025
689f525
Correct assert of arg number
saffronmciver Jun 7, 2025
4d97080
Remove unnecessary header inclusions
saffronmciver Jun 8, 2025
a7b7bd9
Attempt to diagnose planarity issue by restoring list argument Vertic…
saffronmciver Jun 8, 2025
ce23d9b
Attempt 2: restoring DigraphPath
saffronmciver Jun 8, 2025
159e593
Attempt 3: restoring DigraphLongestDistanceFromVertex
saffronmciver Jun 8, 2025
711940a
Attempt 4: restoring DominatorTree
saffronmciver Jun 8, 2025
22a8246
Attempt 5: restoring VerticesReachableFrom int argument
saffronmciver Jun 8, 2025
9676f16
Attempt 6: temporarily removing half the tests added in the commit wh…
saffronmciver Jun 8, 2025
c6e65fc
Uncomment out potentially problematic iterative DFS tests
saffronmciver Jun 8, 2025
ed6d496
Reduce size of graph to do recursive DFS on in seg faulting planarity…
saffronmciver Jun 8, 2025
804f863
Change recursive DFS to use explicit gotos to avoid unoptimized tail …
saffronmciver Jun 8, 2025
aa8b88a
Fix spelling mistake
saffronmciver Jun 8, 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
1 change: 1 addition & 0 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
384 changes: 384 additions & 0 deletions doc/oper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2489,3 +2489,387 @@ true]]>
gap> DIGRAPHS_FREE_CLIQUES_DATA();
]]></Example>
<#/GAPDoc>

<#GAPDoc Label="NewDFSRecord">
<ManSection>
<Oper Name="NewDFSRecord" Arg="digraph" Label="for a digraph"/>
<Oper Name="NewDFSRecord" Arg="digraph, conf" Label="for a digraph and a record"/>
<Returns>A record.</Returns>
<Description>
This record contains four lists (parents, edge, preorder and postorder) with their length
equal to the number of vertices in the <A>digraph</A>. Each index <C>i</C> of each list
corresponds to vertex <C>i</C> in <A>digraph</A>.
These lists store the following (where <C>record</C> is returned by <C>NewDFSRecord</C>):
<List>
<Mark>parents</Mark>
<Item>
At each index, the parent of the vertex is stored. <P/>

Note that when
<C>record.config.iterative</C> is <K>true</K>, both <C>record.parents</C>[<C>i</C>]
and <C>record.edge</C>[<C>i</C>] for all vertices encountered in DFS
search <C>i</C> are updated as successors are pushed to the
stack, rather than when <C>i</C> is visited (which occurs when <C>record.config.iterative</C>
is <K>false</K>). Once <C>i</C> has been visited,
<C>record.parents</C>[<C>i</C>] and <C>record.edge</C>[<C>i</C>]
correspond to the true edge and parent <C>i</C> was visited through. When <C>record.config.revisit</C>
is <K>true</K>, a vertex may have more than one
parent or edge it is visited through.
</Item>
<Mark>edge</Mark>
<Item>At each index <C>i</C>, the index of the edge
in <C>OutNeighboursOfVertex(</C><C>j</C>) of which
<C>i</C> was visited through is stored, where <C>j</C>
was the parent of <C>i</C> in the DFS.</Item>
<Mark>preorder</Mark>
<Item>At each index, the preorder number (order in which the vertex is visited)
is stored.</Item>
<Mark>postorder</Mark>
<Item>At each index, the postorder number (order in which the vertex is backtracked on)
is stored.</Item>
</List><P/>

The record also stores a further 5 attributes:
<List>
<Mark>current</Mark>
<Item>The current vertex that is being visited.<P/>

When <C>record.PostOrderFunc</C> is called, <C>record.current</C>
refers to the parent of the backtracking vertex.<P/>

Initial: -1
</Item>
<Mark>child</Mark>
<Item>
The child of the current vertex.<P/>

When <C>record.PostOrderFunc</C> is called, <C>record.child</C>
refers to the backtracking vertex.<P/>

Initial: -1
</Item>
<Mark>graph</Mark>
<Item>
The <A>digraph</A> to carry out DFS on.
</Item>
<Mark>stop</Mark>
<Item>
Whether to stop the depth first search.<P/>

Initial: <K>false</K>
</Item>
<Mark>config</Mark>
<Item>A configuration for DFS as defined using <Ref Oper="NewDFSFlags"/>
or <Ref Oper="NewDFSFlagsLightweight"/>. This field should be set
by calling <C>NewDFSRecord</C>(<C>graph</C>, <C>config</C>)
where <C>config</C> was initially generated by <Ref Oper="NewDFSFlags"/>
or <Ref Oper="NewDFSFlagsLightweight"/>.<P/>

Default when defined using <Ref Oper="NewDFSRecord" Label="for a digraph"/> with
no <C>config</C> argument: A record as returned by
<Ref Oper="NewDFSFlags"/>.
</Item>
</List>

When this function is called as <C>NewDFSRecord</C>(<A>record</A>, <A>conf</A>), this function
returns a <Ref Oper="NewDFSRecord" Label="for a digraph"/> with the
<C>record.config</C> field set to <A>conf</A>.<P/>

Initially, the <C>current</C> and <C>child</C> attributes will have <C>-1</C> values and the lists (<C>parents</C>,
<C>preorder</C>, <C>edge</C> and <C>postorder</C>) will have <C>-1</C> values at all of their indices as no vertex has
been visited (if any of the respective <C>record.config.use_preorder</C>
flags are set to <K>false</K>, the value of the respective field is <K>fail</K>). The <C>stop</C> attribute will initially be <C>false</C>.
<E>This record should be passed into the <C>ExecuteDFS</C> function.</E>
See <Ref Oper="ExecuteDFS"/>.
<Example><![CDATA[
gap> 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;
<immutable complete digraph with 2 vertices>
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
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="NewDFSFlags">
<ManSection>
<Oper Name="NewDFSFlags" Arg=""/>
<Returns>A record.</Returns>
<Description>
This function returns a DFSFlags <C>record</C> to be
used as the <C>config</C> field of a DFSRecord.
<Ref Oper="NewDFSRecord" Label="for a digraph"/> called as <C>NewDFSRecord</C>(<C>record</C>, <C>config</C>) should be used to set
<C>config</C> (as shown by the below example), since
fields such as <C>record.config.use_postorder</C>
change the behaviour of <Ref Oper="NewDFSRecord" Label="for a digraph"/>. For
example, <C>record.config.use_postorder</C> tells <Ref Oper="NewDFSRecord" Label="for a digraph"/>
not to create the <C>record.postorder</C> field.<P/>

The fields for both NewDFSFlags and <Ref Oper="NewDFSFlagsLightweight"/>
are described as follows, assuming <C>config</C> was returned
by <Ref Oper="NewDFSFlags"/> or <Ref Oper="NewDFSFlagsLightweight"/>,
and <C>NewDFSRecord</C>(<C>d</C>, <C>config</C>) is
called for a digraph <C>d</C>, returning <C>record</C>:<P/>

<List>
<Mark>use_preorder</Mark>
<Item>
When <C>config.use_preorder</C> is <K>true</K>, <C>record.preorder</C>
is <K>fail</K>, and not used during the <Ref Oper="ExecuteDFS"/> procedure
(when called with first argument <C>record</C>).<P/>

Default: <K>true</K>
</Item>
<Mark>use_postorder</Mark>
<Item>
When <C>config.use_postorder</C> is <K>true</K>, <C>record.postorder</C>
is <K>fail</K>, and not used during the <Ref Oper="ExecuteDFS"/> procedure
(when called with first argument <C>record</C>).<P/>

Default: <K>true</K>
</Item>
<Mark>use_parents</Mark>
<Item>
When <C>config.use_parents</C> is <K>true</K>, <C>record.parents</C>
is <K>fail</K>, and not used during the <Ref Oper="ExecuteDFS"/> procedure
(when called with first argument <C>record</C>).<P/>

Default: <K>true</K>
</Item>
<Mark>use_edge</Mark>
<Item>
When <C>config.use_edge</C> is <K>true</K>, <C>record.edge</C>
is <K>fail</K>, and not used during the <Ref Oper="ExecuteDFS"/> procedure
(when called with first argument <C>record</C>).<P/>

Default: <K>true</K>
</Item>
<Mark>iterative</Mark>
<Item>
Executes the iterative <Ref Oper="ExecuteDFS"/> procedure. Memory usage
is generally lower for non iterative DFS. When <C>config.iterative</C> is
<K>false</K> (recursive DFS), <C>config.use_parents</C> and <C>config.use_edge</C>
must also be <K>true</K>.<P/>

Default: <K>false</K>
</Item>
<Mark>revisit</Mark>
<Item>
Allows nodes to be revited during <Ref Oper="ExecuteDFS"/> if they
are encountered as a successor, and have been backtracked in the current
DFS tree. When <C>config.revisit</C>
is <K>true</K>, <C>config.iterative</C> must also be <K>true</K>.
Requires <C>config.iterative</C> to be <K>true</K>.<P/>

Default: <K>false</K>
</Item>
<Mark>forest</Mark>
<Item>
Ensures all nodes are visited during <Ref Oper="ExecuteDFS"/>. <Ref
Oper="ExecuteDFS"/>
is first ran with a start node of <C>record.start</C>. For each
<C>v</C> in <C>DigraphVertices</C>(<C>record.graph</C>),
if <C>v</C> has not been visited in any DFS traversals yet (if
<C>config.use_preorder</C> then this is the case when
<C>record.preorder</C>[<C>v</C>] is <C>-1</C>), a
DFS traversal with a start node of <C>v</C> is called until
all nodes in <C>DigraphVertices</C>(<C>record.graph</C>)
have been visited (traversing all disconnected components). Requires
<C>config.forest_specific</C> to be <K>fail</K>.<P/>

Default: <K>false</K>
</Item>
<Mark>forest_specific</Mark>
<Item>
<C>config.forest_specific</C> is either <K>fail</K> (in which
case this field does not affect <Ref Oper="ExecuteDFS"/>),
or a list of vertices in <C>DigraphVertices</C>(<C>record.graph</C>)
which are guaranteed to be visited during <Ref Oper="ExecuteDFS"/>.
This is achieved in the same way as <C>config.forest</C>
but instead of ensuring all vertices from <C>DigraphVertices</C>(<C>record.graph</C>)
are visited, the same behaviour exists for all vertices
from <C>config.forest_specific</C> (if it is not <K>fail</K>).
Requires <C>config.forest</C> to be <K>false</K>.<P/>

Default: <K>fail</K>
</Item>
</List>

<Example><![CDATA[
gap> 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
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="NewDFSFlagsLightweight">
<ManSection>
<Oper Name="NewDFSFlagsLightweight" Arg=""/>
<Returns>A record.</Returns>
<Description>
This function returns a DFSFlags <C>record</C> to be
used as the <C>config</C> field of a DFSRecord (see <Ref Oper="NewDFSRecord" Label="for a digraph"/>). It
differs to the default <C>config</C> returned by see <Ref Oper="NewDFSFlags"/>
since all of <C>use_preorder</C>, <C>use_postorder</C>, <C>use_parents</C> and
<C>use_edge</C> are set to <K>false</K>.
<Example><![CDATA[
gap> 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
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>

<#GAPDoc Label="ExecuteDFS">
<ManSection>
<Oper Name="ExecuteDFS"
Arg="record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc"/>
<Description>
This performs a full depth first search from the <A>start</A> vertex (where <A>start</A> is a vertex within the graph).
The depth first search can be terminated by changing the <A>record</A>.stop attribute to <K>true</K> in the
<A>PreOrderFunc</A>, <A>PostOrderFunc</A>, <A>AncestorFunc</A> or <A>CrossFunc</A> functions.
<C>ExecuteDFS</C> takes 7 arguments:
<List>
<Mark>record</Mark>
<Item>The depth first search record (created using <Ref Oper="NewDFSRecord" Label="for a digraph"/>).</Item>
<Mark>data</Mark>
<Item>An object that you want to manipulate in the functions passed.</Item>
<Mark>start</Mark>
<Item>The vertex where the depth first search begins.</Item>
<Mark>PreOrderFunc</Mark>
<Item>This function is called when a vertex is first visited. This vertex
is stored in <A>record</A>.current. A vertex can be backtracked
more than once when <A>record</A>.config.revisit is <K>true</K>.</Item>
<Mark>PostOrderFunc</Mark>
<Item>This function is called when a vertex has no more unvisited children
causing us to backtrack. This vertex is stored in <A>record</A>.child and its parent is stored
in <A>record</A>.current. A vertex can be visited
more than once when <A>record</A>.config.revisit is <K>true</K>.<P/>

When <C><A>record</A>.PostOrderFunc</C> is not <K>fail</K>, then
<C><A>record</A>.config.use_parents</C>
must be true (recursive DFS requires <C><A>record</A>.config.use_parents</C>
in general).
</Item>
<Mark>AncestorFunc</Mark>
<Item>This function is called when <C>[<A>record</A>.current</C>,
<C><A>record</A>.child]</C> is an edge and <C><A>record</A>.child</C> is an ancestor of <C><A>record</A>.current</C>. An ancestor here means that
<C><A>record</A>.child</C> is on the same branch as
<C><A>record</A>.current</C> but was visited prior to
<C><A>record</A>.current</C> (<C><A>record</A>.current</C> has been backtracked
on). Equivalently, <C>[<A>record</A>.current,
<A>record</A>.child]</C> is a back edge. </Item>
<Mark>CrossFunc</Mark>
<Item>This function is called when <C>[<A>record</A>.current</C>,
<C><A>record</A>.child]</C> is an edge and <C><A>record</A>.child</C> has been visited before <C><A>record</A>.current</C>
and it is not an ancestor of <C><A>record</A>.current</C>. Equivalently, <C>[<A>record</A>.current,
<A>record</A>.child]</C> is a cross edge.<P/>

When <C><A>record</A>.CrossFunc</C> is not <K>fail</K>, <C><A>record</A>.config.use_preorder</C>
must be true (to determine visit order).
</Item>
</List>
<A>PreOrderFunc</A>, <A>PostOrderFunc</A>, <A>AncestorFunc</A> and <A>CrossFunc</A>
should be set to <K>fail</K> unless a function is specified.

Note that this function only performs a depth first search on the vertices reachable from <A>start</A>
unless <C><A>record.config</A>.forest</C> is <K>true</K> or <C><A>record.config</A>.forest_specific</C>
is a <A>list</A> of vertices to visit.
It is also important to note that all functions passed need to accept arguments <A>record</A> and <A>data</A>.
Finally, for the <A>start</A> vertex, its parent is itself and the <A>PreOrderFunc</A>
will be called on it. This is also the case for the start vertex of any additional
searches required when <C><A>record</A>.config.forest</C> is <K>true</K> or
<C><A>record</A>.config.forest_specific</C> is a list of vertices (not <K>fail</K>).

See <Ref Oper="NewDFSRecord" Label="for a digraph"/> for more details on <A>record</A>.
<Example><![CDATA[
gap> 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 ]
]]></Example>
</Description>
</ManSection>
<#/GAPDoc>
Loading
Loading