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();