diff --git a/Makefile.am b/Makefile.am index 3ef5f57a2..2927390d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,7 +31,6 @@ pkginclude_HEADERS += src/cliques.h pkginclude_HEADERS += src/perms.h pkginclude_HEADERS += src/planar.h pkginclude_HEADERS += src/schreier-sims.h -pkginclude_HEADERS += src/dfs.h if WITH_INCLUDED_BLISS pkginclude_HEADERS += extern/bliss-0.73/bignum.hh @@ -57,7 +56,6 @@ digraphs_la_SOURCES += src/homos-graphs.c digraphs_la_SOURCES += src/perms.c digraphs_la_SOURCES += src/planar.c digraphs_la_SOURCES += src/schreier-sims.c -digraphs_la_SOURCES += src/dfs.c if WITH_INCLUDED_BLISS digraphs_la_SOURCES += extern/bliss-0.73/defs.cc diff --git a/doc/oper.xml b/doc/oper.xml index 7ba411a9a..54d8d0ed7 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -1321,7 +1321,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 2, 1, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 3, 2, 4, 5 ] +[ 1, 2, 3, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -2126,173 +2126,6 @@ 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 268ca3d79..00224832d 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -18,9 +18,6 @@ <#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 5b1b54e94..b18f8a891 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -13,10 +13,23 @@ 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, PostOrderFunc, PreOrderFunc, AncestorCrossFunc, data, record, - connected; + local N, copy, articulation_points, bridges, orientation, nbs, counter, pre, + low, nr_children, stack, u, v, i, w, connected; N := DigraphNrVertices(D); @@ -28,109 +41,115 @@ 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) or IsMultiDigraph(D) then - copy := DigraphSymmetricClosure(DigraphMutableCopy(D)); - copy := DigraphRemoveAllMultipleEdges(copy); + elif not IsSymmetricDigraph(D) then + copy := DigraphSymmetricClosure(DigraphMutableCopyIfMutable(D)); MakeImmutable(copy); else copy := D; fi; - 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; - - 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; + # outputs + articulation_points := []; + bridges := []; + orientation := List([1 .. N], x -> BlistList([1 .. N], [])); - 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; + # Get out-neighbours once, to avoid repeated copying for mutable digraphs. + nbs := OutNeighbours(copy); - data := rec(); + # number of nodes encountered in the search so far + counter := 0; - # outputs - data.articulation_points := []; - data.bridges := []; - data.orientation := List([1 .. N], x -> BlistList([1 .. N], [])); + # the order in which the nodes are visited, -1 indicates "not yet visited". + pre := ListWithIdenticalEntries(N, -1); # low[i] is the lowest value in pre currently reachable from node i. - data.low := []; - - # number of nodes encountered in the search so far - data.counter := 0; + low := []; # 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. - data.nr_children := 0; - - record := NewDFSRecord(copy); - ExecuteDFS(record, - data, - 1, - PreOrderFunc, - PostOrderFunc, - AncestorCrossFunc, - AncestorCrossFunc); - if data.counter = DigraphNrVertices(D) then + 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 connected := true; - if data.nr_children > 1 then - Add(data.articulation_points, 1); + if nr_children > 1 then + Add(articulation_points, 1); fi; - if not IsEmpty(data.bridges) then - data.orientation := fail; + if not IsEmpty(bridges) then + orientation := fail; else - data.orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), - data.orientation); + orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), + orientation); fi; else - connected := false; - data.articulation_points := []; - data.bridges := []; - data.orientation := fail; + connected := false; + articulation_points := []; + bridges := []; + orientation := fail; fi; if IsImmutableDigraph(D) then SetIsConnectedDigraph(D, connected); - SetArticulationPoints(D, data.articulation_points); - SetBridges(D, data.bridges); + SetArticulationPoints(D, articulation_points); + SetBridges(D, bridges); if IsSymmetricDigraph(D) then - SetStrongOrientationAttr(D, data.orientation); + SetStrongOrientationAttr(D, orientation); fi; fi; - return [connected, data.articulation_points, data.bridges, data.orientation]; + return [connected, articulation_points, bridges, orientation]; end); InstallMethod(ArticulationPoints, "for a digraph by out-neighbours", @@ -683,37 +702,7 @@ 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 - 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; - 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); +D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); InstallMethod(DigraphStronglyConnectedComponents, "for a digraph by out-neighbours", @@ -999,7 +988,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 @@ -2278,30 +2267,18 @@ InstallMethod(DigraphReflexiveTransitiveReductionAttr, InstallMethod(UndirectedSpanningForest, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local C, record, data, PreOrderFunc, i; + local C; if DigraphNrVertices(D) = 0 then return fail; fi; - C := MaximalSymmetricSubdigraph(D); - 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; + C := MaximalSymmetricSubdigraph(D)!.OutNeighbours; + C := DIGRAPH_SYMMETRIC_SPANNING_FOREST(C); if IsMutableDigraph(D) then - D!.OutNeighbours := data; + D!.OutNeighbours := C; ClearDigraphEdgeLabels(D); return D; fi; - C := ConvertToImmutableDigraphNC(data); + C := ConvertToImmutableDigraphNC(C); SetUndirectedSpanningForestAttr(D, C); SetIsUndirectedForest(C, true); SetIsMultiDigraph(C, false); diff --git a/gap/grahom.gi b/gap/grahom.gi index b02e70fcf..d555639ae 100644 --- a/gap/grahom.gi +++ b/gap/grahom.gi @@ -238,7 +238,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 71494aad1..e8fa1f176 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -142,8 +142,3 @@ 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 8d7c02812..29187ced8 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1388,7 +1388,7 @@ 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 verts; verts := DigraphVertices(D); if not (u in verts and v in verts) then @@ -1405,38 +1405,7 @@ function(D, u, v) DigraphConnectedComponents(D).id[v] 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; - 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; + return DIGRAPH_PATH(OutNeighbours(D), u, v); end); InstallMethod(IsDigraphPath, "for a digraph and list", @@ -1731,40 +1700,17 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc; + local dist; if not v in DigraphVertices(D) then ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", "argument ,"); fi; - record := NewDFSRecord(D); - data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), - prev := 0); - AncestorFunc := function(record, data) - record.stop := true; - 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 + dist := DIGRAPH_LONGEST_DIST_VERTEX(OutNeighbours(D), v); + if dist = -2 then return infinity; fi; - return data.depth[v]; + return dist; end); InstallMethod(DigraphLayers, "for a digraph, and a positive integer", @@ -2017,47 +1963,50 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N, record, data, AncestorFunc, PreOrderFunc; - + local N, index, current, succ, visited, prev, n, i, parent, + have_visited_root; 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; - - record := NewDFSRecord(D); - data := rec(result := [], root_is_child := 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_is_child then - data.root_is_child := true; - Add(data.result, root); + index := ListWithIdenticalEntries(N, 0); + have_visited_root := false; + index[root] := 1; + current := root; + succ := OutNeighbours(D); + visited := []; + parent := []; + parent[root] := fail; + repeat + prev := current; + for i in [index[current] .. Length(succ[current])] do + n := succ[current][i]; + if n = root and not have_visited_root then + Add(visited, root); + have_visited_root := true; + elif index[n] = 0 then + Add(visited, n); + parent[n] := current; + index[current] := i + 1; + current := n; + index[current] := 1; + break; + fi; + od; + if prev = current then + current := parent[current]; fi; - end; - - ExecuteDFS(record, - data, - root, - PreOrderFunc, - DFSDefault, - AncestorFunc, - DFSDefault); - return data.result; + until current = fail; + return visited; end); InstallMethod(DominatorTree, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - 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; - + 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; M := DigraphNrVertices(D); if 0 = root or root > M then @@ -2065,25 +2014,36 @@ function(D, root) "argument (a digraph)"); fi; - preorder_num_to_node := []; - - PreOrderFunc := function(record, data) - Add(data, record.current); - end; + node_to_preorder_num := []; + node_to_preorder_num[root] := 1; + preorder_num_to_node := [root]; - record := NewDFSRecord(D); - ExecuteDFS(record, - preorder_num_to_node, - root, - PreOrderFunc, - DFSDefault, - DFSDefault, - DFSDefault); - - parent := record.parent; + parent := []; parent[root] := fail; - node_to_preorder_num := record.preorder; + index := ListWithIdenticalEntries(M, 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 := []; @@ -2129,7 +2089,7 @@ function(D, root) od; bucket[w] := []; for v in pred[w] do - if node_to_preorder_num[v] <> -1 then + if IsBound(node_to_preorder_num[v]) then x := eval(v); if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then semi[w] := semi[x]; @@ -2240,50 +2200,3 @@ function(D, i, j) return fail; 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) -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 0f08f1e08..955f540de 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -173,7 +173,7 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local n, i, record, FoundCycle, components; + local n; n := DigraphNrVertices(D); if n = 0 then return true; @@ -188,26 +188,7 @@ function(D) fi; return false; fi; - record := NewDFSRecord(D); - FoundCycle := function(record, data) - record.stop := true; - 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; + return IS_ACYCLIC_DIGRAPH(OutNeighbours(D)); end); # Complexity O(number of edges) @@ -327,37 +308,7 @@ D -> DigraphPeriod(D) = 1); InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -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; - 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); +D -> IS_ANTISYMMETRIC_DIGRAPH(OutNeighbours(D))); InstallMethod(IsTransitiveDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], diff --git a/src/dfs.c b/src/dfs.c deleted file mode 100644 index e7430a533..000000000 --- a/src/dfs.c +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* -** -*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 deleted file mode 100644 index 40ec66e35..000000000 --- a/src/dfs.h +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* -** -*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 7ca058e19..842abe830 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -13,14 +13,13 @@ *******************************************************************************/ #include "digraphs.h" +#include "digraphs-config.h" #include // for false, true, bool #include // for uint64_t #include // for NULL, free #include "cliques.h" -#include "dfs.h" // for ExecuteDFS -#include "digraphs-config.h" #include "digraphs-debug.h" // for DIGRAPHS_ASSERT #include "homos.h" // for FuncHomomorphismDigraphsFinder #include "planar.h" // for FUNC_IS_PLANAR, . . . @@ -2314,12 +2313,6 @@ static StructGVarFunc GVarFuncs[] = { FuncSUBGRAPH_HOMEOMORPHIC_TO_K4, "src/planar.c:FuncSUBGRAPH_HOMEOMORPHIC_TO_K4"}, - {"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 3d0940f96..967ef2669 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1279,16 +1279,6 @@ 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);; @@ -1855,7 +1845,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 2, 1, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 3, 2, 4, 5 ] +[ 1, 2, 3, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -1901,7 +1891,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 3, 2, 4, 5 ] +[ 1, 2, 4, 3, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -1913,7 +1903,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 3, 2, 4, 5 ] +[ 1, 2, 4, 3, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -2690,6 +2680,54 @@ rec( idom := [ fail ], preorder := [ 1 ] ) gap> DominatorTree(D, 6); rec( idom := [ ,,,,, fail ], preorder := [ 6 ] ) +# IsDigraphPath +gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); + +gap> DigraphReflexiveTransitiveReduction(D); + +gap> MakeImmutable(D); + +gap> IsDigraphPath(D, []); +Error, the 2nd argument (a list) must have length 2, but found length 0 +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 +gap> IsDigraphPath(D, DigraphPath(D, 6, 1)); +true +gap> ForAll(List(IteratorOfPaths(D, 6, 1)), x -> IsDigraphPath(D, x)); +true + +# IsDigraphPath: failing example with new DFS code (issue #487) +gap> D := Digraph([ +> [2, 3, 4, 5, 5], [6, 3, 4, 7, 5], [8, 9, 10, 8, 11], +> [12, 13, 14, 15, 16], [2, 13, 4, 12, 17], [6, 9, 4, 16, 11], +> [18, 13, 4, 12, 8], [8, 19, 10, 19, 20], [8, 9, 10, 8, 21], +> [12, 13, 14, 15, 16], [22, 13, 14, 12, 16], [23, 13, 24, 12, 8], +> [19, 9, 19, 8, 24], [19, 13, 19, 15, 16], [21, 19, 24, 19, 20], +> [25, 13, 10, 12, 8], [26, 13, 10, 12, 17], [6, 3, 4, 7, 27], +> [19, 19, 19, 19, 19], [28, 13, 19, 12, 16], [29, 13, 14, 12, 16], +> [23, 3, 24, 7, 30], [29, 9, 14, 16, 24], [12, 19, 14, 19, 19], +> [8, 8, 10, 24, 15], [8, 8, 10, 24, 31], [30, 19, 4, 19, 20], +> [19, 8, 19, 24, 12], [23, 9, 24, 16, 21], [6, 13, 4, 12, 17], +> [32, 13, 24, 12, 17], [29, 3, 14, 7, 7]]);; +gap> path := DigraphPath(D, 5, 5);; +gap> IsDigraphPath(D, path); +true + #DIGRAPHS_UnbindVariables gap> Unbind(a); gap> Unbind(adj); @@ -2753,120 +2791,6 @@ gap> Unbind(U); gap> Unbind(u1); gap> Unbind(u2); -# 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 8cdca9acf..c55a58892 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -179,14 +179,6 @@ 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);