From e3c3f6d738ddda66a2510a8efdb0984a98dad2f8 Mon Sep 17 00:00:00 2001 From: Wilf Wilson Date: Wed, 27 Oct 2021 13:58:13 +0100 Subject: [PATCH 1/4] Re-add PR #459: ExecuteDFS (generic depth first search) --- Makefile.in | 1 + doc/oper.xml | 169 ++++++++++++++++++++++++++++- doc/z-chap4.xml | 3 + gap/attr.gi | 243 +++++++++++++++++++++++------------------- gap/grahom.gi | 2 +- gap/oper.gd | 5 + gap/oper.gi | 213 ++++++++++++++++++++++++++---------- gap/prop.gi | 57 +++++++++- src/dfs.c | 124 +++++++++++++++++++++ src/dfs.h | 21 ++++ src/digraphs.c | 7 ++ tst/standard/oper.tst | 130 +++++++++++++++++++++- tst/standard/prop.tst | 8 ++ 13 files changed, 808 insertions(+), 175 deletions(-) create mode 100644 src/dfs.c create mode 100644 src/dfs.h 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 92782fed8..1ee322da7 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -1440,7 +1440,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 1, 2, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -2329,6 +2329,173 @@ gap> LexicographicProduct(NullDigraph(0), CompleteDigraph(10)); <#/GAPDoc> +<#GAPDoc Label="NewDFSRecord"> + + + A record. + + This record contains three lists (parent, preorder and postorder) with their length + equal to the number of verticies in the digraph. Each index of the lists maps to the + vertex within the digraph equating to the vertex number. These lists store + the following: + + parent + at each index, the parent of the vertex is stored + 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 4 attributes. + + current + the current vertex that is being visited + child + the child of the current vertex + graph + the digraph + stop + whether to stop the depth first search + + + Initially, the current and child attributes will have -1 values and the lists (parent, + preorder and postorder) will have -1 values at all of their indicies as no vertex has + been visited. The stop attribute will initially be false. + This record should be passed into the ExecuteDFS function. + See . + record := NewDFSRecord(CompleteDigraph(2)); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1 ], postorder := [ -1, -1 ], + preorder := [ -1, -1 ], stop := false ) +gap> record.preorder; +[ -1, -1 ] +gap> record.postorder; +[ -1, -1 ] +gap> record.stop; +false +gap> record.parent; +[ -1, -1 ] +gap> record.child; +-1 +gap> record.current; +-1 +gap> record.graph; + +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="DFSDefault"> + + + + This is a default function to be passed into the ExecuteDFS function. + This does nothing and can be used in place of the PreOrderFunc, PostOrderFunc, + AncestorFunc and/or CrossFunc of the ExecuteDFS function. + See . + PreOrderFunc := function(record, data) +> data.num_vertices := data.num_vertices + 1; +> end;; +gap> record := NewDFSRecord(CompleteDigraph(2));; +gap> data := rec(num_vertices := 0);; +gap> ExecuteDFS(record, data, 1, PreOrderFunc, +> DFSDefault, DFSDefault, DFSDefault); +gap> data; +rec( num_vertices := 2 ) +gap> record := NewDFSRecord(CompleteDigraph(2));; +gap> ExecuteDFS(record, [], 1, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +gap> record; +rec( child := 1, current := 1, + graph := , + parent := [ 1, 1 ], postorder := [ 2, 1 ], preorder := [ 1, 2 ], + stop := false ) +]]> + + +<#/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 NewDFSRecord) + data + an object that you want to manipulate in the functions passed. + start + the vertex where we begin the depth first search. + PreOrderFunc + this function is called when a vertex is first visited. This vertex + is stored in record.current + 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 + 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 + 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 + + Note that this function only performs a depth first search on the vertices reachable from start. + 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. + See . + record := NewDFSRecord(CycleDigraph(10));; +gap> ExecuteDFS(record, [], 1, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +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, DFSDefault, +> DFSDefault, AncestorFunc, DFSDefault); +gap> record.stop; +true +gap> data.cycle_vertex; +1 +gap> record.preorder; +[ 1, 2, 3, -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, DFSDefault, +> DFSDefault, DFSDefault, CrossFunc); +gap> record.stop; +true +gap> data; +[ 4 ] +]]> + + +<#/GAPDoc> + <#GAPDoc Label="IsDigraphPath"> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 999d01486..27fd75c43 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -20,6 +20,9 @@ <#Include Label="IsMatching"> <#Include Label="DigraphMaximalMatching"> <#Include Label="DigraphMaximumMatching"> + <#Include Label="NewDFSRecord"> + <#Include Label="DFSDefault"> + <#Include Label="ExecuteDFS">
Neighbours and degree diff --git a/gap/attr.gi b/gap/attr.gi index 2f2dca390..6a968532d 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -13,23 +13,10 @@ InstallMethod(DigraphNrVertices, "for a digraph by out-neighbours", InstallGlobalFunction(OutNeighbors, OutNeighbours); -# The next method is (yet another) DFS which simultaneously computes: -# 1. *articulation points* as described in -# https://www.eecs.wsu.edu/~holder/courses/CptS223/spr08/slides/graphapps.pdf -# 2. *bridges* as described in https://stackoverflow.com/q/28917290/ -# (this is a minor adaption of the algorithm described in point 1). -# 3. a *strong orientation* as alluded to somewhere on the internet that I can -# no longer find. It's essentially just "orient every edge in the DFS tree -# away from the root, and every other edge (back edges) from the node with -# higher `pre` value to the one with lower `pre` value (i.e. they point -# backwards from later nodes in the DFS to earlier ones). If the graph is -# bridgeless, then it is guaranteed that the orientation of the last -# sentence is strongly connected." - 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; N := DigraphNrVertices(D); @@ -41,118 +28,109 @@ 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); else copy := D; fi; - # outputs - articulation_points := []; - bridges := []; - orientation := List([1 .. N], x -> BlistList([1 .. N], [])); + PostOrderFunc := function(record, data) + local child, current; + child := record.child; + current := record.parent[child]; + if record.preorder[child] > record.preorder[current] 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, parent; + current := record.current; + if current <> 1 then + parent := record.parent[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.parent[current]; + # current -> child is a back 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 - AddSet(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); + ExecuteDFS(record, + data, + 1, + PreOrderFunc, + PostOrderFunc, + AncestorCrossFunc, + AncestorCrossFunc); + if data.counter = DigraphNrVertices(D) then connected := true; - if nr_children > 1 then - # The `AddSet` is not really needed, and could just be `Add`, since 1 - # should not already be in articulation_points. But let's use AddSet - # to keep the output sorted. - AddSet(articulation_points, 1); + if data.nr_children > 1 then + AddSet(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", @@ -1067,7 +1045,38 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); +function(D) + local i, record, num_vertices, data, AncestorFunc, PostOrderFunc; + if DigraphNrVertices(D) = 0 then + return []; + fi; + record := NewDFSRecord(D); + num_vertices := DigraphNrVertices(D); + data := rec(count := 0, + out := ListWithIdenticalEntries(num_vertices, 0)); + AncestorFunc := function(record, data) + if record.current <> record.child then + record.stop := true; + fi; + data; # to make the linter happy + end; + PostOrderFunc := function(record, data) + data.count := data.count + 1; + data.out[data.count] := record.child; + end; + for i in DigraphVertices(D) do + if record.preorder[i] <> -1 then + continue; + fi; + ExecuteDFS(record, data, i, DFSDefault, + PostOrderFunc, AncestorFunc, + DFSDefault); + if record.stop then + return fail; + fi; + od; + return data.out; +end); InstallMethod(DigraphStronglyConnectedComponents, "for a digraph by out-neighbours", @@ -1358,7 +1367,7 @@ function(D, v) # # localParameters is a list of 3-tuples [a_{i - 1}, b_{i - 1}, c_{i - 1}] for # each i between 1 and localDiameter where c_i (respectively a_i and b_i) is - # the number of vertices at distance i − 1 (respectively i and i + 1) from v + # the number of vertices at distance i - 1 (respectively i and i + 1) from v # that are adjacent to a vertex w at distance i from v. # gives a shortest path spanning tree rooted at and is used by @@ -3019,13 +3028,31 @@ InstallMethod(UndirectedSpanningForest, "for an immutable digraph", InstallMethod(UndirectedSpanningForestAttr, "for an immutable digraph", [IsImmutableDigraph and IsDigraphByOutNeighboursRep], function(D) - local C; + local C, record, data, PreOrderFunc, i; if DigraphHasNoVertices(D) then return fail; fi; C := MaximalSymmetricSubdigraph(D); - C := DIGRAPH_SYMMETRIC_SPANNING_FOREST(C!.OutNeighbours); - C := ConvertToImmutableDigraphNC(C); + record := NewDFSRecord(C); + data := List(DigraphVertices(C), x -> []); + + PreOrderFunc := function(record, data) + if record.parent[record.current] <> record.current then + Add(data[record.parent[record.current]], record.current); + Add(data[record.current], record.parent[record.current]); + fi; + end; + for i in DigraphVertices(C) do + ExecuteDFS(record, data, i, PreOrderFunc, DFSDefault, + DFSDefault, DFSDefault); + od; + if IsMutableDigraph(D) then + D!.OutNeighbours := data; + ClearDigraphEdgeLabels(D); + return D; + fi; + C := ConvertToImmutableDigraphNC(data); + SetUndirectedSpanningForestAttr(D, C); SetIsUndirectedForest(C, true); SetIsMultiDigraph(C, false); SetDigraphHasLoops(C, false); diff --git a/gap/grahom.gi b/gap/grahom.gi index 5628a3650..f33b6fe44 100644 --- a/gap/grahom.gi +++ b/gap/grahom.gi @@ -237,7 +237,7 @@ end); # Finds a set S of homomorphism from gr1 to gr2 such that every homomorphism g # between the two graphs can expressed as a composition g = f * x of an element -# f in S and an automorphism x of gr2 +# f in S and an automorphism x of gr2 InstallMethod(HomomorphismsDigraphsRepresentatives, "for a digraph and a digraph", diff --git a/gap/oper.gd b/gap/oper.gd index 619de1db3..f8a5b635e 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -157,3 +157,8 @@ DeclareOperation("PartialOrderDigraphJoinOfVertices", [IsDigraph, IsPosInt, IsPosInt]); DeclareOperation("PartialOrderDigraphMeetOfVertices", [IsDigraph, IsPosInt, IsPosInt]); + +# 11. DFS +DeclareOperation("NewDFSRecord", [IsDigraph]); +DeclareOperation("DFSDefault", [IsRecord, IsObject]); +DeclareGlobalFunction("ExecuteDFS"); diff --git a/gap/oper.gi b/gap/oper.gi index 13a42a6b0..b8b52bfe2 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1680,7 +1680,7 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local verts; + local verts, record, out, path_info, PostOrderFunc, AncestorFunc, AddToPath; verts := DigraphVertices(D); if not (u in verts and v in verts) then @@ -1697,7 +1697,39 @@ function(D, u, v) DigraphConnectedComponents(D).id[v] then return fail; fi; - return DIGRAPH_PATH(OutNeighbours(D), u, v); + record := NewDFSRecord(D); + path_info := Stack(); + AddToPath := function(current, child) + local edge; + edge := Position(OutNeighborsOfVertex(D, current), child); + Push(path_info, edge); + Push(path_info, child); + end; + AncestorFunc := function(record, data) + if u = v and record.child = u and Size(data) = 0 then + AddToPath(record.current, record.child); + record.preorder[v] := DigraphNrVertices(D) + 1; + fi; + end; + PostOrderFunc := function(record, data) + if record.child <> u and + record.preorder[record.child] <= record.preorder[v] then + AddToPath(record.current, record.child); + fi; + data; # to make the linter happy + end; + ExecuteDFS(record, path_info, u, + DFSDefault, PostOrderFunc, + AncestorFunc, DFSDefault); + if Size(path_info) <= 1 then + return fail; + fi; + out := [[u], []]; + while Size(path_info) <> 0 do + Add(out[1], Pop(path_info)); + Add(out[2], Pop(path_info)); + od; + return out; end); InstallMethod(IsDigraphPath, "for a digraph and list", @@ -1992,17 +2024,41 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local dist; + local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc; 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 + record := NewDFSRecord(D); + data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), + prev := 0); + AncestorFunc := function(record, data) + record.stop := true; + data; # to make the linter happy + end; + PostOrderFunc := function(record, data) + data.depth[record.current] := data.prev; + data.prev := data.prev + 1; + end; + PreOrderFunc := function(record, data) + local i, neighbours; + data.prev := 0; + neighbours := OutNeighborsOfVertex(record.graph, record.current); + for i in [1 .. Size(neighbours)] do + # need to bypass the CrossFunc + if record.postorder[neighbours[i]] <> -1 then + record.preorder[neighbours[i]] := -1; + fi; + od; + end; + ExecuteDFS(record, data, v, + PreOrderFunc, PostOrderFunc, + AncestorFunc, DFSDefault); + if record.stop then return infinity; fi; - return dist; + return data.depth[v]; end); InstallMethod(DigraphRandomWalk, @@ -2300,8 +2356,7 @@ 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 N, record, data, PreOrderFunc, AncestorFunc, root; N := DigraphNrVertices(D); @@ -2313,29 +2368,30 @@ function(D, roots) fi; od; - visited := BlistList([1 .. N], []); + record := NewDFSRecord(D); + data := rec(result := [], root_is_child := false); - graph_out_neighbors := OutNeighbors(D); - queue := EmptyPlist(N); - Append(queue, roots); - - queue_tail := Length(roots); + PreOrderFunc := function(record, data) + if record.current <> root then + Add(data.result, record.current); + fi; + end; - 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; + AncestorFunc := function(record, data) + if record.child = root and not data.root_is_child then + data.root_is_child := true; + Add(data.result, root); + fi; + end; - return ListBlist([1 .. N], visited); + ExecuteDFS(record, + data, + root, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); + return data.result; end); InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices", @@ -2364,9 +2420,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, parent, + node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, + pred, N, w, y, x, i, v; + M := DigraphNrVertices(D); if 0 = root or root > M then @@ -2374,36 +2431,25 @@ 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); + record := NewDFSRecord(D); + ExecuteDFS(record, + preorder_num_to_node, + root, + PreOrderFunc, + DFSDefault, + DFSDefault, + DFSDefault); + + parent := record.parent; + parent[root] := fail; + node_to_preorder_num := record.preorder; - 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 := []; @@ -2449,7 +2495,7 @@ 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 semi[w] := semi[x]; @@ -2711,3 +2757,52 @@ function(D, n) od; return kings; end); + +############################################################################# +# 11. DFS +############################################################################# + +InstallMethod(NewDFSRecord, +"for a digraph", [IsDigraph], +function(graph) + local record; + record := rec(); + record.graph := graph; + record.child := -1; + record.current := -1; + record.stop := false; + record.parent := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + return record; +end); + +InstallMethod(DFSDefault, +"for a record and an object", [IsRecord, IsObject], +function(record, data) + record; # to make the linter happy + data; # to make the linter happy +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) + if not IsEqualSet(RecNames(record), ["stop", "graph", "child", "parent", + "preorder", "postorder", "current"]) then + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); + fi; + ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, + AncestorFunc, CrossFunc); +end); diff --git a/gap/prop.gi b/gap/prop.gi index 069a790f3..15855d451 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -244,7 +244,7 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local n; + local n, i, record, FoundCycle, components; n := DigraphNrVertices(D); if n = 0 then return true; @@ -259,7 +259,27 @@ function(D) fi; return false; fi; - return IS_ACYCLIC_DIGRAPH(OutNeighbours(D)); + record := NewDFSRecord(D); + FoundCycle := function(record, data) + record.stop := true; + data; # to make the linter happy + end; + components := DigraphConnectedComponents(D); + if Size(components.comps) = 1 then + ExecuteDFS(record, [], 1, DFSDefault, + DFSDefault, FoundCycle, DFSDefault); + return not record.stop; + fi; + # handles disconnected digraphs + for i in [1 .. Size(components.comps)] do + record := NewDFSRecord(D); + ExecuteDFS(record, [], components.comps[i][1], + DFSDefault, DFSDefault, FoundCycle, DFSDefault); + if record.stop then + return false; + fi; + od; + return true; end); # Complexity O(number of edges) @@ -383,7 +403,38 @@ D -> DigraphPeriod(D) = 1); InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> IS_ANTISYMMETRIC_DIGRAPH(OutNeighbours(D))); +function(D) + local record, AncestorFunc, i, components; + if DigraphNrVertices(D) <= 1 then + return true; + fi; + record := NewDFSRecord(D); + + AncestorFunc := function(record, data) + local pos, neighbours; + if record.child = record.current then + return; + fi; + # checks if the child has a symmetric edge with current node + neighbours := OutNeighboursOfVertex(record.graph, record.child); + pos := Position(neighbours, record.current); + if pos <> fail then + record.stop := true; + fi; + data; # to make the linter happy + end; + + components := DigraphConnectedComponents(D); + for i in [1 .. Size(components.comps)] do + record := NewDFSRecord(D); + ExecuteDFS(record, [], components.comps[i][1], DFSDefault, DFSDefault, + AncestorFunc, DFSDefault); + if record.stop then + return false; + fi; + od; + 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..e7430a533 --- /dev/null +++ b/src/dfs.c @@ -0,0 +1,124 @@ +/******************************************************************************* +** +*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 false, true, bool +#include // for uint64_t +#include // for NULL, free + +#include "digraphs-config.h" +#include "digraphs-debug.h" +#include "digraphs.h" + +// Extreme examples are on the pull request #459 + +Obj ExecuteDFS(Obj self, Obj args) { + DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); + Obj record = ELM_PLIST(args, 1); + 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(NARG_FUNC(PreorderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(AncestorFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(AncestorFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(PostOrderFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(PostOrderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(PreorderFunc)); + DIGRAPHS_ASSERT(IS_PREC(record)); + DIGRAPHS_ASSERT(IS_INTOBJ(start)); + DIGRAPHS_ASSERT(IS_FUNC(CrossFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(CrossFunc) == 2); + + Obj D = ElmPRec(record, RNamName("graph")); + Int N = DigraphNrVertices(D); + + if (INT_INTOBJ(start) > N) { + ErrorQuit( + "the third argument must be a vertex in your graph,", 0L, 0L); + } + Int top = 0; + Obj stack = NEW_PLIST(T_PLIST_CYC, N); + AssPlist(stack, ++top, start); + + UInt preorder_num = 0; + UInt postorder_num = 0; + + Int current = 0; + + Obj parent = ElmPRec(record, RNamName("parent")); + Obj postorder = ElmPRec(record, RNamName("postorder")); + Obj preorder = ElmPRec(record, RNamName("preorder")); + + DIGRAPHS_ASSERT(LEN_PLIST(parent) == N); + DIGRAPHS_ASSERT(LEN_PLIST(postorder) == N); + DIGRAPHS_ASSERT(LEN_PLIST(preorder) == N); + + SET_ELM_PLIST(parent, INT_INTOBJ(start), start); + + Obj neighbors = FuncOutNeighbours(self, D); + DIGRAPHS_ASSERT(IS_PLIST(neighbors)); + + Int RNamChild = RNamName("child"); + Int RNamCurrent = RNamName("current"); + Int RNamStop = RNamName("stop"); + + while (top > 0) { + current = INT_INTOBJ(ELM_PLIST(stack, top--)); + DIGRAPHS_ASSERT(current != 0); + if (current < 0) { + Int child = -1 * current; + AssPRec(record, RNamChild, INTOBJ_INT(child)); + AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); + CALL_2ARGS(PostOrderFunc, record, data); + SET_ELM_PLIST(postorder, child, INTOBJ_INT(++postorder_num)); + CHANGED_BAG(record); + continue; + } else if (INT_INTOBJ(ELM_PLIST(preorder, current)) > 0) { + continue; + } else { + AssPRec(record, RNamCurrent, INTOBJ_INT(current)); + CALL_2ARGS(PreorderFunc, record, data); + SET_ELM_PLIST(preorder, current, INTOBJ_INT(++preorder_num)); + CHANGED_BAG(record); + AssPlist(stack, ++top, INTOBJ_INT(-1 * current)); + } + + if (ElmPRec(record, RNamStop) == True) { + break; + } + + Obj succ = ELM_PLIST(neighbors, current); + for (UInt j = 0; j < LEN_LIST(succ); ++j) { + UInt v = INT_INTOBJ(ELM_LIST(succ, LEN_LIST(succ) - j)); + AssPRec(record, RNamChild, INTOBJ_INT(v)); + if (INT_INTOBJ(ELM_PLIST(preorder, v)) == -1) { + SET_ELM_PLIST(parent, v, INTOBJ_INT(current)); + CHANGED_BAG(record); + AssPlist(stack, ++top, INTOBJ_INT(v)); + } else if (INT_INTOBJ(ELM_PLIST(postorder, v)) == -1) { + CALL_2ARGS(AncestorFunc, record, data); + } else if (INT_INTOBJ(ELM_PLIST(preorder, v)) + < INT_INTOBJ(ELM_PLIST(preorder, current))) { + CALL_2ARGS(CrossFunc, record, data); + } + if (ElmPRec(record, RNamStop) == True) { + break; + } + } + } + return record; +} diff --git a/src/dfs.h b/src/dfs.h new file mode 100644 index 000000000..40ec66e35 --- /dev/null +++ b/src/dfs.h @@ -0,0 +1,21 @@ +/******************************************************************************* +** +*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_ + +// GAP headers +#include "compiled.h" // for Obj, Int + +Obj ExecuteDFS(Obj self, Obj args); + +#endif // DIGRAPHS_SRC_DFS_H_ diff --git a/src/digraphs.c b/src/digraphs.c index ba0011b31..d15bee97e 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -21,6 +21,7 @@ #include "bliss-includes.h" // for bliss stuff #include "cliques.h" // for FuncDIGRAPHS_FREE_CLIQUES_DATA +#include "dfs.h" // for FuncExecuteDFS #include "digraphs-config.h" // for DIGRAPHS_WITH_INCLUDED_BLISS #include "digraphs-debug.h" // for DIGRAPHS_ASSERT #include "homos.h" // for FuncHomomorphismDigraphsFinder @@ -2224,6 +2225,12 @@ static StructGVarFunc GVarFuncs[] = { GVAR_FUNC(DIGRAPHS_FREE_HOMOS_DATA, 0, ""), GVAR_FUNC(DIGRAPHS_FREE_CLIQUES_DATA, 0, ""), + {"ExecuteDFS_C", + 7, + "record, data, start, PreorderFunc, x, y, z", + ExecuteDFS, + "src/dfs.c:ExecuteDFS"}, + {0, 0, 0, 0, 0} /* Finish with an empty entry */ }; diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 1eb29b2a5..7769e69e0 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1360,6 +1360,16 @@ ent , gap> DigraphPath(gr, 11, 11); Error, the 2nd and 3rd arguments and must be vertices of the 1st argum\ ent , +gap> D := Digraph([[2], [3], [2, 3]]); + +gap> DigraphPath(D, 1, 3); +[ [ 1, 2, 3 ], [ 1, 1 ] ] +gap> DigraphPath(D, 2, 1); +fail +gap> DigraphPath(D, 3, 3); +[ [ 3, 3 ], [ 2 ] ] +gap> DigraphPath(D, 1, 1); +fail # IteratorOfPaths gap> gr := CompleteDigraph(5);; @@ -1966,7 +1976,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 1, 2, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -2014,7 +2024,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -2026,7 +2036,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -3314,6 +3324,120 @@ gap> Unbind(u2); gap> Unbind(x); gap> Unbind(TestPartialOrderDigraph); +# DFS + +# NewDFSRecord +gap> NewDFSRecord(ChainDigraph(10)); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + postorder := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + preorder := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], stop := false ) +gap> NewDFSRecord(CompleteDigraph(2)); +rec( child := -1, current := -1, + graph := , parent := [ -1, -1 ], + postorder := [ -1, -1 ], preorder := [ -1, -1 ], stop := false ) +gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1, -1, -1, -1 ], postorder := [ -1, -1, -1, -1, -1 ], + preorder := [ -1, -1, -1, -1, -1 ], stop := false ) + +# DFSDefault +gap> DFSDefault(rec(), []); +gap> DFSDefault(rec(), rec()); + +# ExecuteDFS +gap> record := NewDFSRecord(CompleteDigraph(10));; +gap> ExecuteDFS(record, [], 2, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +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, DFSDefault, +> DFSDefault, AncestorFunc, DFSDefault); +gap> record.stop; +true +gap> data.cycle_vertex; +1 +gap> record.preorder; +[ 1, 2, 3, -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, DFSDefault, +> DFSDefault, DFSDefault, CrossFunc); +gap> record.stop; +true +gap> data; +[ 4 ] +gap> AncestorFunc := function(record, data) +> Add(data.cycle_vertex, record.child); +> end;; +gap> CrossFunc := function(record, data) +> Add(data.cross_vertex, record.child); +> end;; +gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; +gap> data := rec(cycle_vertex := [], cross_vertex := []);; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, AncestorFunc, CrossFunc); +gap> data; +rec( cross_vertex := [ 4 ], cycle_vertex := [ 1, 1 ] ) +gap> ExecuteDFS(rec(), data, 1, DFSDefault, +> DFSDefault, AncestorFunc, CrossFunc); +Error, the 1st argument must be created with NewDFSRecord, +gap> D := ChainDigraph(1);; +gap> ExecuteDFS(NewDFSRecord(D), [], 3, DFSDefault, DFSDefault, DFSDefault, +> DFSDefault); +Error, the third argument must be a vertex in your graph, + +# 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 + # gap> DIGRAPHS_StopTest(); gap> STOP_TEST("Digraphs package: standard/oper.tst", 0); diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst index efb34d9e5..37a874cae 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -187,6 +187,14 @@ gap> HasIsAcyclicDigraph(gr); false gap> IsAcyclicDigraph(gr); false +gap> gr := DigraphDisjointUnion(ChainDigraph(10), ChainDigraph(2)); + +gap> IsAcyclicDigraph(gr); +true +gap> gr := DigraphDisjointUnion(CompleteDigraph(5), ChainDigraph(2)); + +gap> IsAcyclicDigraph(gr); +false # IsFunctionalDigraph gap> IsFunctionalDigraph(multiple); From d8d5adc752dbea73056915f34de22714db1693db Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Sat, 29 May 2021 20:29:25 +0100 Subject: [PATCH 2/4] dfs: attempt to fix --- gap/attr.gi | 2 +- gap/oper.gi | 108 +++++++++++++++++++++++++++++++--------------------- src/dfs.c | 47 ++++++++++++++--------- 3 files changed, 95 insertions(+), 62 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 6a968532d..83e863122 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -1065,7 +1065,7 @@ function(D) data.out[data.count] := record.child; end; for i in DigraphVertices(D) do - if record.preorder[i] <> -1 then + if not IsBound(record.preorder[i]) then continue; fi; ExecuteDFS(record, data, i, DFSDefault, diff --git a/gap/oper.gi b/gap/oper.gi index b8b52bfe2..7844da4c5 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1680,10 +1680,10 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local verts, record, out, path_info, PostOrderFunc, AncestorFunc, AddToPath; + local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current; - 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 @@ -1696,40 +1696,60 @@ 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; record := NewDFSRecord(D); - path_info := Stack(); - AddToPath := function(current, child) - local edge; - edge := Position(OutNeighborsOfVertex(D, current), child); - Push(path_info, edge); - Push(path_info, child); - end; - AncestorFunc := function(record, data) - if u = v and record.child = u and Size(data) = 0 then - AddToPath(record.current, record.child); - record.preorder[v] := DigraphNrVertices(D) + 1; - fi; - end; - PostOrderFunc := function(record, data) - if record.child <> u and - record.preorder[record.child] <= record.preorder[v] then - AddToPath(record.current, record.child); - fi; - data; # to make the linter happy - end; - ExecuteDFS(record, path_info, u, - DFSDefault, PostOrderFunc, - AncestorFunc, DFSDefault); - if Size(path_info) <= 1 then + 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, data) + if record.current = v then + record.stop := true; + fi; + data; # to make the linter happy + end; + AncestorFunc := DFSDefault; + 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 := DFSDefault; + AncestorFunc := function(record, data) + if record.child = v then + record.stop := true; + fi; + data; # to make the linter happy + end; + fi; + ExecuteDFS(record, + fail, + u, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); + if not record.stop then return fail; fi; - out := [[u], []]; - while Size(path_info) <> 0 do - Add(out[1], Pop(path_info)); - Add(out[2], Pop(path_info)); + 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.parent[current]; + Add(nodes, current); od; - return out; + return [Reversed(nodes), Reversed(edges)]; end); InstallMethod(IsDigraphPath, "for a digraph and list", @@ -2385,12 +2405,12 @@ function(D, roots) end; ExecuteDFS(record, - data, - root, - PreOrderFunc, - DFSDefault, - AncestorFunc, - DFSDefault); + data, + root, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); return data.result; end); @@ -2771,9 +2791,10 @@ function(graph) record.child := -1; record.current := -1; record.stop := false; - record.parent := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.parent := [fail]; + record.preorder := [fail]; + record.postorder := [fail]; + record.edge := [fail]; return record; end); @@ -2798,8 +2819,9 @@ end); InstallGlobalFunction(ExecuteDFS, function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) - if not IsEqualSet(RecNames(record), ["stop", "graph", "child", "parent", - "preorder", "postorder", "current"]) then + if not IsEqualSet(RecNames(record), + ["stop", "graph", "child", "parent", "preorder", + "postorder", "current", "edge"]) then ErrorNoReturn("the 1st argument must be created with ", "NewDFSRecord,"); fi; diff --git a/src/dfs.c b/src/dfs.c index e7430a533..3cbc68260 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -32,16 +32,16 @@ Obj ExecuteDFS(Obj self, Obj args) { Obj AncestorFunc = ELM_PLIST(args, 6); Obj CrossFunc = ELM_PLIST(args, 7); - DIGRAPHS_ASSERT(NARG_FUNC(PreorderFunc) == 2); - DIGRAPHS_ASSERT(IS_FUNC(AncestorFunc)); - DIGRAPHS_ASSERT(NARG_FUNC(AncestorFunc) == 2); - DIGRAPHS_ASSERT(IS_FUNC(PostOrderFunc)); - DIGRAPHS_ASSERT(NARG_FUNC(PostOrderFunc) == 2); - DIGRAPHS_ASSERT(IS_FUNC(PreorderFunc)); DIGRAPHS_ASSERT(IS_PREC(record)); DIGRAPHS_ASSERT(IS_INTOBJ(start)); + // DIGRAPHS_ASSERT(NARG_FUNC(PreorderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(PreorderFunc)); + DIGRAPHS_ASSERT(IS_FUNC(PostOrderFunc)); + // DIGRAPHS_ASSERT(NARG_FUNC(PostOrderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(AncestorFunc)); + // DIGRAPHS_ASSERT(NARG_FUNC(AncestorFunc) == 2); DIGRAPHS_ASSERT(IS_FUNC(CrossFunc)); - DIGRAPHS_ASSERT(NARG_FUNC(CrossFunc) == 2); + // DIGRAPHS_ASSERT(NARG_FUNC(CrossFunc) == 2); Obj D = ElmPRec(record, RNamName("graph")); Int N = DigraphNrVertices(D); @@ -51,7 +51,7 @@ Obj ExecuteDFS(Obj self, Obj args) { "the third argument must be a vertex in your graph,", 0L, 0L); } Int top = 0; - Obj stack = NEW_PLIST(T_PLIST_CYC, N); + Obj stack = NEW_PLIST(T_PLIST_CYC, 0); AssPlist(stack, ++top, start); UInt preorder_num = 0; @@ -62,12 +62,14 @@ Obj ExecuteDFS(Obj self, Obj args) { Obj parent = ElmPRec(record, RNamName("parent")); Obj postorder = ElmPRec(record, RNamName("postorder")); Obj preorder = ElmPRec(record, RNamName("preorder")); + Obj edge = ElmPRec(record, RNamName("edge")); - DIGRAPHS_ASSERT(LEN_PLIST(parent) == N); - DIGRAPHS_ASSERT(LEN_PLIST(postorder) == N); - DIGRAPHS_ASSERT(LEN_PLIST(preorder) == N); + DIGRAPHS_ASSERT(LEN_PLIST(parent) == 1); + DIGRAPHS_ASSERT(LEN_PLIST(postorder) == 1); + DIGRAPHS_ASSERT(LEN_PLIST(preorder) == 1); + DIGRAPHS_ASSERT(LEN_PLIST(edge) == 1); - SET_ELM_PLIST(parent, INT_INTOBJ(start), start); + AssPlist(parent, INT_INTOBJ(start), start); Obj neighbors = FuncOutNeighbours(self, D); DIGRAPHS_ASSERT(IS_PLIST(neighbors)); @@ -77,6 +79,9 @@ Obj ExecuteDFS(Obj self, Obj args) { Int RNamStop = RNamName("stop"); while (top > 0) { + if (ElmPRec(record, RNamStop) == True) { + break; + } current = INT_INTOBJ(ELM_PLIST(stack, top--)); DIGRAPHS_ASSERT(current != 0); if (current < 0) { @@ -84,15 +89,17 @@ Obj ExecuteDFS(Obj self, Obj args) { AssPRec(record, RNamChild, INTOBJ_INT(child)); AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); CALL_2ARGS(PostOrderFunc, record, data); - SET_ELM_PLIST(postorder, child, INTOBJ_INT(++postorder_num)); + AssPlist(postorder, child, INTOBJ_INT(++postorder_num)); CHANGED_BAG(record); continue; - } else if (INT_INTOBJ(ELM_PLIST(preorder, current)) > 0) { + } else if (current <= LEN_PLIST(preorder) + && ELM_PLIST(preorder, current) != 0 + && ELM_PLIST(preorder, current) != Fail) { continue; } else { AssPRec(record, RNamCurrent, INTOBJ_INT(current)); CALL_2ARGS(PreorderFunc, record, data); - SET_ELM_PLIST(preorder, current, INTOBJ_INT(++preorder_num)); + AssPlist(preorder, current, INTOBJ_INT(++preorder_num)); CHANGED_BAG(record); AssPlist(stack, ++top, INTOBJ_INT(-1 * current)); } @@ -105,11 +112,15 @@ Obj ExecuteDFS(Obj self, Obj args) { for (UInt j = 0; j < LEN_LIST(succ); ++j) { UInt v = INT_INTOBJ(ELM_LIST(succ, LEN_LIST(succ) - j)); AssPRec(record, RNamChild, INTOBJ_INT(v)); - if (INT_INTOBJ(ELM_PLIST(preorder, v)) == -1) { - SET_ELM_PLIST(parent, v, INTOBJ_INT(current)); + if (v > LEN_PLIST(preorder) || ELM_PLIST(preorder, v) == 0 + || ELM_PLIST(preorder, v) == Fail) { + AssPlist(parent, v, INTOBJ_INT(current)); + AssPlist(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); CHANGED_BAG(record); AssPlist(stack, ++top, INTOBJ_INT(v)); - } else if (INT_INTOBJ(ELM_PLIST(postorder, v)) == -1) { + } else if (current > LEN_PLIST(preorder) + || ELM_PLIST(preorder, current) != 0 + || ELM_PLIST(preorder, current) != Fail) { CALL_2ARGS(AncestorFunc, record, data); } else if (INT_INTOBJ(ELM_PLIST(preorder, v)) < INT_INTOBJ(ELM_PLIST(preorder, current))) { From a05b9df2f747868136b9a3246f9c424a8f1aa3c5 Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Sat, 29 May 2021 21:18:23 +0100 Subject: [PATCH 3/4] dfs: attempt to fix x 2 --- Makefile.in | 2 +- gap/attr.gi | 34 ++++++++++++++++++++-------------- gap/oper.gi | 8 ++++---- src/dfs.c | 29 +++++++++++++++-------------- src/dfs.h | 2 +- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Makefile.in b/Makefile.in index 3b36c7713..ef91e67be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -17,6 +17,7 @@ PLANARITY_SUITE_DIR = @PLANARITY_SUITE_DIR@ # sources KEXT_SOURCES = src/digraphs.c +KEXT_SOURCES += src/dfs.c KEXT_SOURCES += src/bitarray.c KEXT_SOURCES += src/conditions.c KEXT_SOURCES += src/homos.c @@ -26,7 +27,6 @@ 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/gap/attr.gi b/gap/attr.gi index 83e863122..8c00e4b66 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -1046,36 +1046,42 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local i, record, num_vertices, data, AncestorFunc, PostOrderFunc; - if DigraphNrVertices(D) = 0 then + local N, record, count, out, PostOrderFunc, AncestorFunc, i; + + N := DigraphNrVertices(D); + if N = 0 then return []; fi; record := NewDFSRecord(D); - num_vertices := DigraphNrVertices(D); - data := rec(count := 0, - out := ListWithIdenticalEntries(num_vertices, 0)); + count := 0; + out := []; + PostOrderFunc := function(record, data) + count := count + 1; + out[count] := record.child; + data; # to make the linter happy + end; AncestorFunc := function(record, data) if record.current <> record.child then record.stop := true; fi; data; # to make the linter happy end; - PostOrderFunc := function(record, data) - data.count := data.count + 1; - data.out[data.count] := record.child; - end; for i in DigraphVertices(D) do - if not IsBound(record.preorder[i]) then + if IsBound(record.preorder[i]) then continue; fi; - ExecuteDFS(record, data, i, DFSDefault, - PostOrderFunc, AncestorFunc, - DFSDefault); + ExecuteDFS(record, + fail, + i, + DFSDefault, + PostOrderFunc, + AncestorFunc, + DFSDefault); if record.stop then return fail; fi; od; - return data.out; + return out; end); InstallMethod(DigraphStronglyConnectedComponents, diff --git a/gap/oper.gi b/gap/oper.gi index 7844da4c5..bbf7e3612 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2791,10 +2791,10 @@ function(graph) record.child := -1; record.current := -1; record.stop := false; - record.parent := [fail]; - record.preorder := [fail]; - record.postorder := [fail]; - record.edge := [fail]; + record.parent := []; + record.preorder := []; + record.postorder := []; + record.edge := []; return record; end); diff --git a/src/dfs.c b/src/dfs.c index 3cbc68260..ae67d2168 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -16,6 +16,9 @@ #include // for uint64_t #include // for NULL, free +// GAP headers +#include "gap-includes.h" // for Obj, ELM_LIST, Int, etc + #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" @@ -64,12 +67,14 @@ Obj ExecuteDFS(Obj self, Obj args) { Obj preorder = ElmPRec(record, RNamName("preorder")); Obj edge = ElmPRec(record, RNamName("edge")); + // FIXME edge needs to be off by 1, so that the first entry is bound + DIGRAPHS_ASSERT(LEN_PLIST(parent) == 1); DIGRAPHS_ASSERT(LEN_PLIST(postorder) == 1); DIGRAPHS_ASSERT(LEN_PLIST(preorder) == 1); DIGRAPHS_ASSERT(LEN_PLIST(edge) == 1); - AssPlist(parent, INT_INTOBJ(start), start); + ASS_LIST(parent, INT_INTOBJ(start), start); Obj neighbors = FuncOutNeighbours(self, D); DIGRAPHS_ASSERT(IS_PLIST(neighbors)); @@ -89,19 +94,18 @@ Obj ExecuteDFS(Obj self, Obj args) { AssPRec(record, RNamChild, INTOBJ_INT(child)); AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); CALL_2ARGS(PostOrderFunc, record, data); - AssPlist(postorder, child, INTOBJ_INT(++postorder_num)); + ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); CHANGED_BAG(record); continue; } else if (current <= LEN_PLIST(preorder) - && ELM_PLIST(preorder, current) != 0 - && ELM_PLIST(preorder, current) != Fail) { + && ELM_PLIST(preorder, current) != 0) { continue; } else { AssPRec(record, RNamCurrent, INTOBJ_INT(current)); CALL_2ARGS(PreorderFunc, record, data); - AssPlist(preorder, current, INTOBJ_INT(++preorder_num)); + ASS_LIST(preorder, current, INTOBJ_INT(++preorder_num)); CHANGED_BAG(record); - AssPlist(stack, ++top, INTOBJ_INT(-1 * current)); + ASS_LIST(stack, ++top, INTOBJ_INT(-1 * current)); } if (ElmPRec(record, RNamStop) == True) { @@ -112,15 +116,12 @@ Obj ExecuteDFS(Obj self, Obj args) { for (UInt j = 0; j < LEN_LIST(succ); ++j) { UInt v = INT_INTOBJ(ELM_LIST(succ, LEN_LIST(succ) - j)); AssPRec(record, RNamChild, INTOBJ_INT(v)); - if (v > LEN_PLIST(preorder) || ELM_PLIST(preorder, v) == 0 - || ELM_PLIST(preorder, v) == Fail) { - AssPlist(parent, v, INTOBJ_INT(current)); - AssPlist(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); + if (v > LEN_PLIST(preorder) || ELM_PLIST(preorder, v) == 0) { + ASS_LIST(parent, v, INTOBJ_INT(current)); + ASS_LIST(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); CHANGED_BAG(record); - AssPlist(stack, ++top, INTOBJ_INT(v)); - } else if (current > LEN_PLIST(preorder) - || ELM_PLIST(preorder, current) != 0 - || ELM_PLIST(preorder, current) != Fail) { + ASS_LIST(stack, ++top, INTOBJ_INT(v)); + } else if (v > LEN_PLIST(postorder) || ELM_PLIST(postorder, v) == 0) { CALL_2ARGS(AncestorFunc, record, data); } else if (INT_INTOBJ(ELM_PLIST(preorder, v)) < INT_INTOBJ(ELM_PLIST(preorder, current))) { diff --git a/src/dfs.h b/src/dfs.h index 40ec66e35..d60921f65 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -14,7 +14,7 @@ #define DIGRAPHS_SRC_DFS_H_ // GAP headers -#include "compiled.h" // for Obj, Int +#include "gap-includes.h" // for Obj, Int Obj ExecuteDFS(Obj self, Obj args); From 7f9f7b1fd2b0349f50d5e964fd8b0b7cb58c2667 Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Mon, 31 May 2021 13:50:52 +0100 Subject: [PATCH 4/4] dfs: attempt to fix x 3 --- src/dfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dfs.c b/src/dfs.c index ae67d2168..b74892f2b 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -68,6 +68,7 @@ Obj ExecuteDFS(Obj self, Obj args) { Obj edge = ElmPRec(record, RNamName("edge")); // FIXME edge needs to be off by 1, so that the first entry is bound + // FIXME use hash maps for parent, postorder, preorder, and edge DIGRAPHS_ASSERT(LEN_PLIST(parent) == 1); DIGRAPHS_ASSERT(LEN_PLIST(postorder) == 1);