From 4c1a7e4321878d087f06450f9e056cd24ff36c1b Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 2 Mar 2025 19:02:04 +0000 Subject: [PATCH 01/54] Initial DFS Generic, stack now fixed size --- Makefile.in | 1 + gap/oper.gd | 5 ++ gap/oper.gi | 49 +++++++++++++ src/dfs.c | 157 ++++++++++++++++++++++++++++++++++++++++++ src/dfs.h | 21 ++++++ src/digraphs.c | 5 ++ tst/standard/oper.tst | 75 ++++++++++++++++++++ 7 files changed, 313 insertions(+) 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/gap/oper.gd b/gap/oper.gd index 5c9f6878a..23c551152 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -152,3 +152,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 60e47745b..f808e04c2 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2653,3 +2653,52 @@ 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); + # record.edge := []; + 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 # and edge? + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); + fi; + ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, + AncestorFunc, CrossFunc); +end); diff --git a/src/dfs.c b/src/dfs.c new file mode 100644 index 000000000..5ecdea431 --- /dev/null +++ b/src/dfs.c @@ -0,0 +1,157 @@ +/******************************************************************************* +** +*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 "bitarray.h" +#include "digraphs-config.h" +#include "digraphs-debug.h" +#include "digraphs.h" + +// Extreme examples are on the pull request #459 + +bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { + CALL_2ARGS(f, record, data); + if (ElmPRec(record, RNamStop) == True) { + CHANGED_BAG(record); + return true; + } + return false; +} + +Obj FuncExecuteDFS_C(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(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); + + 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; // referencing the last element in stack + // Length of stack fixed, since no vertices are added to it more than once + Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + + AssPlist(stack, ++top, start); + + UInt preorder_num = 1; + 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")); + // 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 + + ASS_LIST(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"); + + if (ElmPRec(record, RNamStop) == True) return record; + + // Tracks whether a vertex is visited, or in the stack to be visited so that + // it does not get added more than once for different ancestors + BitArray* will_visit = new_bit_array(N); + set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); + + while (top > 0) { + // visit current + current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node + if (current < 0) { + Int child = current * -1; + // backtracking on current + AssPRec(record, RNamChild, INTOBJ_INT(child)); + AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); + ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); + CHANGED_BAG(record); + if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { + return record; + } + continue; + } + // otherwise, visit this node + + AssPRec(record, RNamCurrent, INTOBJ_INT(current)); + CHANGED_BAG(record); + ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); + + if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { + return record; + } + + // Add back to the stack for backtracking + ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); + CHANGED_BAG(record); + + Obj succ = ELM_PLIST(neighbors, current); + for (UInt j = LEN_LIST(succ); j >= 1; j--) { + // Push so that the top of the stack is the first vertex in succ + UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + AssPRec(record, RNamChild, INTOBJ_INT(v)); + CHANGED_BAG(record); + if (!get_bit_array(will_visit, v - 1)) { // v is unvisited + ASS_LIST(parent, v, INTOBJ_INT(current)); + ASS_LIST(stack, ++top, INTOBJ_INT(v)); + set_bit_array(will_visit, v - 1, true); + } + else { // v is either visited, or in the stack to be visited + // If v was visited prior, but has not been backtracked on + bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; + bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; + if (visited && backtracked) { + if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { + return record; + } + } + // v has been visited and backtracked on + else if (visited) { + if (CallCheckStop(CrossFunc, RNamStop, record, data)) { + return record; + } + } + } + } + } + return record; +} diff --git a/src/dfs.h b/src/dfs.h new file mode 100644 index 000000000..535d19b59 --- /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 "gap-includes.h" // for Obj, Int + +Obj FuncExecuteDFS_C(Obj self, Obj args); + +#endif // DIGRAPHS_SRC_DFS_H_ diff --git a/src/digraphs.c b/src/digraphs.c index 996b26b9a..3df20cab8 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -26,6 +26,7 @@ #include "homos.h" // for FuncHomomorphismDigraphsFinder #include "planar.h" // for FUNC_IS_PLANAR, . . . #include "safemalloc.h" // for safe_malloc +#include "dfs.h" // for generic DFS #undef PACKAGE #undef PACKAGE_BUGREPORT @@ -2222,6 +2223,10 @@ static StructGVarFunc GVarFuncs[] = { GVAR_FUNC(SUBGRAPH_HOMEOMORPHIC_TO_K4, 1, "digraph"), GVAR_FUNC(DIGRAPHS_FREE_HOMOS_DATA, 0, ""), GVAR_FUNC(DIGRAPHS_FREE_CLIQUES_DATA, 0, ""), + GVAR_FUNC(ExecuteDFS_C, + -1, + "record, data, start, PreOrderFunc, PostOrderFunc, " + "AncestorFunc, CrossFunc"), {0, 0, 0, 0, 0} /* Finish with an empty entry */ }; diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 782bdd79f..43500c394 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3299,3 +3299,78 @@ gap> Unbind(TestPartialOrderDigraph); # gap> DIGRAPHS_StopTest(); gap> STOP_TEST("Digraphs package: standard/oper.tst", 0); + +# 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, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ] +gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; +gap> CrossFunc := function(record, data) +> record.stop := true; +> Add(data, record.child); +> end;; +gap> data := [];; +gap> ExecuteDFS(record, data, 1, 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, From 2703528299eb139d072656d151371359439feaf8 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 2 Mar 2025 20:47:04 +0000 Subject: [PATCH 02/54] Fix bit array usage --- src/dfs.c | 251 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 128 insertions(+), 123 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 5ecdea431..2d0d0f511 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -32,126 +32,131 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -Obj FuncExecuteDFS_C(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(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); - - 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; // referencing the last element in stack - // Length of stack fixed, since no vertices are added to it more than once - Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); - - AssPlist(stack, ++top, start); - - UInt preorder_num = 1; - 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")); - // 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 - - ASS_LIST(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"); - - if (ElmPRec(record, RNamStop) == True) return record; - - // Tracks whether a vertex is visited, or in the stack to be visited so that - // it does not get added more than once for different ancestors - BitArray* will_visit = new_bit_array(N); - set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); - - while (top > 0) { - // visit current - current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node - if (current < 0) { - Int child = current * -1; - // backtracking on current - AssPRec(record, RNamChild, INTOBJ_INT(child)); - AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); - ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); - CHANGED_BAG(record); - if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { - return record; - } - continue; - } - // otherwise, visit this node - - AssPRec(record, RNamCurrent, INTOBJ_INT(current)); - CHANGED_BAG(record); - ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); - - if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { - return record; - } - - // Add back to the stack for backtracking - ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); - CHANGED_BAG(record); - - Obj succ = ELM_PLIST(neighbors, current); - for (UInt j = LEN_LIST(succ); j >= 1; j--) { - // Push so that the top of the stack is the first vertex in succ - UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - AssPRec(record, RNamChild, INTOBJ_INT(v)); - CHANGED_BAG(record); - if (!get_bit_array(will_visit, v - 1)) { // v is unvisited - ASS_LIST(parent, v, INTOBJ_INT(current)); - ASS_LIST(stack, ++top, INTOBJ_INT(v)); - set_bit_array(will_visit, v - 1, true); - } - else { // v is either visited, or in the stack to be visited - // If v was visited prior, but has not been backtracked on - bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; - bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; - if (visited && backtracked) { - if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { - return record; - } - } - // v has been visited and backtracked on - else if (visited) { - if (CallCheckStop(CrossFunc, RNamStop, record, data)) { - return record; - } - } - } - } - } - return record; -} +/* Obj FuncExecuteDFS_C(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(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); */ + +/* 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; // referencing the last element in stack */ +/* // Length of stack fixed, since no vertices are added to it more than once */ +/* Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); */ + +/* AssPlist(stack, ++top, start); */ + +/* UInt preorder_num = 1; */ +/* 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")); */ +/* // 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 */ + +/* ASS_LIST(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"); */ + +/* if (ElmPRec(record, RNamStop) == True) return record; */ + +/* // Tracks whether a vertex is visited, or in the stack to be visited so that */ +/* // it does not get added more than once for different ancestors */ +/* BitArray* will_visit = new_bit_array(N); */ +/* set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); */ + +/* while (top > 0) { */ +/* // visit current */ +/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node */ +/* if (current < 0) { */ +/* Int child = current * -1; */ +/* // backtracking on current */ +/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ +/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); */ +/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ +/* CHANGED_BAG(record); */ +/* if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { */ +/* free_bit_array(will_visit); */ +/* return record; */ +/* } */ +/* continue; */ +/* } */ +/* // otherwise, visit this node */ + +/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ +/* CHANGED_BAG(record); */ +/* ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); */ + +/* if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { */ +/* free_bit_array(will_visit); */ +/* return record; */ +/* } */ + +/* // Add back to the stack for backtracking */ +/* ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); */ +/* CHANGED_BAG(record); */ + +/* Obj succ = ELM_PLIST(neighbors, current); */ +/* for (UInt j = LEN_LIST(succ); j >= 1; j--) { */ +/* // Push so that the top of the stack is the first vertex in succ */ +/* UInt v = INT_INTOBJ(ELM_LIST(succ, j)); */ +/* AssPRec(record, RNamChild, INTOBJ_INT(v)); */ +/* CHANGED_BAG(record); */ +/* if (!get_bit_array(will_visit, v - 1)) { // v is unvisited */ +/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ +/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ +/* set_bit_array(will_visit, v - 1, true); */ +/* } */ +/* else { // v is either visited, or in the stack to be visited */ +/* // If v was visited prior, but has not been backtracked on */ +/* bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; */ +/* bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; */ +/* if (visited && backtracked) { */ +/* if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { */ +/* free_bit_array(will_visit); */ +/* return record; */ +/* } */ +/* } */ +/* // v has been visited and backtracked on */ +/* else if (visited) { */ +/* if (CallCheckStop(CrossFunc, RNamStop, record, data)) { */ +/* free_bit_array(will_visit); */ +/* return record; */ +/* } */ +/* } */ +/* } */ +/* } */ +/* } */ +/* free_bit_array(will_visit); */ +/* return record; */ +/* } */ From b0a1fe7cb3170445ab6158a4ac3ca4729c26ec6a Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 2 Mar 2025 20:49:16 +0000 Subject: [PATCH 03/54] Fix last commit --- src/dfs.c | 256 +++++++++++++++++++++++++++--------------------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 2d0d0f511..c434f56ce 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -32,131 +32,131 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -/* Obj FuncExecuteDFS_C(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(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); */ - -/* 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; // referencing the last element in stack */ -/* // Length of stack fixed, since no vertices are added to it more than once */ -/* Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); */ - -/* AssPlist(stack, ++top, start); */ - -/* UInt preorder_num = 1; */ -/* 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")); */ -/* // 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 */ - -/* ASS_LIST(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"); */ - -/* if (ElmPRec(record, RNamStop) == True) return record; */ - -/* // Tracks whether a vertex is visited, or in the stack to be visited so that */ -/* // it does not get added more than once for different ancestors */ -/* BitArray* will_visit = new_bit_array(N); */ -/* set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); */ - -/* while (top > 0) { */ -/* // visit current */ -/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node */ -/* if (current < 0) { */ -/* Int child = current * -1; */ -/* // backtracking on current */ -/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ -/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); */ -/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ -/* CHANGED_BAG(record); */ -/* if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { */ -/* free_bit_array(will_visit); */ -/* return record; */ -/* } */ -/* continue; */ -/* } */ -/* // otherwise, visit this node */ - -/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ -/* CHANGED_BAG(record); */ -/* ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); */ - -/* if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { */ -/* free_bit_array(will_visit); */ -/* return record; */ -/* } */ - -/* // Add back to the stack for backtracking */ -/* ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); */ -/* CHANGED_BAG(record); */ - -/* Obj succ = ELM_PLIST(neighbors, current); */ -/* for (UInt j = LEN_LIST(succ); j >= 1; j--) { */ -/* // Push so that the top of the stack is the first vertex in succ */ -/* UInt v = INT_INTOBJ(ELM_LIST(succ, j)); */ -/* AssPRec(record, RNamChild, INTOBJ_INT(v)); */ -/* CHANGED_BAG(record); */ -/* if (!get_bit_array(will_visit, v - 1)) { // v is unvisited */ -/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ -/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ -/* set_bit_array(will_visit, v - 1, true); */ -/* } */ -/* else { // v is either visited, or in the stack to be visited */ -/* // If v was visited prior, but has not been backtracked on */ -/* bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; */ -/* bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; */ -/* if (visited && backtracked) { */ -/* if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { */ -/* free_bit_array(will_visit); */ -/* return record; */ -/* } */ -/* } */ -/* // v has been visited and backtracked on */ -/* else if (visited) { */ -/* if (CallCheckStop(CrossFunc, RNamStop, record, data)) { */ -/* free_bit_array(will_visit); */ -/* return record; */ -/* } */ -/* } */ -/* } */ -/* } */ -/* } */ -/* free_bit_array(will_visit); */ -/* return record; */ -/* } */ +Obj FuncExecuteDFS_C(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(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); + + 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; // referencing the last element in stack + // Length of stack fixed, since no vertices are added to it more than once + Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + + AssPlist(stack, ++top, start); + + UInt preorder_num = 1; + 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")); + // 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 + + ASS_LIST(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"); + + if (ElmPRec(record, RNamStop) == True) return record; + + // Tracks whether a vertex is visited, or in the stack to be visited so that + // it does not get added more than once for different ancestors + BitArray* will_visit = new_bit_array(N); + set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); + + while (top > 0) { + // visit current + current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node + if (current < 0) { + Int child = current * -1; + // backtracking on current + AssPRec(record, RNamChild, INTOBJ_INT(child)); + AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); + ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); + CHANGED_BAG(record); + if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { + free_bit_array(will_visit); + return record; + } + continue; + } + // otherwise, visit this node + + AssPRec(record, RNamCurrent, INTOBJ_INT(current)); + CHANGED_BAG(record); + ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); + + if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { + free_bit_array(will_visit); + return record; + } + + // Add back to the stack for backtracking + ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); + CHANGED_BAG(record); + + Obj succ = ELM_PLIST(neighbors, current); + for (UInt j = LEN_LIST(succ); j >= 1; j--) { + // Push so that the top of the stack is the first vertex in succ + UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + AssPRec(record, RNamChild, INTOBJ_INT(v)); + CHANGED_BAG(record); + if (!get_bit_array(will_visit, v - 1)) { // v is unvisited + ASS_LIST(parent, v, INTOBJ_INT(current)); + ASS_LIST(stack, ++top, INTOBJ_INT(v)); + set_bit_array(will_visit, v - 1, true); + } + else { // v is either visited, or in the stack to be visited + // If v was visited prior, but has not been backtracked on + bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; + bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; + if (visited && backtracked) { + if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { + free_bit_array(will_visit); + return record; + } + } + // v has been visited and backtracked on + else if (visited) { + if (CallCheckStop(CrossFunc, RNamStop, record, data)) { + free_bit_array(will_visit); + return record; + } + } + } + } + } + free_bit_array(will_visit); + return record; +} From 12f4013ff1115f7692739ee2b84a68cdba81e018 Mon Sep 17 00:00:00 2001 From: Saffron Date: Mon, 3 Mar 2025 14:53:09 +0000 Subject: [PATCH 04/54] Add back vertices reachable from --- gap/oper.gi | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index f808e04c2..7c2713972 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1,4 +1,5 @@ -############################################################################# + + ############################################################################# ## ## oper.gi ## Copyright (C) 2014-19 James D. Mitchell @@ -2238,18 +2239,56 @@ function(D, v) return spanningtree; end); +# InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", +# [IsDigraph, IsPosInt], +# function(D, root) +# local N; +# N := DigraphNrVertices(D); + +# if 0 = root or root > N then +# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", +# "argument (a digraph)"); +# fi; + +# return VerticesReachableFrom(D, [root]); +# end); + InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N; - N := DigraphNrVertices(D); + local N, record, data, AncestorFunc, PreOrderFunc; + N := DigraphNrVertices(D); if 0 = root or root > N then ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", "argument (a digraph)"); fi; - return VerticesReachableFrom(D, [root]); + record := NewDFSRecord(D); + data := rec(result := [], root_reached := false); + + PreOrderFunc := function(record, data) + if record.current <> root then + Add(data.result, record.current); + fi; + end; + + AncestorFunc := function(record, data) + if record.child = root and not data.root_reached then + data.root_reached := true; + Add(data.result, root); + fi; + end; + + ExecuteDFS(record, + data, + root, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); + Sort(data.result); + return data.result; end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", From 5c7e7b4e1557921e13bd2cc59b0d21af766bd3cc Mon Sep 17 00:00:00 2001 From: Saffron Date: Mon, 3 Mar 2025 18:49:24 +0000 Subject: [PATCH 05/54] Fix ExecuteDFS and add back some uses --- gap/attr.gi | 80 ++++++++++++++++++++++- gap/oper.gi | 182 ++++++++++++++++++++++++++++++++++++++++------------ gap/prop.gi | 68 ++++++++++++++++---- src/dfs.c | 27 +++----- 4 files changed, 287 insertions(+), 70 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index c3e5bff2e..97458009b 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -872,7 +872,85 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); +function(D) + local N, record, count, out, PostOrderFunc, AncestorFunc, i; + + N := DigraphNrVertices(D); + if N = 0 then + return []; + fi; + record := NewDFSRecord(D); + count := 0; + out := []; + PostOrderFunc := function(record, data) + count := count + 1; + out[count] := record.child; + end; + AncestorFunc := function(record, data) + if record.current <> record.child then + record.stop := true; + fi; + end; + for i in DigraphVertices(D) do + if (record.preorder[i] <> -1) then + continue; + fi; + ExecuteDFS(record, + fail, + i, + DFSDefault, + PostOrderFunc, + AncestorFunc, + DFSDefault); + if record.stop then + return fail; + fi; + od; + return out; +end); + +# InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", +# [IsDigraphByOutNeighboursRep], +# function(D) +# local N, record, count, out, PostOrderFunc, AncestorFunc, i; + +# N := DigraphNrVertices(D); +# if N = 0 then +# return []; +# fi; +# record := NewDFSRecord(D); +# count := 0; +# out := []; +# PostOrderFunc := function(record, data) +# count := count + 1; +# out[count] := record.child; +# end; +# AncestorFunc := function(record, data) +# if record.current <> record.child then +# record.stop := true; +# fi; +# end; +# for i in DigraphVertices(D) do +# if (record.preorder[i] <> -1) then +# continue; +# fi; +# ExecuteDFS(record, +# fail, +# i, +# DFSDefault, +# PostOrderFunc, +# AncestorFunc, +# DFSDefault); +# if record.stop then +# return fail; +# fi; +# od; +# return out; +# end); + +# InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", +# [IsDigraphByOutNeighboursRep], +# D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); InstallMethod(DigraphStronglyConnectedComponents, "for a digraph by out-neighbours", diff --git a/gap/oper.gi b/gap/oper.gi index 7c2713972..41f255e4e 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1633,13 +1633,36 @@ function(D, u, v) return DigraphPath(D, u, v) <> fail; end); +# InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", +# [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], +# function(D, u, v) +# local verts; + +# verts := DigraphVertices(D); +# if not (u in verts and v in verts) then +# ErrorNoReturn("the 2nd and 3rd arguments and must be ", +# "vertices of the 1st argument ,"); +# elif IsDigraphEdge(D, u, v) then +# return [[u, v], [Position(OutNeighboursOfVertex(D, u), v)]]; +# elif HasIsTransitiveDigraph(D) and IsTransitiveDigraph(D) then +# # If it's a known transitive digraph, just check whether the edge exists +# return fail; +# # Glean information from WCC if we have it +# elif HasDigraphConnectedComponents(D) +# and DigraphConnectedComponents(D).id[u] <> +# DigraphConnectedComponents(D).id[v] then +# return fail; +# fi; +# return DIGRAPH_PATH(OutNeighbours(D), u, v); +# end); + InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local verts; + local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current; - 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 @@ -1652,8 +1675,58 @@ 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; - return DIGRAPH_PATH(OutNeighbours(D), u, v); + record := NewDFSRecord(D); + 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; + 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; + end; + fi; + ExecuteDFS(record, + fail, + u, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); + if not record.stop then + return fail; + fi; + nodes := [v]; + edges := []; + current := v; + if u = v then + # Go one back from v to the last node in the tree + current := record.current; + Add(nodes, current); + Add(edges, Position(OutNeighboursOfVertex(D, current), u)); + fi; + # Follow the path from current (which is a descendant of u) back to u + while current <> u do + Add(edges, record.edge[current]); # edge = + current := record.parent[current]; + Add(nodes, current); + od; + return [Reversed(nodes), Reversed(edges)]; end); InstallMethod(IsDigraphPath, "for a digraph and list", @@ -1945,20 +2018,59 @@ function(D, u, v) return IteratorByFunctions(record); end); +# InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", +# [IsDigraphByOutNeighboursRep, IsPosInt], +# function(D, v) +# local dist; + +# 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 +# return infinity; +# fi; +# return dist; +# 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; + 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, @@ -2358,9 +2470,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 @@ -2368,36 +2481,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; + + record := NewDFSRecord(D); + ExecuteDFS(record, + preorder_num_to_node, + root, + PreOrderFunc, + DFSDefault, + DFSDefault, + DFSDefault); - index := ListWithIdenticalEntries(M, 1); + 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 := []; @@ -2443,7 +2545,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]; @@ -2734,7 +2836,7 @@ function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) if not IsEqualSet(RecNames(record), ["stop", "graph", "child", "parent", "preorder", - "postorder", "current"]) then # and edge? + "postorder", "current"]) then ErrorNoReturn("the 1st argument must be created with ", "NewDFSRecord,"); fi; diff --git a/gap/prop.gi b/gap/prop.gi index e519535b2..168714f8e 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -239,22 +239,66 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local n; + local n, i, record, D2, PostOrderFunc, PreOrderFunc, data, edge, AncestorFunc, + edges; n := DigraphNrVertices(D); if n = 0 then return true; - elif HasDigraphTopologicalSort(D) and - DigraphTopologicalSort(D) = fail then - return false; - elif HasDigraphHasLoops(D) and DigraphHasLoops(D) then - return false; - elif HasDigraphStronglyConnectedComponents(D) then - if DigraphNrStronglyConnectedComponents(D) = n then - return not DigraphHasLoops(D); - fi; - return false; fi; - return IS_ACYCLIC_DIGRAPH(OutNeighbours(D)); + + # A Digraph is acyclic if it has no back edges + + D2 := MakeImmutable(DigraphMutableCopy(D)); + D2 := DigraphAddVertex(D2); + edges := []; + + for i in [1..n] do + Add(edges, [n + 1, i]); + od; + + D2 := DigraphAddEdges(D2, edges); + + record := NewDFSRecord(D2); + + # Starts at the new vertex + ## data := rec(start := [], last := [], clock := 0); + + # PreOrderFunc := function(record, data) + # data.start[record.current] := data.clock; + # data.clock := data.clock + 1; + # end; + + # PostOrderFunc := function(record, data) + # data.last[record.child] := data.clock; + # data.clock := data.clock + 1; + # end; + + AncestorFunc := function(record, data) + record.stop := true; + end; + + + ExecuteDFS(record, [], n + 1, DFSDefault, + DFSDefault, AncestorFunc, DFSDefault); + + # ExecuteDFS(record, data, n + 1, PreOrderFunc, + # PostOrderFunc, DFSDefault, DFSDefault); + + # return not record.stop; + return not record.stop; + + # for edge in DigraphEdges(D2) do + # if edge[1] = edge[2] then + # return false; + # fi; + + # if data.start[edge[1]] > data.start[edge[2]] and + # data.last[edge[1]] < data.last[edge[2]] then + # return false; + # fi; + # od; + + # return true; end); # Complexity O(number of edges) diff --git a/src/dfs.c b/src/dfs.c index c434f56ce..3a7b3b9fa 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -90,11 +90,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ElmPRec(record, RNamStop) == True) return record; - // Tracks whether a vertex is visited, or in the stack to be visited so that - // it does not get added more than once for different ancestors - BitArray* will_visit = new_bit_array(N); - set_bit_array(will_visit, INT_INTOBJ(start) - 1, true); - while (top > 0) { // visit current current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node @@ -106,19 +101,20 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); CHANGED_BAG(record); if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { - free_bit_array(will_visit); return record; } continue; } + + if (INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) continue; + // otherwise, visit this node AssPRec(record, RNamCurrent, INTOBJ_INT(current)); - CHANGED_BAG(record); ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); + CHANGED_BAG(record); if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { - free_bit_array(will_visit); return record; } @@ -130,33 +126,30 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { for (UInt j = LEN_LIST(succ); j >= 1; j--) { // Push so that the top of the stack is the first vertex in succ UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; AssPRec(record, RNamChild, INTOBJ_INT(v)); CHANGED_BAG(record); - if (!get_bit_array(will_visit, v - 1)) { // v is unvisited + + if (!visited) { // v is unvisited ASS_LIST(parent, v, INTOBJ_INT(current)); ASS_LIST(stack, ++top, INTOBJ_INT(v)); - set_bit_array(will_visit, v - 1, true); } else { // v is either visited, or in the stack to be visited // If v was visited prior, but has not been backtracked on - bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; - bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) == -1; - if (visited && backtracked) { + bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) != -1; + if (!backtracked) { // Back edge if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { - free_bit_array(will_visit); return record; } } // v has been visited and backtracked on - else if (visited) { + else { if (CallCheckStop(CrossFunc, RNamStop, record, data)) { - free_bit_array(will_visit); return record; } } } } } - free_bit_array(will_visit); return record; } From 31cd038a12a3dce16b80f088bf3ab8140debf636 Mon Sep 17 00:00:00 2001 From: Saffron Date: Mon, 3 Mar 2025 20:26:20 +0000 Subject: [PATCH 06/54] Investigate recursive solution to save stack additions --- gap/oper.gi | 4 +- src/dfs.c | 304 ++++++++++++++++++++++++++++++------------ tst/standard/oper.tst | 187 ++++++++++++++++---------- 3 files changed, 336 insertions(+), 159 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 41f255e4e..b390040bb 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2811,7 +2811,7 @@ function(graph) record.parent := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - # record.edge := []; + record.edge := HashMap(); return record; end); @@ -2836,7 +2836,7 @@ function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) if not IsEqualSet(RecNames(record), ["stop", "graph", "child", "parent", "preorder", - "postorder", "current"]) then + "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 3a7b3b9fa..8c04bb737 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -20,6 +20,7 @@ #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" +#include "safemalloc.h" // Extreme examples are on the pull request #459 @@ -32,13 +33,207 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } +/* Obj FuncExecuteDFS_C(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(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); */ + +/* 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; // referencing the last element in stack */ +/* // Length of stack fixed, since no vertices are added to it more than once */ +/* Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); */ + +/* AssPlist(stack, ++top, start); */ + +/* UInt preorder_num = 1; */ +/* 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")); */ +/* // 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 */ + +/* ASS_LIST(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"); */ + +/* if (ElmPRec(record, RNamStop) == True) return record; */ + +/* while (top > 0) { */ +/* // visit current */ +/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node */ +/* if (current < 0) { */ +/* Int child = current * -1; */ +/* // backtracking on current */ +/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ +/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); */ +/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ +/* CHANGED_BAG(record); */ +/* if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { */ +/* return record; */ +/* } */ +/* continue; */ +/* } */ + +/* if (INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) continue; */ + +/* // otherwise, visit this node */ + +/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ +/* ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); */ +/* CHANGED_BAG(record); */ + +/* if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { */ +/* return record; */ +/* } */ + +/* // Add back to the stack for backtracking */ +/* ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); */ +/* CHANGED_BAG(record); */ + +/* Obj succ = ELM_PLIST(neighbors, current); */ +/* for (UInt j = LEN_LIST(succ); j >= 1; j--) { */ +/* // Push so that the top of the stack is the first vertex in succ */ +/* UInt v = INT_INTOBJ(ELM_LIST(succ, j)); */ +/* bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; */ +/* AssPRec(record, RNamChild, INTOBJ_INT(v)); */ +/* CHANGED_BAG(record); */ + +/* if (!visited) { // v is unvisited */ +/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ +/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ +/* } */ +/* else { // v is either visited, or in the stack to be visited */ +/* // If v was visited prior, but has not been backtracked on */ +/* bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) != -1; */ +/* if (!backtracked) { // Back edge */ +/* if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { */ +/* return record; */ +/* } */ +/* } */ +/* // v has been visited and backtracked on */ +/* else { */ +/* if (CallCheckStop(CrossFunc, RNamStop, record, data)) { */ +/* return record; */ +/* } */ +/* } */ +/* } */ +/* } */ +/* } */ +/* return record; */ +/* } */ + + +struct dfs_args { + Int RNamChild; + Int RNamCurrent; + Int RNamStop; + + Obj record; + UInt* preorder_num; + UInt* postorder_num; + + Obj parent; + Obj postorder; + Obj preorder; + + Obj neighbors; + + Obj data; + Obj PreorderFunc; + Obj PostorderFunc; + Obj AncestorFunc; + Obj CrossFunc; +}; + +bool ExecuteDFSRec(UInt current, struct dfs_args* args) { + AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); + ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); + CHANGED_BAG(args -> record); + + if (CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, args -> data)) { + return args -> record; + } + + Obj succ = ELM_PLIST(args -> neighbors, current); + for (UInt j = 1; j <= LEN_LIST(succ); j++) { + UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); + CHANGED_BAG(args -> record); + + if (!visited) { + ASS_LIST(args -> parent, v, INTOBJ_INT(current)); + bool rec_res = ExecuteDFSRec(v, args); + if (!rec_res) return false; // Stop + } + else { + bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; + if (!backtracked) { // Back edge + if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, args -> record, args -> data)) { + return false; + } + } + // v has been visited and backtracked on + else { + if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, args -> data)) { + return false; + } + } + } + } + + // backtracking on current + AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); + AssPRec(args -> record, args -> RNamCurrent, ELM_PLIST(args -> parent, current)); + ASS_LIST(args -> postorder, current, INTOBJ_INT(++(*args -> postorder_num))); + CHANGED_BAG(args -> record); + if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, args -> data)) { + return false; // Stop execution + } + return true; // Continue + +} + Obj FuncExecuteDFS_C(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 PostorderFunc = ELM_PLIST(args, 5); Obj AncestorFunc = ELM_PLIST(args, 6); Obj CrossFunc = ELM_PLIST(args, 7); @@ -46,7 +241,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { DIGRAPHS_ASSERT(IS_INTOBJ(start)); // DIGRAPHS_ASSERT(NARG_FUNC(PreorderFunc) == 2); DIGRAPHS_ASSERT(IS_FUNC(PreorderFunc)); - DIGRAPHS_ASSERT(IS_FUNC(PostOrderFunc)); + DIGRAPHS_ASSERT(IS_FUNC(PostorderFunc)); // DIGRAPHS_ASSERT(NARG_FUNC(PostOrderFunc) == 2); DIGRAPHS_ASSERT(IS_FUNC(AncestorFunc)); // DIGRAPHS_ASSERT(NARG_FUNC(AncestorFunc) == 2); @@ -60,96 +255,39 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { ErrorQuit( "the third argument must be a vertex in your graph,", 0L, 0L); } - Int top = 0; // referencing the last element in stack - // Length of stack fixed, since no vertices are added to it more than once - Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); - - AssPlist(stack, ++top, start); - - UInt preorder_num = 1; - 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")); - // 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 - - ASS_LIST(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"); if (ElmPRec(record, RNamStop) == True) return record; - while (top > 0) { - // visit current - current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node - if (current < 0) { - Int child = current * -1; - // backtracking on current - AssPRec(record, RNamChild, INTOBJ_INT(child)); - AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); - ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); - CHANGED_BAG(record); - if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { - return record; - } - continue; - } - - if (INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) continue; + struct dfs_args* rec_args = (struct dfs_args*) safe_malloc(sizeof(struct dfs_args)); - // otherwise, visit this node + UInt preorder_num = 1; + UInt postorder_num = 0; - AssPRec(record, RNamCurrent, INTOBJ_INT(current)); - ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); - CHANGED_BAG(record); + rec_args -> RNamChild = RNamName("child"); + rec_args -> RNamCurrent = RNamName("current"); + rec_args -> RNamStop = RNamStop; + rec_args -> record = record; + rec_args -> preorder_num = &preorder_num; + rec_args -> postorder_num = &postorder_num; + rec_args -> parent = ElmPRec(record, RNamName("parent")); + rec_args -> postorder = ElmPRec(record, RNamName("postorder")); + rec_args -> preorder = ElmPRec(record, RNamName("preorder")); + rec_args -> neighbors = FuncOutNeighbours(self, D); + rec_args -> data = data; + rec_args -> PreorderFunc = PreorderFunc; + rec_args -> PostorderFunc = PostorderFunc; + rec_args -> CrossFunc = CrossFunc; + rec_args -> AncestorFunc = AncestorFunc; - if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { - return record; - } + ASS_LIST(rec_args -> parent, INT_INTOBJ(start), start); - // Add back to the stack for backtracking - ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); - CHANGED_BAG(record); + DIGRAPHS_ASSERT(IS_PLIST(rec_args -> neighbors)); + UInt current = INT_INTOBJ(start); + ExecuteDFSRec(current, rec_args); - Obj succ = ELM_PLIST(neighbors, current); - for (UInt j = LEN_LIST(succ); j >= 1; j--) { - // Push so that the top of the stack is the first vertex in succ - UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; - AssPRec(record, RNamChild, INTOBJ_INT(v)); - CHANGED_BAG(record); - - if (!visited) { // v is unvisited - ASS_LIST(parent, v, INTOBJ_INT(current)); - ASS_LIST(stack, ++top, INTOBJ_INT(v)); - } - else { // v is either visited, or in the stack to be visited - // If v was visited prior, but has not been backtracked on - bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) != -1; - if (!backtracked) { // Back edge - if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { - return record; - } - } - // v has been visited and backtracked on - else { - if (CallCheckStop(CrossFunc, RNamStop, record, data)) { - return record; - } - } - } - } - } + free(rec_args); return record; + } diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 43500c394..a8ca2d530 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3234,87 +3234,21 @@ gap> DigraphEdges(D); gap> DigraphVertexLabels(D); [ 1, 2, 3, 6, [ 4, 5 ] ] -# DIGRAPHS_UnbindVariables -gap> Unbind(C); -gap> Unbind(D); -gap> Unbind(D1); -gap> Unbind(D2); -gap> Unbind(D3); -gap> Unbind(D3_edges); -gap> Unbind(DD); -gap> Unbind(G); -gap> Unbind(G1); -gap> Unbind(L); -gap> Unbind(a); -gap> Unbind(adj); -gap> Unbind(b); -gap> Unbind(copy); -gap> Unbind(d); -gap> Unbind(edges); -gap> Unbind(edges2); -gap> Unbind(func); -gap> Unbind(g); -gap> Unbind(gr); -gap> Unbind(gr1); -gap> Unbind(gr2); -gap> Unbind(gr3); -gap> Unbind(gr4); -gap> Unbind(gri); -gap> Unbind(grrt); -gap> Unbind(grt); -gap> Unbind(h); -gap> Unbind(i); -gap> Unbind(i1); -gap> Unbind(i2); -gap> Unbind(in1); -gap> Unbind(in2); -gap> Unbind(in3); -gap> Unbind(iter); -gap> Unbind(j1); -gap> Unbind(j2); -gap> Unbind(m); -gap> Unbind(m1); -gap> Unbind(m2); -gap> Unbind(mat); -gap> Unbind(n); -gap> Unbind(nbs); -gap> Unbind(out); -gap> Unbind(out1); -gap> Unbind(out2); -gap> Unbind(out3); -gap> Unbind(p1); -gap> Unbind(p2); -gap> Unbind(path); -gap> Unbind(qr); -gap> Unbind(r); -gap> Unbind(res); -gap> Unbind(rtclosure); -gap> Unbind(t); -gap> Unbind(tclosure); -gap> Unbind(u1); -gap> Unbind(u2); -gap> Unbind(x); -gap> Unbind(TestPartialOrderDigraph); - -# -gap> DIGRAPHS_StopTest(); -gap> STOP_TEST("Digraphs package: standard/oper.tst", 0); - # DFS # NewDFSRecord gap> NewDFSRecord(ChainDigraph(10)); -rec( child := -1, current := -1, +rec( child := -1, current := -1, edge := HashMap([]), 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, +rec( child := -1, current := -1, edge := HashMap([]), 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, +rec( child := -1, current := -1, edge := HashMap([]), graph := , parent := [ -1, -1, -1, -1, -1 ], postorder := [ -1, -1, -1, -1, -1 ], preorder := [ -1, -1, -1, -1, -1 ], stop := false ) @@ -3326,7 +3260,7 @@ gap> DFSDefault(rec(), rec()); # ExecuteDFS gap> record := NewDFSRecord(CompleteDigraph(10));; gap> ExecuteDFS(record, [], 2, DFSDefault, -> DFSDefault, DFSDefault, DFSDefault);; +> DFSDefault, DFSDefault, DFSDefault); gap> record.preorder; [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ] gap> record := NewDFSRecord(CompleteDigraph(15));; @@ -3336,7 +3270,7 @@ gap> AncestorFunc := function(record, data) > data.cycle_vertex := record.child; > end;; gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, AncestorFunc, DFSDefault);; +> DFSDefault, AncestorFunc, DFSDefault); gap> record.stop; true gap> data.cycle_vertex; @@ -3350,7 +3284,7 @@ gap> CrossFunc := function(record, data) > end;; gap> data := [];; gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, DFSDefault, CrossFunc);; +> DFSDefault, DFSDefault, CrossFunc); gap> record.stop; true gap> data; @@ -3364,13 +3298,118 @@ gap> CrossFunc := function(record, data) 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);; +> DFSDefault, AncestorFunc, CrossFunc); gap> data; rec( cross_vertex := [ 4 ], cycle_vertex := [ 1, 1 ] ) gap> ExecuteDFS(rec(), data, 1, DFSDefault, -> DFSDefault, AncestorFunc, CrossFunc);; +> 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 + +# DIGRAPHS_UnbindVariables +gap> Unbind(C); +gap> Unbind(D); +gap> Unbind(D1); +gap> Unbind(D2); +gap> Unbind(D3); +gap> Unbind(D3_edges); +gap> Unbind(DD); +gap> Unbind(G); +gap> Unbind(G1); +gap> Unbind(L); +gap> Unbind(a); +gap> Unbind(adj); +gap> Unbind(b); +gap> Unbind(copy); +gap> Unbind(d); +gap> Unbind(edges); +gap> Unbind(edges2); +gap> Unbind(func); +gap> Unbind(g); +gap> Unbind(gr); +gap> Unbind(gr1); +gap> Unbind(gr2); +gap> Unbind(gr3); +gap> Unbind(gr4); +gap> Unbind(gri); +gap> Unbind(grrt); +gap> Unbind(grt); +gap> Unbind(h); +gap> Unbind(i); +gap> Unbind(i1); +gap> Unbind(i2); +gap> Unbind(in1); +gap> Unbind(in2); +gap> Unbind(in3); +gap> Unbind(iter); +gap> Unbind(j1); +gap> Unbind(j2); +gap> Unbind(m); +gap> Unbind(m1); +gap> Unbind(m2); +gap> Unbind(mat); +gap> Unbind(n); +gap> Unbind(nbs); +gap> Unbind(out); +gap> Unbind(out1); +gap> Unbind(out2); +gap> Unbind(out3); +gap> Unbind(p1); +gap> Unbind(p2); +gap> Unbind(path); +gap> Unbind(qr); +gap> Unbind(r); +gap> Unbind(res); +gap> Unbind(rtclosure); +gap> Unbind(t); +gap> Unbind(tclosure); +gap> Unbind(u1); +gap> Unbind(u2); +gap> Unbind(x); +gap> Unbind(TestPartialOrderDigraph); + +# +gap> DIGRAPHS_StopTest(); +gap> STOP_TEST("Digraphs package: standard/oper.tst", 0); From d8be4202b77f29448b50df9ef5c2d67a7d8ee8b4 Mon Sep 17 00:00:00 2001 From: Saffron Date: Mon, 3 Mar 2025 21:47:57 +0000 Subject: [PATCH 07/54] Before converting to HashMap --- gap/attr.gi | 39 --------------------------------------- src/dfs.c | 1 - 2 files changed, 40 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 97458009b..99a992082 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -909,45 +909,6 @@ function(D) return out; end); -# InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", -# [IsDigraphByOutNeighboursRep], -# function(D) -# local N, record, count, out, PostOrderFunc, AncestorFunc, i; - -# N := DigraphNrVertices(D); -# if N = 0 then -# return []; -# fi; -# record := NewDFSRecord(D); -# count := 0; -# out := []; -# PostOrderFunc := function(record, data) -# count := count + 1; -# out[count] := record.child; -# end; -# AncestorFunc := function(record, data) -# if record.current <> record.child then -# record.stop := true; -# fi; -# end; -# for i in DigraphVertices(D) do -# if (record.preorder[i] <> -1) then -# continue; -# fi; -# ExecuteDFS(record, -# fail, -# i, -# DFSDefault, -# PostOrderFunc, -# AncestorFunc, -# DFSDefault); -# if record.stop then -# return fail; -# fi; -# od; -# return out; -# end); - # InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", # [IsDigraphByOutNeighboursRep], # D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); diff --git a/src/dfs.c b/src/dfs.c index 8c04bb737..bb3c99d06 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -224,7 +224,6 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { return false; // Stop execution } return true; // Continue - } Obj FuncExecuteDFS_C(Obj self, Obj args) { From d7c2d3bd187ed601e701ac745cac8d0fee56bea7 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 11:53:43 +0000 Subject: [PATCH 08/54] Add recursive DFS with HashMaps, fix issue with cross / back edges --- gap/attr.gi | 6 +- gap/oper.gd | 1 + gap/oper.gi | 207 ++++++++++++++++++++++++++----- gap/prop.gi | 24 +--- src/dfs.c | 281 +++++++++++++++++++++++------------------- src/dfs.h | 1 + src/digraphs.c | 17 +++ tst/standard/oper.tst | 50 +++++--- 8 files changed, 386 insertions(+), 201 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 99a992082..cedd4429a 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -882,17 +882,17 @@ function(D) record := NewDFSRecord(D); count := 0; out := []; - PostOrderFunc := function(record, data) + PostOrderFunc := function(record, _) count := count + 1; out[count] := record.child; end; - AncestorFunc := function(record, data) + AncestorFunc := function(record, _) if record.current <> record.child then record.stop := true; fi; end; for i in DigraphVertices(D) do - if (record.preorder[i] <> -1) then + if (IsBound(record.preorder[i])) then continue; fi; ExecuteDFS(record, diff --git a/gap/oper.gd b/gap/oper.gd index 23c551152..50d8a3014 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -157,3 +157,4 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", DeclareOperation("NewDFSRecord", [IsDigraph]); DeclareOperation("DFSDefault", [IsRecord, IsObject]); DeclareGlobalFunction("ExecuteDFS"); +DeclareGlobalFunction("ExecuteDFSIter"); # TODO remove? diff --git a/gap/oper.gi b/gap/oper.gi index b390040bb..fb8355caa 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1,5 +1,4 @@ - - ############################################################################# +############################################################################# ## ## oper.gi ## Copyright (C) 2014-19 James D. Mitchell @@ -1684,7 +1683,7 @@ function(D, u, v) # 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) + PreOrderFunc := function(record, _) if record.current = v then record.stop := true; fi; @@ -1695,7 +1694,7 @@ function(D, u, v) # itself, but PreOrderFunc won't be called (because u has already been # discovered). PreOrderFunc := DFSDefault; - AncestorFunc := function(record, data) + AncestorFunc := function(record, _) if record.child = v then record.stop := true; fi; @@ -1722,7 +1721,7 @@ function(D, u, v) fi; # Follow the path from current (which is a descendant of u) back to u while current <> u do - Add(edges, record.edge[current]); # edge = + Add(edges, record.edge[current]); current := record.parent[current]; Add(nodes, current); od; @@ -2046,7 +2045,7 @@ function(D, v) record := NewDFSRecord(D); data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), prev := 0); - AncestorFunc := function(record, data) + AncestorFunc := function(record, _) record.stop := true; end; PostOrderFunc := function(record, data) @@ -2059,8 +2058,8 @@ function(D, v) 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; + if IsBound(record.postorder[neighbours[i]]) then + Unbind(record.preorder[neighbours[i]]); fi; od; end; @@ -2467,13 +2466,12 @@ InstallMethod(IsOrderFilter, "for a digraph and a list of vertices", [IsDigraph, IsList], {D, roots} -> IsOrderIdeal(DigraphReverse(D), roots)); -InstallMethod(DominatorTree, "for a digraph and a vertex", + 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 @@ -2481,25 +2479,36 @@ function(D, root) "argument (a digraph)"); fi; - preorder_num_to_node := []; + node_to_preorder_num := []; + node_to_preorder_num[root] := 1; + preorder_num_to_node := [root]; - PreOrderFunc := function(record, data) - Add(data, record.current); - end; - - 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 := []; @@ -2545,7 +2554,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]; @@ -2573,6 +2582,123 @@ function(D, root) return rec(idom := idom, preorder := preorder_num_to_node); end); +# TODO Fix for hashmaps +# 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; + +# M := DigraphNrVertices(D); + +# if 0 = root or root > M then +# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", +# "argument (a digraph)"); +# fi; + +# preorder_num_to_node := []; + +# PreOrderFunc := function(record, data) +# Add(data, record.current); +# end; + +# record := NewDFSRecord(D); +# ExecuteDFS(record, +# preorder_num_to_node, +# root, +# PreOrderFunc, +# DFSDefault, +# DFSDefault, +# DFSDefault); + +# parent := record.parent; +# Unbind(parent[root]); +# # parent[root] := fail; +# node_to_preorder_num := record.preorder; + +# for i in [1..M] do +# if not IsBound(node_to_preorder_num[i]) then +# node_to_preorder_num[i] := -1; +# fi; +# if not IsBound(parent[i]) then +# parent[i] := -1; +# fi; +# od; + +# semi := [1 .. M]; +# lastlinked := M + 1; +# label := []; +# bucket := List([1 .. M], x -> []); +# idom := []; +# idom[root] := root; + +# compress := function(v) +# local u; +# u := parent[v]; +# if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= +# node_to_preorder_num[lastlinked] then +# compress(u); +# if node_to_preorder_num[semi[label[u]]] +# < node_to_preorder_num[semi[label[v]]] then +# label[v] := label[u]; +# fi; +# parent[v] := parent[u]; +# fi; +# end; + +# eval := function(v) +# if lastlinked <= M and node_to_preorder_num[v] >= +# node_to_preorder_num[lastlinked] then +# compress(v); +# return label[v]; +# else +# return v; +# fi; +# end; + +# pred := InNeighbours(D); +# N := Length(preorder_num_to_node); +# for i in [N, N - 1 .. 2] do +# w := preorder_num_to_node[i]; +# for v in bucket[w] do +# y := eval(v); +# if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then +# idom[v] := y; +# else +# idom[v] := w; +# fi; +# od; +# bucket[w] := []; +# for v in pred[w] do +# 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]; +# fi; +# fi; +# od; +# if parent[w] = semi[w] then +# idom[w] := parent[w]; +# else +# Add(bucket[semi[w]], w); +# fi; +# lastlinked := w; +# label[w] := semi[w]; +# od; +# for v in bucket[root] do +# idom[v] := root; +# od; +# for i in [2 .. N] do +# w := preorder_num_to_node[i]; +# if idom[w] <> semi[w] then +# idom[w] := idom[semi[w]]; +# fi; +# od; +# idom[root] := fail; +# return rec(idom := idom, preorder := preorder_num_to_node); +# end); + InstallMethod(Dominators, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) @@ -2808,16 +2934,16 @@ 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 := HashMap(); + record.preorder := HashMap(); + record.postorder := HashMap(); record.edge := HashMap(); return record; end); InstallMethod(DFSDefault, "for a record and an object", [IsRecord, IsObject], -function(record, data) +function(record, data) # gaplint: disable=W046 end); # * PreOrderFunc is called with (record, data) when a vertex is popped from the @@ -2843,3 +2969,16 @@ function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); + +InstallGlobalFunction(ExecuteDFSIter, # TODO remove? +function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, + CrossFunc) + if not IsEqualSet(RecNames(record), + ["stop", "graph", "child", "parent", "preorder", + "postorder", "current", "edge"]) then + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); + fi; + ExecuteDFSIter_C(record, data, start, PreOrderFunc, PostOrderFunc, + AncestorFunc, CrossFunc); +end); diff --git a/gap/prop.gi b/gap/prop.gi index 168714f8e..e80182a61 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -237,9 +237,9 @@ IsStronglyConnectedDigraph, 0, D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", -[IsDigraphByOutNeighboursRep], +[IsDigraphByOutNeighboursRep], # TODO call DFS for each component function(D) - local n, i, record, D2, PostOrderFunc, PreOrderFunc, data, edge, AncestorFunc, + local n, i, record, D2, AncestorFunc, edges; n := DigraphNrVertices(D); if n = 0 then @@ -252,7 +252,7 @@ function(D) D2 := DigraphAddVertex(D2); edges := []; - for i in [1..n] do + for i in [1 .. n] do Add(edges, [n + 1, i]); od; @@ -261,30 +261,14 @@ function(D) record := NewDFSRecord(D2); # Starts at the new vertex - ## data := rec(start := [], last := [], clock := 0); - - # PreOrderFunc := function(record, data) - # data.start[record.current] := data.clock; - # data.clock := data.clock + 1; - # end; - - # PostOrderFunc := function(record, data) - # data.last[record.child] := data.clock; - # data.clock := data.clock + 1; - # end; - AncestorFunc := function(record, data) + AncestorFunc := function(record, _) record.stop := true; end; - ExecuteDFS(record, [], n + 1, DFSDefault, DFSDefault, AncestorFunc, DFSDefault); - # ExecuteDFS(record, data, n + 1, PreOrderFunc, - # PostOrderFunc, DFSDefault, DFSDefault); - - # return not record.stop; return not record.stop; # for edge in DigraphEdges(D2) do diff --git a/src/dfs.c b/src/dfs.c index bb3c99d06..521aa87a1 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -22,6 +22,13 @@ #include "digraphs.h" #include "safemalloc.h" +// Datastructures Functions +extern Obj DS_Hash_SetValue; +extern Obj DS_Hash_Lookup; +extern Obj DS_Hash_Contains; +extern Obj DS_Hash_Value; +extern Obj IsBound; + // Extreme examples are on the pull request #459 bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { @@ -33,128 +40,15 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -/* Obj FuncExecuteDFS_C(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(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); */ - -/* 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; // referencing the last element in stack */ -/* // Length of stack fixed, since no vertices are added to it more than once */ -/* Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); */ - -/* AssPlist(stack, ++top, start); */ - -/* UInt preorder_num = 1; */ -/* 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")); */ -/* // 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 */ - -/* ASS_LIST(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"); */ - -/* if (ElmPRec(record, RNamStop) == True) return record; */ - -/* while (top > 0) { */ -/* // visit current */ -/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node */ -/* if (current < 0) { */ -/* Int child = current * -1; */ -/* // backtracking on current */ -/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ -/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); */ -/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ -/* CHANGED_BAG(record); */ -/* if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { */ -/* return record; */ -/* } */ -/* continue; */ -/* } */ - -/* if (INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) continue; */ - -/* // otherwise, visit this node */ - -/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ -/* ASS_LIST(preorder, current, INTOBJ_INT(preorder_num++)); */ -/* CHANGED_BAG(record); */ - -/* if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { */ -/* return record; */ -/* } */ - -/* // Add back to the stack for backtracking */ -/* ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); */ -/* CHANGED_BAG(record); */ - -/* Obj succ = ELM_PLIST(neighbors, current); */ -/* for (UInt j = LEN_LIST(succ); j >= 1; j--) { */ -/* // Push so that the top of the stack is the first vertex in succ */ -/* UInt v = INT_INTOBJ(ELM_LIST(succ, j)); */ -/* bool visited = INT_INTOBJ(ELM_PLIST(preorder, v)) != -1; */ -/* AssPRec(record, RNamChild, INTOBJ_INT(v)); */ -/* CHANGED_BAG(record); */ - -/* if (!visited) { // v is unvisited */ -/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ -/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ -/* } */ -/* else { // v is either visited, or in the stack to be visited */ -/* // If v was visited prior, but has not been backtracked on */ -/* bool backtracked = INT_INTOBJ(ELM_PLIST(postorder, v)) != -1; */ -/* if (!backtracked) { // Back edge */ -/* if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { */ -/* return record; */ -/* } */ -/* } */ -/* // v has been visited and backtracked on */ -/* else { */ -/* if (CallCheckStop(CrossFunc, RNamStop, record, data)) { */ -/* return record; */ -/* } */ -/* } */ -/* } */ -/* } */ -/* } */ -/* return record; */ -/* } */ +#define HASH_SET(map, key, val) \ + CALL_3ARGS(DS_Hash_SetValue, map, key, val) \ +Obj HASH_GET(Obj map, Obj key) { + return CALL_2ARGS(DS_Hash_Value, map, key); +} +bool HASH_CONTAINS(Obj map, Obj key) { + return CALL_2ARGS(DS_Hash_Contains, map, key) == True; +} struct dfs_args { Int RNamChild; @@ -168,6 +62,7 @@ struct dfs_args { Obj parent; Obj postorder; Obj preorder; + Obj edge; Obj neighbors; @@ -190,17 +85,18 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { Obj succ = ELM_PLIST(args -> neighbors, current); for (UInt j = 1; j <= LEN_LIST(succ); j++) { UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; - AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); - CHANGED_BAG(args -> record); + bool visited = HASH_CONTAINS(args -> preorder, INTOBJ_INT(v)); if (!visited) { - ASS_LIST(args -> parent, v, INTOBJ_INT(current)); + HASH_SET(args -> parent, INTOBJ_INT(v), INTOBJ_INT(current)); + HASH_SET(args -> edge, INTOBJ_INT(v), INTOBJ_INT(j)); bool rec_res = ExecuteDFSRec(v, args); if (!rec_res) return false; // Stop } else { - bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; + AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); + CHANGED_BAG(args -> record); + bool backtracked = HASH_CONTAINS(args -> postorder, INTOBJ_INT(v)); if (!backtracked) { // Back edge if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, args -> record, args -> data)) { return false; @@ -217,8 +113,8 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { // backtracking on current AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); - AssPRec(args -> record, args -> RNamCurrent, ELM_PLIST(args -> parent, current)); - ASS_LIST(args -> postorder, current, INTOBJ_INT(++(*args -> postorder_num))); + AssPRec(args -> record, args -> RNamCurrent, HASH_GET(args -> parent, INTOBJ_INT(current))); + HASH_SET(args -> postorder, INTOBJ_INT(current), INTOBJ_INT(++(*args -> postorder_num))); CHANGED_BAG(args -> record); if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, args -> data)) { return false; // Stop execution @@ -226,6 +122,7 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { return true; // Continue } + Obj FuncExecuteDFS_C(Obj self, Obj args) { DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); @@ -236,6 +133,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Obj AncestorFunc = ELM_PLIST(args, 6); Obj CrossFunc = ELM_PLIST(args, 7); + DIGRAPHS_ASSERT(IS_PREC(record)); DIGRAPHS_ASSERT(IS_INTOBJ(start)); // DIGRAPHS_ASSERT(NARG_FUNC(PreorderFunc) == 2); @@ -273,6 +171,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { rec_args -> parent = ElmPRec(record, RNamName("parent")); rec_args -> postorder = ElmPRec(record, RNamName("postorder")); rec_args -> preorder = ElmPRec(record, RNamName("preorder")); + rec_args -> edge = ElmPRec(record, RNamName("edge")); rec_args -> neighbors = FuncOutNeighbours(self, D); rec_args -> data = data; rec_args -> PreorderFunc = PreorderFunc; @@ -280,7 +179,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { rec_args -> CrossFunc = CrossFunc; rec_args -> AncestorFunc = AncestorFunc; - ASS_LIST(rec_args -> parent, INT_INTOBJ(start), start); + HASH_SET(rec_args -> parent, start, start); DIGRAPHS_ASSERT(IS_PLIST(rec_args -> neighbors)); UInt current = INT_INTOBJ(start); @@ -290,3 +189,127 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { return record; } + +// Adapted Old Iterative Function +Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? + 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(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); + + 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; // referencing the last element in stack + // Length of stack fixed, since no vertices are added to it more than once + Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + + AssPlist(stack, ++top, start); + + UInt preorder_num = 1; + 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")); + 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 + + HASH_SET(parent, start, start); + + Obj neighbors = FuncOutNeighbours(self, D); + DIGRAPHS_ASSERT(IS_PLIST(neighbors)); + + Int RNamChild = RNamName("child"); + Int RNamCurrent = RNamName("current"); + Int RNamStop = RNamName("stop"); + + if (ElmPRec(record, RNamStop) == True) return record; + + while (top > 0) { + // visit current + current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node + if (current < 0) { + Int child = current * -1; + // backtracking on current + AssPRec(record, RNamChild, INTOBJ_INT(child)); + AssPRec(record, RNamCurrent, HASH_GET(parent, INTOBJ_INT(child))); + HASH_SET(postorder, INTOBJ_INT(child), INTOBJ_INT(++postorder_num)); + CHANGED_BAG(record); + if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { + return record; + } + continue; + } + + if (INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(current))) != -1) continue; + + // otherwise, visit this node + + AssPRec(record, RNamCurrent, INTOBJ_INT(current)); + HASH_SET(preorder, INTOBJ_INT(current), INTOBJ_INT(preorder_num++)); + CHANGED_BAG(record); + + if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { + return record; + } + + // Add back to the stack for backtracking + ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); + CHANGED_BAG(record); + + Obj succ = ELM_PLIST(neighbors, current); + for (UInt j = LEN_LIST(succ); j >= 1; j--) { + // Push so that the top of the stack is the first vertex in succ + UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + bool visited = INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(v))) != -1; + AssPRec(record, RNamChild, INTOBJ_INT(v)); + CHANGED_BAG(record); + + if (!visited) { // v is unvisited + HASH_SET(parent, INTOBJ_INT(v), INTOBJ_INT(current)); + HASH_SET(edge, INTOBJ_INT(v), INTOBJ_INT(j)); + ASS_LIST(stack, ++top, INTOBJ_INT(v)); + } + else { // v is either visited, or in the stack to be visited + // If v was visited prior, but has not been backtracked on + bool backtracked = INT_INTOBJ(HASH_GET(postorder, INTOBJ_INT(v))) != -1; + if (!backtracked) { // Back edge + if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { + return record; + } + } + // v has been visited and backtracked on + else { + if (CallCheckStop(CrossFunc, RNamStop, record, data)) { + return record; + } + } + } + } + } + return record; +} diff --git a/src/dfs.h b/src/dfs.h index 535d19b59..c5071a740 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -17,5 +17,6 @@ #include "gap-includes.h" // for Obj, Int Obj FuncExecuteDFS_C(Obj self, Obj args); +Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? #endif // DIGRAPHS_SRC_DFS_H_ diff --git a/src/digraphs.c b/src/digraphs.c index 3df20cab8..96c333e83 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -60,6 +60,13 @@ Obj Group; Obj ClosureGroup; Obj InfoWarning; +// Added for HashMap (DFS) + +Obj DS_Hash_SetValue; +Obj DS_Hash_Lookup; +Obj DS_Hash_Contains; +Obj DS_Hash_Value; + static inline bool IsAttributeStoringRep(Obj o) { return (CALL_1ARGS(IsAttributeStoringRepObj, o) == True ? true : false); } @@ -2227,6 +2234,10 @@ static StructGVarFunc GVarFuncs[] = { -1, "record, data, start, PreOrderFunc, PostOrderFunc, " "AncestorFunc, CrossFunc"), + GVAR_FUNC(ExecuteDFSIter_C, + -1, + "record, data, start, PreOrderFunc, PostOrderFunc, " + "AncestorFunc, CrossFunc"), {0, 0, 0, 0, 0} /* Finish with an empty entry */ }; @@ -2260,6 +2271,12 @@ static Int InitKernel(StructInitInfo* module) { ImportGVarFromLibrary("Group", &Group); ImportGVarFromLibrary("ClosureGroup", &ClosureGroup); ImportGVarFromLibrary("InfoWarning", &InfoWarning); + // For DFS + ImportGVarFromLibrary("DS_Hash_SetValue", &DS_Hash_SetValue); + ImportGVarFromLibrary("DS_Hash_Lookup", &DS_Hash_Lookup); + ImportGVarFromLibrary("DS_Hash_Contains", &DS_Hash_Contains); + ImportGVarFromLibrary("DS_Hash_Value", &DS_Hash_Value); +Obj DS_Hash_GetValue; /* return success */ return 0; } diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index a8ca2d530..ef92d17e2 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3239,29 +3239,40 @@ gap> DigraphVertexLabels(D); # NewDFSRecord gap> NewDFSRecord(ChainDigraph(10)); rec( child := -1, current := -1, edge := HashMap([]), - 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 ) + graph := , parent := HashMap([]), + postorder := HashMap([]), preorder := HashMap([]), stop := false ) gap> NewDFSRecord(CompleteDigraph(2)); rec( child := -1, current := -1, edge := HashMap([]), - graph := , parent := [ -1, -1 ], - postorder := [ -1, -1 ], preorder := [ -1, -1 ], stop := false ) + graph := , + parent := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), + stop := false ) gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); rec( child := -1, current := -1, edge := HashMap([]), graph := , - parent := [ -1, -1, -1, -1, -1 ], postorder := [ -1, -1, -1, -1, -1 ], - preorder := [ -1, -1, -1, -1, -1 ], stop := false ) + parent := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), + stop := false ) # DFSDefault gap> DFSDefault(rec(), []); gap> DFSDefault(rec(), rec()); # ExecuteDFS +gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for +> local i; +> for i in [1..len] do # printing +> if not IsBound(map[i]) then +> Add(list, -1); +> else +> Add(list, map[i]); +> fi; +> od; +> end;; gap> record := NewDFSRecord(CompleteDigraph(10));; gap> ExecuteDFS(record, [], 2, DFSDefault, > DFSDefault, DFSDefault, DFSDefault); -gap> record.preorder; +gap> preorder_list := [];; +gap> mapToList(record.preorder, 10, preorder_list);; +gap> preorder_list; [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ] gap> record := NewDFSRecord(CompleteDigraph(15));; gap> data := rec(cycle_vertex := 0);; @@ -3275,7 +3286,9 @@ gap> record.stop; true gap> data.cycle_vertex; 1 -gap> record.preorder; +gap> preorder_list := [];; +gap> mapToList(record.preorder, 15, preorder_list);; +gap> preorder_list; [ 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ] gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; gap> CrossFunc := function(record, data) @@ -3290,17 +3303,24 @@ true gap> data; [ 4 ] gap> AncestorFunc := function(record, data) -> Add(data.cycle_vertex, record.child); +> Add(data.back_edges, [record.current, record.child]); > end;; gap> CrossFunc := function(record, data) -> Add(data.cross_vertex, record.child); +> Add(data.cross_edges, [record.current, record.child]); > end;; gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; -gap> data := rec(cycle_vertex := [], cross_vertex := []);; +gap> data := rec(back_edges := [], cross_edges := []);; gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, AncestorFunc, CrossFunc); +> DFSDefault, AncestorFunc, CrossFunc);; +gap> data; +rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], + cross_edges := [ [ 2, 4 ], [ 5, 4 ], [ 1, 3 ] ] ) +gap> record := NewDFSRecord(Digraph([[2, 3], [3], []]));; +gap> data := rec(back_edges := [], cross_edges := []);; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, AncestorFunc, CrossFunc);; gap> data; -rec( cross_vertex := [ 4 ], cycle_vertex := [ 1, 1 ] ) +rec( back_edges := [ ], cross_edges := [ [ 1, 3 ] ] ) gap> ExecuteDFS(rec(), data, 1, DFSDefault, > DFSDefault, AncestorFunc, CrossFunc); Error, the 1st argument must be created with NewDFSRecord, From f367047f7c55aac7ecd30adb3858f31327efa85b Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 14:01:35 +0000 Subject: [PATCH 09/54] Fix lint errors --- src/dfs.c | 60 +++++++++++++++++++++---------------------- src/dfs.h | 2 +- tst/standard/oper.tst | 4 +-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 521aa87a1..d5ed68c39 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -78,7 +78,8 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); CHANGED_BAG(args -> record); - if (CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, args -> data)) { + if (CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, + args -> data)) { return args -> record; } @@ -91,20 +92,19 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { HASH_SET(args -> parent, INTOBJ_INT(v), INTOBJ_INT(current)); HASH_SET(args -> edge, INTOBJ_INT(v), INTOBJ_INT(j)); bool rec_res = ExecuteDFSRec(v, args); - if (!rec_res) return false; // Stop - } - else { + if (!rec_res) return false; // Stop + } else { AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); CHANGED_BAG(args -> record); bool backtracked = HASH_CONTAINS(args -> postorder, INTOBJ_INT(v)); - if (!backtracked) { // Back edge - if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, args -> record, args -> data)) { + if (!backtracked) { // Back edge + if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, + args -> record, args -> data)) { return false; } - } - // v has been visited and backtracked on - else { - if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, args -> data)) { + } else { // v has been visited and backtracked on + if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, + args -> data)) { return false; } } @@ -113,13 +113,16 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { // backtracking on current AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); - AssPRec(args -> record, args -> RNamCurrent, HASH_GET(args -> parent, INTOBJ_INT(current))); - HASH_SET(args -> postorder, INTOBJ_INT(current), INTOBJ_INT(++(*args -> postorder_num))); + AssPRec(args -> record, args -> RNamCurrent, HASH_GET(args -> parent, + INTOBJ_INT(current))); + HASH_SET(args -> postorder, INTOBJ_INT(current), + INTOBJ_INT(++(*args -> postorder_num))); CHANGED_BAG(args -> record); - if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, args -> data)) { - return false; // Stop execution + if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, + args -> data)) { + return false; // Stop execution } - return true; // Continue + return true; // Continue } @@ -157,7 +160,8 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ElmPRec(record, RNamStop) == True) return record; - struct dfs_args* rec_args = (struct dfs_args*) safe_malloc(sizeof(struct dfs_args)); + struct dfs_args* rec_args = + (struct dfs_args*) safe_malloc(sizeof(struct dfs_args)); UInt preorder_num = 1; UInt postorder_num = 0; @@ -187,11 +191,10 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { free(rec_args); return record; - } // Adapted Old Iterative Function -Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? +Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); Obj data = ELM_PLIST(args, 2); @@ -219,9 +222,8 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? ErrorQuit( "the third argument must be a vertex in your graph,", 0L, 0L); } - Int top = 0; // referencing the last element in stack - // Length of stack fixed, since no vertices are added to it more than once - Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + Int top = 0; // referencing the last element in stack + Obj stack = NEW_PLIST(T_PLIST_CYC, N); AssPlist(stack, ++top, start); @@ -251,7 +253,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? while (top > 0) { // visit current - current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node + current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node if (current < 0) { Int child = current * -1; // backtracking on current @@ -289,21 +291,19 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? AssPRec(record, RNamChild, INTOBJ_INT(v)); CHANGED_BAG(record); - if (!visited) { // v is unvisited + if (!visited) { // v is unvisited HASH_SET(parent, INTOBJ_INT(v), INTOBJ_INT(current)); HASH_SET(edge, INTOBJ_INT(v), INTOBJ_INT(j)); ASS_LIST(stack, ++top, INTOBJ_INT(v)); - } - else { // v is either visited, or in the stack to be visited + } else { // v is either visited, or in the stack to be visited // If v was visited prior, but has not been backtracked on - bool backtracked = INT_INTOBJ(HASH_GET(postorder, INTOBJ_INT(v))) != -1; - if (!backtracked) { // Back edge + bool backtracked = INT_INTOBJ(HASH_GET(postorder, + INTOBJ_INT(v))) != -1; + if (!backtracked) { // Back edge if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { return record; } - } - // v has been visited and backtracked on - else { + } else { // v has been visited and backtracked on if (CallCheckStop(CrossFunc, RNamStop, record, data)) { return record; } diff --git a/src/dfs.h b/src/dfs.h index c5071a740..94cc145d6 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -17,6 +17,6 @@ #include "gap-includes.h" // for Obj, Int Obj FuncExecuteDFS_C(Obj self, Obj args); -Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? +Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? #endif // DIGRAPHS_SRC_DFS_H_ diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index ef92d17e2..9e16503fc 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3257,9 +3257,9 @@ gap> DFSDefault(rec(), []); gap> DFSDefault(rec(), rec()); # ExecuteDFS -gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for +gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for > local i; -> for i in [1..len] do # printing +> for i in [ 1..len ] do # printing > if not IsBound(map[i]) then > Add(list, -1); > else From 99955c64ec120adc501d837f156e080691c4d0cc Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 15:51:03 +0000 Subject: [PATCH 10/54] Fix wrong result from DigraphLongestDistanceFromVertex and CrossFunc calls --- gap/oper.gi | 16 ++++++++++------ src/dfs.c | 15 +++++++++------ tst/standard/oper.tst | 12 ++++-------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index fb8355caa..019e77535 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2044,17 +2044,20 @@ function(D, v) fi; record := NewDFSRecord(D); data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), - prev := 0); + prev := -1, best := 0); AncestorFunc := function(record, _) record.stop := true; end; - PostOrderFunc := function(record, data) - data.depth[record.current] := data.prev; - data.prev := data.prev + 1; + PostOrderFunc := function(_, data) + # data.depth[record.current] := data.prev; + data.prev := data.prev - 1; end; PreOrderFunc := function(record, data) local i, neighbours; - data.prev := 0; + data.prev := data.prev + 1; + if data.prev > data.best then + data.best := data.prev; + fi; neighbours := OutNeighborsOfVertex(record.graph, record.current); for i in [1 .. Size(neighbours)] do # need to bypass the CrossFunc @@ -2069,7 +2072,8 @@ function(D, v) if record.stop then return infinity; fi; - return data.depth[v]; + return data.best; + end); InstallMethod(DigraphRandomWalk, diff --git a/src/dfs.c b/src/dfs.c index d5ed68c39..ea71bbad7 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -102,7 +102,9 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { args -> record, args -> data)) { return false; } - } else { // v has been visited and backtracked on + } else if (INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(v))) + < INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(current)))) { + // v was visited before current if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, args -> data)) { return false; @@ -267,7 +269,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? continue; } - if (INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(current))) != -1) continue; + if (HASH_CONTAINS(preorder, INTOBJ_INT(current))) continue; // otherwise, visit this node @@ -287,7 +289,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? for (UInt j = LEN_LIST(succ); j >= 1; j--) { // Push so that the top of the stack is the first vertex in succ UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - bool visited = INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(v))) != -1; + bool visited = HASH_CONTAINS(preorder, INTOBJ_INT(v)); AssPRec(record, RNamChild, INTOBJ_INT(v)); CHANGED_BAG(record); @@ -297,13 +299,14 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? ASS_LIST(stack, ++top, INTOBJ_INT(v)); } else { // v is either visited, or in the stack to be visited // If v was visited prior, but has not been backtracked on - bool backtracked = INT_INTOBJ(HASH_GET(postorder, - INTOBJ_INT(v))) != -1; + bool backtracked = HASH_CONTAINS(postorder, INTOBJ_INT(v)); if (!backtracked) { // Back edge if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { return record; } - } else { // v has been visited and backtracked on + } else if (INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(v))) + < INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(current)))) { + // v was visited before current if (CallCheckStop(CrossFunc, RNamStop, record, data)) { return record; } diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 9e16503fc..b00d10a07 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1446,6 +1446,9 @@ gap> DigraphLongestDistanceFromVertex(gr, 15); infinity gap> DigraphLongestDistanceFromVertex(gr, 16); Error, the 2nd argument must be a vertex of the 1st argument , +gap> D := Digraph([[2, 4], [3, 4], [5], [], []]);; +gap> DigraphLongestDistanceFromVertex(D, 1); +3 # DigraphRandomWalk gap> gr := CompleteDigraph(5); @@ -3313,14 +3316,7 @@ gap> data := rec(back_edges := [], cross_edges := []);; gap> ExecuteDFS(record, data, 1, DFSDefault, > DFSDefault, AncestorFunc, CrossFunc);; gap> data; -rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], - cross_edges := [ [ 2, 4 ], [ 5, 4 ], [ 1, 3 ] ] ) -gap> record := NewDFSRecord(Digraph([[2, 3], [3], []]));; -gap> data := rec(back_edges := [], cross_edges := []);; -gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, AncestorFunc, CrossFunc);; -gap> data; -rec( back_edges := [ ], cross_edges := [ [ 1, 3 ] ] ) +rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] ) gap> ExecuteDFS(rec(), data, 1, DFSDefault, > DFSDefault, AncestorFunc, CrossFunc); Error, the 1st argument must be created with NewDFSRecord, From bf7eb4fefc62dda32b46a51f43ee8576a905d3a5 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 15:54:04 +0000 Subject: [PATCH 11/54] Fix missed gaplint errors --- tst/standard/oper.tst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index b00d10a07..a2a8a9cd7 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3262,7 +3262,7 @@ gap> DFSDefault(rec(), rec()); # ExecuteDFS gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for > local i; -> for i in [ 1..len ] do # printing +> for i in [1 .. len] do # printing > if not IsBound(map[i]) then > Add(list, -1); > else From 97d981dcd1a31ef7b8bd6e7b42b8afa5efcf1515 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 16:12:32 +0000 Subject: [PATCH 12/54] Fix compile warnings --- src/dfs.c | 4 ++-- src/dfs.h | 5 +++++ src/digraphs.c | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index ea71bbad7..35636bf82 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -84,7 +84,7 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { } Obj succ = ELM_PLIST(args -> neighbors, current); - for (UInt j = 1; j <= LEN_LIST(succ); j++) { + for (Int j = 1; j <= LEN_LIST(succ); j++) { UInt v = INT_INTOBJ(ELM_LIST(succ, j)); bool visited = HASH_CONTAINS(args -> preorder, INTOBJ_INT(v)); @@ -286,7 +286,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? CHANGED_BAG(record); Obj succ = ELM_PLIST(neighbors, current); - for (UInt j = LEN_LIST(succ); j >= 1; j--) { + for (Int j = LEN_LIST(succ); j >= 1; j--) { // Push so that the top of the stack is the first vertex in succ UInt v = INT_INTOBJ(ELM_LIST(succ, j)); bool visited = HASH_CONTAINS(preorder, INTOBJ_INT(v)); diff --git a/src/dfs.h b/src/dfs.h index 94cc145d6..283edce1b 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -15,6 +15,11 @@ // GAP headers #include "gap-includes.h" // for Obj, Int +#include // for false, true, bool + +bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); +Obj HASH_GET(Obj map, Obj key); +bool HASH_CONTAINS(Obj map, Obj key); Obj FuncExecuteDFS_C(Obj self, Obj args); Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? diff --git a/src/digraphs.c b/src/digraphs.c index 96c333e83..2069b6680 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -2276,7 +2276,6 @@ static Int InitKernel(StructInitInfo* module) { ImportGVarFromLibrary("DS_Hash_Lookup", &DS_Hash_Lookup); ImportGVarFromLibrary("DS_Hash_Contains", &DS_Hash_Contains); ImportGVarFromLibrary("DS_Hash_Value", &DS_Hash_Value); -Obj DS_Hash_GetValue; /* return success */ return 0; } From 091a150320cb330c4195c7a921c5ad057431cc13 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 16:18:32 +0000 Subject: [PATCH 13/54] Fix include order --- src/dfs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dfs.h b/src/dfs.h index 283edce1b..91cebbd9f 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -13,9 +13,9 @@ #ifndef DIGRAPHS_SRC_DFS_H_ #define DIGRAPHS_SRC_DFS_H_ +#include // for false, true, bool // GAP headers #include "gap-includes.h" // for Obj, Int -#include // for false, true, bool bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); Obj HASH_GET(Obj map, Obj key); From 5febb4f70506a113e376116d312621d9bd759fe3 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 4 Mar 2025 16:39:52 +0000 Subject: [PATCH 14/54] Fix compile warnings (2) --- src/dfs.c | 25 +------------------------ src/dfs.h | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 35636bf82..80a9ec8c8 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -12,7 +12,6 @@ #include "dfs.h" -#include // for false, true, bool #include // for uint64_t #include // for NULL, free @@ -22,6 +21,7 @@ #include "digraphs.h" #include "safemalloc.h" + // Datastructures Functions extern Obj DS_Hash_SetValue; extern Obj DS_Hash_Lookup; @@ -50,29 +50,6 @@ bool HASH_CONTAINS(Obj map, Obj key) { return CALL_2ARGS(DS_Hash_Contains, map, key) == True; } -struct dfs_args { - Int RNamChild; - Int RNamCurrent; - Int RNamStop; - - Obj record; - UInt* preorder_num; - UInt* postorder_num; - - Obj parent; - Obj postorder; - Obj preorder; - Obj edge; - - Obj neighbors; - - Obj data; - Obj PreorderFunc; - Obj PostorderFunc; - Obj AncestorFunc; - Obj CrossFunc; -}; - bool ExecuteDFSRec(UInt current, struct dfs_args* args) { AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); diff --git a/src/dfs.h b/src/dfs.h index 91cebbd9f..2e00a55af 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -21,6 +21,30 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); Obj HASH_GET(Obj map, Obj key); bool HASH_CONTAINS(Obj map, Obj key); +struct dfs_args { + Int RNamChild; + Int RNamCurrent; + Int RNamStop; + + Obj record; + UInt* preorder_num; + UInt* postorder_num; + + Obj parent; + Obj postorder; + Obj preorder; + Obj edge; + + Obj neighbors; + + Obj data; + Obj PreorderFunc; + Obj PostorderFunc; + Obj AncestorFunc; + Obj CrossFunc; +}; + +bool ExecuteDFSRec(UInt current, struct dfs_args* args); Obj FuncExecuteDFS_C(Obj self, Obj args); Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? From c9c2da4cb34eb5a8d38db4c0d26e6168bc2cbaee Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 5 Mar 2025 15:44:12 +0000 Subject: [PATCH 15/54] Add back DominatorTree --- gap/oper.gi | 206 ++++++++++++---------------------------------------- gap/prop.gi | 46 ++++-------- src/dfs.c | 20 ++--- src/dfs.h | 2 - 4 files changed, 68 insertions(+), 206 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 019e77535..a162ef075 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2470,12 +2470,21 @@ InstallMethod(IsOrderFilter, "for a digraph and a list of vertices", [IsDigraph, IsList], {D, roots} -> IsOrderIdeal(DigraphReverse(D), roots)); - InstallMethod(DominatorTree, "for a digraph and a vertex", +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, map, key, hashGet; + + hashGet := function(map, key) + if not IsBound(map[key]) then + return -1; + else + return map[key]; + fi; + end; + M := DigraphNrVertices(D); if 0 = root or root > M then @@ -2483,36 +2492,26 @@ 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; + node_to_preorder_num := record.preorder; + + Unbind(parent[root]); - 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 := []; @@ -2522,21 +2521,21 @@ function(D, root) compress := function(v) local u; - u := parent[v]; - if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= - node_to_preorder_num[lastlinked] then + u := hashGet(parent, v); + if u <> fail and lastlinked <= M and hashGet(node_to_preorder_num, u) >= + hashGet(node_to_preorder_num, lastlinked) then compress(u); - if node_to_preorder_num[semi[label[u]]] - < node_to_preorder_num[semi[label[v]]] then + if hashGet(node_to_preorder_num, semi[label[u]]) + < hashGet(node_to_preorder_num, semi[label[v]]) then label[v] := label[u]; fi; - parent[v] := parent[u]; + parent[v] := hashGet(parent, u); fi; end; eval := function(v) - if lastlinked <= M and node_to_preorder_num[v] >= - node_to_preorder_num[lastlinked] then + if lastlinked <= M and hashGet(node_to_preorder_num, v) >= + hashGet(node_to_preorder_num, lastlinked) then compress(v); return label[v]; else @@ -2550,7 +2549,8 @@ function(D, root) w := preorder_num_to_node[i]; for v in bucket[w] do y := eval(v); - if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then + if hashGet(node_to_preorder_num, semi[y]) < + hashGet(node_to_preorder_num, w) then idom[v] := y; else idom[v] := w; @@ -2558,15 +2558,16 @@ function(D, root) od; bucket[w] := []; for v in pred[w] do - if IsBound(node_to_preorder_num[v]) then + if hashGet(node_to_preorder_num, v) <> -1 then x := eval(v); - if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then + if hashGet(node_to_preorder_num, semi[x]) < + hashGet(node_to_preorder_num, semi[w]) then semi[w] := semi[x]; fi; fi; od; - if parent[w] = semi[w] then - idom[w] := parent[w]; + if hashGet(parent, w) = semi[w] then + idom[w] := hashGet(parent, w); else Add(bucket[semi[w]], w); fi; @@ -2586,123 +2587,6 @@ function(D, root) return rec(idom := idom, preorder := preorder_num_to_node); end); -# TODO Fix for hashmaps -# 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; - -# M := DigraphNrVertices(D); - -# if 0 = root or root > M then -# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", -# "argument (a digraph)"); -# fi; - -# preorder_num_to_node := []; - -# PreOrderFunc := function(record, data) -# Add(data, record.current); -# end; - -# record := NewDFSRecord(D); -# ExecuteDFS(record, -# preorder_num_to_node, -# root, -# PreOrderFunc, -# DFSDefault, -# DFSDefault, -# DFSDefault); - -# parent := record.parent; -# Unbind(parent[root]); -# # parent[root] := fail; -# node_to_preorder_num := record.preorder; - -# for i in [1..M] do -# if not IsBound(node_to_preorder_num[i]) then -# node_to_preorder_num[i] := -1; -# fi; -# if not IsBound(parent[i]) then -# parent[i] := -1; -# fi; -# od; - -# semi := [1 .. M]; -# lastlinked := M + 1; -# label := []; -# bucket := List([1 .. M], x -> []); -# idom := []; -# idom[root] := root; - -# compress := function(v) -# local u; -# u := parent[v]; -# if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= -# node_to_preorder_num[lastlinked] then -# compress(u); -# if node_to_preorder_num[semi[label[u]]] -# < node_to_preorder_num[semi[label[v]]] then -# label[v] := label[u]; -# fi; -# parent[v] := parent[u]; -# fi; -# end; - -# eval := function(v) -# if lastlinked <= M and node_to_preorder_num[v] >= -# node_to_preorder_num[lastlinked] then -# compress(v); -# return label[v]; -# else -# return v; -# fi; -# end; - -# pred := InNeighbours(D); -# N := Length(preorder_num_to_node); -# for i in [N, N - 1 .. 2] do -# w := preorder_num_to_node[i]; -# for v in bucket[w] do -# y := eval(v); -# if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then -# idom[v] := y; -# else -# idom[v] := w; -# fi; -# od; -# bucket[w] := []; -# for v in pred[w] do -# 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]; -# fi; -# fi; -# od; -# if parent[w] = semi[w] then -# idom[w] := parent[w]; -# else -# Add(bucket[semi[w]], w); -# fi; -# lastlinked := w; -# label[w] := semi[w]; -# od; -# for v in bucket[root] do -# idom[v] := root; -# od; -# for i in [2 .. N] do -# w := preorder_num_to_node[i]; -# if idom[w] <> semi[w] then -# idom[w] := idom[semi[w]]; -# fi; -# od; -# idom[root] := fail; -# return rec(idom := idom, preorder := preorder_num_to_node); -# end); - InstallMethod(Dominators, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) diff --git a/gap/prop.gi b/gap/prop.gi index e80182a61..4be1cadb2 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -239,50 +239,30 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], # TODO call DFS for each component function(D) - local n, i, record, D2, AncestorFunc, - edges; + local n, i, record, AncestorFunc; n := DigraphNrVertices(D); if n = 0 then return true; fi; - # A Digraph is acyclic if it has no back edges - - D2 := MakeImmutable(DigraphMutableCopy(D)); - D2 := DigraphAddVertex(D2); - edges := []; - - for i in [1 .. n] do - Add(edges, [n + 1, i]); - od; - - D2 := DigraphAddEdges(D2, edges); - - record := NewDFSRecord(D2); - - # Starts at the new vertex + record := NewDFSRecord(D); + # A Digraph is acyclic if it has no back edges AncestorFunc := function(record, _) record.stop := true; end; - ExecuteDFS(record, [], n + 1, DFSDefault, - DFSDefault, AncestorFunc, DFSDefault); - - return not record.stop; - - # for edge in DigraphEdges(D2) do - # if edge[1] = edge[2] then - # return false; - # fi; - - # if data.start[edge[1]] > data.start[edge[2]] and - # data.last[edge[1]] < data.last[edge[2]] then - # return false; - # fi; - # od; + for i in [1 .. n] do + if not IsBound(record.preorder[i]) then + ExecuteDFS(record, [], i, DFSDefault, + DFSDefault, AncestorFunc, DFSDefault); - # return true; + if record.stop then + return false; + fi; + fi; + od; + return true; end); # Complexity O(number of edges) diff --git a/src/dfs.c b/src/dfs.c index 80a9ec8c8..242591c35 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -21,6 +21,16 @@ #include "digraphs.h" #include "safemalloc.h" +// Macros + +#define HASH_SET(map, key, val) \ + CALL_3ARGS(DS_Hash_SetValue, map, key, val) + +#define HASH_GET(map, key) \ + CALL_2ARGS(DS_Hash_Value, map, key) + +#define HASH_CONTAINS(map, key) \ + CALL_2ARGS(DS_Hash_Contains, map, key) == True // Datastructures Functions extern Obj DS_Hash_SetValue; @@ -40,16 +50,6 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -#define HASH_SET(map, key, val) \ - CALL_3ARGS(DS_Hash_SetValue, map, key, val) \ - -Obj HASH_GET(Obj map, Obj key) { - return CALL_2ARGS(DS_Hash_Value, map, key); -} -bool HASH_CONTAINS(Obj map, Obj key) { - return CALL_2ARGS(DS_Hash_Contains, map, key) == True; -} - bool ExecuteDFSRec(UInt current, struct dfs_args* args) { AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); diff --git a/src/dfs.h b/src/dfs.h index 2e00a55af..a11321d75 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -18,8 +18,6 @@ #include "gap-includes.h" // for Obj, Int bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); -Obj HASH_GET(Obj map, Obj key); -bool HASH_CONTAINS(Obj map, Obj key); struct dfs_args { Int RNamChild; From 8bdf4387369034f750f5dea20daa64293bdac881 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 12 Mar 2025 16:50:52 +0000 Subject: [PATCH 16/54] Change TopologicalSort to use DFS forest flag, and reserving hashmaps to improve performance --- gap/attr.gi | 32 +++++++-------- gap/oper.gd | 1 + gap/oper.gi | 53 +++++++++++++++++-------- gap/prop.gi | 2 +- src/dfs.c | 92 +++++++++++++++++++++++++++---------------- src/dfs.h | 6 ++- src/digraphs.c | 5 ++- tst/standard/oper.tst | 18 ++++----- 8 files changed, 130 insertions(+), 79 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index cedd4429a..59eac60f8 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -873,7 +873,7 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local N, record, count, out, PostOrderFunc, AncestorFunc, i; + local N, record, count, out, PostOrderFunc, AncestorFunc, flags; N := DigraphNrVertices(D); if N = 0 then @@ -891,21 +891,21 @@ function(D) record.stop := true; fi; end; - for i in DigraphVertices(D) do - if (IsBound(record.preorder[i])) then - continue; - fi; - ExecuteDFS(record, - fail, - i, - DFSDefault, - PostOrderFunc, - AncestorFunc, - DFSDefault); - if record.stop then - return fail; - fi; - od; + flags := NewDFSFlags(); + flags.forest := true; + + ExecuteDFS(record, + flags, + fail, + 1, + DFSDefault, + PostOrderFunc, + AncestorFunc, + DFSDefault); + if record.stop then + return fail; + fi; + return out; end); diff --git a/gap/oper.gd b/gap/oper.gd index 50d8a3014..fea1f4570 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -155,6 +155,7 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", # 11. DFS DeclareOperation("NewDFSRecord", [IsDigraph]); +DeclareOperation("NewDFSFlags", []); DeclareOperation("DFSDefault", [IsRecord, IsObject]); DeclareGlobalFunction("ExecuteDFS"); DeclareGlobalFunction("ExecuteDFSIter"); # TODO remove? diff --git a/gap/oper.gi b/gap/oper.gi index a162ef075..e7939a8ad 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1658,7 +1658,7 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current; + local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents; N := DigraphNrVertices(D); if u > N or v > N then @@ -1678,6 +1678,7 @@ function(D, u, v) or (HasInNeighbours(D) and InDegreeOfVertex(D, v) = 0) then return fail; fi; + record := NewDFSRecord(D); if u <> v then # if v is reachable from u, then u is an ancestor of v, and so at some @@ -1701,6 +1702,7 @@ function(D, u, v) end; fi; ExecuteDFS(record, + NewDFSFlags(), fail, u, PreOrderFunc, @@ -1722,7 +1724,7 @@ function(D, u, v) # 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]; + current := record.parents[current]; Add(nodes, current); od; return [Reversed(nodes), Reversed(edges)]; @@ -2066,7 +2068,7 @@ function(D, v) fi; od; end; - ExecuteDFS(record, data, v, + ExecuteDFS(record, NewDFSFlags(), data, v, PreOrderFunc, PostOrderFunc, AncestorFunc, DFSDefault); if record.stop then @@ -2396,6 +2398,7 @@ function(D, root) end; ExecuteDFS(record, + NewDFSFlags(), data, root, PreOrderFunc, @@ -2473,7 +2476,7 @@ 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, preorder_num_to_node, PreOrderFunc, record, parent, + local M, preorder_num_to_node, PreOrderFunc, record, parents, node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, pred, N, w, y, x, i, v, map, key, hashGet; @@ -2500,6 +2503,7 @@ function(D, root) record := NewDFSRecord(D); ExecuteDFS(record, + NewDFSFlags(), preorder_num_to_node, root, PreOrderFunc, @@ -2507,10 +2511,10 @@ function(D, root) DFSDefault, DFSDefault); - parent := record.parent; + parents := record.parents; node_to_preorder_num := record.preorder; - Unbind(parent[root]); + Unbind(parents[root]); semi := [1 .. M]; lastlinked := M + 1; @@ -2521,7 +2525,7 @@ function(D, root) compress := function(v) local u; - u := hashGet(parent, v); + u := hashGet(parents, v); if u <> fail and lastlinked <= M and hashGet(node_to_preorder_num, u) >= hashGet(node_to_preorder_num, lastlinked) then compress(u); @@ -2529,7 +2533,7 @@ function(D, root) < hashGet(node_to_preorder_num, semi[label[v]]) then label[v] := label[u]; fi; - parent[v] := hashGet(parent, u); + parents[v] := hashGet(parents, u); fi; end; @@ -2566,8 +2570,8 @@ function(D, root) fi; fi; od; - if hashGet(parent, w) = semi[w] then - idom[w] := hashGet(parent, w); + if hashGet(parents, w) = semi[w] then + idom[w] := hashGet(parents, w); else Add(bucket[semi[w]], w); fi; @@ -2822,13 +2826,22 @@ function(graph) record.child := -1; record.current := -1; record.stop := false; - record.parent := HashMap(); + record.parents := HashMap(); record.preorder := HashMap(); record.postorder := HashMap(); record.edge := HashMap(); return record; end); +InstallMethod(NewDFSFlags, +"", [], +function() + local record; + record := rec(); + record.forest := false; + return record; +end); + InstallMethod(DFSDefault, "for a record and an object", [IsRecord, IsObject], function(record, data) # gaplint: disable=W046 @@ -2845,16 +2858,24 @@ end); # 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, +function(record, flags, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) if not IsEqualSet(RecNames(record), - ["stop", "graph", "child", "parent", "preorder", + ["stop", "graph", "child", "parents", "preorder", "postorder", "current", "edge"]) then ErrorNoReturn("the 1st argument must be created with ", "NewDFSRecord,"); fi; - ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, + + if not IsEqualSet(RecNames(flags), + ["forest"]) then + ErrorNoReturn("the 2nd argument must be created with ", + "NewDFSFlags,"); + fi; + + ExecuteDFS_C(record, flags, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); @@ -2862,11 +2883,11 @@ InstallGlobalFunction(ExecuteDFSIter, # TODO remove? function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) if not IsEqualSet(RecNames(record), - ["stop", "graph", "child", "parent", "preorder", + ["stop", "graph", "child", "parents", "preorder", "postorder", "current", "edge"]) then ErrorNoReturn("the 1st argument must be created with ", "NewDFSRecord,"); fi; - ExecuteDFSIter_C(record, data, start, PreOrderFunc, PostOrderFunc, + ExecuteDFSIter_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); diff --git a/gap/prop.gi b/gap/prop.gi index 4be1cadb2..308568546 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -254,7 +254,7 @@ function(D) for i in [1 .. n] do if not IsBound(record.preorder[i]) then - ExecuteDFS(record, [], i, DFSDefault, + ExecuteDFS(record, NewDFSFlags(), [], i, DFSDefault, DFSDefault, AncestorFunc, DFSDefault); if record.stop then diff --git a/src/dfs.c b/src/dfs.c index 242591c35..fd98b2c4e 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -21,6 +21,13 @@ #include "digraphs.h" #include "safemalloc.h" +// Datastructures Functions +extern Obj DS_Hash_SetValue; +extern Obj DS_Hash_Contains; +extern Obj DS_Hash_Value; +extern Obj IsBound; +extern Obj DS_Hash_Reserve; + // Macros #define HASH_SET(map, key, val) \ @@ -32,28 +39,28 @@ #define HASH_CONTAINS(map, key) \ CALL_2ARGS(DS_Hash_Contains, map, key) == True -// Datastructures Functions -extern Obj DS_Hash_SetValue; -extern Obj DS_Hash_Lookup; -extern Obj DS_Hash_Contains; -extern Obj DS_Hash_Value; -extern Obj IsBound; +#define HASH_RESERVE(map, capacity) \ + CALL_2ARGS(DS_Hash_Reserve, map, capacity) + +// Global Variables // Extreme examples are on the pull request #459 bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { CALL_2ARGS(f, record, data); if (ElmPRec(record, RNamStop) == True) { - CHANGED_BAG(record); + // CHANGED_BAG(record); return true; } return false; } -bool ExecuteDFSRec(UInt current, struct dfs_args* args) { +bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args) { + // TODO add parents field in record? with prev as parents + AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); - CHANGED_BAG(args -> record); + // CHANGED_BAG(args -> record); if (CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, args -> data)) { @@ -66,13 +73,13 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { bool visited = HASH_CONTAINS(args -> preorder, INTOBJ_INT(v)); if (!visited) { - HASH_SET(args -> parent, INTOBJ_INT(v), INTOBJ_INT(current)); + HASH_SET(args -> parents, INTOBJ_INT(v), INTOBJ_INT(current)); HASH_SET(args -> edge, INTOBJ_INT(v), INTOBJ_INT(j)); - bool rec_res = ExecuteDFSRec(v, args); + bool rec_res = ExecuteDFSRec(v, current, args); if (!rec_res) return false; // Stop } else { AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); - CHANGED_BAG(args -> record); + // CHANGED_BAG(args -> record); bool backtracked = HASH_CONTAINS(args -> postorder, INTOBJ_INT(v)); if (!backtracked) { // Back edge if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, @@ -92,11 +99,10 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { // backtracking on current AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); - AssPRec(args -> record, args -> RNamCurrent, HASH_GET(args -> parent, - INTOBJ_INT(current))); + AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); HASH_SET(args -> postorder, INTOBJ_INT(current), INTOBJ_INT(++(*args -> postorder_num))); - CHANGED_BAG(args -> record); + /* CHANGED_BAG(args -> record); */ if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, args -> data)) { return false; // Stop execution @@ -106,15 +112,15 @@ bool ExecuteDFSRec(UInt current, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { - DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); + DIGRAPHS_ASSERT(LEN_PLIST(args) == 8); 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); - + Obj flags = ELM_PLIST(args, 2); + Obj data = ELM_PLIST(args, 3); + Obj start = ELM_PLIST(args, 4); + Obj PreorderFunc = ELM_PLIST(args, 5); + Obj PostorderFunc = ELM_PLIST(args, 6); + Obj AncestorFunc = ELM_PLIST(args, 7); + Obj CrossFunc = ELM_PLIST(args, 8); DIGRAPHS_ASSERT(IS_PREC(record)); DIGRAPHS_ASSERT(IS_INTOBJ(start)); @@ -151,10 +157,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { rec_args -> record = record; rec_args -> preorder_num = &preorder_num; rec_args -> postorder_num = &postorder_num; - rec_args -> parent = ElmPRec(record, RNamName("parent")); - rec_args -> postorder = ElmPRec(record, RNamName("postorder")); - rec_args -> preorder = ElmPRec(record, RNamName("preorder")); - rec_args -> edge = ElmPRec(record, RNamName("edge")); rec_args -> neighbors = FuncOutNeighbours(self, D); rec_args -> data = data; rec_args -> PreorderFunc = PreorderFunc; @@ -162,13 +164,37 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { rec_args -> CrossFunc = CrossFunc; rec_args -> AncestorFunc = AncestorFunc; - HASH_SET(rec_args -> parent, start, start); + + // if (verbose == True) { + rec_args -> edge = ElmPRec(record, RNamName("edge")); + rec_args -> parents = ElmPRec(record, RNamName("parents")); + HASH_SET(rec_args -> parents, start, start); + rec_args -> postorder = ElmPRec(record, RNamName("postorder")); + rec_args -> preorder = ElmPRec(record, RNamName("preorder")); + + HASH_RESERVE(rec_args -> edge, INTOBJ_INT(N)); + HASH_RESERVE(rec_args -> parents, INTOBJ_INT(N)); + HASH_RESERVE(rec_args -> preorder, INTOBJ_INT(N)); + HASH_RESERVE(rec_args -> postorder, INTOBJ_INT(N)); + // } DIGRAPHS_ASSERT(IS_PLIST(rec_args -> neighbors)); UInt current = INT_INTOBJ(start); - ExecuteDFSRec(current, rec_args); + if (ElmPRec(flags, RNamName("forest")) == True) { + for (int i = 1; i <= N; i++) { + bool visited = HASH_CONTAINS(rec_args -> preorder, INTOBJ_INT(i)); + if (!visited) { + ExecuteDFSRec(i, i, rec_args); + } + } + } else { + ExecuteDFSRec(current, current, rec_args); + } + free(rec_args); + + CHANGED_BAG(record); return record; } @@ -211,7 +237,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? Int current = 0; - Obj parent = ElmPRec(record, RNamName("parent")); + Obj parents = ElmPRec(record, RNamName("parents")); Obj postorder = ElmPRec(record, RNamName("postorder")); Obj preorder = ElmPRec(record, RNamName("preorder")); Obj edge = ElmPRec(record, RNamName("edge")); @@ -219,7 +245,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? // 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 - HASH_SET(parent, start, start); + HASH_SET(parents, start, start); Obj neighbors = FuncOutNeighbours(self, D); DIGRAPHS_ASSERT(IS_PLIST(neighbors)); @@ -237,7 +263,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? Int child = current * -1; // backtracking on current AssPRec(record, RNamChild, INTOBJ_INT(child)); - AssPRec(record, RNamCurrent, HASH_GET(parent, INTOBJ_INT(child))); + AssPRec(record, RNamCurrent, HASH_GET(parents, INTOBJ_INT(child))); HASH_SET(postorder, INTOBJ_INT(child), INTOBJ_INT(++postorder_num)); CHANGED_BAG(record); if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { @@ -271,7 +297,7 @@ Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? CHANGED_BAG(record); if (!visited) { // v is unvisited - HASH_SET(parent, INTOBJ_INT(v), INTOBJ_INT(current)); + HASH_SET(parents, INTOBJ_INT(v), INTOBJ_INT(current)); HASH_SET(edge, INTOBJ_INT(v), INTOBJ_INT(j)); ASS_LIST(stack, ++top, INTOBJ_INT(v)); } else { // v is either visited, or in the stack to be visited diff --git a/src/dfs.h b/src/dfs.h index a11321d75..c3c403c23 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -17,6 +17,8 @@ // GAP headers #include "gap-includes.h" // for Obj, Int +#include "bitarray.h" + bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); struct dfs_args { @@ -28,7 +30,7 @@ struct dfs_args { UInt* preorder_num; UInt* postorder_num; - Obj parent; + Obj parents; Obj postorder; Obj preorder; Obj edge; @@ -42,7 +44,7 @@ struct dfs_args { Obj CrossFunc; }; -bool ExecuteDFSRec(UInt current, struct dfs_args* args); +bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args); Obj FuncExecuteDFS_C(Obj self, Obj args); Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? diff --git a/src/digraphs.c b/src/digraphs.c index 2069b6680..77471b0ee 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -63,9 +63,9 @@ Obj InfoWarning; // Added for HashMap (DFS) Obj DS_Hash_SetValue; -Obj DS_Hash_Lookup; Obj DS_Hash_Contains; Obj DS_Hash_Value; +Obj DS_Hash_Reserve; static inline bool IsAttributeStoringRep(Obj o) { return (CALL_1ARGS(IsAttributeStoringRepObj, o) == True ? true : false); @@ -2273,9 +2273,10 @@ static Int InitKernel(StructInitInfo* module) { ImportGVarFromLibrary("InfoWarning", &InfoWarning); // For DFS ImportGVarFromLibrary("DS_Hash_SetValue", &DS_Hash_SetValue); - ImportGVarFromLibrary("DS_Hash_Lookup", &DS_Hash_Lookup); ImportGVarFromLibrary("DS_Hash_Contains", &DS_Hash_Contains); ImportGVarFromLibrary("DS_Hash_Value", &DS_Hash_Value); + ImportGVarFromLibrary("DS_Hash_Reserve", &DS_Hash_Reserve); + /* return success */ return 0; } diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index a2a8a9cd7..188564dbb 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3242,17 +3242,17 @@ gap> DigraphVertexLabels(D); # NewDFSRecord gap> NewDFSRecord(ChainDigraph(10)); rec( child := -1, current := -1, edge := HashMap([]), - graph := , parent := HashMap([]), + graph := , parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), stop := false ) gap> NewDFSRecord(CompleteDigraph(2)); rec( child := -1, current := -1, edge := HashMap([]), graph := , - parent := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), + parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), stop := false ) gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); rec( child := -1, current := -1, edge := HashMap([]), graph := , - parent := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), + parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), stop := false ) # DFSDefault @@ -3271,7 +3271,7 @@ gap> mapToList := function(map, len, list) # For turning record hashmaps -> lis > od; > end;; gap> record := NewDFSRecord(CompleteDigraph(10));; -gap> ExecuteDFS(record, [], 2, DFSDefault, +gap> ExecuteDFS(record, NewDFSFlags(), [], 2, DFSDefault, > DFSDefault, DFSDefault, DFSDefault); gap> preorder_list := [];; gap> mapToList(record.preorder, 10, preorder_list);; @@ -3283,7 +3283,7 @@ gap> AncestorFunc := function(record, data) > record.stop := true; > data.cycle_vertex := record.child; > end;; -gap> ExecuteDFS(record, data, 1, DFSDefault, +gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, > DFSDefault, AncestorFunc, DFSDefault); gap> record.stop; true @@ -3299,7 +3299,7 @@ gap> CrossFunc := function(record, data) > Add(data, record.child); > end;; gap> data := [];; -gap> ExecuteDFS(record, data, 1, DFSDefault, +gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, > DFSDefault, DFSDefault, CrossFunc); gap> record.stop; true @@ -3313,15 +3313,15 @@ gap> CrossFunc := function(record, data) > end;; gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; gap> data := rec(back_edges := [], cross_edges := []);; -gap> ExecuteDFS(record, data, 1, DFSDefault, +gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, > DFSDefault, AncestorFunc, CrossFunc);; gap> data; rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] ) -gap> ExecuteDFS(rec(), data, 1, DFSDefault, +gap> ExecuteDFS(rec(), NewDFSFlags(), 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, +gap> ExecuteDFS(NewDFSRecord(D), NewDFSFlags(), [], 3, DFSDefault, DFSDefault, DFSDefault, > DFSDefault); Error, the third argument must be a vertex in your graph, From a81fe2f8666b1520f9981bddcecfe6b1dff7a330 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 18 Mar 2025 16:54:24 +0000 Subject: [PATCH 17/54] Change IsNotEqualSet to checking IsBound in ExecuteDFS to improve performance of running ExecuteDFS multiple times in a function --- gap/attr.gi | 7 +- gap/oper.gi | 29 ++--- gap/prop.gi | 2 +- src/dfs.c | 16 +-- src/dfs_pre.c | 269 ++++++++++++++++++++++++++++++++++++++++++ tst/standard/oper.tst | 42 +++---- 6 files changed, 314 insertions(+), 51 deletions(-) create mode 100644 src/dfs_pre.c diff --git a/gap/attr.gi b/gap/attr.gi index 59eac60f8..a54708ebe 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -873,7 +873,7 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local N, record, count, out, PostOrderFunc, AncestorFunc, flags; + local N, record, count, out, PostOrderFunc, AncestorFunc; N := DigraphNrVertices(D); if N = 0 then @@ -891,11 +891,10 @@ function(D) record.stop := true; fi; end; - flags := NewDFSFlags(); - flags.forest := true; + + record.config.forest := true; ExecuteDFS(record, - flags, fail, 1, DFSDefault, diff --git a/gap/oper.gi b/gap/oper.gi index e7939a8ad..40b5cfa78 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1702,7 +1702,6 @@ function(D, u, v) end; fi; ExecuteDFS(record, - NewDFSFlags(), fail, u, PreOrderFunc, @@ -2068,7 +2067,7 @@ function(D, v) fi; od; end; - ExecuteDFS(record, NewDFSFlags(), data, v, + ExecuteDFS(record, data, v, PreOrderFunc, PostOrderFunc, AncestorFunc, DFSDefault); if record.stop then @@ -2398,7 +2397,6 @@ function(D, root) end; ExecuteDFS(record, - NewDFSFlags(), data, root, PreOrderFunc, @@ -2503,7 +2501,6 @@ function(D, root) record := NewDFSRecord(D); ExecuteDFS(record, - NewDFSFlags(), preorder_num_to_node, root, PreOrderFunc, @@ -2830,6 +2827,7 @@ function(graph) record.preorder := HashMap(); record.postorder := HashMap(); record.edge := HashMap(); + record.config := NewDFSFlags(); return record; end); @@ -2860,22 +2858,19 @@ end); # by ancestry. InstallGlobalFunction(ExecuteDFS, -function(record, flags, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, +function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) - if not IsEqualSet(RecNames(record), - ["stop", "graph", "child", "parents", "preorder", - "postorder", "current", "edge"]) then - ErrorNoReturn("the 1st argument must be created with ", - "NewDFSRecord,"); - fi; + local name; - if not IsEqualSet(RecNames(flags), - ["forest"]) then - ErrorNoReturn("the 2nd argument must be created with ", - "NewDFSFlags,"); - fi; + for name in ["stop", "graph", "child", "parents", "preorder", "postorder", + "current", "edge"] do + if not IsBound(record.(name)) then + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); + fi; + od; - ExecuteDFS_C(record, flags, data, start, PreOrderFunc, PostOrderFunc, + ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); diff --git a/gap/prop.gi b/gap/prop.gi index 308568546..4be1cadb2 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -254,7 +254,7 @@ function(D) for i in [1 .. n] do if not IsBound(record.preorder[i]) then - ExecuteDFS(record, NewDFSFlags(), [], i, DFSDefault, + ExecuteDFS(record, [], i, DFSDefault, DFSDefault, AncestorFunc, DFSDefault); if record.stop then diff --git a/src/dfs.c b/src/dfs.c index fd98b2c4e..87c103a61 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -112,15 +112,15 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { - DIGRAPHS_ASSERT(LEN_PLIST(args) == 8); + DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); - Obj flags = ELM_PLIST(args, 2); - Obj data = ELM_PLIST(args, 3); - Obj start = ELM_PLIST(args, 4); - Obj PreorderFunc = ELM_PLIST(args, 5); - Obj PostorderFunc = ELM_PLIST(args, 6); - Obj AncestorFunc = ELM_PLIST(args, 7); - Obj CrossFunc = ELM_PLIST(args, 8); + Obj flags = ElmPRec(record, RNamName("config")); + Obj data = ELM_PLIST(args, 2); + Obj start = ELM_PLIST(args, 3); + Obj PreorderFunc = ELM_PLIST(args, 4); + Obj PostorderFunc = ELM_PLIST(args, 5); + Obj AncestorFunc = ELM_PLIST(args, 6); + Obj CrossFunc = ELM_PLIST(args, 7); DIGRAPHS_ASSERT(IS_PREC(record)); DIGRAPHS_ASSERT(IS_INTOBJ(start)); diff --git a/src/dfs_pre.c b/src/dfs_pre.c new file mode 100644 index 000000000..17429f8f2 --- /dev/null +++ b/src/dfs_pre.c @@ -0,0 +1,269 @@ +/******************************************************************************* +** +*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(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); */ + +/* 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, 0); */ +/* AssPlist(stack, ++top, start); */ + +/* UInt preorder_num = 0; */ +/* UInt postorder_num = 0; */ + +/* // current vertex being visited */ +/* Int current = 0; */ + +/* // parent of each vertex visited stored */ +/* Obj parent = ElmPRec(record, RNamName("parent")); */ +/* // postorder number (backtrack order) */ +/* Obj postorder = ElmPRec(record, RNamName("postorder")); */ +/* // preorder number (order visited) */ +/* 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 */ +/* // FIXME use hash maps for parent, postorder, preorder, and edge */ + +/* DIGRAPHS_ASSERT(LEN_PLIST(parent) == 1); */ +/* DIGRAPHS_ASSERT(LEN_PLIST(postorder) == 1); */ +/* DIGRAPHS_ASSERT(LEN_PLIST(preorder) == 1); */ +/* DIGRAPHS_ASSERT(LEN_PLIST(edge) == 1); */ + +/* ASS_LIST(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"); */ + +/* // PostOrderFunc :: called when a vertex has no more unvisited children */ + +/* // PreOrderFunc :: When a vertex is first visited */ + +/* // AncestorFunc :: When current, child is an edge and */ +/* // child is an ancestor of current */ + +/* // TODO why does the record need child or current */ +/* while (top > 0) { // top init 1 */ +/* if (ElmPRec(record, RNamStop) == True) { */ +/* break; */ +/* } */ +/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // current init start (stack init [start]) */ +/* DIGRAPHS_ASSERT(current != 0); */ +/* if (current < 0) { // No more unvisited children, backtrack (postorder) */ +/* Int child = -1 * current; // positive vertex number of node to backtrack from */ +/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ +/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); // record current = parent of backtracked node */ +/* CALL_2ARGS(PostOrderFunc, record, data); */ +/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ +/* CHANGED_BAG(record); */ +/* continue; // backtracked, < 0 indicates backtrack from this node */ +/* } else if (current <= LEN_PLIST(preorder) */ +/* && ELM_PLIST(preorder, current) != 0) { */ +/* continue; // TODO is this needed when we check this before adding to the stack */ +/* } else { // Vertex first visit (preorder) */ +/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); // visit this node */ +/* CALL_2ARGS(PreorderFunc, record, data); */ +/* ASS_LIST(preorder, current, INTOBJ_INT(++preorder_num)); */ +/* CHANGED_BAG(record); */ +/* ASS_LIST(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 (v > LEN_PLIST(preorder) || ELM_PLIST(preorder, v) == 0) { // unvisited */ +/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ +/* ASS_LIST(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); */ +// edge is a list of the index in neighbours for each child when they are visited +/* CHANGED_BAG(record); */ +/* 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))) { */ +/* CALL_2ARGS(CrossFunc, record, data); */ +/* } */ +/* if (ElmPRec(record, RNamStop) == True) { // Functions can set this */ +/* break; */ +/* } */ +/* } */ +/* } */ +/* return record; */ +/* } */ + + + + + + + /* AncestorFunc is called when a child of an */ + /* edge has been visited prior, and has not */ + /* been backtracked on yet */ + + /* CrossFunc is called for an edge (current, child [v]) */ + /* without any descendent ancestor connection, */ + /* so when child has been visited before (with */ + /* a different parent) */ + + +/* Obj FuncExecuteDFS_C(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(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); */ + +/* 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, 0); */ +/* 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")); */ +/* // 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 */ + + +/* ASS_LIST(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) { */ +/* if (ElmPRec(record, RNamStop) == True) { */ +/* break; */ +/* } */ +/* 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); */ +/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ +/* CHANGED_BAG(record); */ +/* continue; */ +/* } else if (current <= LEN_PLIST(preorder) */ +/* && INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) { */ +/* continue; */ +/* } else { */ +/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ +/* CALL_2ARGS(PreorderFunc, record, data); */ +/* ASS_LIST(preorder, current, INTOBJ_INT(++preorder_num)); */ +/* CHANGED_BAG(record); */ +/* ASS_LIST(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 (v > LEN_PLIST(preorder) || INT_INTOBJ(ELM_PLIST(preorder, v)) == -1) { */ +/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ +/* // ASS_LIST(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); */ +/* CHANGED_BAG(record); */ +/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ +/* } else if (v > LEN_PLIST(postorder) || 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/tst/standard/oper.tst b/tst/standard/oper.tst index 188564dbb..257b91164 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3239,21 +3239,21 @@ gap> DigraphVertexLabels(D); # DFS -# NewDFSRecord -gap> NewDFSRecord(ChainDigraph(10)); -rec( child := -1, current := -1, edge := HashMap([]), - graph := , parents := HashMap([]), - postorder := HashMap([]), preorder := HashMap([]), stop := false ) -gap> NewDFSRecord(CompleteDigraph(2)); -rec( child := -1, current := -1, edge := HashMap([]), - graph := , - parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), - stop := false ) -gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); -rec( child := -1, current := -1, edge := HashMap([]), - graph := , - parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), - stop := false ) +# NewDFSRecord TODO uncomment when know what record will be +# gap> NewDFSRecord(ChainDigraph(10)); +# rec( child := -1, current := -1, edge := HashMap([]), +# graph := , parents := HashMap([]), +# postorder := HashMap([]), preorder := HashMap([]), stop := false ) +# gap> NewDFSRecord(CompleteDigraph(2)); +# rec( child := -1, current := -1, edge := HashMap([]), +# graph := , +# parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), +# stop := false ) +# gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); +# rec( child := -1, current := -1, edge := HashMap([]), +# graph := , +# parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), +# stop := false ) # DFSDefault gap> DFSDefault(rec(), []); @@ -3271,7 +3271,7 @@ gap> mapToList := function(map, len, list) # For turning record hashmaps -> lis > od; > end;; gap> record := NewDFSRecord(CompleteDigraph(10));; -gap> ExecuteDFS(record, NewDFSFlags(), [], 2, DFSDefault, +gap> ExecuteDFS(record, [], 2, DFSDefault, > DFSDefault, DFSDefault, DFSDefault); gap> preorder_list := [];; gap> mapToList(record.preorder, 10, preorder_list);; @@ -3283,7 +3283,7 @@ gap> AncestorFunc := function(record, data) > record.stop := true; > data.cycle_vertex := record.child; > end;; -gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, +gap> ExecuteDFS(record, data, 1, DFSDefault, > DFSDefault, AncestorFunc, DFSDefault); gap> record.stop; true @@ -3299,7 +3299,7 @@ gap> CrossFunc := function(record, data) > Add(data, record.child); > end;; gap> data := [];; -gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, +gap> ExecuteDFS(record, data, 1, DFSDefault, > DFSDefault, DFSDefault, CrossFunc); gap> record.stop; true @@ -3313,15 +3313,15 @@ gap> CrossFunc := function(record, data) > end;; gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; gap> data := rec(back_edges := [], cross_edges := []);; -gap> ExecuteDFS(record, NewDFSFlags(), data, 1, DFSDefault, +gap> ExecuteDFS(record, data, 1, DFSDefault, > DFSDefault, AncestorFunc, CrossFunc);; gap> data; rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] ) -gap> ExecuteDFS(rec(), NewDFSFlags(), data, 1, DFSDefault, +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), NewDFSFlags(), [], 3, DFSDefault, DFSDefault, DFSDefault, +gap> ExecuteDFS(NewDFSRecord(D), [], 3, DFSDefault, DFSDefault, DFSDefault, > DFSDefault); Error, the third argument must be a vertex in your graph, From 6ebbf449a62740e03932ad689795bdc5ffc95d47 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 18 Mar 2025 16:56:40 +0000 Subject: [PATCH 18/54] Remove file --- src/dfs_pre.c | 269 -------------------------------------------------- 1 file changed, 269 deletions(-) delete mode 100644 src/dfs_pre.c diff --git a/src/dfs_pre.c b/src/dfs_pre.c deleted file mode 100644 index 17429f8f2..000000000 --- a/src/dfs_pre.c +++ /dev/null @@ -1,269 +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(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); */ - -/* 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, 0); */ -/* AssPlist(stack, ++top, start); */ - -/* UInt preorder_num = 0; */ -/* UInt postorder_num = 0; */ - -/* // current vertex being visited */ -/* Int current = 0; */ - -/* // parent of each vertex visited stored */ -/* Obj parent = ElmPRec(record, RNamName("parent")); */ -/* // postorder number (backtrack order) */ -/* Obj postorder = ElmPRec(record, RNamName("postorder")); */ -/* // preorder number (order visited) */ -/* 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 */ -/* // FIXME use hash maps for parent, postorder, preorder, and edge */ - -/* DIGRAPHS_ASSERT(LEN_PLIST(parent) == 1); */ -/* DIGRAPHS_ASSERT(LEN_PLIST(postorder) == 1); */ -/* DIGRAPHS_ASSERT(LEN_PLIST(preorder) == 1); */ -/* DIGRAPHS_ASSERT(LEN_PLIST(edge) == 1); */ - -/* ASS_LIST(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"); */ - -/* // PostOrderFunc :: called when a vertex has no more unvisited children */ - -/* // PreOrderFunc :: When a vertex is first visited */ - -/* // AncestorFunc :: When current, child is an edge and */ -/* // child is an ancestor of current */ - -/* // TODO why does the record need child or current */ -/* while (top > 0) { // top init 1 */ -/* if (ElmPRec(record, RNamStop) == True) { */ -/* break; */ -/* } */ -/* current = INT_INTOBJ(ELM_PLIST(stack, top--)); // current init start (stack init [start]) */ -/* DIGRAPHS_ASSERT(current != 0); */ -/* if (current < 0) { // No more unvisited children, backtrack (postorder) */ -/* Int child = -1 * current; // positive vertex number of node to backtrack from */ -/* AssPRec(record, RNamChild, INTOBJ_INT(child)); */ -/* AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); // record current = parent of backtracked node */ -/* CALL_2ARGS(PostOrderFunc, record, data); */ -/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ -/* CHANGED_BAG(record); */ -/* continue; // backtracked, < 0 indicates backtrack from this node */ -/* } else if (current <= LEN_PLIST(preorder) */ -/* && ELM_PLIST(preorder, current) != 0) { */ -/* continue; // TODO is this needed when we check this before adding to the stack */ -/* } else { // Vertex first visit (preorder) */ -/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); // visit this node */ -/* CALL_2ARGS(PreorderFunc, record, data); */ -/* ASS_LIST(preorder, current, INTOBJ_INT(++preorder_num)); */ -/* CHANGED_BAG(record); */ -/* ASS_LIST(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 (v > LEN_PLIST(preorder) || ELM_PLIST(preorder, v) == 0) { // unvisited */ -/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ -/* ASS_LIST(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); */ -// edge is a list of the index in neighbours for each child when they are visited -/* CHANGED_BAG(record); */ -/* 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))) { */ -/* CALL_2ARGS(CrossFunc, record, data); */ -/* } */ -/* if (ElmPRec(record, RNamStop) == True) { // Functions can set this */ -/* break; */ -/* } */ -/* } */ -/* } */ -/* return record; */ -/* } */ - - - - - - - /* AncestorFunc is called when a child of an */ - /* edge has been visited prior, and has not */ - /* been backtracked on yet */ - - /* CrossFunc is called for an edge (current, child [v]) */ - /* without any descendent ancestor connection, */ - /* so when child has been visited before (with */ - /* a different parent) */ - - -/* Obj FuncExecuteDFS_C(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(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); */ - -/* 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, 0); */ -/* 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")); */ -/* // 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 */ - - -/* ASS_LIST(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) { */ -/* if (ElmPRec(record, RNamStop) == True) { */ -/* break; */ -/* } */ -/* 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); */ -/* ASS_LIST(postorder, child, INTOBJ_INT(++postorder_num)); */ -/* CHANGED_BAG(record); */ -/* continue; */ -/* } else if (current <= LEN_PLIST(preorder) */ -/* && INT_INTOBJ(ELM_PLIST(preorder, current)) != -1) { */ -/* continue; */ -/* } else { */ -/* AssPRec(record, RNamCurrent, INTOBJ_INT(current)); */ -/* CALL_2ARGS(PreorderFunc, record, data); */ -/* ASS_LIST(preorder, current, INTOBJ_INT(++preorder_num)); */ -/* CHANGED_BAG(record); */ -/* ASS_LIST(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 (v > LEN_PLIST(preorder) || INT_INTOBJ(ELM_PLIST(preorder, v)) == -1) { */ -/* ASS_LIST(parent, v, INTOBJ_INT(current)); */ -/* // ASS_LIST(edge, v, INTOBJ_INT(LEN_LIST(succ) - j)); */ -/* CHANGED_BAG(record); */ -/* ASS_LIST(stack, ++top, INTOBJ_INT(v)); */ -/* } else if (v > LEN_PLIST(postorder) || 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; */ -/* } */ From 7e8d627c0ab6c50a4f8b08893830e1ddeb4c4cd1 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 25 Mar 2025 18:48:58 +0000 Subject: [PATCH 19/54] Add visited / backtrack bitarrays to reduce hash lookups. Add revisit config for DigraphLongestDistanceFromVertex --- gap/attr.gi | 351 ++++++++++++++++++++++++++++++------------ gap/oper.gd | 2 - gap/oper.gi | 59 +++---- gap/prop.gi | 4 +- src/dfs.c | 276 +++++++++++---------------------- src/dfs.h | 27 +++- src/digraphs.c | 8 +- tst/standard/oper.tst | 28 ++-- 8 files changed, 399 insertions(+), 356 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index a54708ebe..e4b7c05c3 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -28,8 +28,8 @@ InstallGlobalFunction(OutNeighbors, OutNeighbours); BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation", function(D) - local N, copy, articulation_points, bridges, orientation, nbs, counter, pre, - low, nr_children, stack, u, v, i, w, connected; + local N, copy, PostOrderFunc, PreOrderFunc, AncestorCrossFunc, data, record, + connected, parent; N := DigraphNrVertices(D); @@ -41,117 +41,237 @@ 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.parents[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, parents; + current := record.current; + if current <> 1 then + parent := record.parents[current]; + if parent = 1 then + data.nr_children := data.nr_children + 1; + fi; + data.orientation[parent][current] := true; + fi; + data.counter := data.counter + 1; + data.low[current] := data.counter; + end; - # number of nodes encountered in the search so far - counter := 0; + AncestorCrossFunc := function(record, data) + local current, child, parent; + current := record.current; + child := record.child; + parent := record.parents[current]; + # current -> child is a back edge + if child <> parent and record.preorder[child] < data.low[current] then + data.low[current] := record.preorder[child]; + fi; + data.orientation[current][child] := not data.orientation[child][current]; + end; - # the order in which the nodes are visited, -1 indicates "not yet visited". - pre := ListWithIdenticalEntries(N, -1); + data := rec(); + + # outputs + data.articulation_points := []; + data.bridges := []; + data.orientation := List([1 .. N], x -> BlistList([1 .. N], [])); # low[i] is the lowest value in pre currently reachable from node i. - low := []; + data.low := []; + + # number of nodes encountered in the search so far + data.counter := 0; # nr_children of node 1, for articulation points the root node (1) is an # articulation point if and only if it has at least 2 children. - nr_children := 0; - - stack := Stack(); - u := 1; - v := 1; - i := 0; - - repeat - if pre[v] <> -1 then - # backtracking - i := Pop(stack); - v := Pop(stack); - u := Pop(stack); - w := nbs[v][i]; - - if v <> 1 and low[w] >= pre[v] then - Add(articulation_points, v); - fi; - if low[w] = pre[w] then - Add(bridges, [v, w]); - fi; - if low[w] < low[v] then - low[v] := low[w]; - fi; - else - # diving - part 1 - counter := counter + 1; - pre[v] := counter; - low[v] := counter; - fi; - i := PositionProperty(nbs[v], w -> w <> v, i); - while i <> fail do - w := nbs[v][i]; - if pre[w] <> -1 then - # v -> w is a back edge - if w <> u and pre[w] < low[v] then - low[v] := pre[w]; - fi; - orientation[v][w] := not orientation[w][v]; - i := PositionProperty(nbs[v], w -> w <> v, i); - else - # diving - part 2 - if v = 1 then - nr_children := nr_children + 1; - fi; - orientation[v][w] := true; - Push(stack, u); - Push(stack, v); - Push(stack, i); - u := v; - v := w; - i := 0; - break; - fi; - od; - until Size(stack) = 0; + data.nr_children := 0; - if counter = DigraphNrVertices(D) then + record := NewDFSRecord(copy); + ExecuteDFS(record, + data, + 1, + PreOrderFunc, + PostOrderFunc, + AncestorCrossFunc, + AncestorCrossFunc); + if data.counter = DigraphNrVertices(D) then connected := true; - if nr_children > 1 then - Add(articulation_points, 1); + if data.nr_children > 1 then + Add(data.articulation_points, 1); fi; - if not IsEmpty(bridges) then - orientation := fail; + if not IsEmpty(data.bridges) then + data.orientation := fail; else - orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), - orientation); + data.orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), + data.orientation); fi; else - connected := false; - articulation_points := []; - bridges := []; - orientation := fail; + connected := false; + data.articulation_points := []; + data.bridges := []; + data.orientation := fail; fi; if IsImmutableDigraph(D) then SetIsConnectedDigraph(D, connected); - SetArticulationPoints(D, articulation_points); - SetBridges(D, bridges); + SetArticulationPoints(D, data.articulation_points); + SetBridges(D, data.bridges); if IsSymmetricDigraph(D) then - SetStrongOrientationAttr(D, orientation); + SetStrongOrientationAttr(D, data.orientation); fi; fi; - return [connected, articulation_points, bridges, orientation]; + return [connected, data.articulation_points, data.bridges, data.orientation]; end); +# BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation", +# function(D) +# local N, copy, articulation_points, bridges, orientation, nbs, counter, pre, +# low, nr_children, stack, u, v, i, w, connected; +# +# N := DigraphNrVertices(D); +# +# if HasIsConnectedDigraph(D) and not IsConnectedDigraph(D) then +# # not connected, no articulation points, no bridges, no strong orientation +# return [false, [], [], fail]; +# elif N < 2 then +# # connected, no articulation points (removing 0 or 1 nodes does not make +# # 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)); +# MakeImmutable(copy); +# else +# copy := D; +# fi; +# +# # outputs +# articulation_points := []; +# bridges := []; +# orientation := List([1 .. N], x -> BlistList([1 .. N], [])); +# +# # Get out-neighbours once, to avoid repeated copying for mutable digraphs. +# nbs := OutNeighbours(copy); +# +# # number of nodes encountered in the search so far +# counter := 0; +# +# # 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. +# 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. +# 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 nr_children > 1 then +# Add(articulation_points, 1); +# fi; +# if not IsEmpty(bridges) then +# orientation := fail; +# else +# orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), +# orientation); +# fi; +# else +# connected := false; +# articulation_points := []; +# bridges := []; +# orientation := fail; +# fi; +# if IsImmutableDigraph(D) then +# SetIsConnectedDigraph(D, connected); +# SetArticulationPoints(D, articulation_points); +# SetBridges(D, bridges); +# if IsSymmetricDigraph(D) then +# SetStrongOrientationAttr(D, orientation); +# fi; +# fi; +# return [connected, articulation_points, bridges, orientation]; +# end); + InstallMethod(ArticulationPoints, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], D -> DIGRAPHS_ArticulationPointsBridgesStrongOrientation(D)[2]); @@ -897,10 +1017,10 @@ function(D) ExecuteDFS(record, fail, 1, - DFSDefault, + fail, PostOrderFunc, AncestorFunc, - DFSDefault); + fail); if record.stop then return fail; fi; @@ -2659,19 +2779,58 @@ InstallMethod(DigraphReflexiveTransitiveReductionAttr, "for an immutable digraph", [IsImmutableDigraph], DigraphReflexiveTransitiveReduction); -InstallMethod(UndirectedSpanningForest, -"for a mutable digraph by out-neighbours", -[IsMutableDigraph and IsDigraphByOutNeighboursRep], +InstallMethod(UndirectedSpanningForest, "for a digraph by out-neighbours", +[IsDigraphByOutNeighboursRep], function(D) - if DigraphHasNoVertices(D) then + local C, record, data, PreOrderFunc; + if DigraphNrVertices(D) = 0 then return fail; fi; - MaximalSymmetricSubdigraph(D); - D!.OutNeighbours := DIGRAPH_SYMMETRIC_SPANNING_FOREST(D!.OutNeighbours); - ClearDigraphEdgeLabels(D); - return D; + C := MaximalSymmetricSubdigraph(D); + record := NewDFSRecord(C); + data := List(DigraphVertices(C), x -> []); + + PreOrderFunc := function(record, data) + if record.parents[record.current] <> record.current then + Add(data[record.parents[record.current]], record.current); + Add(data[record.current], record.parents[record.current]); + fi; + end; + + record.config.forest := true; + + ExecuteDFS(record, data, 1, PreOrderFunc, fail, + fail, fail); + + # for i in DigraphVertices(C) do + # 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); + return C; end); +# InstallMethod(UndirectedSpanningForest, +# "for a mutable digraph by out-neighbours", +# [IsMutableDigraph and IsDigraphByOutNeighboursRep], +# function(D) +# if DigraphHasNoVertices(D) then +# return fail; +# fi; +# MaximalSymmetricSubdigraph(D); +# D!.OutNeighbours := DIGRAPH_SYMMETRIC_SPANNING_FOREST(D!.OutNeighbours); +# ClearDigraphEdgeLabels(D); +# return D; +# end); + InstallMethod(UndirectedSpanningForest, "for an immutable digraph", [IsImmutableDigraph], UndirectedSpanningForestAttr); diff --git a/gap/oper.gd b/gap/oper.gd index fea1f4570..23b90fb7d 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -156,6 +156,4 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", # 11. DFS DeclareOperation("NewDFSRecord", [IsDigraph]); DeclareOperation("NewDFSFlags", []); -DeclareOperation("DFSDefault", [IsRecord, IsObject]); DeclareGlobalFunction("ExecuteDFS"); -DeclareGlobalFunction("ExecuteDFSIter"); # TODO remove? diff --git a/gap/oper.gi b/gap/oper.gi index 40b5cfa78..4b17a5562 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1689,12 +1689,12 @@ function(D, u, v) record.stop := true; fi; end; - AncestorFunc := DFSDefault; + AncestorFunc := fail; else # if u is reachable from u, then u will be encountered as an ancestor of # itself, but PreOrderFunc won't be called (because u has already been # discovered). - PreOrderFunc := DFSDefault; + PreOrderFunc := fail; AncestorFunc := function(record, _) if record.child = v then record.stop := true; @@ -1705,9 +1705,9 @@ function(D, u, v) fail, u, PreOrderFunc, - DFSDefault, + fail, AncestorFunc, - DFSDefault); + fail); if not record.stop then return fail; fi; @@ -2044,32 +2044,25 @@ function(D, v) "argument ,"); fi; record := NewDFSRecord(D); - data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), - prev := -1, best := 0); + data := rec(prev := -1, best := 0); AncestorFunc := function(record, _) record.stop := true; end; PostOrderFunc := function(_, data) - # data.depth[record.current] := data.prev; data.prev := data.prev - 1; end; - PreOrderFunc := function(record, data) - local i, neighbours; + PreOrderFunc := function(_, data) data.prev := data.prev + 1; if data.prev > data.best then data.best := data.prev; fi; - neighbours := OutNeighborsOfVertex(record.graph, record.current); - for i in [1 .. Size(neighbours)] do - # need to bypass the CrossFunc - if IsBound(record.postorder[neighbours[i]]) then - Unbind(record.preorder[neighbours[i]]); - fi; - od; end; + + record.config.revisit := true; + ExecuteDFS(record, data, v, PreOrderFunc, PostOrderFunc, - AncestorFunc, DFSDefault); + AncestorFunc, fail); if record.stop then return infinity; fi; @@ -2400,9 +2393,9 @@ function(D, root) data, root, PreOrderFunc, - DFSDefault, + fail, AncestorFunc, - DFSDefault); + fail); Sort(data.result); return data.result; end); @@ -2504,9 +2497,9 @@ function(D, root) preorder_num_to_node, root, PreOrderFunc, - DFSDefault, - DFSDefault, - DFSDefault); + fail, + fail, + fail); parents := record.parents; node_to_preorder_num := record.preorder; @@ -2837,14 +2830,13 @@ function() local record; record := rec(); record.forest := false; + record.revisit := false; # If revisit = true, then when visiting some node, + # and one of it's neighbors has been visited in + # a different branch (backtracked on), + # visit it again from this node return record; end); -InstallMethod(DFSDefault, -"for a record and an object", [IsRecord, IsObject], -function(record, data) # gaplint: disable=W046 -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 @@ -2873,16 +2865,3 @@ function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); - -InstallGlobalFunction(ExecuteDFSIter, # TODO remove? -function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, - CrossFunc) - if not IsEqualSet(RecNames(record), - ["stop", "graph", "child", "parents", "preorder", - "postorder", "current", "edge"]) then - ErrorNoReturn("the 1st argument must be created with ", - "NewDFSRecord,"); - fi; - ExecuteDFSIter_C(record, data, start, PreOrderFunc, PostOrderFunc, - AncestorFunc, CrossFunc); -end); diff --git a/gap/prop.gi b/gap/prop.gi index 4be1cadb2..2199e9df8 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -254,8 +254,8 @@ function(D) for i in [1 .. n] do if not IsBound(record.preorder[i]) then - ExecuteDFS(record, [], i, DFSDefault, - DFSDefault, AncestorFunc, DFSDefault); + ExecuteDFS(record, [], i, fail, + fail, AncestorFunc, fail); if record.stop then return false; diff --git a/src/dfs.c b/src/dfs.c index 87c103a61..141156faf 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -21,12 +21,13 @@ #include "digraphs.h" #include "safemalloc.h" -// Datastructures Functions +// Datastructures Functions TODO use GAP level functions extern Obj DS_Hash_SetValue; extern Obj DS_Hash_Contains; extern Obj DS_Hash_Value; extern Obj IsBound; extern Obj DS_Hash_Reserve; +extern Obj DS_Hash_Delete; // Macros @@ -42,6 +43,9 @@ extern Obj DS_Hash_Reserve; #define HASH_RESERVE(map, capacity) \ CALL_2ARGS(DS_Hash_Reserve, map, capacity) +#define HASH_DELETE(map, key) \ + CALL_2ARGS(DS_Hash_Delete, map, key) + // Global Variables // Extreme examples are on the pull request #459 @@ -55,14 +59,16 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args) { - // TODO add parents field in record? with prev as parents - +bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, + struct dfs_rec_flags* flags) { + Int cur_preorder_num = *args -> preorder_num; + HASH_SET(args -> preorder, INTOBJ_INT(current), + INTOBJ_INT((*args -> preorder_num)++)); + args -> visited[current] = true; AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); - ASS_LIST(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); - // CHANGED_BAG(args -> record); - if (CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, + if (args -> CallPreorder && + CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, args -> data)) { return args -> record; } @@ -70,28 +76,37 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args) { Obj succ = ELM_PLIST(args -> neighbors, current); for (Int j = 1; j <= LEN_LIST(succ); j++) { UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - bool visited = HASH_CONTAINS(args -> preorder, INTOBJ_INT(v)); + + if (flags -> revisit && args -> backtracked[v]) { + args -> visited[v] = false; + HASH_DELETE(args -> preorder, INTOBJ_INT(v)); + } + + bool visited = args -> visited[v]; if (!visited) { HASH_SET(args -> parents, INTOBJ_INT(v), INTOBJ_INT(current)); HASH_SET(args -> edge, INTOBJ_INT(v), INTOBJ_INT(j)); - bool rec_res = ExecuteDFSRec(v, current, args); + bool rec_res = ExecuteDFSRec(v, current, args, flags); if (!rec_res) return false; // Stop } else { AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); // CHANGED_BAG(args -> record); - bool backtracked = HASH_CONTAINS(args -> postorder, INTOBJ_INT(v)); - if (!backtracked) { // Back edge - if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, - args -> record, args -> data)) { - return false; - } - } else if (INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(v))) - < INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(current)))) { - // v was visited before current - if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, - args -> data)) { - return false; + if (args -> CallAncestor || args -> CallCross) { + bool backtracked = args -> backtracked[v]; + if (args -> CallAncestor && !backtracked) { // Back edge + if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, + args -> record, args -> data)) { + return false; + } + } else if (args -> CallCross && backtracked && + INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(v))) + < cur_preorder_num) { + // v was visited before current + if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, + args -> data)) { + return false; + } } } } @@ -100,10 +115,12 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args) { // backtracking on current AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); + args -> backtracked[current] = true; HASH_SET(args -> postorder, INTOBJ_INT(current), INTOBJ_INT(++(*args -> postorder_num))); /* CHANGED_BAG(args -> record); */ - if (CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, + if (args -> CallPostorder && + CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, args -> data)) { return false; // Stop execution } @@ -124,14 +141,11 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { 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); + + PreorderFunc = !IS_FUNC(PreorderFunc) ? Fail : PreorderFunc; + PostorderFunc = !IS_FUNC(PostorderFunc) ? Fail : PostorderFunc; + AncestorFunc = !IS_FUNC(AncestorFunc) ? Fail : AncestorFunc; + CrossFunc = !IS_FUNC(CrossFunc) ? Fail : CrossFunc; Obj D = ElmPRec(record, RNamName("graph")); Int N = DigraphNrVertices(D); @@ -145,177 +159,61 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ElmPRec(record, RNamStop) == True) return record; - struct dfs_args* rec_args = - (struct dfs_args*) safe_malloc(sizeof(struct dfs_args)); - UInt preorder_num = 1; UInt postorder_num = 0; + bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + memset(visited_boolarr, false, (N + 1) * sizeof(bool)); + memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); + + struct dfs_rec_flags dfs_flags = + {ElmPRec(flags, RNamName("revisit")) == True}; + + struct dfs_args rec_args = { + .record = record, + .preorder_num = &preorder_num, + .postorder_num = &postorder_num, + .parents = ElmPRec(record, RNamName("parents")), + .postorder = ElmPRec(record, RNamName("postorder")), + .preorder = ElmPRec(record, RNamName("preorder")), + .edge = ElmPRec(record, RNamName("edge")), + .neighbors = FuncOutNeighbours(self, D), .data = data, + .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc, + .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc, + .visited = visited_boolarr, .backtracked = backtracked_boolarr, + .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"), + .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail, + .CallPostorder = PostorderFunc != Fail, + .CallAncestor = AncestorFunc != Fail, .CallCross = CrossFunc != Fail}; + + // TODO handle errors with setting values in HashMaps + + + HASH_RESERVE(rec_args.edge, INTOBJ_INT(N)); // TODO reserve in chunks? + // is using a map better if + // reserving all of it at once + HASH_RESERVE(rec_args.parents, INTOBJ_INT(N)); + HASH_RESERVE(rec_args.preorder, INTOBJ_INT(N)); + HASH_RESERVE(rec_args.postorder, INTOBJ_INT(N)); + + HASH_SET(rec_args.parents, start, start); - rec_args -> RNamChild = RNamName("child"); - rec_args -> RNamCurrent = RNamName("current"); - rec_args -> RNamStop = RNamStop; - rec_args -> record = record; - rec_args -> preorder_num = &preorder_num; - rec_args -> postorder_num = &postorder_num; - rec_args -> neighbors = FuncOutNeighbours(self, D); - rec_args -> data = data; - rec_args -> PreorderFunc = PreorderFunc; - rec_args -> PostorderFunc = PostorderFunc; - rec_args -> CrossFunc = CrossFunc; - rec_args -> AncestorFunc = AncestorFunc; - - - // if (verbose == True) { - rec_args -> edge = ElmPRec(record, RNamName("edge")); - rec_args -> parents = ElmPRec(record, RNamName("parents")); - HASH_SET(rec_args -> parents, start, start); - rec_args -> postorder = ElmPRec(record, RNamName("postorder")); - rec_args -> preorder = ElmPRec(record, RNamName("preorder")); - - HASH_RESERVE(rec_args -> edge, INTOBJ_INT(N)); - HASH_RESERVE(rec_args -> parents, INTOBJ_INT(N)); - HASH_RESERVE(rec_args -> preorder, INTOBJ_INT(N)); - HASH_RESERVE(rec_args -> postorder, INTOBJ_INT(N)); - // } - - DIGRAPHS_ASSERT(IS_PLIST(rec_args -> neighbors)); UInt current = INT_INTOBJ(start); + ExecuteDFSRec(current, current, &rec_args, &dfs_flags); + if (ElmPRec(flags, RNamName("forest")) == True) { for (int i = 1; i <= N; i++) { - bool visited = HASH_CONTAINS(rec_args -> preorder, INTOBJ_INT(i)); + bool visited = HASH_CONTAINS(rec_args.preorder, INTOBJ_INT(i)); if (!visited) { - ExecuteDFSRec(i, i, rec_args); + HASH_SET(rec_args.parents, INTOBJ_INT(i), INTOBJ_INT(i)); + ExecuteDFSRec(i, i, &rec_args, &dfs_flags); } } - } else { - ExecuteDFSRec(current, current, rec_args); } - - free(rec_args); + free(rec_args.backtracked); + free(rec_args.visited); CHANGED_BAG(record); return record; } - -// Adapted Old Iterative Function -Obj FuncExecuteDFSIter_C(Obj self, Obj args) { // TODO remove? - 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(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); - - 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; // referencing the last element in stack - Obj stack = NEW_PLIST(T_PLIST_CYC, N); - - AssPlist(stack, ++top, start); - - UInt preorder_num = 1; - UInt postorder_num = 0; - - Int current = 0; - - Obj parents = ElmPRec(record, RNamName("parents")); - Obj postorder = ElmPRec(record, RNamName("postorder")); - 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 - // FIXME use hash maps for parent, postorder, preorder, and edge - - HASH_SET(parents, start, start); - - Obj neighbors = FuncOutNeighbours(self, D); - DIGRAPHS_ASSERT(IS_PLIST(neighbors)); - - Int RNamChild = RNamName("child"); - Int RNamCurrent = RNamName("current"); - Int RNamStop = RNamName("stop"); - - if (ElmPRec(record, RNamStop) == True) return record; - - while (top > 0) { - // visit current - current = INT_INTOBJ(ELM_PLIST(stack, top--)); // an unvisited node - if (current < 0) { - Int child = current * -1; - // backtracking on current - AssPRec(record, RNamChild, INTOBJ_INT(child)); - AssPRec(record, RNamCurrent, HASH_GET(parents, INTOBJ_INT(child))); - HASH_SET(postorder, INTOBJ_INT(child), INTOBJ_INT(++postorder_num)); - CHANGED_BAG(record); - if (CallCheckStop(PostOrderFunc, RNamStop, record, data)) { - return record; - } - continue; - } - - if (HASH_CONTAINS(preorder, INTOBJ_INT(current))) continue; - - // otherwise, visit this node - - AssPRec(record, RNamCurrent, INTOBJ_INT(current)); - HASH_SET(preorder, INTOBJ_INT(current), INTOBJ_INT(preorder_num++)); - CHANGED_BAG(record); - - if (CallCheckStop(PreorderFunc, RNamStop, record, data)) { - return record; - } - - // Add back to the stack for backtracking - ASS_LIST(stack, ++top, INTOBJ_INT(current * -1)); - CHANGED_BAG(record); - - Obj succ = ELM_PLIST(neighbors, current); - for (Int j = LEN_LIST(succ); j >= 1; j--) { - // Push so that the top of the stack is the first vertex in succ - UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - bool visited = HASH_CONTAINS(preorder, INTOBJ_INT(v)); - AssPRec(record, RNamChild, INTOBJ_INT(v)); - CHANGED_BAG(record); - - if (!visited) { // v is unvisited - HASH_SET(parents, INTOBJ_INT(v), INTOBJ_INT(current)); - HASH_SET(edge, INTOBJ_INT(v), INTOBJ_INT(j)); - ASS_LIST(stack, ++top, INTOBJ_INT(v)); - } else { // v is either visited, or in the stack to be visited - // If v was visited prior, but has not been backtracked on - bool backtracked = HASH_CONTAINS(postorder, INTOBJ_INT(v)); - if (!backtracked) { // Back edge - if (CallCheckStop(AncestorFunc, RNamStop, record, data)) { - return record; - } - } else if (INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(v))) - < INT_INTOBJ(HASH_GET(preorder, INTOBJ_INT(current)))) { - // v was visited before current - if (CallCheckStop(CrossFunc, RNamStop, record, data)) { - return record; - } - } - } - } - } - return record; -} diff --git a/src/dfs.h b/src/dfs.h index c3c403c23..b5b9c3623 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -22,10 +22,6 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); struct dfs_args { - Int RNamChild; - Int RNamCurrent; - Int RNamStop; - Obj record; UInt* preorder_num; UInt* postorder_num; @@ -42,10 +38,29 @@ struct dfs_args { Obj PostorderFunc; Obj AncestorFunc; Obj CrossFunc; + + // Checking if visited from a bit array is faster for large digraphs than + // using the preorder HashMap + bool* visited; // Using bool* rather than bitarray for more than + // 16 bit vertex counts + bool* backtracked; + + Int RNamChild; + Int RNamCurrent; + Int RNamStop; + + bool CallPreorder; + bool CallPostorder; + bool CallAncestor; + bool CallCross; +}; + +struct dfs_rec_flags { + bool revisit; }; -bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args); +bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, + struct dfs_rec_flags* flags); Obj FuncExecuteDFS_C(Obj self, Obj args); -Obj FuncExecuteDFSIter_C(Obj self, Obj args); // TODO remove? #endif // DIGRAPHS_SRC_DFS_H_ diff --git a/src/digraphs.c b/src/digraphs.c index 77471b0ee..b2123b007 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -60,12 +60,13 @@ Obj Group; Obj ClosureGroup; Obj InfoWarning; -// Added for HashMap (DFS) +// Added for DFS Obj DS_Hash_SetValue; Obj DS_Hash_Contains; Obj DS_Hash_Value; Obj DS_Hash_Reserve; +Obj DS_Hash_Delete; static inline bool IsAttributeStoringRep(Obj o) { return (CALL_1ARGS(IsAttributeStoringRepObj, o) == True ? true : false); @@ -2234,10 +2235,6 @@ static StructGVarFunc GVarFuncs[] = { -1, "record, data, start, PreOrderFunc, PostOrderFunc, " "AncestorFunc, CrossFunc"), - GVAR_FUNC(ExecuteDFSIter_C, - -1, - "record, data, start, PreOrderFunc, PostOrderFunc, " - "AncestorFunc, CrossFunc"), {0, 0, 0, 0, 0} /* Finish with an empty entry */ }; @@ -2276,6 +2273,7 @@ static Int InitKernel(StructInitInfo* module) { ImportGVarFromLibrary("DS_Hash_Contains", &DS_Hash_Contains); ImportGVarFromLibrary("DS_Hash_Value", &DS_Hash_Value); ImportGVarFromLibrary("DS_Hash_Reserve", &DS_Hash_Reserve); + ImportGVarFromLibrary("DS_Hash_Delete", &DS_Hash_Delete); /* return success */ return 0; diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 257b91164..a6964354e 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3255,10 +3255,6 @@ gap> DigraphVertexLabels(D); # parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), # stop := false ) -# DFSDefault -gap> DFSDefault(rec(), []); -gap> DFSDefault(rec(), rec()); - # ExecuteDFS gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for > local i; @@ -3271,8 +3267,8 @@ gap> mapToList := function(map, len, list) # For turning record hashmaps -> lis > od; > end;; gap> record := NewDFSRecord(CompleteDigraph(10));; -gap> ExecuteDFS(record, [], 2, DFSDefault, -> DFSDefault, DFSDefault, DFSDefault); +gap> ExecuteDFS(record, [], 2, fail, +> fail, fail, fail); gap> preorder_list := [];; gap> mapToList(record.preorder, 10, preorder_list);; gap> preorder_list; @@ -3283,8 +3279,8 @@ gap> AncestorFunc := function(record, data) > record.stop := true; > data.cycle_vertex := record.child; > end;; -gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, AncestorFunc, DFSDefault); +gap> ExecuteDFS(record, data, 1, fail, +> fail, AncestorFunc, fail); gap> record.stop; true gap> data.cycle_vertex; @@ -3299,8 +3295,8 @@ gap> CrossFunc := function(record, data) > Add(data, record.child); > end;; gap> data := [];; -gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, DFSDefault, CrossFunc); +gap> ExecuteDFS(record, data, 1, fail, +> fail, fail, CrossFunc); gap> record.stop; true gap> data; @@ -3313,16 +3309,16 @@ gap> CrossFunc := function(record, data) > end;; gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; gap> data := rec(back_edges := [], cross_edges := []);; -gap> ExecuteDFS(record, data, 1, DFSDefault, -> DFSDefault, AncestorFunc, CrossFunc);; +gap> ExecuteDFS(record, data, 1, fail, +> fail, AncestorFunc, CrossFunc);; gap> data; rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] ) -gap> ExecuteDFS(rec(), data, 1, DFSDefault, -> DFSDefault, AncestorFunc, CrossFunc); +gap> ExecuteDFS(rec(), data, 1, fail, +> fail, AncestorFunc, CrossFunc); Error, the 1st argument must be created with NewDFSRecord, gap> D := ChainDigraph(1);; -gap> ExecuteDFS(NewDFSRecord(D), [], 3, DFSDefault, DFSDefault, DFSDefault, -> DFSDefault); +gap> ExecuteDFS(NewDFSRecord(D), [], 3, fail, fail, fail, +> fail); Error, the third argument must be a vertex in your graph, # IsDigraphPath From 0ea0274f1d6a2380140c5438806c8017bba6301c Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 25 Mar 2025 19:23:20 +0000 Subject: [PATCH 20/54] Remove reserve for hashmaps --- src/dfs.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 141156faf..fcf54c5af 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -188,14 +188,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { // TODO handle errors with setting values in HashMaps - - HASH_RESERVE(rec_args.edge, INTOBJ_INT(N)); // TODO reserve in chunks? - // is using a map better if - // reserving all of it at once - HASH_RESERVE(rec_args.parents, INTOBJ_INT(N)); - HASH_RESERVE(rec_args.preorder, INTOBJ_INT(N)); - HASH_RESERVE(rec_args.postorder, INTOBJ_INT(N)); - HASH_SET(rec_args.parents, start, start); UInt current = INT_INTOBJ(start); From 9efd4b3653de60f5e86f7c5aa696856f3bb501d6 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 25 Mar 2025 19:42:08 +0000 Subject: [PATCH 21/54] List version with bit arrays --- gap/oper.gi | 22 ++++++++++++---------- gap/prop.gi | 2 +- src/dfs.c | 15 ++++++++------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 4b17a5562..5728d0615 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2472,11 +2472,12 @@ function(D, root) pred, N, w, y, x, i, v, map, key, hashGet; hashGet := function(map, key) - if not IsBound(map[key]) then - return -1; - else - return map[key]; - fi; + return map[key]; + # if not IsBound(map[key]) then + # return -1; + # else + # return map[key]; + # fi; end; M := DigraphNrVertices(D); @@ -2504,7 +2505,8 @@ function(D, root) parents := record.parents; node_to_preorder_num := record.preorder; - Unbind(parents[root]); + parents[root] := -1; + # Unbind(parents[root]); semi := [1 .. M]; lastlinked := M + 1; @@ -2816,10 +2818,10 @@ function(graph) record.child := -1; record.current := -1; record.stop := false; - record.parents := HashMap(); - record.preorder := HashMap(); - record.postorder := HashMap(); - record.edge := HashMap(); + record.parents := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.postorder:= ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.edge := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.config := NewDFSFlags(); return record; end); diff --git a/gap/prop.gi b/gap/prop.gi index 2199e9df8..51b7d402e 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -253,7 +253,7 @@ function(D) end; for i in [1 .. n] do - if not IsBound(record.preorder[i]) then + if record.preorder[i] = -1 then ExecuteDFS(record, [], i, fail, fail, AncestorFunc, fail); diff --git a/src/dfs.c b/src/dfs.c index fcf54c5af..a49568a51 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -32,19 +32,20 @@ extern Obj DS_Hash_Delete; // Macros #define HASH_SET(map, key, val) \ - CALL_3ARGS(DS_Hash_SetValue, map, key, val) + ASS_LIST(map, INT_INTOBJ(key), val) + // CALL_3ARGS(DS_Hash_SetValue, map, key, val) #define HASH_GET(map, key) \ - CALL_2ARGS(DS_Hash_Value, map, key) + ELM_PLIST(map, INT_INTOBJ(key)) + // CALL_2ARGS(DS_Hash_Value, map, key) #define HASH_CONTAINS(map, key) \ - CALL_2ARGS(DS_Hash_Contains, map, key) == True - -#define HASH_RESERVE(map, capacity) \ - CALL_2ARGS(DS_Hash_Reserve, map, capacity) + INT_INTOBJ(ELM_PLIST(map, INT_INTOBJ(key))) != -1 + // CALL_2ARGS(DS_Hash_Contains, map, key) == True #define HASH_DELETE(map, key) \ - CALL_2ARGS(DS_Hash_Delete, map, key) + HASH_SET(map, key, INTOBJ_INT(-1)) + /* CALL_2ARGS(DS_Hash_Delete, map, key) */ // Global Variables From 09c900946f2f0256d83148e3b7abae115697b1df Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 25 Mar 2025 20:08:41 +0000 Subject: [PATCH 22/54] List version without bit arrays --- gap/oper.gi | 2 +- src/dfs.c | 49 +++++++++++++++++++++++-------------------------- src/dfs.h | 6 +++--- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 5728d0615..c386b8df1 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2820,7 +2820,7 @@ function(graph) record.stop := false; record.parents := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.postorder:= ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.edge := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.config := NewDFSFlags(); return record; diff --git a/src/dfs.c b/src/dfs.c index a49568a51..4e02f04d5 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -32,15 +32,15 @@ extern Obj DS_Hash_Delete; // Macros #define HASH_SET(map, key, val) \ - ASS_LIST(map, INT_INTOBJ(key), val) + ASS_LIST(map, key, val) // CALL_3ARGS(DS_Hash_SetValue, map, key, val) #define HASH_GET(map, key) \ - ELM_PLIST(map, INT_INTOBJ(key)) + ELM_PLIST(map, key) // CALL_2ARGS(DS_Hash_Value, map, key) #define HASH_CONTAINS(map, key) \ - INT_INTOBJ(ELM_PLIST(map, INT_INTOBJ(key))) != -1 + INT_INTOBJ(ELM_PLIST(map, key)) != -1 // CALL_2ARGS(DS_Hash_Contains, map, key) == True #define HASH_DELETE(map, key) \ @@ -63,9 +63,9 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, struct dfs_rec_flags* flags) { Int cur_preorder_num = *args -> preorder_num; - HASH_SET(args -> preorder, INTOBJ_INT(current), + HASH_SET(args -> preorder, current, INTOBJ_INT((*args -> preorder_num)++)); - args -> visited[current] = true; + // args -> visited[current] = true; AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); if (args -> CallPreorder && @@ -78,30 +78,31 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, for (Int j = 1; j <= LEN_LIST(succ); j++) { UInt v = INT_INTOBJ(ELM_LIST(succ, j)); - if (flags -> revisit && args -> backtracked[v]) { - args -> visited[v] = false; - HASH_DELETE(args -> preorder, INTOBJ_INT(v)); + if (flags -> revisit && + INT_INTOBJ(HASH_GET(args -> postorder, v)) != -1) { + // args -> visited[v] = false; + HASH_DELETE(args -> preorder, v); } - bool visited = args -> visited[v]; + bool visited = HASH_GET(args -> preorder, v) != INTOBJ_INT(-1); if (!visited) { - HASH_SET(args -> parents, INTOBJ_INT(v), INTOBJ_INT(current)); - HASH_SET(args -> edge, INTOBJ_INT(v), INTOBJ_INT(j)); + HASH_SET(args -> parents, v, INTOBJ_INT(current)); + HASH_SET(args -> edge, v, INTOBJ_INT(j)); bool rec_res = ExecuteDFSRec(v, current, args, flags); if (!rec_res) return false; // Stop } else { AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); // CHANGED_BAG(args -> record); if (args -> CallAncestor || args -> CallCross) { - bool backtracked = args -> backtracked[v]; + bool backtracked = INT_INTOBJ(HASH_GET(args -> postorder, v)) != -1; if (args -> CallAncestor && !backtracked) { // Back edge if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, args -> record, args -> data)) { return false; } } else if (args -> CallCross && backtracked && - INT_INTOBJ(HASH_GET(args -> preorder, INTOBJ_INT(v))) + INT_INTOBJ(HASH_GET(args -> preorder, v)) < cur_preorder_num) { // v was visited before current if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, @@ -116,8 +117,7 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, // backtracking on current AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); - args -> backtracked[current] = true; - HASH_SET(args -> postorder, INTOBJ_INT(current), + HASH_SET(args -> postorder, current, INTOBJ_INT(++(*args -> postorder_num))); /* CHANGED_BAG(args -> record); */ if (args -> CallPostorder && @@ -162,10 +162,10 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { UInt preorder_num = 1; UInt postorder_num = 0; - bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - memset(visited_boolarr, false, (N + 1) * sizeof(bool)); - memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); + // bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + // bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + // memset(visited_boolarr, false, (N + 1) * sizeof(bool)); + // memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); struct dfs_rec_flags dfs_flags = {ElmPRec(flags, RNamName("revisit")) == True}; @@ -181,7 +181,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { .neighbors = FuncOutNeighbours(self, D), .data = data, .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc, .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc, - .visited = visited_boolarr, .backtracked = backtracked_boolarr, + /* .visited = visited_boolarr, .backtracked = backtracked_boolarr, */ .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"), .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail, .CallPostorder = PostorderFunc != Fail, @@ -189,24 +189,21 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { // TODO handle errors with setting values in HashMaps - HASH_SET(rec_args.parents, start, start); + HASH_SET(rec_args.parents, INT_INTOBJ(start), start); UInt current = INT_INTOBJ(start); ExecuteDFSRec(current, current, &rec_args, &dfs_flags); if (ElmPRec(flags, RNamName("forest")) == True) { for (int i = 1; i <= N; i++) { - bool visited = HASH_CONTAINS(rec_args.preorder, INTOBJ_INT(i)); + bool visited = INT_INTOBJ(HASH_GET(rec_args.preorder, i)) != -1; if (!visited) { - HASH_SET(rec_args.parents, INTOBJ_INT(i), INTOBJ_INT(i)); + HASH_SET(rec_args.parents, i, INTOBJ_INT(i)); ExecuteDFSRec(i, i, &rec_args, &dfs_flags); } } } - free(rec_args.backtracked); - free(rec_args.visited); - CHANGED_BAG(record); return record; } diff --git a/src/dfs.h b/src/dfs.h index b5b9c3623..88a7a2de8 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -41,9 +41,9 @@ struct dfs_args { // Checking if visited from a bit array is faster for large digraphs than // using the preorder HashMap - bool* visited; // Using bool* rather than bitarray for more than - // 16 bit vertex counts - bool* backtracked; + // bool* visited; // Using bool* rather than bitarray for more than + // 16 bit vertex counts + // bool* backtracked; Int RNamChild; Int RNamCurrent; From 4bb12ef47237e44096af4de8340a1d3fbc663279 Mon Sep 17 00:00:00 2001 From: Saffron Date: Fri, 28 Mar 2025 17:30:51 +0000 Subject: [PATCH 23/54] Refactor dfs function for tail call optimisation on deeper trees (e.g. ChainDigraph(200000)) --- gap/oper.gi | 41 ++++++-------- src/dfs.c | 156 +++++++++++++++++++++++----------------------------- src/dfs.h | 11 ++-- 3 files changed, 91 insertions(+), 117 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index c386b8df1..0c200e4c7 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2469,16 +2469,7 @@ InstallMethod(DominatorTree, "for a digraph and a vertex", function(D, root) local M, preorder_num_to_node, PreOrderFunc, record, parents, node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, - pred, N, w, y, x, i, v, map, key, hashGet; - - hashGet := function(map, key) - return map[key]; - # if not IsBound(map[key]) then - # return -1; - # else - # return map[key]; - # fi; - end; + pred, N, w, y, x, i, v; M := DigraphNrVertices(D); @@ -2517,21 +2508,21 @@ function(D, root) compress := function(v) local u; - u := hashGet(parents, v); - if u <> fail and lastlinked <= M and hashGet(node_to_preorder_num, u) >= - hashGet(node_to_preorder_num, lastlinked) then + u := parents[v]; + if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= + node_to_preorder_num[lastlinked] then compress(u); - if hashGet(node_to_preorder_num, semi[label[u]]) - < hashGet(node_to_preorder_num, semi[label[v]]) then + if node_to_preorder_num[semi[label[u]]] + < node_to_preorder_num[semi[label[v]]] then label[v] := label[u]; fi; - parents[v] := hashGet(parents, u); + parents[v] := parents[u]; fi; end; eval := function(v) - if lastlinked <= M and hashGet(node_to_preorder_num, v) >= - hashGet(node_to_preorder_num, lastlinked) then + if lastlinked <= M and node_to_preorder_num[v] >= + node_to_preorder_num[lastlinked] then compress(v); return label[v]; else @@ -2545,8 +2536,8 @@ function(D, root) w := preorder_num_to_node[i]; for v in bucket[w] do y := eval(v); - if hashGet(node_to_preorder_num, semi[y]) < - hashGet(node_to_preorder_num, w) then + if node_to_preorder_num[semi[y]] < + node_to_preorder_num[w] then idom[v] := y; else idom[v] := w; @@ -2554,16 +2545,16 @@ function(D, root) od; bucket[w] := []; for v in pred[w] do - if hashGet(node_to_preorder_num, v) <> -1 then + if node_to_preorder_num[v] <> -1 then x := eval(v); - if hashGet(node_to_preorder_num, semi[x]) < - hashGet(node_to_preorder_num, semi[w]) then + if node_to_preorder_num[semi[x]] < + node_to_preorder_num[semi[w]] then semi[w] := semi[x]; fi; fi; od; - if hashGet(parents, w) = semi[w] then - idom[w] := hashGet(parents, w); + if parents[w] = semi[w] then + idom[w] := parents[w]; else Add(bucket[semi[w]], w); fi; diff --git a/src/dfs.c b/src/dfs.c index 4e02f04d5..0a2242279 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -21,89 +21,77 @@ #include "digraphs.h" #include "safemalloc.h" -// Datastructures Functions TODO use GAP level functions -extern Obj DS_Hash_SetValue; -extern Obj DS_Hash_Contains; -extern Obj DS_Hash_Value; -extern Obj IsBound; -extern Obj DS_Hash_Reserve; -extern Obj DS_Hash_Delete; - -// Macros - -#define HASH_SET(map, key, val) \ - ASS_LIST(map, key, val) - // CALL_3ARGS(DS_Hash_SetValue, map, key, val) - -#define HASH_GET(map, key) \ - ELM_PLIST(map, key) - // CALL_2ARGS(DS_Hash_Value, map, key) - -#define HASH_CONTAINS(map, key) \ - INT_INTOBJ(ELM_PLIST(map, key)) != -1 - // CALL_2ARGS(DS_Hash_Contains, map, key) == True - -#define HASH_DELETE(map, key) \ - HASH_SET(map, key, INTOBJ_INT(-1)) - /* CALL_2ARGS(DS_Hash_Delete, map, key) */ - -// Global Variables - // Extreme examples are on the pull request #459 bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { CALL_2ARGS(f, record, data); if (ElmPRec(record, RNamStop) == True) { - // CHANGED_BAG(record); return true; } return false; } -bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, +bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args, struct dfs_rec_flags* flags) { - Int cur_preorder_num = *args -> preorder_num; - HASH_SET(args -> preorder, current, - INTOBJ_INT((*args -> preorder_num)++)); - // args -> visited[current] = true; - AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); - - if (args -> CallPreorder && - CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, - args -> data)) { - return args -> record; + if (idx == 1) { // visit + ASS_LIST(args -> preorder, current, INTOBJ_INT(++(*args -> preorder_num))); + AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); + + args -> visited[current] = true; + + if (args -> CallPreorder && + CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, + args -> data)) { + return args -> record; + } } Obj succ = ELM_PLIST(args -> neighbors, current); - for (Int j = 1; j <= LEN_LIST(succ); j++) { - UInt v = INT_INTOBJ(ELM_LIST(succ, j)); + if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) + AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); + AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); + args -> backtracked[current] = true; + ASS_LIST(args -> postorder, current, + INTOBJ_INT(++(*args -> postorder_num))); + + if (args -> CallPostorder && + CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, + args -> data)) { + return false; // Stop execution + } + Int out_neighbor = INT_INTOBJ(ELM_PLIST(args -> edge, current)); + Int parents_parent = INT_INTOBJ(ELM_PLIST(args -> parents, prev)); - if (flags -> revisit && - INT_INTOBJ(HASH_GET(args -> postorder, v)) != -1) { - // args -> visited[v] = false; - HASH_DELETE(args -> preorder, v); + if (prev == current) return true; // At root + + return ExecuteDFSRec(prev, parents_parent, out_neighbor + 1, args, flags); + } else { + Int v = INT_INTOBJ(ELM_LIST(succ, idx)); + + if (flags -> revisit && args -> backtracked[v]) { + args -> visited[v] = false; + ASS_LIST(args -> preorder, v, INTOBJ_INT(-1)); } - bool visited = HASH_GET(args -> preorder, v) != INTOBJ_INT(-1); + bool visited = args -> visited[v]; if (!visited) { - HASH_SET(args -> parents, v, INTOBJ_INT(current)); - HASH_SET(args -> edge, v, INTOBJ_INT(j)); - bool rec_res = ExecuteDFSRec(v, current, args, flags); - if (!rec_res) return false; // Stop + ASS_LIST(args -> parents, v, INTOBJ_INT(current)); + ASS_LIST(args -> edge, v, INTOBJ_INT(idx)); + return ExecuteDFSRec(v, current, 1, args, flags); } else { AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); - // CHANGED_BAG(args -> record); + if (args -> CallAncestor || args -> CallCross) { - bool backtracked = INT_INTOBJ(HASH_GET(args -> postorder, v)) != -1; + bool backtracked = args -> backtracked[v]; if (args -> CallAncestor && !backtracked) { // Back edge if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, args -> record, args -> data)) { return false; } - } else if (args -> CallCross && backtracked && - INT_INTOBJ(HASH_GET(args -> preorder, v)) - < cur_preorder_num) { + } else if (args -> CallCross && (backtracked && + INT_INTOBJ(ELM_PLIST(args -> preorder, v)) + < INT_INTOBJ(ELM_PLIST(args -> preorder, current)))) { // v was visited before current if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, args -> data)) { @@ -111,21 +99,9 @@ bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, } } } + return ExecuteDFSRec(current, prev, idx + 1, args, flags); } } - - // backtracking on current - AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); - AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); - HASH_SET(args -> postorder, current, - INTOBJ_INT(++(*args -> postorder_num))); - /* CHANGED_BAG(args -> record); */ - if (args -> CallPostorder && - CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, - args -> data)) { - return false; // Stop execution - } - return true; // Continue } @@ -149,6 +125,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { CrossFunc = !IS_FUNC(CrossFunc) ? Fail : CrossFunc; Obj D = ElmPRec(record, RNamName("graph")); + Obj outNeighbours = FuncOutNeighbours(self, D); Int N = DigraphNrVertices(D); if (INT_INTOBJ(start) > N) { @@ -160,12 +137,17 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ElmPRec(record, RNamStop) == True) return record; - UInt preorder_num = 1; - UInt postorder_num = 0; - // bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - // bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - // memset(visited_boolarr, false, (N + 1) * sizeof(bool)); - // memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); + Int preorder_num = 0; + Int postorder_num = 0; + bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); + + if (visited_boolarr == NULL || backtracked_boolarr == NULL) { + ErrorQuit( + "the given graph is too large for memory to be allocated", 0L, 0L); + } + memset(visited_boolarr, false, (N + 1) * sizeof(bool)); + memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); struct dfs_rec_flags dfs_flags = {ElmPRec(flags, RNamName("revisit")) == True}; @@ -178,32 +160,34 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { .postorder = ElmPRec(record, RNamName("postorder")), .preorder = ElmPRec(record, RNamName("preorder")), .edge = ElmPRec(record, RNamName("edge")), - .neighbors = FuncOutNeighbours(self, D), .data = data, + .neighbors = outNeighbours, .data = data, .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc, .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc, - /* .visited = visited_boolarr, .backtracked = backtracked_boolarr, */ + .visited = visited_boolarr, .backtracked = backtracked_boolarr, .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"), .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail, .CallPostorder = PostorderFunc != Fail, .CallAncestor = AncestorFunc != Fail, .CallCross = CrossFunc != Fail}; - // TODO handle errors with setting values in HashMaps + ASS_LIST(rec_args.parents, INT_INTOBJ(start), start); - HASH_SET(rec_args.parents, INT_INTOBJ(start), start); - - UInt current = INT_INTOBJ(start); - ExecuteDFSRec(current, current, &rec_args, &dfs_flags); + Int current = INT_INTOBJ(start); + Int init_idx = 1; + ExecuteDFSRec(current, current, init_idx, &rec_args, &dfs_flags); if (ElmPRec(flags, RNamName("forest")) == True) { - for (int i = 1; i <= N; i++) { - bool visited = INT_INTOBJ(HASH_GET(rec_args.preorder, i)) != -1; + for (Int i = 1; i <= N; i++) { + bool visited = rec_args.visited[i]; if (!visited) { - HASH_SET(rec_args.parents, i, INTOBJ_INT(i)); - ExecuteDFSRec(i, i, &rec_args, &dfs_flags); + ASS_LIST(rec_args.parents, i, INTOBJ_INT(i)); + ExecuteDFSRec(i, i, 1, &rec_args, &dfs_flags); } } } + free(rec_args.backtracked); + free(rec_args.visited); + CHANGED_BAG(record); return record; } diff --git a/src/dfs.h b/src/dfs.h index 88a7a2de8..a6e33f117 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -23,8 +23,8 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); struct dfs_args { Obj record; - UInt* preorder_num; - UInt* postorder_num; + Int* preorder_num; + Int* postorder_num; Obj parents; Obj postorder; @@ -41,9 +41,8 @@ struct dfs_args { // Checking if visited from a bit array is faster for large digraphs than // using the preorder HashMap - // bool* visited; // Using bool* rather than bitarray for more than - // 16 bit vertex counts - // bool* backtracked; + bool* visited; + bool* backtracked; Int RNamChild; Int RNamCurrent; @@ -59,7 +58,7 @@ struct dfs_rec_flags { bool revisit; }; -bool ExecuteDFSRec(UInt current, UInt prev, struct dfs_args* args, +bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args, struct dfs_rec_flags* flags); Obj FuncExecuteDFS_C(Obj self, Obj args); From 6856b6d3e64e615b31aeb28cb0f4b5f22ead2c8b Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 2 Apr 2025 12:06:39 +0100 Subject: [PATCH 24/54] Add an iterative version for revisiting nodes (DigraphLongestDistanceFromVertex) --- gap/attr.gi | 126 --------------------- gap/oper.gi | 29 ++--- gap/prop.gi | 20 ++-- src/dfs.c | 252 ++++++++++++++++++++++++++++-------------- src/dfs.h | 21 ++-- tst/standard/oper.tst | 13 +++ 6 files changed, 208 insertions(+), 253 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index e4b7c05c3..baccc24f3 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -146,132 +146,6 @@ function(D) return [connected, data.articulation_points, data.bridges, data.orientation]; end); -# BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation", -# function(D) -# local N, copy, articulation_points, bridges, orientation, nbs, counter, pre, -# low, nr_children, stack, u, v, i, w, connected; -# -# N := DigraphNrVertices(D); -# -# if HasIsConnectedDigraph(D) and not IsConnectedDigraph(D) then -# # not connected, no articulation points, no bridges, no strong orientation -# return [false, [], [], fail]; -# elif N < 2 then -# # connected, no articulation points (removing 0 or 1 nodes does not make -# # 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)); -# MakeImmutable(copy); -# else -# copy := D; -# fi; -# -# # outputs -# articulation_points := []; -# bridges := []; -# orientation := List([1 .. N], x -> BlistList([1 .. N], [])); -# -# # Get out-neighbours once, to avoid repeated copying for mutable digraphs. -# nbs := OutNeighbours(copy); -# -# # number of nodes encountered in the search so far -# counter := 0; -# -# # 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. -# 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. -# 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 nr_children > 1 then -# Add(articulation_points, 1); -# fi; -# if not IsEmpty(bridges) then -# orientation := fail; -# else -# orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), -# orientation); -# fi; -# else -# connected := false; -# articulation_points := []; -# bridges := []; -# orientation := fail; -# fi; -# if IsImmutableDigraph(D) then -# SetIsConnectedDigraph(D, connected); -# SetArticulationPoints(D, articulation_points); -# SetBridges(D, bridges); -# if IsSymmetricDigraph(D) then -# SetStrongOrientationAttr(D, orientation); -# fi; -# fi; -# return [connected, articulation_points, bridges, orientation]; -# end); - InstallMethod(ArticulationPoints, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], D -> DIGRAPHS_ArticulationPointsBridgesStrongOrientation(D)[2]); diff --git a/gap/oper.gi b/gap/oper.gi index 0c200e4c7..609b82d71 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1701,6 +1701,7 @@ function(D, u, v) fi; end; fi; + ExecuteDFS(record, fail, u, @@ -2018,22 +2019,6 @@ function(D, u, v) return IteratorByFunctions(record); end); -# InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", -# [IsDigraphByOutNeighboursRep, IsPosInt], -# function(D, v) -# local dist; - -# 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 -# return infinity; -# fi; -# return dist; -# end); - InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) @@ -2051,6 +2036,7 @@ function(D, v) PostOrderFunc := function(_, data) data.prev := data.prev - 1; end; + PreOrderFunc := function(_, data) data.prev := data.prev + 1; if data.prev > data.best then @@ -2058,7 +2044,9 @@ function(D, v) fi; end; - record.config.revisit := true; + record.config.revisit := true; # If found another edge to an already + # visited and backtracked on node, + # set to unvisited, and visit it ExecuteDFS(record, data, v, PreOrderFunc, PostOrderFunc, @@ -2497,7 +2485,6 @@ function(D, root) node_to_preorder_num := record.preorder; parents[root] := -1; - # Unbind(parents[root]); semi := [1 .. M]; lastlinked := M + 1; @@ -2823,10 +2810,8 @@ function() local record; record := rec(); record.forest := false; - record.revisit := false; # If revisit = true, then when visiting some node, - # and one of it's neighbors has been visited in - # a different branch (backtracked on), - # visit it again from this node + record.revisit := false; # Use for revisiting nodes + record.iterative := false; return record; end); diff --git a/gap/prop.gi b/gap/prop.gi index 51b7d402e..52c6f3110 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -237,9 +237,9 @@ IsStronglyConnectedDigraph, 0, D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", -[IsDigraphByOutNeighboursRep], # TODO call DFS for each component +[IsDigraphByOutNeighboursRep], function(D) - local n, i, record, AncestorFunc; + local n, record, AncestorFunc; n := DigraphNrVertices(D); if n = 0 then return true; @@ -252,16 +252,14 @@ function(D) record.stop := true; end; - for i in [1 .. n] do - if record.preorder[i] = -1 then - ExecuteDFS(record, [], i, fail, - fail, AncestorFunc, fail); + record.config.forest := true; + ExecuteDFS(record, fail, 1, fail, + fail, AncestorFunc, fail); + + if record.stop then + return false; + fi; - if record.stop then - return false; - fi; - fi; - od; return true; end); diff --git a/src/dfs.c b/src/dfs.c index 0a2242279..38cfcfbed 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -15,11 +15,75 @@ #include // for uint64_t #include // for NULL, free -#include "bitarray.h" #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" -#include "safemalloc.h" + +// Macros used for both recursive and iterative + +#define ON_PREORDER(args, current) \ + ASS_LIST(args->preorder, current, INTOBJ_INT(++(*args->preorder_num))); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ + if (args->CallPreorder \ + && CallCheckStop( \ + args->PreorderFunc, args->RNamStop, args->record, args->data)) { \ + return args->record; \ + } \ + CHANGED_BAG(args->record); + +#define ANCESTOR_CROSS(current, v, backtracked, args) \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ + if (args->CallAncestor || args->CallCross) { \ + if (args->CallAncestor && !backtracked) { \ + if (CallCheckStop( \ + args->AncestorFunc, args->RNamStop, args->record, args->data)) { \ + return false; \ + } \ + } else if (args->CallCross \ + && (backtracked \ + && INT_INTOBJ(ELM_PLIST(args->preorder, v)) \ + < INT_INTOBJ(ELM_PLIST(args->preorder, current)))) { \ + if (CallCheckStop( \ + args->CrossFunc, args->RNamStop, args->record, args->data)) { \ + return false; \ + } \ + } \ + CHANGED_BAG(args->record); \ + } + +#define ON_BACKTRACK(current, prev, args) \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(prev)); \ + ASS_LIST(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ + CHANGED_BAG(args->record); \ + if (args->CallPostorder \ + && CallCheckStop( \ + args->PostorderFunc, args->RNamStop, args->record, args->data)) { \ + return false; \ + } \ + CHANGED_BAG(args->record); + +#define ON_ADD_SUCC(current, succ, idx, args) \ + ASS_LIST(args->parents, succ, INTOBJ_INT(current)); \ + ASS_LIST(args->edge, succ, INTOBJ_INT(idx)); \ + CHANGED_BAG(args->record); + +#define STACK_PUSH(stack, size, val) \ + AssPlist(stack, ++size, val) + +#define STACK_POP(stack, size) \ + ELM_PLIST(stack, size--) + +#define PREORDER_IDX 0 + +void parseConfig(struct dfs_config* conf, Obj conf_record) { + conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; + conf -> revisit = ElmPRec(conf_record, RNamName("revisit")) == True; + conf -> forest = ElmPRec(conf_record, RNamName("forest")) == True; +} // Extreme examples are on the pull request #459 @@ -31,76 +95,105 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args, - struct dfs_rec_flags* flags) { - if (idx == 1) { // visit - ASS_LIST(args -> preorder, current, INTOBJ_INT(++(*args -> preorder_num))); - AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(current)); +// Iterative DFS (for revisiting vertices) + +bool ExecuteDFSIter(Int start, struct dfs_args* args, struct dfs_config* conf) { + Int N = LEN_LIST(args -> neighbors); + Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + + Int stack_size = 1; - args -> visited[current] = true; + AssPlist(stack, 1, INTOBJ_INT(start)); - if (args -> CallPreorder && - CallCheckStop(args -> PreorderFunc, args -> RNamStop, args -> record, - args -> data)) { - return args -> record; + if (!iter_loop(stack, stack_size, args, conf)) return false; + + if (conf -> forest) { + for (Int v = 1; v <= LEN_LIST(args -> neighbors); v++) { + bool visited = INT_INTOBJ(ELM_LIST(args -> preorder, v)) != -1; + + if (!visited) { + ASS_LIST(args -> parents, v, INTOBJ_INT(v)); + CHANGED_BAG(args -> record); + AssPlist(stack, 1, INTOBJ_INT(v)); + + if (!iter_loop(stack, 1, args, conf)) return false; + } + } } + + return true; +} + +bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args, + struct dfs_config* conf) { + while (stack_size > 0) { + Int current = INT_INTOBJ(STACK_POP(stack, stack_size)); + + if (current < 0) { + Int bt_on = current * -1; + Int parent = INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); + ON_BACKTRACK(bt_on, parent, args); + continue; + } else if (INT_INTOBJ(ELM_LIST(args->preorder, current)) != -1) { + continue; + } + + ON_PREORDER(args, current); // and push backtrack node + STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); + + Obj succ = ELM_PLIST(args -> neighbors, current); + + for (Int i = LEN_LIST(succ); i > 0; i--) { + Int v = INT_INTOBJ(ELM_LIST(succ, i)); + bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, + v)) != -1; + bool revisit = (conf -> revisit && backtracked); + + if (!visited || revisit) { + if (revisit) ASS_LIST(args -> preorder, v, INTOBJ_INT(-1)); + ON_ADD_SUCC(current, v, i, args); + STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); + } else { + ANCESTOR_CROSS(current, v, backtracked, args); + } + } + } + return true; +} + +// Recursive DFS + +bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { + if (idx == PREORDER_IDX) { // visit current + ON_PREORDER(args, current); + // Start recursing on successors + return ExecuteDFSRec(current, parent, idx + 1, args); } Obj succ = ELM_PLIST(args -> neighbors, current); + if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) - AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(current)); - AssPRec(args -> record, args -> RNamCurrent, INTOBJ_INT(prev)); - args -> backtracked[current] = true; - ASS_LIST(args -> postorder, current, - INTOBJ_INT(++(*args -> postorder_num))); - - if (args -> CallPostorder && - CallCheckStop(args -> PostorderFunc, args -> RNamStop, args -> record, - args -> data)) { - return false; // Stop execution - } + ON_BACKTRACK(current, parent, args); + Int out_neighbor = INT_INTOBJ(ELM_PLIST(args -> edge, current)); - Int parents_parent = INT_INTOBJ(ELM_PLIST(args -> parents, prev)); + Int parents_parent = INT_INTOBJ(ELM_PLIST(args -> parents, parent)); - if (prev == current) return true; // At root + if (parent == current) return true; // At root - return ExecuteDFSRec(prev, parents_parent, out_neighbor + 1, args, flags); + return ExecuteDFSRec(parent, parents_parent, out_neighbor + 1, args); } else { - Int v = INT_INTOBJ(ELM_LIST(succ, idx)); - - if (flags -> revisit && args -> backtracked[v]) { - args -> visited[v] = false; - ASS_LIST(args -> preorder, v, INTOBJ_INT(-1)); - } + Int v = INT_INTOBJ(ELM_LIST(succ, idx)); + bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; - bool visited = args -> visited[v]; - - if (!visited) { - ASS_LIST(args -> parents, v, INTOBJ_INT(current)); - ASS_LIST(args -> edge, v, INTOBJ_INT(idx)); - return ExecuteDFSRec(v, current, 1, args, flags); - } else { - AssPRec(args -> record, args -> RNamChild, INTOBJ_INT(v)); - - if (args -> CallAncestor || args -> CallCross) { - bool backtracked = args -> backtracked[v]; - if (args -> CallAncestor && !backtracked) { // Back edge - if (CallCheckStop(args -> AncestorFunc, args -> RNamStop, - args -> record, args -> data)) { - return false; - } - } else if (args -> CallCross && (backtracked && - INT_INTOBJ(ELM_PLIST(args -> preorder, v)) - < INT_INTOBJ(ELM_PLIST(args -> preorder, current)))) { - // v was visited before current - if (CallCheckStop(args -> CrossFunc, args -> RNamStop, args -> record, - args -> data)) { - return false; - } - } + if (!visited) { + ON_ADD_SUCC(current, v, idx, args); + return ExecuteDFSRec(v, current, 0, args); + } else { + ANCESTOR_CROSS(current, v, backtracked, args); + return ExecuteDFSRec(current, parent, idx + 1, args); // Skip } - return ExecuteDFSRec(current, prev, idx + 1, args, flags); - } } } @@ -108,7 +201,7 @@ bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args, Obj FuncExecuteDFS_C(Obj self, Obj args) { DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); - Obj flags = ElmPRec(record, RNamName("config")); + Obj config = ElmPRec(record, RNamName("config")); Obj data = ELM_PLIST(args, 2); Obj start = ELM_PLIST(args, 3); Obj PreorderFunc = ELM_PLIST(args, 4); @@ -139,18 +232,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Int preorder_num = 0; Int postorder_num = 0; - bool* visited_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - bool* backtracked_boolarr = (bool*) safe_malloc((N + 1) * sizeof(bool)); - - if (visited_boolarr == NULL || backtracked_boolarr == NULL) { - ErrorQuit( - "the given graph is too large for memory to be allocated", 0L, 0L); - } - memset(visited_boolarr, false, (N + 1) * sizeof(bool)); - memset(backtracked_boolarr, false, (N + 1) * sizeof(bool)); - - struct dfs_rec_flags dfs_flags = - {ElmPRec(flags, RNamName("revisit")) == True}; struct dfs_args rec_args = { .record = record, @@ -163,7 +244,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { .neighbors = outNeighbours, .data = data, .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc, .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc, - .visited = visited_boolarr, .backtracked = backtracked_boolarr, .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"), .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail, .CallPostorder = PostorderFunc != Fail, @@ -172,22 +252,26 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { ASS_LIST(rec_args.parents, INT_INTOBJ(start), start); Int current = INT_INTOBJ(start); - Int init_idx = 1; - ExecuteDFSRec(current, current, init_idx, &rec_args, &dfs_flags); - if (ElmPRec(flags, RNamName("forest")) == True) { - for (Int i = 1; i <= N; i++) { - bool visited = rec_args.visited[i]; - if (!visited) { - ASS_LIST(rec_args.parents, i, INTOBJ_INT(i)); - ExecuteDFSRec(i, i, 1, &rec_args, &dfs_flags); + struct dfs_config dfs_conf = {0}; + parseConfig(&dfs_conf, config); + + if (dfs_conf.iter || dfs_conf.revisit) { + ExecuteDFSIter(current, &rec_args, &dfs_conf); + } else { + if (ElmPRec(config, RNamName("forest")) == True) { + for (Int i = 1; i <= N; i++) { + bool visited = INT_INTOBJ(ELM_PLIST(rec_args.preorder, i)) != -1; + if (!visited) { + ASS_LIST(rec_args.parents, i, INTOBJ_INT(i)); + ExecuteDFSRec(i, i, PREORDER_IDX, &rec_args); + } } + } else { + ExecuteDFSRec(current, current, PREORDER_IDX, &rec_args); } } - free(rec_args.backtracked); - free(rec_args.visited); - CHANGED_BAG(record); return record; } diff --git a/src/dfs.h b/src/dfs.h index a6e33f117..e8a8cd6af 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -17,8 +17,6 @@ // GAP headers #include "gap-includes.h" // for Obj, Int -#include "bitarray.h" - bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); struct dfs_args { @@ -39,11 +37,6 @@ struct dfs_args { Obj AncestorFunc; Obj CrossFunc; - // Checking if visited from a bit array is faster for large digraphs than - // using the preorder HashMap - bool* visited; - bool* backtracked; - Int RNamChild; Int RNamCurrent; Int RNamStop; @@ -54,12 +47,20 @@ struct dfs_args { bool CallCross; }; -struct dfs_rec_flags { +struct dfs_config { bool revisit; + bool iter; + bool forest; }; -bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args, - struct dfs_rec_flags* flags); + + +bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args, + struct dfs_config* conf); +bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args); +bool ExecuteDFSIter(Int start, struct dfs_args* args, struct dfs_config* conf); Obj FuncExecuteDFS_C(Obj self, Obj args); +void parseConfig(struct dfs_config*, Obj conf_record); + #endif // DIGRAPHS_SRC_DFS_H_ diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index a6964354e..f2aa261bf 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1449,6 +1449,9 @@ Error, the 2nd argument must be a vertex of the 1st argument , gap> D := Digraph([[2, 4], [3, 4], [5], [], []]);; gap> DigraphLongestDistanceFromVertex(D, 1); 3 +gap> D := Digraph([[2, 3], [], [2]]);; +gap> DigraphLongestDistanceFromVertex(D, 1); +2 # DigraphRandomWalk gap> gr := CompleteDigraph(5); @@ -3320,6 +3323,16 @@ gap> D := ChainDigraph(1);; gap> ExecuteDFS(NewDFSRecord(D), [], 3, fail, fail, fail, > fail); Error, the third argument must be a vertex in your graph, +gap> record := NewDFSRecord(Digraph([[2, 3], [], [2]]));; +gap> data := rec(back_edges := [], cross_edges := []);; +gap> ExecuteDFS(record, data, 1, fail, fail, AncestorFunc, +> CrossFunc); +gap> record.preorder; +[ 1, 2, 3 ] +gap> record.postorder; +[ 3, 1, 2 ] +gap> data; +rec( back_edges := [ ], cross_edges := [ [ 3, 2 ] ] ) # IsDigraphPath gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); From 3ecf4dee77f582b0d9ba195495e0ac41c392f2de Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 9 Apr 2025 12:42:25 +0100 Subject: [PATCH 25/54] Add back (DFS Versions of) IsAntiSymmetricDigraph and UndirectedSpanningForest, add options to turn off specific record fields when not necessary --- gap/attr.gi | 37 ++-------- gap/oper.gd | 2 + gap/oper.gi | 110 ++++++++++++++++++++++------ gap/prop.gi | 34 ++++++++- src/dfs.c | 162 ++++++++++++++++++++++++++++-------------- src/dfs.h | 12 ++-- tst/standard/oper.tst | 38 ++++++---- 7 files changed, 267 insertions(+), 128 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index baccc24f3..1c095345d 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -45,6 +45,7 @@ function(D) copy := DigraphSymmetricClosure(DigraphMutableCopy(D)); copy := DigraphRemoveAllMultipleEdges(copy); MakeImmutable(copy); + # No forward edges or cross edges now exist else copy := D; fi; @@ -53,7 +54,8 @@ function(D) local child, current; child := record.child; current := record.parents[child]; - if record.preorder[child] > record.preorder[current] then + # record.preorder[child] > record.preorder[current] then + if current <> child then # stops the duplication of articulation_points if current <> 1 and data.low[child] >= record.preorder[current] then Add(data.articulation_points, current); @@ -86,7 +88,7 @@ function(D) current := record.current; child := record.child; parent := record.parents[current]; - # current -> child is a back edge + # current -> child is a back edge or cross edge if child <> parent and record.preorder[child] < data.low[current] then data.low[current] := record.preorder[child]; fi; @@ -2676,9 +2678,6 @@ function(D) ExecuteDFS(record, data, 1, PreOrderFunc, fail, fail, fail); - # for i in DigraphVertices(C) do - # od; - if IsMutableDigraph(D) then D!.OutNeighbours := data; ClearDigraphEdgeLabels(D); @@ -2692,37 +2691,11 @@ function(D) return C; end); -# InstallMethod(UndirectedSpanningForest, -# "for a mutable digraph by out-neighbours", -# [IsMutableDigraph and IsDigraphByOutNeighboursRep], -# function(D) -# if DigraphHasNoVertices(D) then -# return fail; -# fi; -# MaximalSymmetricSubdigraph(D); -# D!.OutNeighbours := DIGRAPH_SYMMETRIC_SPANNING_FOREST(D!.OutNeighbours); -# ClearDigraphEdgeLabels(D); -# return D; -# end); - InstallMethod(UndirectedSpanningForest, "for an immutable digraph", [IsImmutableDigraph], UndirectedSpanningForestAttr); InstallMethod(UndirectedSpanningForestAttr, "for an immutable digraph", -[IsImmutableDigraph and IsDigraphByOutNeighboursRep], -function(D) - local C; - if DigraphHasNoVertices(D) then - return fail; - fi; - C := MaximalSymmetricSubdigraph(D); - C := DIGRAPH_SYMMETRIC_SPANNING_FOREST(C!.OutNeighbours); - C := ConvertToImmutableDigraphNC(C); - SetIsUndirectedForest(C, true); - SetIsMultiDigraph(C, false); - SetDigraphHasLoops(C, false); - return C; -end); +[IsImmutableDigraph and IsDigraphByOutNeighboursRep], UndirectedSpanningForest); InstallMethod(UndirectedSpanningTree, "for a mutable digraph", [IsMutableDigraph], diff --git a/gap/oper.gd b/gap/oper.gd index 23b90fb7d..b534f8665 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -155,5 +155,7 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", # 11. DFS DeclareOperation("NewDFSRecord", [IsDigraph]); +DeclareOperation("NewDFSRecordLightweight", [IsDigraph, IsRecord]); DeclareOperation("NewDFSFlags", []); +DeclareOperation("NewDFSFlagsLightweight", []); DeclareGlobalFunction("ExecuteDFS"); diff --git a/gap/oper.gi b/gap/oper.gi index 609b82d71..b70e8c224 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2030,9 +2030,11 @@ function(D, v) fi; record := NewDFSRecord(D); data := rec(prev := -1, best := 0); + AncestorFunc := function(record, _) record.stop := true; end; + PostOrderFunc := function(_, data) data.prev := data.prev - 1; end; @@ -2048,6 +2050,8 @@ function(D, v) # visited and backtracked on node, # set to unvisited, and visit it + record.config.iterative := true; # revisit DFS must be iterative + ExecuteDFS(record, data, v, PreOrderFunc, PostOrderFunc, AncestorFunc, fail); @@ -2353,7 +2357,7 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N, record, data, AncestorFunc, PreOrderFunc; + local N, record, conf, data, AncestorFunc, PreOrderFunc; N := DigraphNrVertices(D); if 0 = root or root > N then @@ -2361,7 +2365,13 @@ function(D, root) "argument (a digraph)"); fi; - record := NewDFSRecord(D); + conf := NewDFSFlagsLightweight(); + + conf.use_postorder := true; + conf.use_edge := true; + conf.use_parents := true; + + record := NewDFSRecordLightweight(D, conf); data := rec(result := [], root_reached := false); PreOrderFunc := function(record, data) @@ -2787,32 +2797,85 @@ end); # 11. DFS ############################################################################# +DIGRAPHS_DFSRecNames := function() + return ["stop", "graph", "child", "parents", "preorder", "postorder", + "current", "edge"]; + end; + +DIGRAPHS_DFSFlagNames := function() + return ["iterative", "forest", "revisit", "use_parents", "use_edge", + "use_postorder"]; + end; + +DIGRAPHS_DFSError := function() + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); +end; + InstallMethod(NewDFSRecord, "for a digraph", [IsDigraph], -function(graph) - local record; +Graph -> NewDFSRecordLightweight(Graph, NewDFSFlags())); + +InstallMethod(NewDFSRecordLightweight, +"for a digraph and a record", [IsDigraph, IsRecord], +function(graph, conf) + local record, config_names; + + config_names := DIGRAPHS_DFSFlagNames(); record := rec(); + if ForAny(config_names, n -> not IsBound(conf.(n))) then + DIGRAPHS_DFSError(); + fi; + record.graph := graph; record.child := -1; record.current := -1; record.stop := false; - record.parents := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.edge := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); - record.config := NewDFSFlags(); + if conf.use_parents then + record.parents := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + else + record.parents := fail; + fi; + if conf.use_postorder then + record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + else + record.postorder := fail; + fi; + if conf.use_edge then + record.edge := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + else + record.edge := fail; + fi; + + record.config := conf; return record; end); InstallMethod(NewDFSFlags, "", [], function() - local record; - record := rec(); - record.forest := false; - record.revisit := false; # Use for revisiting nodes - record.iterative := false; - return record; + local config; + config := rec(); + config.forest := false; # Visit all vertices (all connected components) + config.revisit := false; # Use for revisiting nodes (requires iterative) + config.iterative := false; + config.use_edge := true; # Whether these record fields are necessary + config.use_postorder := true; + config.use_parents := true; + return config; +end); + +InstallMethod(NewDFSFlagsLightweight, +"", [], +function() + local config; + config := NewDFSFlags(); + config.iterative := false; + config.use_postorder := false; + config.use_parents := false; + config.use_edge := false; + return config; end); # * PreOrderFunc is called with (record, data) when a vertex is popped from the @@ -2830,15 +2893,18 @@ end); InstallGlobalFunction(ExecuteDFS, function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) - local name; + local names, config_names; - for name in ["stop", "graph", "child", "parents", "preorder", "postorder", - "current", "edge"] do - if not IsBound(record.(name)) then - ErrorNoReturn("the 1st argument must be created with ", - "NewDFSRecord,"); - fi; - od; + names := DIGRAPHS_DFSRecNames(); + config_names := DIGRAPHS_DFSFlagNames(); + + if not IsBound(record.config) then + DIGRAPHS_DFSError(); + elif ForAny(config_names, n -> not IsBound(record.config.(n))) then + DIGRAPHS_DFSError(); + elif ForAny(names, n -> not IsBound(record.(n))) then + DIGRAPHS_DFSError(); + fi; ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); diff --git a/gap/prop.gi b/gap/prop.gi index 52c6f3110..34e94ccec 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -384,7 +384,39 @@ D -> DigraphPeriod(D) = 1); InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> IS_ANTISYMMETRIC_DIGRAPH(OutNeighbours(D))); +function(D) + local record, AncestorFunc; + + if DigraphNrVertices(D) <= 1 then + return true; + fi; + record := NewDFSRecord(D); + record.config.forest := true; + + AncestorFunc := function(record, _) + local pos, neighbours; + if record.child = record.current then + return; + fi; + + # back edge record.current -> record.child + # checks if the child has a symmetric edge with current node + neighbours := OutNeighboursOfVertex(D, record.child); + pos := Position(neighbours, record.current); + if pos <> fail then + record.stop := true; + fi; + end; + + ExecuteDFS(record, [], 1, fail, fail, + AncestorFunc, fail); + + if record.stop then + return false; + fi; + + return true; +end); InstallMethod(IsTransitiveDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], diff --git a/src/dfs.c b/src/dfs.c index 38cfcfbed..8252fff49 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -18,10 +18,11 @@ #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" +#include "stdio.h" // Macros used for both recursive and iterative -#define ON_PREORDER(args, current) \ +#define ON_PREORDER(current, args) \ ASS_LIST(args->preorder, current, INTOBJ_INT(++(*args->preorder_num))); \ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ CHANGED_BAG(args->record); \ @@ -33,10 +34,10 @@ CHANGED_BAG(args->record); #define ANCESTOR_CROSS(current, v, backtracked, args) \ - AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ - CHANGED_BAG(args->record); \ if (args->CallAncestor || args->CallCross) { \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ if (args->CallAncestor && !backtracked) { \ if (CallCheckStop( \ args->AncestorFunc, args->RNamStop, args->record, args->data)) { \ @@ -54,21 +55,28 @@ CHANGED_BAG(args->record); \ } -#define ON_BACKTRACK(current, prev, args) \ - AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(prev)); \ - ASS_LIST(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ - CHANGED_BAG(args->record); \ - if (args->CallPostorder \ - && CallCheckStop( \ - args->PostorderFunc, args->RNamStop, args->record, args->data)) { \ - return false; \ - } \ - CHANGED_BAG(args->record); +#define ON_BACKTRACK(current, parent, args) \ + if (args->dfs_conf->use_postorder) { \ + ASS_LIST(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ + } \ + if (args->CallPostorder) { \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ + CHANGED_BAG(args->record); \ + if (CallCheckStop( \ + args->PostorderFunc, args->RNamStop, args->record, args->data)) { \ + return false; \ + } \ + CHANGED_BAG(args->record); \ + } -#define ON_ADD_SUCC(current, succ, idx, args) \ - ASS_LIST(args->parents, succ, INTOBJ_INT(current)); \ - ASS_LIST(args->edge, succ, INTOBJ_INT(idx)); \ +#define ON_ADD_SUCC(current, succ, idx, args) \ + if (args->dfs_conf->use_parents) { \ + ASS_LIST(args->parents, succ, INTOBJ_INT(current)); \ + } \ + if (args->dfs_conf->use_edge) { \ + ASS_LIST(args->edge, succ, INTOBJ_INT(idx)); \ + } \ CHANGED_BAG(args->record); #define STACK_PUSH(stack, size, val) \ @@ -77,12 +85,42 @@ #define STACK_POP(stack, size) \ ELM_PLIST(stack, size--) -#define PREORDER_IDX 0 +#define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to + // visit the current node) -void parseConfig(struct dfs_config* conf, Obj conf_record) { +void parseConfig(struct dfs_args* args, Obj conf_record) { + struct dfs_config* conf = args -> dfs_conf; conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; conf -> revisit = ElmPRec(conf_record, RNamName("revisit")) == True; conf -> forest = ElmPRec(conf_record, RNamName("forest")) == True; + conf -> use_postorder = + ElmPRec(conf_record, RNamName("use_postorder")) == True; + conf -> use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; + conf -> use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; + + if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) { + ErrorQuit( + "In a DFSRecord where the config flag iter is false, use_edge and " + "use_parents must be true", 0L, 0L); + } + + if (conf -> revisit && !(conf -> iter && conf -> use_postorder)) { + ErrorQuit( + "In a DFSRecord where the config flag revisit is true, use_postorder " + "and iterative must also be true", 0L, 0L); + } + + if ((args -> CallAncestor || args -> CallCross) && !conf -> use_postorder) { + ErrorQuit( + "In a DFSRecord where either an AncestorFunc or CrossFunc exists, " + "the config flag use_postorder must be true", 0L, 0L); + } + + if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) { + ErrorQuit( + "In a DFSRecord where a PostorderFunc exists, where the config flag " + "iter is true, the flag use_postorder must also be true", 0L, 0L); + } } // Extreme examples are on the pull request #459 @@ -95,9 +133,12 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { return false; } -// Iterative DFS (for revisiting vertices) +/* Iterative DFS (used for revisiting vertices) + Necessary record elements: preorder + If CallPostorder, then parents is necessary +*/ -bool ExecuteDFSIter(Int start, struct dfs_args* args, struct dfs_config* conf) { +bool ExecuteDFSIter(Int start, struct dfs_args* args) { Int N = LEN_LIST(args -> neighbors); Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); @@ -105,50 +146,53 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args, struct dfs_config* conf) { AssPlist(stack, 1, INTOBJ_INT(start)); - if (!iter_loop(stack, stack_size, args, conf)) return false; + if (!iter_loop(stack, stack_size, args)) return false; - if (conf -> forest) { + if (args -> dfs_conf -> forest) { for (Int v = 1; v <= LEN_LIST(args -> neighbors); v++) { bool visited = INT_INTOBJ(ELM_LIST(args -> preorder, v)) != -1; if (!visited) { - ASS_LIST(args -> parents, v, INTOBJ_INT(v)); + if (args->dfs_conf->use_parents) { + ASS_LIST(args->parents, v, INTOBJ_INT(v)); + } CHANGED_BAG(args -> record); AssPlist(stack, 1, INTOBJ_INT(v)); - if (!iter_loop(stack, 1, args, conf)) return false; + if (!iter_loop(stack, 1, args)) return false; } } } - return true; } -bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args, - struct dfs_config* conf) { +bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { while (stack_size > 0) { Int current = INT_INTOBJ(STACK_POP(stack, stack_size)); if (current < 0) { Int bt_on = current * -1; - Int parent = INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); + Int parent = !args -> dfs_conf -> use_parents ? -1 : + INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); ON_BACKTRACK(bt_on, parent, args); continue; } else if (INT_INTOBJ(ELM_LIST(args->preorder, current)) != -1) { continue; } - ON_PREORDER(args, current); // and push backtrack node - STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); + ON_PREORDER(current, args); // and push backtrack node + if (args -> dfs_conf -> use_postorder || args -> CallPostorder) { + STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); + } Obj succ = ELM_PLIST(args -> neighbors, current); for (Int i = LEN_LIST(succ); i > 0; i--) { Int v = INT_INTOBJ(ELM_LIST(succ, i)); bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; - bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, - v)) != -1; - bool revisit = (conf -> revisit && backtracked); + bool backtracked = !args -> dfs_conf -> use_postorder || + INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; + bool revisit = (args -> dfs_conf -> revisit && backtracked); if (!visited || revisit) { if (revisit) ASS_LIST(args -> preorder, v, INTOBJ_INT(-1)); @@ -162,11 +206,13 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args, return true; } -// Recursive DFS +/* Recursive DFS + Necessary record elements: edge, preorder, parents +*/ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { if (idx == PREORDER_IDX) { // visit current - ON_PREORDER(args, current); + ON_PREORDER(current, args); // Start recursing on successors return ExecuteDFSRec(current, parent, idx + 1, args); } @@ -176,21 +222,22 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) ON_BACKTRACK(current, parent, args); - Int out_neighbor = INT_INTOBJ(ELM_PLIST(args -> edge, current)); + Int prev_idx = INT_INTOBJ(ELM_PLIST(args -> edge, current)); Int parents_parent = INT_INTOBJ(ELM_PLIST(args -> parents, parent)); if (parent == current) return true; // At root - return ExecuteDFSRec(parent, parents_parent, out_neighbor + 1, args); + return ExecuteDFSRec(parent, parents_parent, prev_idx + 1, args); } else { Int v = INT_INTOBJ(ELM_LIST(succ, idx)); bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; - bool backtracked = INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; if (!visited) { ON_ADD_SUCC(current, v, idx, args); return ExecuteDFSRec(v, current, 0, args); } else { + bool backtracked = !args -> dfs_conf -> use_postorder || + INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; ANCESTOR_CROSS(current, v, backtracked, args); return ExecuteDFSRec(current, parent, idx + 1, args); // Skip } @@ -201,7 +248,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); - Obj config = ElmPRec(record, RNamName("config")); + Obj config = ElmPRec(record, RNamName("config")); Obj data = ELM_PLIST(args, 2); Obj start = ELM_PLIST(args, 3); Obj PreorderFunc = ELM_PLIST(args, 4); @@ -226,14 +273,17 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { "the third argument must be a vertex in your graph,", 0L, 0L); } - Int RNamStop = RNamName("stop"); + Int RNamStop = RNamName("stop"); if (ElmPRec(record, RNamStop) == True) return record; - Int preorder_num = 0; + Int preorder_num = 0; Int postorder_num = 0; - struct dfs_args rec_args = { + struct dfs_config dfs_conf = {0}; + + struct dfs_args dfs_args_ = { + .dfs_conf = &dfs_conf, .record = record, .preorder_num = &preorder_num, .postorder_num = &postorder_num, @@ -249,26 +299,30 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { .CallPostorder = PostorderFunc != Fail, .CallAncestor = AncestorFunc != Fail, .CallCross = CrossFunc != Fail}; - ASS_LIST(rec_args.parents, INT_INTOBJ(start), start); + parseConfig(&dfs_args_, config); - Int current = INT_INTOBJ(start); + if (dfs_conf.use_parents) { + ASS_LIST(dfs_args_.parents, INT_INTOBJ(start), start); + } - struct dfs_config dfs_conf = {0}; - parseConfig(&dfs_conf, config); + Int current = INT_INTOBJ(start); if (dfs_conf.iter || dfs_conf.revisit) { - ExecuteDFSIter(current, &rec_args, &dfs_conf); + ExecuteDFSIter(current, &dfs_args_); } else { - if (ElmPRec(config, RNamName("forest")) == True) { + if (dfs_conf.forest) { + ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_); for (Int i = 1; i <= N; i++) { - bool visited = INT_INTOBJ(ELM_PLIST(rec_args.preorder, i)) != -1; + bool visited = INT_INTOBJ(ELM_PLIST(dfs_args_.preorder, i)) != -1; if (!visited) { - ASS_LIST(rec_args.parents, i, INTOBJ_INT(i)); - ExecuteDFSRec(i, i, PREORDER_IDX, &rec_args); + if (dfs_conf.use_parents) { + ASS_LIST(dfs_args_.parents, i, INTOBJ_INT(i)); + } + ExecuteDFSRec(i, i, PREORDER_IDX, &dfs_args_); } } } else { - ExecuteDFSRec(current, current, PREORDER_IDX, &rec_args); + ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_); } } diff --git a/src/dfs.h b/src/dfs.h index e8a8cd6af..a82595bda 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -20,6 +20,8 @@ bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); struct dfs_args { + struct dfs_config* dfs_conf; + Obj record; Int* preorder_num; Int* postorder_num; @@ -51,16 +53,18 @@ struct dfs_config { bool revisit; bool iter; bool forest; + bool use_postorder; + bool use_parents; + bool use_edge; }; -bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args, - struct dfs_config* conf); +bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args); bool ExecuteDFSRec(Int current, Int prev, Int idx, struct dfs_args* args); -bool ExecuteDFSIter(Int start, struct dfs_args* args, struct dfs_config* conf); +bool ExecuteDFSIter(Int start, struct dfs_args* args); Obj FuncExecuteDFS_C(Obj self, Obj args); -void parseConfig(struct dfs_config*, Obj conf_record); +void parseConfig(struct dfs_args*, Obj conf_record); #endif // DIGRAPHS_SRC_DFS_H_ diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index f2aa261bf..17d4cb7df 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1342,6 +1342,11 @@ ent , gap> DigraphPath(gr, 11, 11); Error, the 2nd and 3rd arguments and must be vertices of the 1st argum\ ent , +gap> gr := Digraph([[1, 2], [3], [1]]);; +gap> path := DigraphPath(gr, 1, 1); +[ [ 1, 1 ], [ 1 ] ] +gap> IsDigraphPath(gr, path); +true # IteratorOfPaths gap> gr := CompleteDigraph(5);; @@ -3259,22 +3264,11 @@ gap> DigraphVertexLabels(D); # stop := false ) # ExecuteDFS -gap> mapToList := function(map, len, list) # For turning record hashmaps -> lists for -> local i; -> for i in [1 .. len] do # printing -> if not IsBound(map[i]) then -> Add(list, -1); -> else -> Add(list, map[i]); -> fi; -> od; -> end;; gap> record := NewDFSRecord(CompleteDigraph(10));; gap> ExecuteDFS(record, [], 2, fail, > fail, fail, fail); gap> preorder_list := [];; -gap> mapToList(record.preorder, 10, preorder_list);; -gap> preorder_list; +gap> record.preorder; [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ] gap> record := NewDFSRecord(CompleteDigraph(15));; gap> data := rec(cycle_vertex := 0);; @@ -3288,9 +3282,7 @@ gap> record.stop; true gap> data.cycle_vertex; 1 -gap> preorder_list := [];; -gap> mapToList(record.preorder, 15, preorder_list);; -gap> preorder_list; +gap> record.preorder; [ 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ] gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; gap> CrossFunc := function(record, data) @@ -3333,6 +3325,17 @@ gap> record.postorder; [ 3, 1, 2 ] gap> data; rec( back_edges := [ ], cross_edges := [ [ 3, 2 ] ] ) +gap> record := NewDFSRecord(Digraph([[2, 3], [3], []]));; +gap> data := rec(explored_edges := []);; +gap> PreorderFunc := function(record, data) +> if record.current <> record.parents[record.current] then +> Add(data.explored_edges, [record.parents[record.current], record.current]); +> fi; +> end;; +gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.explored_edges; +[ [ 1, 2 ], [ 2, 3 ] ] # IsDigraphPath gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); @@ -3434,6 +3437,11 @@ gap> Unbind(u1); gap> Unbind(u2); gap> Unbind(x); gap> Unbind(TestPartialOrderDigraph); +gap> Unbind(PreorderFunc); +gap> Unbind(AncestorFunc); +gap> Unbind(CrossFunc); +gap> Unbind(record); +gap> Unbind(data); # gap> DIGRAPHS_StopTest(); From e659124d125cfde7219bfb6f2f3518ba39275fc2 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 9 Apr 2025 12:58:07 +0100 Subject: [PATCH 26/54] Change args used for ExecuteDFS instances to remove use of unnecessary record elements --- gap/attr.gi | 10 +++++++--- gap/prop.gi | 1 + src/dfs.c | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 1c095345d..998adb354 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -2658,12 +2658,11 @@ InstallMethod(DigraphReflexiveTransitiveReductionAttr, InstallMethod(UndirectedSpanningForest, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local C, record, data, PreOrderFunc; + local C, record, conf, data, PreOrderFunc; if DigraphNrVertices(D) = 0 then return fail; fi; C := MaximalSymmetricSubdigraph(D); - record := NewDFSRecord(C); data := List(DigraphVertices(C), x -> []); PreOrderFunc := function(record, data) @@ -2673,7 +2672,12 @@ function(D) fi; end; - record.config.forest := true; + conf := NewDFSFlagsLightweight(); + conf.use_parents := true; + conf.use_edge := true; + conf.forest := true; + + record := NewDFSRecordLightweight(C, conf); ExecuteDFS(record, data, 1, PreOrderFunc, fail, fail, fail); diff --git a/gap/prop.gi b/gap/prop.gi index 34e94ccec..ceceabbf8 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -390,6 +390,7 @@ function(D) if DigraphNrVertices(D) <= 1 then return true; fi; + record := NewDFSRecord(D); record.config.forest := true; diff --git a/src/dfs.c b/src/dfs.c index 8252fff49..b43018eae 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -18,7 +18,6 @@ #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" -#include "stdio.h" // Macros used for both recursive and iterative @@ -100,7 +99,7 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) { ErrorQuit( - "In a DFSRecord where the config flag iter is false, use_edge and " + "In a DFSRecord where the config flag iterative is false, use_edge and " "use_parents must be true", 0L, 0L); } From c5110d3d2881e160b2969cc7a403c2344b52be39 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 9 Apr 2025 13:37:56 +0100 Subject: [PATCH 27/54] Attempt 1 to make GAP 4.11 pass (changed all uses to iterative --- gap/attr.gi | 5 ++++- gap/oper.gi | 3 +++ gap/prop.gi | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gap/attr.gi b/gap/attr.gi index 998adb354..d3037797e 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -112,7 +112,8 @@ function(D) # articulation point if and only if it has at least 2 children. data.nr_children := 0; - record := NewDFSRecord(copy); + record := NewDFSRecord(copy); + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, data, 1, @@ -889,6 +890,7 @@ function(D) end; record.config.forest := true; + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, @@ -2678,6 +2680,7 @@ function(D) conf.forest := true; record := NewDFSRecordLightweight(C, conf); + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, data, 1, PreOrderFunc, fail, fail, fail); diff --git a/gap/oper.gi b/gap/oper.gi index b70e8c224..e37975ee4 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1702,6 +1702,7 @@ function(D, u, v) end; fi; + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, u, @@ -2387,6 +2388,7 @@ function(D, root) fi; end; + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, data, root, @@ -2483,6 +2485,7 @@ function(D, root) end; record := NewDFSRecord(D); + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, preorder_num_to_node, root, diff --git a/gap/prop.gi b/gap/prop.gi index ceceabbf8..92b61166c 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -253,6 +253,7 @@ function(D) end; record.config.forest := true; + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, 1, fail, fail, AncestorFunc, fail); @@ -408,6 +409,7 @@ function(D) record.stop := true; fi; end; + record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, [], 1, fail, fail, AncestorFunc, fail); From 30ab1155bfae852c67dfd40ec3426891f77ad52d Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 9 Apr 2025 13:50:36 +0100 Subject: [PATCH 28/54] Attempt 2, added CHANGED_BAG --- gap/attr.gi | 5 +---- gap/oper.gi | 3 --- gap/prop.gi | 2 -- src/dfs.c | 8 +++++++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index d3037797e..998adb354 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -112,8 +112,7 @@ function(D) # articulation point if and only if it has at least 2 children. data.nr_children := 0; - record := NewDFSRecord(copy); - record.config.iterative := true; # TODO DFS remove + record := NewDFSRecord(copy); ExecuteDFS(record, data, 1, @@ -890,7 +889,6 @@ function(D) end; record.config.forest := true; - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, @@ -2680,7 +2678,6 @@ function(D) conf.forest := true; record := NewDFSRecordLightweight(C, conf); - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, data, 1, PreOrderFunc, fail, fail, fail); diff --git a/gap/oper.gi b/gap/oper.gi index e37975ee4..b70e8c224 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1702,7 +1702,6 @@ function(D, u, v) end; fi; - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, u, @@ -2388,7 +2387,6 @@ function(D, root) fi; end; - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, data, root, @@ -2485,7 +2483,6 @@ function(D, root) end; record := NewDFSRecord(D); - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, preorder_num_to_node, root, diff --git a/gap/prop.gi b/gap/prop.gi index 92b61166c..ceceabbf8 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -253,7 +253,6 @@ function(D) end; record.config.forest := true; - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, fail, 1, fail, fail, AncestorFunc, fail); @@ -409,7 +408,6 @@ function(D) record.stop := true; fi; end; - record.config.iterative := true; # TODO DFS remove ExecuteDFS(record, [], 1, fail, fail, AncestorFunc, fail); diff --git a/src/dfs.c b/src/dfs.c index b43018eae..b09d35b57 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -57,6 +57,7 @@ #define ON_BACKTRACK(current, parent, args) \ if (args->dfs_conf->use_postorder) { \ ASS_LIST(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ + CHANGED_BAG(args->record); \ } \ if (args->CallPostorder) { \ AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ @@ -194,7 +195,10 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { bool revisit = (args -> dfs_conf -> revisit && backtracked); if (!visited || revisit) { - if (revisit) ASS_LIST(args -> preorder, v, INTOBJ_INT(-1)); + if (revisit) { + ASS_LIST(args->preorder, v, INTOBJ_INT(-1)); + CHANGED_BAG(args -> record); + } ON_ADD_SUCC(current, v, i, args); STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); } else { @@ -302,6 +306,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (dfs_conf.use_parents) { ASS_LIST(dfs_args_.parents, INT_INTOBJ(start), start); + CHANGED_BAG(record); } Int current = INT_INTOBJ(start); @@ -316,6 +321,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (!visited) { if (dfs_conf.use_parents) { ASS_LIST(dfs_args_.parents, i, INTOBJ_INT(i)); + CHANGED_BAG(record); } ExecuteDFSRec(i, i, PREORDER_IDX, &dfs_args_); } From 4a9a35033bc20eff8aef4c816743b311dd3d8fa8 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 9 Apr 2025 14:05:37 +0100 Subject: [PATCH 29/54] Attempt 3, no PLIST usage for out neighbors --- src/dfs.c | 26 +++++++++++++------------- tst/standard/oper.tst | 4 ++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index b09d35b57..54bbc0a92 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -56,7 +56,7 @@ #define ON_BACKTRACK(current, parent, args) \ if (args->dfs_conf->use_postorder) { \ - ASS_LIST(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ + AssPlist(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ CHANGED_BAG(args->record); \ } \ if (args->CallPostorder) { \ @@ -72,10 +72,10 @@ #define ON_ADD_SUCC(current, succ, idx, args) \ if (args->dfs_conf->use_parents) { \ - ASS_LIST(args->parents, succ, INTOBJ_INT(current)); \ + AssPlist(args->parents, succ, INTOBJ_INT(current)); \ } \ if (args->dfs_conf->use_edge) { \ - ASS_LIST(args->edge, succ, INTOBJ_INT(idx)); \ + AssPlist(args->edge, succ, INTOBJ_INT(idx)); \ } \ CHANGED_BAG(args->record); @@ -150,11 +150,11 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args) { if (args -> dfs_conf -> forest) { for (Int v = 1; v <= LEN_LIST(args -> neighbors); v++) { - bool visited = INT_INTOBJ(ELM_LIST(args -> preorder, v)) != -1; + bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; if (!visited) { if (args->dfs_conf->use_parents) { - ASS_LIST(args->parents, v, INTOBJ_INT(v)); + AssPlist(args->parents, v, INTOBJ_INT(v)); } CHANGED_BAG(args -> record); AssPlist(stack, 1, INTOBJ_INT(v)); @@ -173,10 +173,10 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { if (current < 0) { Int bt_on = current * -1; Int parent = !args -> dfs_conf -> use_parents ? -1 : - INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); + INT_INTOBJ(ELM_PLIST(args -> parents, bt_on)); ON_BACKTRACK(bt_on, parent, args); continue; - } else if (INT_INTOBJ(ELM_LIST(args->preorder, current)) != -1) { + } else if (INT_INTOBJ(ELM_PLIST(args->preorder, current)) != -1) { continue; } @@ -185,7 +185,7 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); } - Obj succ = ELM_PLIST(args -> neighbors, current); + Obj succ = ELM_LIST(args -> neighbors, current); for (Int i = LEN_LIST(succ); i > 0; i--) { Int v = INT_INTOBJ(ELM_LIST(succ, i)); @@ -196,8 +196,8 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { if (!visited || revisit) { if (revisit) { - ASS_LIST(args->preorder, v, INTOBJ_INT(-1)); - CHANGED_BAG(args -> record); + AssPlist(args->preorder, v, INTOBJ_INT(-1)); + CHANGED_BAG(args->record); } ON_ADD_SUCC(current, v, i, args); STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); @@ -220,7 +220,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { return ExecuteDFSRec(current, parent, idx + 1, args); } - Obj succ = ELM_PLIST(args -> neighbors, current); + Obj succ = ELM_LIST(args -> neighbors, current); if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) ON_BACKTRACK(current, parent, args); @@ -305,7 +305,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { parseConfig(&dfs_args_, config); if (dfs_conf.use_parents) { - ASS_LIST(dfs_args_.parents, INT_INTOBJ(start), start); + AssPlist(dfs_args_.parents, INT_INTOBJ(start), start); CHANGED_BAG(record); } @@ -320,7 +320,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { bool visited = INT_INTOBJ(ELM_PLIST(dfs_args_.preorder, i)) != -1; if (!visited) { if (dfs_conf.use_parents) { - ASS_LIST(dfs_args_.parents, i, INTOBJ_INT(i)); + AssPlist(dfs_args_.parents, i, INTOBJ_INT(i)); CHANGED_BAG(record); } ExecuteDFSRec(i, i, PREORDER_IDX, &dfs_args_); diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 17d4cb7df..dc6cf43ab 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3336,6 +3336,10 @@ gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, > fail); gap> data.explored_edges; [ [ 1, 2 ], [ 2, 3 ] ] +gap> gr := Digraph(List([1 .. 5], x -> [1 .. 5]));; +gap> record := NewDFSRecord(gr);; +gap> ExecuteDFS(record, data, 1, fail, fail, fail, +> fail); # IsDigraphPath gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); From dd002e41a4c2c0927c1240c060dd3170c3a29ba2 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 16 Apr 2025 13:15:13 +0100 Subject: [PATCH 30/54] Add forest specific (specific vertex start points for forest DFS), more tests for regular ExecuteDFS and improve performance using macros for calling PreorderFunc, etc. --- gap/oper.gi | 126 ++++++++++++++++++++++++------------- src/dfs.c | 143 +++++++++++++++++++++++++----------------- src/dfs.h | 1 + src/digraphs.c | 14 ----- tst/standard/oper.tst | 53 ++++++++++++++-- 5 files changed, 217 insertions(+), 120 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index b70e8c224..daa36331b 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2340,20 +2340,6 @@ function(D, v) return spanningtree; end); -# InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", -# [IsDigraph, IsPosInt], -# function(D, root) -# local N; -# N := DigraphNrVertices(D); - -# if 0 = root or root > N then -# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", -# "argument (a digraph)"); -# fi; - -# return VerticesReachableFrom(D, [root]); -# end); - InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) @@ -2401,44 +2387,93 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", [IsDigraph, IsList], function(D, roots) - local N, index, visited, queue_tail, queue, - root, element, neighbour, graph_out_neighbors, node_neighbours; + local record, conf, N, PreOrderFunc, AncestorFunc, + data; + + if (roots = []) then + return []; + fi; N := DigraphNrVertices(D); - for root in roots do - if not IsPosInt(N) or 0 = root or root > N then - ErrorNoReturn("an element of the 2nd argument ", - "(roots) is not a vertex of the 1st ", - "argument (a digraph)"); + if (ForAny(roots, v -> v = 0 or v > N)) then + ErrorNoReturn("the 2nd argument (roots) contains a vertex that is not ", + "a vertex of the 1st argument (a digraph)"); + fi; + + data := rec(result := BlistList([1 .. N], [])); + + PreOrderFunc := function(record, data) + if record.parents[record.current] <> record.current then + data.result[record.current] := true; fi; - od; + end; - visited := BlistList([1 .. N], []); + AncestorFunc := function(record, data) + data.result[record.child] := true; + end; - graph_out_neighbors := OutNeighbors(D); - queue := EmptyPlist(N); - Append(queue, roots); + conf := NewDFSFlagsLightweight(); - queue_tail := Length(roots); + conf.use_postorder := true; + conf.use_edge := true; + conf.use_parents := true; + conf.forest_specific := roots; - index := 1; - while IsBound(queue[index]) do - element := queue[index]; - node_neighbours := graph_out_neighbors[element]; - for neighbour in node_neighbours do - if not visited[neighbour] then; - visited[neighbour] := true; - queue_tail := queue_tail + 1; - queue[queue_tail] := neighbour; - fi; - od; - index := index + 1; - od; + record := NewDFSRecordLightweight(D, conf); - return ListBlist([1 .. N], visited); + ExecuteDFS(record, + data, + roots[1], + PreOrderFunc, + fail, + AncestorFunc, + fail); + + return ListBlist([1 .. N], data.result); end); +# InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", +# [IsDigraph, IsList], +# function(D, roots) +# local N, index, visited, queue_tail, queue, +# root, element, neighbour, graph_out_neighbors, node_neighbours; + +# N := DigraphNrVertices(D); + +# for root in roots do +# if not IsPosInt(N) or 0 = root or root > N then +# ErrorNoReturn("an element of the 2nd argument ", +# "(roots) is not a vertex of the 1st ", +# "argument (a digraph)"); +# fi; +# od; + +# visited := BlistList([1 .. N], []); + +# graph_out_neighbors := OutNeighbors(D); +# queue := EmptyPlist(N); +# Append(queue, roots); + +# queue_tail := Length(roots); + +# index := 1; +# while IsBound(queue[index]) do +# element := queue[index]; +# node_neighbours := graph_out_neighbors[element]; +# for neighbour in node_neighbours do +# if not visited[neighbour] then; +# visited[neighbour] := true; +# queue_tail := queue_tail + 1; +# queue[queue_tail] := neighbour; +# fi; +# od; +# index := index + 1; +# od; + +# return ListBlist([1 .. N], visited); +# end); + InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices", [IsDigraph, IsList], # Check if digraph represents a partial order @@ -2804,7 +2839,7 @@ DIGRAPHS_DFSRecNames := function() DIGRAPHS_DFSFlagNames := function() return ["iterative", "forest", "revisit", "use_parents", "use_edge", - "use_postorder"]; + "use_postorder", "forest_specific"]; end; DIGRAPHS_DFSError := function() @@ -2857,10 +2892,11 @@ InstallMethod(NewDFSFlags, function() local config; config := rec(); - config.forest := false; # Visit all vertices (all connected components) - config.revisit := false; # Use for revisiting nodes (requires iterative) + config.forest := false; # Visit all vertices (connected components) + config.forest_specific := fail; # Visit specific vertices + config.revisit := false; # Use for revisiting nodes (requires iter) config.iterative := false; - config.use_edge := true; # Whether these record fields are necessary + config.use_edge := true; # Whether these record fields are necessary config.use_postorder := true; config.use_parents := true; return config; diff --git a/src/dfs.c b/src/dfs.c index 54bbc0a92..d83549eb0 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -18,19 +18,25 @@ #include "digraphs-config.h" #include "digraphs-debug.h" #include "digraphs.h" +// #include "stdio.h" // Macros used for both recursive and iterative -#define ON_PREORDER(current, args) \ - ASS_LIST(args->preorder, current, INTOBJ_INT(++(*args->preorder_num))); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ - CHANGED_BAG(args->record); \ - if (args->CallPreorder \ - && CallCheckStop( \ - args->PreorderFunc, args->RNamStop, args->record, args->data)) { \ - return args->record; \ - } \ - CHANGED_BAG(args->record); +#define CALL_CHECK_STOP(f, RNamStop, record, data) \ + CALL_2ARGS(f, record, data); \ + if (ElmPRec(record, RNamStop) == True) { \ + CHANGED_BAG(record); \ + return false; \ + } + +#define ON_PREORDER(current, args) \ + ASS_LIST(args->preorder, current, INTOBJ_INT(++(*args->preorder_num))); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ + if (args->CallPreorder) { \ + CALL_CHECK_STOP( \ + args->PreorderFunc, args->RNamStop, args->record, args->data) \ + } #define ANCESTOR_CROSS(current, v, backtracked, args) \ if (args->CallAncestor || args->CallCross) { \ @@ -38,18 +44,14 @@ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ CHANGED_BAG(args->record); \ if (args->CallAncestor && !backtracked) { \ - if (CallCheckStop( \ - args->AncestorFunc, args->RNamStop, args->record, args->data)) { \ - return false; \ - } \ + CALL_CHECK_STOP( \ + args->AncestorFunc, args->RNamStop, args->record, args->data) \ } else if (args->CallCross \ && (backtracked \ && INT_INTOBJ(ELM_PLIST(args->preorder, v)) \ < INT_INTOBJ(ELM_PLIST(args->preorder, current)))) { \ - if (CallCheckStop( \ - args->CrossFunc, args->RNamStop, args->record, args->data)) { \ - return false; \ - } \ + CALL_CHECK_STOP( \ + args->CrossFunc, args->RNamStop, args->record, args->data) \ } \ CHANGED_BAG(args->record); \ } @@ -63,10 +65,8 @@ AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ CHANGED_BAG(args->record); \ - if (CallCheckStop( \ - args->PostorderFunc, args->RNamStop, args->record, args->data)) { \ - return false; \ - } \ + CALL_CHECK_STOP( \ + args->PostorderFunc, args->RNamStop, args->record, args->data) \ CHANGED_BAG(args->record); \ } @@ -88,6 +88,33 @@ #define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to // visit the current node) +#define RECURSE_FOREST(dfs_args_, dfs_conf, v) \ + bool visited = INT_INTOBJ(ELM_PLIST(dfs_args_.preorder, v)) != -1; \ + if (!visited) { \ + if (dfs_conf.use_parents) { \ + AssPlist(dfs_args_.parents, v, INTOBJ_INT(v)); \ + CHANGED_BAG(record); \ + } \ + if (!ExecuteDFSRec(v, v, PREORDER_IDX, &dfs_args_)) { \ + CHANGED_BAG(record); \ + return record; \ + } \ + } + +#define ITER_FOREST(args, stack, v) \ + bool visited = INT_INTOBJ(ELM_PLIST(args->preorder, v)) != -1; \ + \ + if (!visited) { \ + if (args->dfs_conf->use_parents) { \ + AssPlist(args->parents, v, INTOBJ_INT(v)); \ + } \ + CHANGED_BAG(args->record); \ + AssPlist(stack, 1, INTOBJ_INT(v)); \ + \ + if (!iter_loop(stack, 1, args)) \ + return false; \ + } + void parseConfig(struct dfs_args* args, Obj conf_record) { struct dfs_config* conf = args -> dfs_conf; conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; @@ -97,6 +124,7 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { ElmPRec(conf_record, RNamName("use_postorder")) == True; conf -> use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; conf -> use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; + conf -> forest_specific = ElmPRec(conf_record, RNamName("forest_specific")); if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) { ErrorQuit( @@ -121,21 +149,23 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { "In a DFSRecord where a PostorderFunc exists, where the config flag " "iter is true, the flag use_postorder must also be true", 0L, 0L); } + + if (conf -> forest_specific != Fail && conf -> forest) { + ErrorQuit( + "In a DFSRecord where the config flag forest_specific is not fail, " + "forest cannot also be true", 0L, 0L); + } } // Extreme examples are on the pull request #459 -bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data) { - CALL_2ARGS(f, record, data); - if (ElmPRec(record, RNamStop) == True) { - return true; - } - return false; -} - /* Iterative DFS (used for revisiting vertices) Necessary record elements: preorder If CallPostorder, then parents is necessary + + Differences with recursive : edge and parents are updated + when successors are pushed to the stack, whereas with + recursive they are updated before visiting */ bool ExecuteDFSIter(Int start, struct dfs_args* args) { @@ -149,18 +179,13 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args) { if (!iter_loop(stack, stack_size, args)) return false; if (args -> dfs_conf -> forest) { - for (Int v = 1; v <= LEN_LIST(args -> neighbors); v++) { - bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; - - if (!visited) { - if (args->dfs_conf->use_parents) { - AssPlist(args->parents, v, INTOBJ_INT(v)); - } - CHANGED_BAG(args -> record); - AssPlist(stack, 1, INTOBJ_INT(v)); - - if (!iter_loop(stack, 1, args)) return false; - } + for (Int i = 1; i <= LEN_LIST(args -> neighbors); i++) { + ITER_FOREST(args, stack, i); + } + } else if (args -> dfs_conf -> forest_specific != Fail) { + Int len = LEN_PLIST(args -> dfs_conf -> forest_specific); + for (Int i = 1; i <= len; i++) { + ITER_FOREST(args, stack, i); } } return true; @@ -189,16 +214,17 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { for (Int i = LEN_LIST(succ); i > 0; i--) { Int v = INT_INTOBJ(ELM_LIST(succ, i)); - bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + bool backtracked = !args -> dfs_conf -> use_postorder || INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; bool revisit = (args -> dfs_conf -> revisit && backtracked); + if (revisit) { + AssPlist(args->preorder, v, INTOBJ_INT(-1)); + CHANGED_BAG(args->record); + } + bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; - if (!visited || revisit) { - if (revisit) { - AssPlist(args->preorder, v, INTOBJ_INT(-1)); - CHANGED_BAG(args->record); - } + if (!visited) { ON_ADD_SUCC(current, v, i, args); STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); } else { @@ -314,16 +340,19 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (dfs_conf.iter || dfs_conf.revisit) { ExecuteDFSIter(current, &dfs_args_); } else { - if (dfs_conf.forest) { - ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_); - for (Int i = 1; i <= N; i++) { - bool visited = INT_INTOBJ(ELM_PLIST(dfs_args_.preorder, i)) != -1; - if (!visited) { - if (dfs_conf.use_parents) { - AssPlist(dfs_args_.parents, i, INTOBJ_INT(i)); - CHANGED_BAG(record); + if (dfs_conf.forest || (dfs_conf.forest_specific != Fail)) { + // Initial DFS with specified start index + if (ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_)) { + if (dfs_conf.forest) { + for (Int i = 1; i <= N; i++) { + RECURSE_FOREST(dfs_args_, dfs_conf, i); + } + } else if (dfs_conf.forest_specific != Fail) { + for (Int i = 1; i <= LEN_PLIST(dfs_conf.forest_specific); i++) { + RECURSE_FOREST(dfs_args_, + dfs_conf, + INT_INTOBJ(ELM_PLIST(dfs_conf.forest_specific, i))); } - ExecuteDFSRec(i, i, PREORDER_IDX, &dfs_args_); } } } else { diff --git a/src/dfs.h b/src/dfs.h index a82595bda..6e2af2c1f 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -53,6 +53,7 @@ struct dfs_config { bool revisit; bool iter; bool forest; + Obj forest_specific; bool use_postorder; bool use_parents; bool use_edge; diff --git a/src/digraphs.c b/src/digraphs.c index b2123b007..2afa56b48 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -60,14 +60,6 @@ Obj Group; Obj ClosureGroup; Obj InfoWarning; -// Added for DFS - -Obj DS_Hash_SetValue; -Obj DS_Hash_Contains; -Obj DS_Hash_Value; -Obj DS_Hash_Reserve; -Obj DS_Hash_Delete; - static inline bool IsAttributeStoringRep(Obj o) { return (CALL_1ARGS(IsAttributeStoringRepObj, o) == True ? true : false); } @@ -2268,12 +2260,6 @@ static Int InitKernel(StructInitInfo* module) { ImportGVarFromLibrary("Group", &Group); ImportGVarFromLibrary("ClosureGroup", &ClosureGroup); ImportGVarFromLibrary("InfoWarning", &InfoWarning); - // For DFS - ImportGVarFromLibrary("DS_Hash_SetValue", &DS_Hash_SetValue); - ImportGVarFromLibrary("DS_Hash_Contains", &DS_Hash_Contains); - ImportGVarFromLibrary("DS_Hash_Value", &DS_Hash_Value); - ImportGVarFromLibrary("DS_Hash_Reserve", &DS_Hash_Reserve); - ImportGVarFromLibrary("DS_Hash_Delete", &DS_Hash_Delete); /* return success */ return 0; diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index dc6cf43ab..f39482eac 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -2824,8 +2824,8 @@ gap> VerticesReachableFrom(D1, [1, 2]); gap> VerticesReachableFrom(D2, [1]); [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] gap> VerticesReachableFrom(D2, [1, 11]); -Error, an element of the 2nd argument (roots) is not a vertex of the 1st argum\ -ent (a digraph) +Error, the 2nd argument (roots) contains a vertex that is not a vertex of the \ +1st argument (a digraph) gap> D3 := CompleteDigraph(7); gap> D3_edges := [1 .. 7]; @@ -2872,8 +2872,8 @@ gap> IsOrderFilter(D, 4); Error, no method found! For debugging hints type ?Recovery from NoMethodFound Error, no 1st choice method found for `IsOrderFilter' on 2 arguments gap> IsOrderFilter(D, [5]); -Error, an element of the 2nd argument (roots) is not a vertex of the 1st argum\ -ent (a digraph) +Error, the 2nd argument (roots) contains a vertex that is not a vertex of the \ +1st argument (a digraph) # DigraphCycleBasis gap> D := NullDigraph(0); @@ -3341,6 +3341,51 @@ gap> record := NewDFSRecord(gr);; gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); +# Stopping ExecuteDFS +gap> gr := CompleteDigraph(10000);; +gap> record := NewDFSRecord(gr);; +gap> data := rec(count := 0);; +gap> PreorderFunc := function(record, data) +> data.count := data.count + 1; +> if record.current = 5000 then +> record.stop := true; +> fi; +> end;; +gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +5000 +gap> record_iter := NewDFSRecord(gr);; +gap> record_iter.config.iterative := true;; +gap> data := rec(count := 0);; +gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +5000 +gap> data := ["postorder", "preorder"];; +gap> ForAll(data, x -> record_iter.(x) = record.(x)); +true +gap> record := NewDFSRecord(gr);; +gap> record.config.revisit := true;; +gap> record.config.iterative := true;; +gap> record.config.use_postorder := true;; +gap> data := rec(count := 0);; +gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +5000 + +# Forest DFS +gap> gr := BinaryTree(5);; +gap> record := NewDFSRecord(gr);; +gap> record.config.forest := true;; +gap> ExecuteDFS(record, fail, 1, fail, fail, fail, +> fail); +gap> ForAll(record.preorder, x -> x <> -1); +true +gap> ForAll([record.preorder, record.postorder], x -> IsSet(x)); +true + # IsDigraphPath gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); From c0fd0ed78379f519ae013278a2aabd646f46b10a Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 16 Apr 2025 13:17:37 +0100 Subject: [PATCH 31/54] Fix lint error --- tst/standard/oper.tst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index f39482eac..ed74b7793 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3383,7 +3383,7 @@ gap> ExecuteDFS(record, fail, 1, fail, fail, fail, > fail); gap> ForAll(record.preorder, x -> x <> -1); true -gap> ForAll([record.preorder, record.postorder], x -> IsSet(x)); +gap> ForAll([record.preorder, record.postorder], IsSet); true # IsDigraphPath From d67d5e635d67af801166cd6a0a8c65401924f4df Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 16 Apr 2025 13:52:14 +0100 Subject: [PATCH 32/54] Correct error on VerticesReachableFrom --- gap/oper.gi | 7 ++++--- tst/standard/oper.tst | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index daa36331b..ff2b4bc66 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2396,9 +2396,10 @@ function(D, roots) N := DigraphNrVertices(D); - if (ForAny(roots, v -> v = 0 or v > N)) then - ErrorNoReturn("the 2nd argument (roots) contains a vertex that is not ", - "a vertex of the 1st argument (a digraph)"); + if (ForAny(roots, v -> v <= 0 or v > N)) then + ErrorNoReturn("an element of the 2nd argument ", + "(roots) is not a vertex of the 1st ", + "argument (a digraph)"); fi; data := rec(result := BlistList([1 .. N], [])); diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index ed74b7793..be2ddef8d 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -2824,8 +2824,8 @@ gap> VerticesReachableFrom(D1, [1, 2]); gap> VerticesReachableFrom(D2, [1]); [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] gap> VerticesReachableFrom(D2, [1, 11]); -Error, the 2nd argument (roots) contains a vertex that is not a vertex of the \ -1st argument (a digraph) +Error, an element of the 2nd argument (roots) is not a vertex of the 1st argum\ +ent (a digraph) gap> D3 := CompleteDigraph(7); gap> D3_edges := [1 .. 7]; @@ -2872,8 +2872,8 @@ gap> IsOrderFilter(D, 4); Error, no method found! For debugging hints type ?Recovery from NoMethodFound Error, no 1st choice method found for `IsOrderFilter' on 2 arguments gap> IsOrderFilter(D, [5]); -Error, the 2nd argument (roots) contains a vertex that is not a vertex of the \ -1st argument (a digraph) +Error, an element of the 2nd argument (roots) is not a vertex of the 1st argum\ +ent (a digraph) # DigraphCycleBasis gap> D := NullDigraph(0); From cbdc208256eb71a27407e3e0df952e795eaa656f Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 23 Apr 2025 14:59:28 +0100 Subject: [PATCH 33/54] Add configuration to remove use of record elements (when use_preorder and use_postorder is false, uses a bit array) --- gap/attr.gi | 8 ++- gap/oper.gi | 20 +++++-- src/dfs.c | 155 ++++++++++++++++++++++++++++++++++++++-------------- src/dfs.h | 15 ++++- 4 files changed, 148 insertions(+), 50 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index 36a151054..84699c2f4 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -1068,8 +1068,14 @@ function(D) return []; fi; record := NewDFSRecord(D); + record.config.iterative := true; + record.config.use_edge := false; + record.config.use_preorder := false; + record.config.use_postorder := false; + record.config.use_parents := true; + count := 0; - out := []; + out := ListWithIdenticalEntries(N, -1); PostOrderFunc := function(record, _) count := count + 1; out[count] := record.child; diff --git a/gap/oper.gi b/gap/oper.gi index 85d5236ad..15890f8f6 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2870,7 +2870,7 @@ DIGRAPHS_DFSRecNames := function() DIGRAPHS_DFSFlagNames := function() return ["iterative", "forest", "revisit", "use_parents", "use_edge", - "use_postorder", "forest_specific"]; + "use_postorder", "use_preorder", "forest_specific"]; end; DIGRAPHS_DFSError := function() @@ -2885,7 +2885,9 @@ Graph -> NewDFSRecordLightweight(Graph, NewDFSFlags())); InstallMethod(NewDFSRecordLightweight, "for a digraph and a record", [IsDigraph, IsRecord], function(graph, conf) - local record, config_names; + local record, config_names, N; + + N := DigraphNrVertices(graph); config_names := DIGRAPHS_DFSFlagNames(); record := rec(); @@ -2897,19 +2899,23 @@ function(graph, conf) record.child := -1; record.current := -1; record.stop := false; - record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + if conf.use_preorder then + record.preorder := ListWithIdenticalEntries(N, -1); + else + record.preorder := fail; + fi; if conf.use_parents then - record.parents := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.parents := ListWithIdenticalEntries(N, -1); else record.parents := fail; fi; if conf.use_postorder then - record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.postorder := ListWithIdenticalEntries(N, -1); else record.postorder := fail; fi; if conf.use_edge then - record.edge := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.edge := ListWithIdenticalEntries(N, -1); else record.edge := fail; fi; @@ -2929,6 +2935,7 @@ function() config.iterative := false; config.use_edge := true; # Whether these record fields are necessary config.use_postorder := true; + config.use_preorder := true; config.use_parents := true; return config; end); @@ -2940,6 +2947,7 @@ function() config := NewDFSFlags(); config.iterative := false; config.use_postorder := false; + config.use_preorder := false; config.use_parents := false; config.use_edge := false; return config; diff --git a/src/dfs.c b/src/dfs.c index d83549eb0..82d1cc565 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -17,6 +17,7 @@ #include "digraphs-config.h" #include "digraphs-debug.h" +#include "safemalloc.h" #include "digraphs.h" // #include "stdio.h" @@ -24,18 +25,57 @@ #define CALL_CHECK_STOP(f, RNamStop, record, data) \ CALL_2ARGS(f, record, data); \ - if (ElmPRec(record, RNamStop) == True) { \ - CHANGED_BAG(record); \ + if (ElmPRec(record, RNamStop) == True) { \ + CHANGED_BAG(record); \ return false; \ } -#define ON_PREORDER(current, args) \ - ASS_LIST(args->preorder, current, INTOBJ_INT(++(*args->preorder_num))); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ - CHANGED_BAG(args->record); \ - if (args->CallPreorder) { \ - CALL_CHECK_STOP( \ - args->PreorderFunc, args->RNamStop, args->record, args->data) \ +#define GET_PREORDER(args, idx) \ + args->dfs_conf->use_preorder ? INT_INTOBJ(ELM_PLIST(args->preorder, idx)) \ + : args->preorder_partial[idx] + +#define SET_PREORDER(args, idx) \ + if (args->dfs_conf->use_preorder) { \ + ASS_LIST(args->preorder, idx, INTOBJ_INT(++(*args->preorder_num))); \ + } else { \ + args->preorder_partial[idx] = true; \ + } + +#define UNSET_PREORDER(args, idx) \ + if (args->dfs_conf->use_preorder) { \ + ASS_LIST(args->preorder, idx, INTOBJ_INT(-1)); \ + } else { \ + args->preorder_partial[idx] = false; \ + } + +#define IS_VISITED(args, idx) \ + args->dfs_conf->use_preorder \ + ? INT_INTOBJ(ELM_PLIST(args->preorder, idx)) != -1 \ + : args->preorder_partial[idx] + +#define GET_POSTORDER(args, idx) \ + args->dfs_conf->use_postorder ? INT_INTOBJ(ELM_PLIST(args->postorder, idx)) \ + : args->postorder_partial[idx] + +#define SET_POSTORDER(args, idx) \ + if (args->dfs_conf->use_postorder) { \ + ASS_LIST(args->postorder, idx, INTOBJ_INT(++(*args->postorder_num))); \ + } else if (args->dfs_conf->partial_postorder) { \ + args->postorder_partial[idx] = true; \ + } + +#define IS_BACKTRACKED(args, idx) \ + args->dfs_conf->use_postorder \ + ? INT_INTOBJ(ELM_PLIST(args->postorder, idx)) != -1 \ + : args->postorder_partial[idx] + +#define ON_PREORDER(current, args) \ + SET_PREORDER(args, current); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ + if (args->CallPreorder) { \ + CALL_CHECK_STOP( \ + args->PreorderFunc, args->RNamStop, args->record, args->data) \ } #define ANCESTOR_CROSS(current, v, backtracked, args) \ @@ -48,26 +88,26 @@ args->AncestorFunc, args->RNamStop, args->record, args->data) \ } else if (args->CallCross \ && (backtracked \ - && INT_INTOBJ(ELM_PLIST(args->preorder, v)) \ - < INT_INTOBJ(ELM_PLIST(args->preorder, current)))) { \ + && ((GET_PREORDER(args, v)) \ + < (GET_PREORDER(args, current))))) { \ CALL_CHECK_STOP( \ args->CrossFunc, args->RNamStop, args->record, args->data) \ } \ CHANGED_BAG(args->record); \ } -#define ON_BACKTRACK(current, parent, args) \ - if (args->dfs_conf->use_postorder) { \ - AssPlist(args->postorder, current, INTOBJ_INT(++(*args->postorder_num))); \ - CHANGED_BAG(args->record); \ - } \ - if (args->CallPostorder) { \ - AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ - CHANGED_BAG(args->record); \ - CALL_CHECK_STOP( \ - args->PostorderFunc, args->RNamStop, args->record, args->data) \ - CHANGED_BAG(args->record); \ +#define ON_BACKTRACK(current, parent, args) \ + if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) { \ + SET_POSTORDER(args, current); \ + CHANGED_BAG(args->record); \ + } \ + if (args->CallPostorder) { \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ + CHANGED_BAG(args->record); \ + CALL_CHECK_STOP( \ + args->PostorderFunc, args->RNamStop, args->record, args->data) \ + CHANGED_BAG(args->record); \ } #define ON_ADD_SUCC(current, succ, idx, args) \ @@ -89,7 +129,8 @@ // visit the current node) #define RECURSE_FOREST(dfs_args_, dfs_conf, v) \ - bool visited = INT_INTOBJ(ELM_PLIST(dfs_args_.preorder, v)) != -1; \ + struct dfs_args* ptr = &dfs_args_; \ + bool visited = IS_VISITED(ptr, v); \ if (!visited) { \ if (dfs_conf.use_parents) { \ AssPlist(dfs_args_.parents, v, INTOBJ_INT(v)); \ @@ -102,7 +143,7 @@ } #define ITER_FOREST(args, stack, v) \ - bool visited = INT_INTOBJ(ELM_PLIST(args->preorder, v)) != -1; \ + bool visited = IS_VISITED(args, v); \ \ if (!visited) { \ if (args->dfs_conf->use_parents) { \ @@ -120,11 +161,14 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; conf -> revisit = ElmPRec(conf_record, RNamName("revisit")) == True; conf -> forest = ElmPRec(conf_record, RNamName("forest")) == True; + conf -> use_preorder = + ElmPRec(conf_record, RNamName("use_preorder")) == True; conf -> use_postorder = ElmPRec(conf_record, RNamName("use_postorder")) == True; conf -> use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; conf -> use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; conf -> forest_specific = ElmPRec(conf_record, RNamName("forest_specific")); + conf -> partial_postorder = false; if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) { ErrorQuit( @@ -134,20 +178,21 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { if (conf -> revisit && !(conf -> iter && conf -> use_postorder)) { ErrorQuit( - "In a DFSRecord where the config flag revisit is true, use_postorder " - "and iterative must also be true", 0L, 0L); + "In a DFSRecord where the config flag revisit is true, iterative " + "must also be true", 0L, 0L); } if ((args -> CallAncestor || args -> CallCross) && !conf -> use_postorder) { - ErrorQuit( - "In a DFSRecord where either an AncestorFunc or CrossFunc exists, " - "the config flag use_postorder must be true", 0L, 0L); + conf -> partial_postorder = true; } if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) { + conf -> partial_postorder = true; + if (!conf -> use_parents) { ErrorQuit( "In a DFSRecord where a PostorderFunc exists, where the config flag " - "iter is true, the flag use_postorder must also be true", 0L, 0L); + "iter is true, the flag use_parents must also be true", 0L, 0L); + } } if (conf -> forest_specific != Fail && conf -> forest) { @@ -155,6 +200,12 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { "In a DFSRecord where the config flag forest_specific is not fail, " "forest cannot also be true", 0L, 0L); } + + if (args -> CallCross && (!conf -> use_preorder)) { + ErrorQuit( + "In a DFSRecord where there is a CrossFunc, the config flag " + "use_preorder must be true", 0L, 0L); + } } // Extreme examples are on the pull request #459 @@ -201,12 +252,14 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { INT_INTOBJ(ELM_PLIST(args -> parents, bt_on)); ON_BACKTRACK(bt_on, parent, args); continue; - } else if (INT_INTOBJ(ELM_PLIST(args->preorder, current)) != -1) { + } else if (IS_VISITED(args, current)) { continue; } ON_PREORDER(current, args); // and push backtrack node - if (args -> dfs_conf -> use_postorder || args -> CallPostorder) { + if (args -> dfs_conf -> use_postorder + || args -> dfs_conf -> partial_postorder + || args -> CallPostorder) { STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); } @@ -215,14 +268,15 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { for (Int i = LEN_LIST(succ); i > 0; i--) { Int v = INT_INTOBJ(ELM_LIST(succ, i)); - bool backtracked = !args -> dfs_conf -> use_postorder || - INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; + bool backtracked = (args -> dfs_conf -> use_postorder + || args -> dfs_conf -> partial_postorder) && + (IS_BACKTRACKED(args, v)); bool revisit = (args -> dfs_conf -> revisit && backtracked); if (revisit) { - AssPlist(args->preorder, v, INTOBJ_INT(-1)); + UNSET_PREORDER(args, v); CHANGED_BAG(args->record); } - bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + bool visited = IS_VISITED(args, v); if (!visited) { ON_ADD_SUCC(current, v, i, args); @@ -259,14 +313,15 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { return ExecuteDFSRec(parent, parents_parent, prev_idx + 1, args); } else { Int v = INT_INTOBJ(ELM_LIST(succ, idx)); - bool visited = INT_INTOBJ(ELM_PLIST(args -> preorder, v)) != -1; + bool visited = IS_VISITED(args, v); if (!visited) { ON_ADD_SUCC(current, v, idx, args); return ExecuteDFSRec(v, current, 0, args); } else { - bool backtracked = !args -> dfs_conf -> use_postorder || - INT_INTOBJ(ELM_PLIST(args -> postorder, v)) != -1; + bool backtracked = (args -> dfs_conf -> use_postorder + || args -> dfs_conf -> partial_postorder) && + (IS_BACKTRACKED(args, v)); ANCESTOR_CROSS(current, v, backtracked, args); return ExecuteDFSRec(current, parent, idx + 1, args); // Skip } @@ -275,7 +330,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { - DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); + /* DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); */ Obj record = ELM_PLIST(args, 1); Obj config = ElmPRec(record, RNamName("config")); Obj data = ELM_PLIST(args, 2); @@ -330,6 +385,16 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { parseConfig(&dfs_args_, config); + if (!dfs_conf.use_preorder) { + dfs_args_.preorder_partial = (bool*) safe_malloc((N * sizeof(bool)) + 1); + memset(dfs_args_.preorder_partial, false, (N * sizeof(bool)) + 1); + } + + if (dfs_conf.partial_postorder) { + dfs_args_.postorder_partial = (bool*) safe_malloc((N * sizeof(bool)) + 1); + memset(dfs_args_.postorder_partial, false, (N * sizeof(bool)) + 1); + } + if (dfs_conf.use_parents) { AssPlist(dfs_args_.parents, INT_INTOBJ(start), start); CHANGED_BAG(record); @@ -360,6 +425,14 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { } } + + if (!dfs_conf.use_preorder) { + free(dfs_args_.preorder_partial); + } + + if (dfs_conf.partial_postorder) { + free(dfs_args_.postorder_partial); + } CHANGED_BAG(record); return record; } diff --git a/src/dfs.h b/src/dfs.h index 6e2af2c1f..aa0182133 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -16,6 +16,7 @@ #include // for false, true, bool // GAP headers #include "gap-includes.h" // for Obj, Int +#include "bitarray.h" bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); @@ -27,8 +28,16 @@ struct dfs_args { Int* postorder_num; Obj parents; - Obj postorder; - Obj preorder; + + union { + Obj postorder; + bool* postorder_partial; + }; + + union { + Obj preorder; + bool* preorder_partial; + }; Obj edge; Obj neighbors; @@ -54,7 +63,9 @@ struct dfs_config { bool iter; bool forest; Obj forest_specific; + bool use_preorder; bool use_postorder; + bool partial_postorder; bool use_parents; bool use_edge; }; From 7b0409927041d5d06f40acbb6e4df4ae55ea5f54 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 23 Apr 2025 18:13:03 +0100 Subject: [PATCH 34/54] Change uses of DFS to remove unnecessary record element usage --- gap/oper.gi | 95 +++++++++++++++++++---------------------------------- gap/prop.gi | 14 +++++--- src/dfs.c | 5 +-- 3 files changed, 47 insertions(+), 67 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 15890f8f6..71d60d7c5 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1688,7 +1688,8 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents; + local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents, + flags; N := DigraphNrVertices(D); if u > N or v > N then @@ -1709,7 +1710,13 @@ function(D, u, v) return fail; fi; - record := NewDFSRecord(D); + flags := NewDFSFlagsLightweight(); + + flags.use_edge := true; + flags.use_parents := true; + + record := NewDFSRecordLightweight(D, flags); + if u <> v then # if v is reachable from u, then u is an ancestor of v, and so at some # point v will be encountered for the first time, and PreOrderFunc will be @@ -2052,13 +2059,22 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc; + local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags; if not v in DigraphVertices(D) then ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", "argument ,"); fi; - record := NewDFSRecord(D); + + flags := NewDFSFlagsLightweight(); + flags.iterative := true; # revisit DFS must be iterative + flags.use_parents := true; + flags.revisit := true; # If found another edge to an already + # visited and backtracked on node, + # set to unvisited, and visit it + + record := NewDFSRecordLightweight(D, flags); + data := rec(prev := -1, best := 0); AncestorFunc := function(record, _) @@ -2076,12 +2092,6 @@ function(D, v) fi; end; - record.config.revisit := true; # If found another edge to an already - # visited and backtracked on node, - # set to unvisited, and visit it - - record.config.iterative := true; # revisit DFS must be iterative - ExecuteDFS(record, data, v, PreOrderFunc, PostOrderFunc, AncestorFunc, fail); @@ -2383,7 +2393,6 @@ function(D, root) conf := NewDFSFlagsLightweight(); - conf.use_postorder := true; conf.use_edge := true; conf.use_parents := true; @@ -2417,7 +2426,7 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", [IsDigraph, IsList], function(D, roots) - local record, conf, N, PreOrderFunc, AncestorFunc, + local record, flags, N, PreOrderFunc, AncestorFunc, data; if (roots = []) then @@ -2444,14 +2453,13 @@ function(D, roots) data.result[record.child] := true; end; - conf := NewDFSFlagsLightweight(); + flags := NewDFSFlagsLightweight(); + flags.use_edge := true; + flags.use_parents := true; - conf.use_postorder := true; - conf.use_edge := true; - conf.use_parents := true; - conf.forest_specific := roots; + flags.forest_specific := roots; - record := NewDFSRecordLightweight(D, conf); + record := NewDFSRecordLightweight(D, flags); ExecuteDFS(record, data, @@ -2464,47 +2472,6 @@ function(D, roots) return ListBlist([1 .. N], data.result); end); -# InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", -# [IsDigraph, IsList], -# function(D, roots) -# local N, index, visited, queue_tail, queue, -# root, element, neighbour, graph_out_neighbors, node_neighbours; - -# N := DigraphNrVertices(D); - -# for root in roots do -# if not IsPosInt(N) or 0 = root or root > N then -# ErrorNoReturn("an element of the 2nd argument ", -# "(roots) is not a vertex of the 1st ", -# "argument (a digraph)"); -# fi; -# od; - -# visited := BlistList([1 .. N], []); - -# graph_out_neighbors := OutNeighbors(D); -# queue := EmptyPlist(N); -# Append(queue, roots); - -# queue_tail := Length(roots); - -# index := 1; -# while IsBound(queue[index]) do -# element := queue[index]; -# node_neighbours := graph_out_neighbors[element]; -# for neighbour in node_neighbours do -# if not visited[neighbour] then; -# visited[neighbour] := true; -# queue_tail := queue_tail + 1; -# queue[queue_tail] := neighbour; -# fi; -# od; -# index := index + 1; -# od; - -# return ListBlist([1 .. N], visited); -# end); - InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices", [IsDigraph, IsList], # Check if digraph represents a partial order @@ -2533,7 +2500,7 @@ InstallMethod(DominatorTree, "for a digraph and a vertex", function(D, root) local M, preorder_num_to_node, PreOrderFunc, record, parents, node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, - pred, N, w, y, x, i, v; + pred, N, w, y, x, i, v, flags; M := DigraphNrVertices(D); @@ -2548,7 +2515,13 @@ function(D, root) Add(data, record.current); end; - record := NewDFSRecord(D); + flags := NewDFSFlagsLightweight(); + flags.use_preorder := true; + flags.use_parents := true; + flags.use_edge := true; + + record := NewDFSRecordLightweight(D, flags); + ExecuteDFS(record, preorder_num_to_node, root, diff --git a/gap/prop.gi b/gap/prop.gi index ceceabbf8..7f88f30cf 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -239,13 +239,16 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local n, record, AncestorFunc; + local n, record, AncestorFunc, flags; n := DigraphNrVertices(D); if n = 0 then return true; fi; - record := NewDFSRecord(D); + flags := NewDFSFlagsLightweight(); + flags.iterative := true; + + record := NewDFSRecordLightweight(D, flags); # A Digraph is acyclic if it has no back edges AncestorFunc := function(record, _) @@ -385,13 +388,16 @@ D -> DigraphPeriod(D) = 1); InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local record, AncestorFunc; + local record, AncestorFunc, flags; if DigraphNrVertices(D) <= 1 then return true; fi; - record := NewDFSRecord(D); + flags := NewDFSFlagsLightweight(); + flags.iterative := true; + + record := NewDFSRecordLightweight(D, flags); record.config.forest := true; AncestorFunc := function(record, _) diff --git a/src/dfs.c b/src/dfs.c index 82d1cc565..aa11fa5ec 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -176,13 +176,14 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { "use_parents must be true", 0L, 0L); } - if (conf -> revisit && !(conf -> iter && conf -> use_postorder)) { + if (conf -> revisit && !conf -> iter) { ErrorQuit( "In a DFSRecord where the config flag revisit is true, iterative " "must also be true", 0L, 0L); } - if ((args -> CallAncestor || args -> CallCross) && !conf -> use_postorder) { + if ((args -> CallAncestor || args -> CallCross || conf -> revisit) && + !conf -> use_postorder) { conf -> partial_postorder = true; } From 1e16a66c2d28aaefe1831e27f64d54e6c8513494 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 30 Apr 2025 13:33:52 +0100 Subject: [PATCH 35/54] Add documentation, input checking for DFSFlags, correct error message --- doc/oper.xml | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/z-chap4.xml | 4 + gap/attr.gi | 31 +++-- gap/oper.gd | 2 +- gap/oper.gi | 81 ++++++----- gap/prop.gi | 4 +- src/dfs.c | 59 ++++---- 7 files changed, 461 insertions(+), 76 deletions(-) diff --git a/doc/oper.xml b/doc/oper.xml index 8c55db1ac..12ae76653 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -2489,3 +2489,359 @@ true]]> gap> DIGRAPHS_FREE_CLIQUES_DATA(); ]]> <#/GAPDoc> + +<#GAPDoc Label="NewDFSRecord"> + + + + A record. + + This record contains four lists (parents, edge, preorder and postorder) with their length + equal to the number of verticies in the digraph. Each index i of each list + corresponds to vertex i in digraph. + These lists store the following (where record is returned by NewDFSRecord): + + parents + + At each index, the parent of the vertex is stored. Note that when + record.config.iterative is true, both record.parents[i] + and record.edge[i] for all vertices encountered in DFS + search i are updated as successors are pushed to the + stack, rather than when i (which occurs when record.config.recursive + is true). Once record.preorder[i] is not -1, + record.parents[i] and record.edge[i] + correspond to the true edge and parent i was visited through. + + edge + At each index i, the index of the edge i was visited + from in OutNeighboursOfVertex(j) where j + was the parent of i in the DFS search 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 5 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 + config + A configuration for DFS as defined using NewDFSFlags + or NewDFSFlagsLightweight. This field should be set + by calling NewDFSRecord(graph, config) + where config was initially generated by + or . + + + + When this function is called as NewDFSRecord(record, conf), this function + returns a with the + record.config field set to conf.

+ + Initially, the current and child attributes will have -1 values and the lists (parents, + preorder, edge and postorder) will have -1 values at all of their indicies as no vertex has + been visited (if any of the respective record.config.use_preorder + flags are set to true, the value of this list is fail). The stop attribute will initially be false. + This record should be passed into the ExecuteDFS function. + See . + record := NewDFSRecord(CompleteDigraph(2));; +gap> record.preorder; +[ -1, -1 ] +gap> record.postorder; +[ -1, -1 ] +gap> record.stop; +false +gap> record.parents; +[ -1, -1 ] +gap> record.child; +-1 +gap> record.current; +-1 +gap> record.graph; + +gap> d := CompleteDigraph(20);; +gap> flags := NewDFSFlagsLightweight();; +gap> record := NewDFSRecord(d, flags);; +gap> flags := NewDFSFlags();; +gap> flags.iterative := true;; +gap> record := NewDFSRecord(d, flags);; +gap> record.config.iterative; +true +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="NewDFSFlags"> + + + A record. + + This function returns a DFSFlags record to be + used as the config field of a DFSRecord. + called as NewDFSRecord(record, config) should be used to set + config (as shown by the below example), since + fields such as record.config.use_postorder + change the behaviour of . For + example, record.config.use_postorder tells + not to create the record.postorder field.

+ + The fields for both NewDFSFlags and + are described as follows; config was returned + by or , + and NewDFSRecord(d, config) is + called for a digraph d, returning record:

+ + + use_preorder + + When config.use_preorder is true, record.preorder + is fail, and not used during the procedure + (when called with first argument record).

+ + Default: true + + use_postorder + + When config.use_postorder is true, record.postorder + is fail, and not used during the procedure + (when called with first argument record).

+ + Default: true + + use_parents + + When config.use_parents is true, record.parents + is fail, and not used during the procedure + (when called with first argument record).

+ + Default: true + + use_edge + + When config.use_edge is true, record.edge + is fail, and not used during the procedure + (when called with first argument record).

+ + Default: true + + iterative + + Executes the iterative procedure. Memory usage + is generally lower for non iterative DFS. When config.iterative is + false (recursive DFS), config.use_parents and config.use_edge + must also be true.

+ + Default: false + + revisit + + Allows nodes to be revited during if they + are encountered as a successor, and have been backtracked in the current + DFS search tree. When config.revisit + is true, config.iterative must also be true true. + Requires config.iterative to be true.

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

+ + Default: false + + forest_specific + + config.forest_specific is either fail (in which + case this field does not affect ), + or a list of vertices in DigraphVertices(record.graph) + which are guaranteed to be visited during . + This is achieved in the same way as config.forest + but instead of ensuring all vertices from DigraphVertices(record.graph) + are visited, the same behaviour exists for all vertices + from config.forest_specific (if it is not fail). + Requires config.forest to be false.

+ + Default: fail + + + + flags := NewDFSFlags();; +gap> flags; +rec( forest := false, forest_specific := fail, iterative := false, + revisit := false, use_edge := true, use_parents := true, + use_postorder := true, use_preorder := true ) +gap> flags.forest := true;; +gap> flags.use_postorder := false;; +gap> flags.use_preorder := false;; +gap> d := CycleDigraph(2);; +gap> record := NewDFSRecord(d, flags);; +gap> record.postorder; +fail +gap> record.config.use_postorder; +false +gap> record.preorder; +fail +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="NewDFSFlagsLightweight"> + + + A record. + + This function returns a DFSFlags record to be + used as the config field of a DFSRecord (see ). It + differs to the default config returned by see + since all of use_preorder, use_postorder, use_parents and + use_edge are set to false. + flags := NewDFSFlagsLightweight();; +gap> flags.use_preorder; +false +gap> flags.use_postorder; +false +gap> flags.use_edge; +false +gap> flags.use_parents; +false +gap> flags.iterative := true; +true +gap> d := BinaryTree(2);; +gap> record := NewDFSRecord(d, flags);; +gap> record.config.use_parents; +false +gap> record.preorder; +fail +gap> record.postorder; +fail +gap> record.edge; +fail +gap> record.parents; +fail +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="ExecuteDFS"> + + + + This performs a full depth first search from the start vertex (where start is a vertex within the graph). + The depth first search can be terminated by changing the record.stop attribute to true in the + PreOrderFunc, PostOrderFunc, AncestorFunc or CrossFunc functions. + ExecuteDFS takes 7 arguments: + + record + The depth first search record (created using ) + data + An object that you want to manipulate in the functions passed. + start + The vertex where we begin the depth first search. + PreOrderFunc + This function is called when a vertex is first visited. This vertex + is stored in record.current. A vertex can be backtracked + more than once when record.config.revisit is true. + PostOrderFunc + This function is called when a vertex has no more unvisited children + causing us to backtrack. This vertex is stored in record.child and its parent is stored + in record.current. A vertex can be visited + more than once when record.config.revisit is true.

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

+ + When record.CrossFunc is not fail, record.config.use_preorder + must be true (to determine visit order). + + + PreOrderFunc, PostOrderFunc, AncestorFunc and CrossFunc + should be set to fail unless a function is specified. + + Note that this function only performs a depth first search on the vertices reachable from start + unless record.config.forest is true or record.config.forest_specific + is a list of vertices to visit. + It is also important to note that all functions passed need to accept arguments record and data. + Finally, for the start vertex, its parent is itself and the PreOrderFunc + will be called on it. This is also the case for the start vertex of any additional + searches required when record.config.forest is true or + record.config.forest_specific is a list of vertices (not fail). + + See for more details on record. + record := NewDFSRecord(CycleDigraph(10));; +gap> ExecuteDFS(record, [], 1, fail, +> fail, fail, fail); +gap> record.preorder; +[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] +gap> record := NewDFSRecord(CompleteDigraph(10));; +gap> data := rec(cycle_vertex := 0);; +gap> AncestorFunc := function(record, data) +> record.stop := true; +> data.cycle_vertex := record.child; +> end;; +gap> ExecuteDFS(record, data, 1, fail, +> fail, AncestorFunc, fail); +gap> record.stop; +true +gap> data.cycle_vertex; +1 +gap> record.preorder; +[ 1, 2, -1, -1, -1, -1, -1, -1, -1, -1 ] +gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; +gap> CrossFunc := function(record, data) +> record.stop := true; +> Add(data, record.child); +> end;; +gap> data := [];; +gap> ExecuteDFS(record, data, 1, fail, +> fail, fail, CrossFunc); +gap> record.stop; +true +gap> data; +[ 4 ] +]]> + + +<#/GAPDoc> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 4a0feb10b..c86cdce3f 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -20,6 +20,10 @@ <#Include Label="IsMatching"> <#Include Label="DigraphMaximalMatching"> <#Include Label="DigraphMaximumMatching"> + <#Include Label="NewDFSRecord"> + <#Include Label="NewDFSFlags"> + <#Include Label="NewDFSFlagsLightweight"> + <#Include Label="ExecuteDFS">

Neighbours and degree diff --git a/gap/attr.gi b/gap/attr.gi index 84699c2f4..24523a6af 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -29,7 +29,7 @@ InstallGlobalFunction(OutNeighbors, OutNeighbours); BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation", function(D) local N, copy, PostOrderFunc, PreOrderFunc, AncestorCrossFunc, data, record, - connected, parent; + connected, parent, flags; N := DigraphNrVertices(D); @@ -50,6 +50,11 @@ function(D) copy := D; fi; + flags := NewDFSFlagsLightweight(); + flags.use_preorder := true; + flags.use_edge := true; + flags.use_parents := true; + PostOrderFunc := function(record, data) local child, current; child := record.child; @@ -112,7 +117,7 @@ function(D) # articulation point if and only if it has at least 2 children. data.nr_children := 0; - record := NewDFSRecord(copy); + record := NewDFSRecord(copy, flags); ExecuteDFS(record, data, 1, @@ -1061,18 +1066,18 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local N, record, count, out, PostOrderFunc, AncestorFunc; + local N, record, count, out, PostOrderFunc, AncestorFunc, flags; N := DigraphNrVertices(D); if N = 0 then return []; fi; - record := NewDFSRecord(D); - record.config.iterative := true; - record.config.use_edge := false; - record.config.use_preorder := false; - record.config.use_postorder := false; - record.config.use_parents := true; + + flags := NewDFSFlagsLightweight(); + flags.use_parents := true; + flags.use_edge := true; + + record := NewDFSRecord(D, flags); count := 0; out := ListWithIdenticalEntries(N, -1); @@ -1102,10 +1107,6 @@ function(D) return out; end); -# InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", -# [IsDigraphByOutNeighboursRep], -# D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); - InstallMethod(DigraphStronglyConnectedComponents, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], @@ -3055,10 +3056,10 @@ function(D) conf := NewDFSFlagsLightweight(); conf.use_parents := true; - conf.use_edge := true; + conf.iterative := true; conf.forest := true; - record := NewDFSRecordLightweight(C, conf); + record := NewDFSRecord(C, conf); ExecuteDFS(record, data, 1, PreOrderFunc, fail, fail, fail); diff --git a/gap/oper.gd b/gap/oper.gd index b9a5f2843..1b64acbd9 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -157,7 +157,7 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", # 11. DFS DeclareOperation("NewDFSRecord", [IsDigraph]); -DeclareOperation("NewDFSRecordLightweight", [IsDigraph, IsRecord]); +DeclareOperation("NewDFSRecord", [IsDigraph, IsRecord]); DeclareOperation("NewDFSFlags", []); DeclareOperation("NewDFSFlagsLightweight", []); DeclareGlobalFunction("ExecuteDFS"); diff --git a/gap/oper.gi b/gap/oper.gi index 71d60d7c5..e562babd6 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1662,29 +1662,6 @@ function(D, u, v) return DigraphPath(D, u, v) <> fail; end); -# InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", -# [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], -# function(D, u, v) -# local verts; - -# verts := DigraphVertices(D); -# if not (u in verts and v in verts) then -# ErrorNoReturn("the 2nd and 3rd arguments and must be ", -# "vertices of the 1st argument ,"); -# elif IsDigraphEdge(D, u, v) then -# return [[u, v], [Position(OutNeighboursOfVertex(D, u), v)]]; -# elif HasIsTransitiveDigraph(D) and IsTransitiveDigraph(D) then -# # If it's a known transitive digraph, just check whether the edge exists -# return fail; -# # Glean information from WCC if we have it -# elif HasDigraphConnectedComponents(D) -# and DigraphConnectedComponents(D).id[u] <> -# DigraphConnectedComponents(D).id[v] then -# return fail; -# fi; -# return DIGRAPH_PATH(OutNeighbours(D), u, v); -# end); - InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) @@ -1715,7 +1692,7 @@ function(D, u, v) flags.use_edge := true; flags.use_parents := true; - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); if u <> v then # if v is reachable from u, then u is an ancestor of v, and so at some @@ -2073,7 +2050,7 @@ function(D, v) # visited and backtracked on node, # set to unvisited, and visit it - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); data := rec(prev := -1, best := 0); @@ -2396,7 +2373,7 @@ function(D, root) conf.use_edge := true; conf.use_parents := true; - record := NewDFSRecordLightweight(D, conf); + record := NewDFSRecord(D, conf); data := rec(result := [], root_reached := false); PreOrderFunc := function(record, data) @@ -2459,7 +2436,7 @@ function(D, roots) flags.forest_specific := roots; - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); ExecuteDFS(record, data, @@ -2520,7 +2497,7 @@ function(D, root) flags.use_parents := true; flags.use_edge := true; - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); ExecuteDFS(record, preorder_num_to_node, @@ -2851,11 +2828,45 @@ DIGRAPHS_DFSError := function() "NewDFSRecord,"); end; +DIGRAPHS_DFSFlagsBoolErr := function(flags, field) + if not IsBool(flags.(field)) then + ErrorNoReturn("the 2nd argument (a record) should have a Bool ", + "value for field .", field); + fi; +end; + +DIGRAPHS_DFS_CheckFlags := function(flags, graph) + # Already confirmed expected fields are bound + DIGRAPHS_DFSFlagsBoolErr(flags, "iterative"); + DIGRAPHS_DFSFlagsBoolErr(flags, "forest"); + DIGRAPHS_DFSFlagsBoolErr(flags, "revisit"); + DIGRAPHS_DFSFlagsBoolErr(flags, "use_preorder"); + DIGRAPHS_DFSFlagsBoolErr(flags, "use_postorder"); + DIGRAPHS_DFSFlagsBoolErr(flags, "use_parents"); + DIGRAPHS_DFSFlagsBoolErr(flags, "use_edge"); + + if (flags.forest_specific <> fail) and + (not (IsDenseList(flags.forest_specific) and + (IsEmpty(flags.forest_specific) + or IsPosInt(flags.forest_specific[1])))) then + ErrorNoReturn("the 2nd argument (a record) should have a Bool ", + "value for field ."); + fi; + + if flags.forest_specific <> fail and + (ForAny(flags.forest_specific, n -> n < 1 + or n > DigraphNrVertices(graph))) then + ErrorNoReturn("the 2nd argument (a record) has elements in ", + ".forest_specific that are not vertices in the ", + "second argument ."); + fi; +end; + InstallMethod(NewDFSRecord, "for a digraph", [IsDigraph], -Graph -> NewDFSRecordLightweight(Graph, NewDFSFlags())); +Graph -> NewDFSRecord(Graph, NewDFSFlags())); -InstallMethod(NewDFSRecordLightweight, +InstallMethod(NewDFSRecord, "for a digraph and a record", [IsDigraph, IsRecord], function(graph, conf) local record, config_names, N; @@ -2863,11 +2874,15 @@ function(graph, conf) N := DigraphNrVertices(graph); config_names := DIGRAPHS_DFSFlagNames(); + record := rec(); + if ForAny(config_names, n -> not IsBound(conf.(n))) then DIGRAPHS_DFSError(); fi; + DIGRAPHS_DFS_CheckFlags(conf, graph); + record.graph := graph; record.child := -1; record.current := -1; @@ -2941,16 +2956,16 @@ end); InstallGlobalFunction(ExecuteDFS, function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) - local names, config_names; + local record_names, config_names; - names := DIGRAPHS_DFSRecNames(); + record_names := DIGRAPHS_DFSRecNames(); config_names := DIGRAPHS_DFSFlagNames(); if not IsBound(record.config) then DIGRAPHS_DFSError(); elif ForAny(config_names, n -> not IsBound(record.config.(n))) then DIGRAPHS_DFSError(); - elif ForAny(names, n -> not IsBound(record.(n))) then + elif ForAny(record_names, n -> not IsBound(record.(n))) then DIGRAPHS_DFSError(); fi; diff --git a/gap/prop.gi b/gap/prop.gi index 7f88f30cf..b97fd2125 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -248,7 +248,7 @@ function(D) flags := NewDFSFlagsLightweight(); flags.iterative := true; - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); # A Digraph is acyclic if it has no back edges AncestorFunc := function(record, _) @@ -397,7 +397,7 @@ function(D) flags := NewDFSFlagsLightweight(); flags.iterative := true; - record := NewDFSRecordLightweight(D, flags); + record := NewDFSRecord(D, flags); record.config.forest := true; AncestorFunc := function(record, _) diff --git a/src/dfs.c b/src/dfs.c index aa11fa5ec..bde589657 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -19,7 +19,7 @@ #include "digraphs-debug.h" #include "safemalloc.h" #include "digraphs.h" -// #include "stdio.h" + // Macros used for both recursive and iterative @@ -70,13 +70,13 @@ : args->postorder_partial[idx] #define ON_PREORDER(current, args) \ - SET_PREORDER(args, current); \ + SET_PREORDER(args, current); \ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ CHANGED_BAG(args->record); \ if (args->CallPreorder) { \ CALL_CHECK_STOP( \ args->PreorderFunc, args->RNamStop, args->record, args->data) \ - } + } \ #define ANCESTOR_CROSS(current, v, backtracked, args) \ if (args->CallAncestor || args->CallCross) { \ @@ -96,18 +96,20 @@ CHANGED_BAG(args->record); \ } -#define ON_BACKTRACK(current, parent, args) \ - if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) { \ - SET_POSTORDER(args, current); \ - CHANGED_BAG(args->record); \ - } \ - if (args->CallPostorder) { \ - AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ - CHANGED_BAG(args->record); \ - CALL_CHECK_STOP( \ - args->PostorderFunc, args->RNamStop, args->record, args->data) \ - CHANGED_BAG(args->record); \ +#define ON_BACKTRACK(current, parent, args) \ + if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) { \ + SET_POSTORDER(args, current); \ + CHANGED_BAG(args->record); \ + } \ + if (args->CallPostorder) { \ + /* When CallPostorder is true, use_parents must be true, parent is valid \ + */ \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ + CHANGED_BAG(args->record); \ + CALL_CHECK_STOP( \ + args->PostorderFunc, args->RNamStop, args->record, args->data) \ + CHANGED_BAG(args->record); \ } #define ON_ADD_SUCC(current, succ, idx, args) \ @@ -187,13 +189,15 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { conf -> partial_postorder = true; } - if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) { - conf -> partial_postorder = true; - if (!conf -> use_parents) { + if ((args -> CallPostorder && conf -> iter) && !conf -> use_parents) { + // use_parents is always required for recursive (only check for iter) ErrorQuit( "In a DFSRecord where a PostorderFunc exists, where the config flag " "iter is true, the flag use_parents must also be true", 0L, 0L); - } + } + + if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) { + conf -> partial_postorder = true; } if (conf -> forest_specific != Fail && conf -> forest) { @@ -212,7 +216,7 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { // Extreme examples are on the pull request #459 /* Iterative DFS (used for revisiting vertices) - Necessary record elements: preorder + Necessary record elements: none If CallPostorder, then parents is necessary Differences with recursive : edge and parents are updated @@ -291,7 +295,7 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { } /* Recursive DFS - Necessary record elements: edge, preorder, parents + Necessary record elements: edge, parents */ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { @@ -386,14 +390,16 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { parseConfig(&dfs_args_, config); + + if (!dfs_conf.use_preorder) { - dfs_args_.preorder_partial = (bool*) safe_malloc((N * sizeof(bool)) + 1); - memset(dfs_args_.preorder_partial, false, (N * sizeof(bool)) + 1); + dfs_args_.preorder_partial = (bool*) safe_malloc((N + 1) * sizeof(bool)); + memset(dfs_args_.preorder_partial, false, (N + 1) * sizeof(bool)); } if (dfs_conf.partial_postorder) { - dfs_args_.postorder_partial = (bool*) safe_malloc((N * sizeof(bool)) + 1); - memset(dfs_args_.postorder_partial, false, (N * sizeof(bool)) + 1); + dfs_args_.postorder_partial = (bool*) safe_malloc((N + 1) * sizeof(bool)); + memset(dfs_args_.postorder_partial, false, (N + 1) * sizeof(bool)); } if (dfs_conf.use_parents) { @@ -403,6 +409,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Int current = INT_INTOBJ(start); + if (dfs_conf.iter || dfs_conf.revisit) { ExecuteDFSIter(current, &dfs_args_); } else { @@ -427,6 +434,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { } + if (!dfs_conf.use_preorder) { free(dfs_args_.preorder_partial); } @@ -434,6 +442,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (dfs_conf.partial_postorder) { free(dfs_args_.postorder_partial); } + CHANGED_BAG(record); return record; } From 6af3e1b6dae5afe406765972e5a792922255c48e Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 30 Apr 2025 13:35:14 +0100 Subject: [PATCH 36/54] Fix documentation spelling --- doc/oper.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/oper.xml b/doc/oper.xml index 12ae76653..b5208ec72 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -2497,7 +2497,7 @@ true]]> A record. This record contains four lists (parents, edge, preorder and postorder) with their length - equal to the number of verticies in the digraph. Each index i of each list + equal to the number of vertices in the digraph. Each index i of each list corresponds to vertex i in digraph. These lists store the following (where record is returned by NewDFSRecord): @@ -2548,7 +2548,7 @@ true]]> record.config field set to conf.

Initially, the current and child attributes will have -1 values and the lists (parents, - preorder, edge and postorder) will have -1 values at all of their indicies as no vertex has + preorder, edge and postorder) will have -1 values at all of their indices as no vertex has been visited (if any of the respective record.config.use_preorder flags are set to true, the value of this list is fail). The stop attribute will initially be false. This record should be passed into the ExecuteDFS function. From f262e5e5452514d92ff400f817c28a77d0d0bd0c Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 30 Apr 2025 15:42:52 +0100 Subject: [PATCH 37/54] Fix valgrind error with record cleanup for forest DFS --- src/dfs.c | 50 ++++++++++++++++++++++++++++---------------------- src/dfs.h | 1 + 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index bde589657..1569e7ee4 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -130,18 +130,19 @@ #define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to // visit the current node) -#define RECURSE_FOREST(dfs_args_, dfs_conf, v) \ - struct dfs_args* ptr = &dfs_args_; \ - bool visited = IS_VISITED(ptr, v); \ - if (!visited) { \ - if (dfs_conf.use_parents) { \ - AssPlist(dfs_args_.parents, v, INTOBJ_INT(v)); \ - CHANGED_BAG(record); \ - } \ - if (!ExecuteDFSRec(v, v, PREORDER_IDX, &dfs_args_)) { \ - CHANGED_BAG(record); \ - return record; \ - } \ +#define RECURSE_FOREST(dfs_args_, dfs_conf, v) \ + struct dfs_args* ptr = &dfs_args_; \ + bool visited = IS_VISITED(ptr, v); \ + if (!visited) { \ + if (dfs_conf.use_parents) { \ + AssPlist(dfs_args_.parents, v, INTOBJ_INT(v)); \ + CHANGED_BAG(record); \ + } \ + if (!ExecuteDFSRec(v, v, PREORDER_IDX, &dfs_args_)) { \ + CHANGED_BAG(record); \ + recordCleanup(&dfs_args_); \ + return record; \ + } \ } #define ITER_FOREST(args, stack, v) \ @@ -158,6 +159,18 @@ return false; \ } +void recordCleanup(struct dfs_args* args) { + struct dfs_config* dfs_conf = args -> dfs_conf; + + if (!dfs_conf -> use_preorder) { + free(args -> preorder_partial); + } + + if (dfs_conf -> partial_postorder) { + free(args -> postorder_partial); + } +} + void parseConfig(struct dfs_args* args, Obj conf_record) { struct dfs_config* conf = args -> dfs_conf; conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; @@ -418,11 +431,11 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_)) { if (dfs_conf.forest) { for (Int i = 1; i <= N; i++) { - RECURSE_FOREST(dfs_args_, dfs_conf, i); + RECURSE_FOREST(dfs_args_, dfs_conf, i); // Returns } } else if (dfs_conf.forest_specific != Fail) { for (Int i = 1; i <= LEN_PLIST(dfs_conf.forest_specific); i++) { - RECURSE_FOREST(dfs_args_, + RECURSE_FOREST(dfs_args_, // Returns dfs_conf, INT_INTOBJ(ELM_PLIST(dfs_conf.forest_specific, i))); } @@ -434,14 +447,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { } - - if (!dfs_conf.use_preorder) { - free(dfs_args_.preorder_partial); - } - - if (dfs_conf.partial_postorder) { - free(dfs_args_.postorder_partial); - } + recordCleanup(&dfs_args_); CHANGED_BAG(record); return record; diff --git a/src/dfs.h b/src/dfs.h index aa0182133..4fa25b776 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -78,5 +78,6 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args); Obj FuncExecuteDFS_C(Obj self, Obj args); void parseConfig(struct dfs_args*, Obj conf_record); +void recordCleanup(struct dfs_args* args); #endif // DIGRAPHS_SRC_DFS_H_ From a40ca2a801e3235acf5e6b300374b643e2707cf7 Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 30 Apr 2025 16:27:30 +0100 Subject: [PATCH 38/54] Correct documentation errors --- doc/oper.xml | 100 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/doc/oper.xml b/doc/oper.xml index b5208ec72..63d8abfc7 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -2492,8 +2492,8 @@ true]]> <#GAPDoc Label="NewDFSRecord"> - - + + A record. This record contains four lists (parents, edge, preorder and postorder) with their length @@ -2503,54 +2503,82 @@ true]]> parents - At each index, the parent of the vertex is stored. Note that when + At each index, the parent of the vertex is stored.

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

- The record also stores a further 5 attributes. + The record also stores a further 5 attributes: current - The current vertex that is being visited + The current vertex that is being visited.

+ + When record.PostOrderFunc is called, record.current + refers to the parent of the backtracking vertex.

+ + Initial: -1 + child - The child of the current vertex + + The child of the current vertex.

+ + When record.PostOrderFunc is called, record.child + refers to the backtracking vertex.

+ + Initial: -1 + graph - The digraph + + The digraph to carry out DFS on. + stop - Whether to stop the depth first search + + Whether to stop the depth first search.

+ + Initial: false + config - A configuration for DFS as defined using NewDFSFlags - or NewDFSFlagsLightweight. This field should be set + A configuration for DFS as defined using + or . This field should be set by calling NewDFSRecord(graph, config) where config was initially generated by - or . + or .

+ + Default when defined using with + no config argument: A record as returned by + . When this function is called as NewDFSRecord(record, conf), this function - returns a with the - record.config field set to conf.

+ returns a with the + record.config field set to conf.

Initially, the current and child attributes will have -1 values and the lists (parents, preorder, edge and postorder) will have -1 values at all of their indices as no vertex has - been visited (if any of the respective record.config.use_preorder - flags are set to true, the value of this list is fail). The stop attribute will initially be false. + been visited (if any of the respective record.config.use_preorder + flags are set to false, the value of the respective field is fail). The stop attribute will initially be false. This record should be passed into the ExecuteDFS function. See . This function returns a DFSFlags record to be used as the config field of a DFSRecord. - called as NewDFSRecord(record, config) should be used to set + called as NewDFSRecord(record, config) should be used to set config (as shown by the below example), since - fields such as record.config.use_postorder - change the behaviour of . For - example, record.config.use_postorder tells + fields such as record.config.use_postorder + change the behaviour of . For + example, record.config.use_postorder tells not to create the record.postorder field.

The fields for both NewDFSFlags and - are described as follows; config was returned + are described as follows, assuming config was returned by or , and NewDFSRecord(d, config) is called for a digraph d, returning record:

@@ -2648,8 +2676,8 @@ true Allows nodes to be revited during if they are encountered as a successor, and have been backtracked in the current - DFS search tree. When config.revisit - is true, config.iterative must also be true true. + DFS tree. When config.revisit + is true, config.iterative must also be true. Requires config.iterative to be true.

Default: false @@ -2714,7 +2742,7 @@ fail A record. This function returns a DFSFlags record to be - used as the config field of a DFSRecord (see ). It + used as the config field of a DFSRecord (see ). It differs to the default config returned by see since all of use_preorder, use_postorder, use_parents and use_edge are set to false. @@ -2753,16 +2781,16 @@ fail Arg="record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc"/> 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 + 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 ) + The depth first search record (created using ). data An object that you want to manipulate in the functions passed. start - The vertex where we begin the depth first search. + The vertex where the depth first search begins. PreOrderFunc This function is called when a vertex is first visited. This vertex is stored in record.current. A vertex can be backtracked @@ -2808,7 +2836,7 @@ fail searches required when record.config.forest is true or record.config.forest_specific is a list of vertices (not fail). - See for more details on record. + See for more details on record. record := NewDFSRecord(CycleDigraph(10));; gap> ExecuteDFS(record, [], 1, fail, From 6b49250d91dd4d171cd3bcdfc029341523ce2162 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sat, 7 Jun 2025 04:01:49 +0100 Subject: [PATCH 39/54] Fix usage of PList for forest_specific flag, add tests for consistency across flags. --- gap/oper.gi | 53 +++++++++++++--------- src/dfs.c | 102 ++++++++++++++++++++---------------------- tst/standard/oper.tst | 63 +++++++++++++++++++------- 3 files changed, 129 insertions(+), 89 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index e562babd6..08dab8f1c 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2831,19 +2831,18 @@ end; DIGRAPHS_DFSFlagsBoolErr := function(flags, field) if not IsBool(flags.(field)) then ErrorNoReturn("the 2nd argument (a record) should have a Bool ", - "value for field .", field); + "value for field ,", field); fi; end; DIGRAPHS_DFS_CheckFlags := function(flags, graph) # Already confirmed expected fields are bound - DIGRAPHS_DFSFlagsBoolErr(flags, "iterative"); - DIGRAPHS_DFSFlagsBoolErr(flags, "forest"); - DIGRAPHS_DFSFlagsBoolErr(flags, "revisit"); - DIGRAPHS_DFSFlagsBoolErr(flags, "use_preorder"); - DIGRAPHS_DFSFlagsBoolErr(flags, "use_postorder"); - DIGRAPHS_DFSFlagsBoolErr(flags, "use_parents"); - DIGRAPHS_DFSFlagsBoolErr(flags, "use_edge"); + local bool_flag; + + for bool_flag in ["iterative", "forest", "revisit", "use_parents", "use_edge", + "use_postorder", "use_preorder"] do + DIGRAPHS_DFSFlagsBoolErr(flags, bool_flag); + od; if (flags.forest_specific <> fail) and (not (IsDenseList(flags.forest_specific) and @@ -2862,6 +2861,29 @@ DIGRAPHS_DFS_CheckFlags := function(flags, graph) fi; end; +DIGRAPHS_ExecuteDFSCheck := function(record) + local record_names, config_names; + + record_names := DIGRAPHS_DFSRecNames(); + config_names := DIGRAPHS_DFSFlagNames(); + + if not IsBound(record.config) then + DIGRAPHS_DFSError(); + elif ForAny(config_names, n -> not IsBound(record.config.(n))) then + DIGRAPHS_DFSError(); + elif ForAny(record_names, n -> not IsBound(record.(n))) then + DIGRAPHS_DFSError(); + elif record.config.forest_specific <> fail and + not IsDenseList(record.config.forest_specific) then + ErrorNoReturn("the 1st argument has a value of", + " .config.forest_specific", + " that is not fail or a dense list,"); + elif ForAny(["parents", "preorder", "postorder", "edge"], + n -> record.(n) <> fail and not IsDenseList(record.(n))) then + DIGRAPHS_DFSError(); + fi; +end; + InstallMethod(NewDFSRecord, "for a digraph", [IsDigraph], Graph -> NewDFSRecord(Graph, NewDFSFlags())); @@ -2887,6 +2909,7 @@ function(graph, conf) record.child := -1; record.current := -1; record.stop := false; + if conf.use_preorder then record.preorder := ListWithIdenticalEntries(N, -1); else @@ -2956,19 +2979,7 @@ end); InstallGlobalFunction(ExecuteDFS, function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc) - local record_names, config_names; - - record_names := DIGRAPHS_DFSRecNames(); - config_names := DIGRAPHS_DFSFlagNames(); - - if not IsBound(record.config) then - DIGRAPHS_DFSError(); - elif ForAny(config_names, n -> not IsBound(record.config.(n))) then - DIGRAPHS_DFSError(); - elif ForAny(record_names, n -> not IsBound(record.(n))) then - DIGRAPHS_DFSError(); - fi; - + DIGRAPHS_ExecuteDFSCheck(record); ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); end); diff --git a/src/dfs.c b/src/dfs.c index 1569e7ee4..94ab8f1be 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -13,6 +13,7 @@ #include "dfs.h" #include // for uint64_t +// #include #include // for NULL, free #include "digraphs-config.h" @@ -20,7 +21,6 @@ #include "safemalloc.h" #include "digraphs.h" - // Macros used for both recursive and iterative #define CALL_CHECK_STOP(f, RNamStop, record, data) \ @@ -31,7 +31,7 @@ } #define GET_PREORDER(args, idx) \ - args->dfs_conf->use_preorder ? INT_INTOBJ(ELM_PLIST(args->preorder, idx)) \ + args->dfs_conf->use_preorder ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) \ : args->preorder_partial[idx] #define SET_PREORDER(args, idx) \ @@ -48,13 +48,13 @@ args->preorder_partial[idx] = false; \ } -#define IS_VISITED(args, idx) \ - args->dfs_conf->use_preorder \ - ? INT_INTOBJ(ELM_PLIST(args->preorder, idx)) != -1 \ +#define IS_VISITED(args, idx) \ + args->dfs_conf->use_preorder \ + ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) != -1 \ : args->preorder_partial[idx] #define GET_POSTORDER(args, idx) \ - args->dfs_conf->use_postorder ? INT_INTOBJ(ELM_PLIST(args->postorder, idx)) \ + args->dfs_conf->use_postorder ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) \ : args->postorder_partial[idx] #define SET_POSTORDER(args, idx) \ @@ -66,7 +66,7 @@ #define IS_BACKTRACKED(args, idx) \ args->dfs_conf->use_postorder \ - ? INT_INTOBJ(ELM_PLIST(args->postorder, idx)) != -1 \ + ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) != -1 \ : args->postorder_partial[idx] #define ON_PREORDER(current, args) \ @@ -76,24 +76,24 @@ if (args->CallPreorder) { \ CALL_CHECK_STOP( \ args->PreorderFunc, args->RNamStop, args->record, args->data) \ - } \ - -#define ANCESTOR_CROSS(current, v, backtracked, args) \ - if (args->CallAncestor || args->CallCross) { \ - AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ - AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ - CHANGED_BAG(args->record); \ - if (args->CallAncestor && !backtracked) { \ - CALL_CHECK_STOP( \ - args->AncestorFunc, args->RNamStop, args->record, args->data) \ - } else if (args->CallCross \ - && (backtracked \ - && ((GET_PREORDER(args, v)) \ - < (GET_PREORDER(args, current))))) { \ - CALL_CHECK_STOP( \ - args->CrossFunc, args->RNamStop, args->record, args->data) \ - } \ - CHANGED_BAG(args->record); \ + } + +#define ANCESTOR_CROSS(current, v, backtracked, args) \ + if (args->CallAncestor || args->CallCross) { \ + AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ + AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ + CHANGED_BAG(args->record); \ + if (args->CallAncestor && !backtracked) { \ + CALL_CHECK_STOP( \ + args->AncestorFunc, args->RNamStop, args->record, args->data) \ + } else if (args->CallCross \ + && (backtracked \ + && ((GET_PREORDER(args, v)) \ + < (GET_PREORDER(args, current))))) { \ + CALL_CHECK_STOP( \ + args->CrossFunc, args->RNamStop, args->record, args->data) \ + } \ + CHANGED_BAG(args->record); \ } #define ON_BACKTRACK(current, parent, args) \ @@ -130,23 +130,22 @@ #define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to // visit the current node) -#define RECURSE_FOREST(dfs_args_, dfs_conf, v) \ - struct dfs_args* ptr = &dfs_args_; \ - bool visited = IS_VISITED(ptr, v); \ - if (!visited) { \ - if (dfs_conf.use_parents) { \ - AssPlist(dfs_args_.parents, v, INTOBJ_INT(v)); \ - CHANGED_BAG(record); \ - } \ - if (!ExecuteDFSRec(v, v, PREORDER_IDX, &dfs_args_)) { \ - CHANGED_BAG(record); \ - recordCleanup(&dfs_args_); \ - return record; \ - } \ +#define RECURSE_FOREST(dfs_args, v) \ + bool visited = IS_VISITED(dfs_args, v); \ + if (!visited) { \ + if (dfs_args -> dfs_conf -> use_parents) { \ + AssPlist(dfs_args->parents, v, INTOBJ_INT(v)); \ + CHANGED_BAG(record); \ + } \ + if (!ExecuteDFSRec(v, v, PREORDER_IDX, dfs_args)) { \ + CHANGED_BAG(record); \ + recordCleanup(dfs_args); \ + return record; \ + } \ } -#define ITER_FOREST(args, stack, v) \ - bool visited = IS_VISITED(args, v); \ +#define ITER_FOREST(args, stack, v) \ + bool visited = IS_VISITED(args, v); \ \ if (!visited) { \ if (args->dfs_conf->use_parents) { \ @@ -224,6 +223,7 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { "In a DFSRecord where there is a CrossFunc, the config flag " "use_preorder must be true", 0L, 0L); } + } // Extreme examples are on the pull request #459 @@ -252,7 +252,7 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args) { ITER_FOREST(args, stack, i); } } else if (args -> dfs_conf -> forest_specific != Fail) { - Int len = LEN_PLIST(args -> dfs_conf -> forest_specific); + Int len = LEN_LIST(args -> dfs_conf -> forest_specific); for (Int i = 1; i <= len; i++) { ITER_FOREST(args, stack, i); } @@ -267,7 +267,7 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { if (current < 0) { Int bt_on = current * -1; Int parent = !args -> dfs_conf -> use_parents ? -1 : - INT_INTOBJ(ELM_PLIST(args -> parents, bt_on)); + INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); ON_BACKTRACK(bt_on, parent, args); continue; } else if (IS_VISITED(args, current)) { @@ -323,8 +323,8 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) ON_BACKTRACK(current, parent, args); - Int prev_idx = INT_INTOBJ(ELM_PLIST(args -> edge, current)); - Int parents_parent = INT_INTOBJ(ELM_PLIST(args -> parents, parent)); + Int prev_idx = INT_INTOBJ(ELM_LIST(args -> edge, current)); + Int parents_parent = INT_INTOBJ(ELM_LIST(args -> parents, parent)); if (parent == current) return true; // At root @@ -348,7 +348,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { - /* DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); */ + DIGRAPHS_ASSERT(LEN_PLIST(args) == 8); Obj record = ELM_PLIST(args, 1); Obj config = ElmPRec(record, RNamName("config")); Obj data = ELM_PLIST(args, 2); @@ -403,8 +403,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { parseConfig(&dfs_args_, config); - - if (!dfs_conf.use_preorder) { dfs_args_.preorder_partial = (bool*) safe_malloc((N + 1) * sizeof(bool)); memset(dfs_args_.preorder_partial, false, (N + 1) * sizeof(bool)); @@ -422,7 +420,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Int current = INT_INTOBJ(start); - if (dfs_conf.iter || dfs_conf.revisit) { ExecuteDFSIter(current, &dfs_args_); } else { @@ -431,13 +428,12 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { if (ExecuteDFSRec(current, current, PREORDER_IDX, &dfs_args_)) { if (dfs_conf.forest) { for (Int i = 1; i <= N; i++) { - RECURSE_FOREST(dfs_args_, dfs_conf, i); // Returns + RECURSE_FOREST((&dfs_args_), i); // Returns } } else if (dfs_conf.forest_specific != Fail) { - for (Int i = 1; i <= LEN_PLIST(dfs_conf.forest_specific); i++) { - RECURSE_FOREST(dfs_args_, // Returns - dfs_conf, - INT_INTOBJ(ELM_PLIST(dfs_conf.forest_specific, i))); + for (Int i = 1; i <= LEN_LIST(dfs_conf.forest_specific); i++) { + RECURSE_FOREST((&dfs_args_), // Returns + INT_INTOBJ(ELM_LIST(dfs_conf.forest_specific, i))); } } } diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 446273b18..a90854b56 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3263,7 +3263,23 @@ gap> DigraphVertexLabels(D); # parents := HashMap([]), postorder := HashMap([]), preorder := HashMap([]), # stop := false ) -# ExecuteDFS +# ExecuteDFS - Error Checking +gap> ExecuteDFS(rec(), [], 1, fail, +> fail, fail, fail); +Error, the 1st argument must be created with NewDFSRecord, +gap> D := ChainDigraph(1);; +gap> ExecuteDFS(NewDFSRecord(D), [], 3, fail, fail, fail, +> fail); +Error, the third argument must be a vertex in your graph, +gap> d := BinaryTree(5);; +gap> record := NewDFSRecord(d);; +gap> record.config.forest_specific := [1,, 3];; +gap> ExecuteDFS(record, [], 3, fail, fail, fail, +> fail); +Error, the 1st argument has a value of .config.forest_specifi\ +c that is not fail or a dense list, + +# ExecuteDFS - Correctness gap> record := NewDFSRecord(CompleteDigraph(10));; gap> ExecuteDFS(record, [], 2, fail, > fail, fail, fail); @@ -3308,13 +3324,6 @@ gap> ExecuteDFS(record, data, 1, fail, > fail, AncestorFunc, CrossFunc);; gap> data; rec( back_edges := [ [ 3, 1 ], [ 3, 1 ] ], cross_edges := [ [ 5, 4 ] ] ) -gap> ExecuteDFS(rec(), data, 1, fail, -> fail, AncestorFunc, CrossFunc); -Error, the 1st argument must be created with NewDFSRecord, -gap> D := ChainDigraph(1);; -gap> ExecuteDFS(NewDFSRecord(D), [], 3, fail, fail, fail, -> fail); -Error, the third argument must be a vertex in your graph, gap> record := NewDFSRecord(Digraph([[2, 3], [], [2]]));; gap> data := rec(back_edges := [], cross_edges := []);; gap> ExecuteDFS(record, data, 1, fail, fail, AncestorFunc, @@ -3375,15 +3384,35 @@ gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, gap> data.count; 5000 -# Forest DFS +# Checking results are consistent for different options - BinaryTree(5) gap> gr := BinaryTree(5);; gap> record := NewDFSRecord(gr);; -gap> record.config.forest := true;; -gap> ExecuteDFS(record, fail, 1, fail, fail, fail, -> fail); -gap> ForAll(record.preorder, x -> x <> -1); -true -gap> ForAll([record.preorder, record.postorder], IsSet); +gap> configs := [];; +gap> temp_conf := ShallowCopy(record.config);; # forest, recursive +gap> temp_conf.forest := true;; +gap> Add(configs, temp_conf);; +gap> temp_conf := ShallowCopy(record.config);; # forest_specific, recursive +gap> temp_conf.forest_specific := DigraphVertices(gr);; +gap> Add(configs, temp_conf);; +gap> temp_conf := ShallowCopy(configs[1]);; # forest, iterative +gap> temp_conf.iterative := true;; +gap> Add(configs, temp_conf);; +gap> temp_conf := ShallowCopy(configs[2]);; # forest_specific, iterative +gap> temp_conf.iterative := true;; +gap> Add(configs, temp_conf);; +gap> records := [];; +gap> for conf in configs do +> record := NewDFSRecord(gr, conf);; +> ExecuteDFS(record, fail, 1, fail, fail, fail, fail);; +> Add(records, record);; +> od; +gap> ForAll(records, r -> +> ((r.edge = records[1].edge) and +> (r.parents = records[1].parents) and +> (r.preorder = records[1].preorder) and +> (r.postorder = records[1].postorder) and +> (IsSet(r.postorder) and IsSet(r.preorder))) +> ); true # IsDigraphPath @@ -3491,6 +3520,10 @@ gap> Unbind(AncestorFunc); gap> Unbind(CrossFunc); gap> Unbind(record); gap> Unbind(data); +gap> Unbind(parents); +gap> Unbind(edge); +gap> Unbind(postorder); +gap> Unbind(preorder); # gap> DIGRAPHS_StopTest(); From 689f525fc87cfeea1cae82c3c47146d0dfa737d5 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sat, 7 Jun 2025 21:10:30 +0100 Subject: [PATCH 40/54] Correct assert of arg number --- src/dfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dfs.c b/src/dfs.c index 94ab8f1be..29fe54f8d 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -348,7 +348,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { Obj FuncExecuteDFS_C(Obj self, Obj args) { - DIGRAPHS_ASSERT(LEN_PLIST(args) == 8); + DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); Obj config = ElmPRec(record, RNamName("config")); Obj data = ELM_PLIST(args, 2); From 4d97080370e77962e80e1e7c4163bfa665af1ee8 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 16:30:01 +0100 Subject: [PATCH 41/54] Remove unnecessary header inclusions --- src/dfs.c | 14 ++++++-------- src/dfs.h | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/dfs.c b/src/dfs.c index 29fe54f8d..2360a7f6b 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -12,14 +12,13 @@ #include "dfs.h" -#include // for uint64_t -// #include -#include // for NULL, free - -#include "digraphs-config.h" +#include // for uint64_t +#include // for NULL, free #include "digraphs-debug.h" -#include "safemalloc.h" -#include "digraphs.h" +#include "safemalloc.h" // for safe_malloc + +Int DigraphNrVertices(Obj); +Obj FuncOutNeighbours(Obj, Obj); // Macros used for both recursive and iterative @@ -442,7 +441,6 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { } } - recordCleanup(&dfs_args_); CHANGED_BAG(record); diff --git a/src/dfs.h b/src/dfs.h index 4fa25b776..9ca3c6e91 100644 --- a/src/dfs.h +++ b/src/dfs.h @@ -16,7 +16,6 @@ #include // for false, true, bool // GAP headers #include "gap-includes.h" // for Obj, Int -#include "bitarray.h" bool CallCheckStop(Obj f, Int RNamStop, Obj record, Obj data); From a7b7bd925e527c38100e55c611d21f034dbb5e1f Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 17:29:46 +0100 Subject: [PATCH 42/54] Attempt to diagnose planarity issue by restoring list argument VerticesReachableFrom --- gap/oper.gi | 109 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 08dab8f1c..0fad5c1b6 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2403,51 +2403,92 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", [IsDigraph, IsList], function(D, roots) - local record, flags, N, PreOrderFunc, AncestorFunc, - data; - - if (roots = []) then - return []; - fi; + local N, index, visited, queue_tail, queue, + root, element, neighbour, graph_out_neighbors, node_neighbours; N := DigraphNrVertices(D); - if (ForAny(roots, v -> v <= 0 or v > N)) then - ErrorNoReturn("an element of the 2nd argument ", - "(roots) is not a vertex of the 1st ", - "argument (a digraph)"); - fi; + for root in roots do + if not IsPosInt(N) or 0 = root or root > N then + ErrorNoReturn("an element of the 2nd argument ", + "(roots) is not a vertex of the 1st ", + "argument (a digraph)"); + fi; + od; - data := rec(result := BlistList([1 .. N], [])); + visited := BlistList([1 .. N], []); - PreOrderFunc := function(record, data) - if record.parents[record.current] <> record.current then - data.result[record.current] := true; - fi; - end; + graph_out_neighbors := OutNeighbors(D); + queue := EmptyPlist(N); + Append(queue, roots); - AncestorFunc := function(record, data) - data.result[record.child] := true; - end; + queue_tail := Length(roots); - flags := NewDFSFlagsLightweight(); - flags.use_edge := true; - flags.use_parents := true; + 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; - flags.forest_specific := roots; + return ListBlist([1 .. N], visited); +end); - record := NewDFSRecord(D, flags); +# InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", +# [IsDigraph, IsList], +# function(D, roots) +# local record, flags, N, PreOrderFunc, AncestorFunc, +# data; - ExecuteDFS(record, - data, - roots[1], - PreOrderFunc, - fail, - AncestorFunc, - fail); +# if (roots = []) then +# return []; +# fi; - return ListBlist([1 .. N], data.result); -end); +# N := DigraphNrVertices(D); + +# if (ForAny(roots, v -> v <= 0 or v > N)) then +# ErrorNoReturn("an element of the 2nd argument ", +# "(roots) is not a vertex of the 1st ", +# "argument (a digraph)"); +# fi; + +# data := rec(result := BlistList([1 .. N], [])); + +# PreOrderFunc := function(record, data) +# if record.parents[record.current] <> record.current then +# data.result[record.current] := true; +# fi; +# end; + +# AncestorFunc := function(record, data) +# data.result[record.child] := true; +# end; + +# flags := NewDFSFlagsLightweight(); +# flags.use_edge := true; +# flags.use_parents := true; + +# flags.forest_specific := roots; + +# record := NewDFSRecord(D, flags); + +# ExecuteDFS(record, +# data, +# roots[1], +# PreOrderFunc, +# fail, +# AncestorFunc, +# fail); + +# return ListBlist([1 .. N], data.result); +# end); InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices", [IsDigraph, IsList], From ce23d9be5209e38020d12a7e38900b0ce08d6e1b Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 17:40:20 +0100 Subject: [PATCH 43/54] Attempt 2: restoring DigraphPath --- gap/oper.gi | 141 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 0fad5c1b6..29e069b1b 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1665,11 +1665,10 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents, - flags; + local verts; - N := DigraphNrVertices(D); - if u > N or v > N then + verts := DigraphVertices(D); + if not (u in verts and v in verts) then ErrorNoReturn("the 2nd and 3rd arguments and must be ", "vertices of the 1st argument ,"); elif IsDigraphEdge(D, u, v) then @@ -1682,67 +1681,91 @@ 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; + return DIGRAPH_PATH(OutNeighbours(D), u, v); +end); - flags := NewDFSFlagsLightweight(); +# InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", +# [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], +# function(D, u, v) +# local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents, +# flags; - flags.use_edge := true; - flags.use_parents := true; +# 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 +# return [[u, v], [Position(OutNeighboursOfVertex(D, u), v)]]; +# elif HasIsTransitiveDigraph(D) and IsTransitiveDigraph(D) then +# # If it's a known transitive digraph, just check whether the edge exists +# return fail; +# # Glean information from WCC if we have it +# elif HasDigraphConnectedComponents(D) +# 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, flags); +# flags := NewDFSFlagsLightweight(); - if u <> v then - # if v is reachable from u, then u is an ancestor of v, and so at some - # point v will be encountered for the first time, and PreOrderFunc will be - # called. - PreOrderFunc := function(record, _) - if record.current = v then - record.stop := true; - fi; - end; - AncestorFunc := fail; - else - # if u is reachable from u, then u will be encountered as an ancestor of - # itself, but PreOrderFunc won't be called (because u has already been - # discovered). - PreOrderFunc := fail; - AncestorFunc := function(record, _) - if record.child = v then - record.stop := true; - fi; - end; - fi; +# flags.use_edge := true; +# flags.use_parents := true; - ExecuteDFS(record, - fail, - u, - PreOrderFunc, - fail, - AncestorFunc, - fail); - if not record.stop then - return fail; - fi; - nodes := [v]; - edges := []; - current := v; - if u = v then - # Go one back from v to the last node in the tree - current := record.current; - Add(nodes, current); - Add(edges, Position(OutNeighboursOfVertex(D, current), u)); - fi; - # Follow the path from current (which is a descendant of u) back to u - while current <> u do - Add(edges, record.edge[current]); - current := record.parents[current]; - Add(nodes, current); - od; - return [Reversed(nodes), Reversed(edges)]; -end); +# record := NewDFSRecord(D, flags); + +# if u <> v then +# # if v is reachable from u, then u is an ancestor of v, and so at some +# # point v will be encountered for the first time, and PreOrderFunc will be +# # called. +# PreOrderFunc := function(record, _) +# if record.current = v then +# record.stop := true; +# fi; +# end; +# AncestorFunc := fail; +# else +# # if u is reachable from u, then u will be encountered as an ancestor of +# # itself, but PreOrderFunc won't be called (because u has already been +# # discovered). +# PreOrderFunc := fail; +# AncestorFunc := function(record, _) +# if record.child = v then +# record.stop := true; +# fi; +# end; +# fi; + +# ExecuteDFS(record, +# fail, +# u, +# PreOrderFunc, +# fail, +# AncestorFunc, +# fail); +# if not record.stop then +# return fail; +# fi; +# nodes := [v]; +# edges := []; +# current := v; +# if u = v then +# # Go one back from v to the last node in the tree +# current := record.current; +# Add(nodes, current); +# Add(edges, Position(OutNeighboursOfVertex(D, current), u)); +# fi; +# # Follow the path from current (which is a descendant of u) back to u +# while current <> u do +# Add(edges, record.edge[current]); +# current := record.parents[current]; +# Add(nodes, current); +# od; +# return [Reversed(nodes), Reversed(edges)]; +# end); InstallMethod(IsDigraphPath, "for a digraph and list", [IsDigraph, IsList], From 159e593f62efa7c43bf7ca9ac3e907a7f86ce625 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 17:45:36 +0100 Subject: [PATCH 44/54] Attempt 3: restoring DigraphLongestDistanceFromVertex --- gap/oper.gi | 74 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 29e069b1b..858d7402d 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2059,48 +2059,64 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags; + local dist; 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 + return infinity; + fi; + return dist; +end); - flags := NewDFSFlagsLightweight(); - flags.iterative := true; # revisit DFS must be iterative - flags.use_parents := true; - flags.revisit := true; # If found another edge to an already - # visited and backtracked on node, - # set to unvisited, and visit it +# InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", +# [IsDigraphByOutNeighboursRep, IsPosInt], +# function(D, v) +# local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags; - record := NewDFSRecord(D, flags); +# if not v in DigraphVertices(D) then +# ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", +# "argument ,"); +# fi; - data := rec(prev := -1, best := 0); +# flags := NewDFSFlagsLightweight(); +# flags.iterative := true; # revisit DFS must be iterative +# flags.use_parents := true; +# flags.revisit := true; # If found another edge to an already +# # visited and backtracked on node, +# # set to unvisited, and visit it - AncestorFunc := function(record, _) - record.stop := true; - end; +# record := NewDFSRecord(D, flags); - PostOrderFunc := function(_, data) - data.prev := data.prev - 1; - end; +# data := rec(prev := -1, best := 0); - PreOrderFunc := function(_, data) - data.prev := data.prev + 1; - if data.prev > data.best then - data.best := data.prev; - fi; - end; +# AncestorFunc := function(record, _) +# record.stop := true; +# end; - ExecuteDFS(record, data, v, - PreOrderFunc, PostOrderFunc, - AncestorFunc, fail); - if record.stop then - return infinity; - fi; - return data.best; +# PostOrderFunc := function(_, data) +# data.prev := data.prev - 1; +# end; -end); +# PreOrderFunc := function(_, data) +# data.prev := data.prev + 1; +# if data.prev > data.best then +# data.best := data.prev; +# fi; +# end; + +# ExecuteDFS(record, data, v, +# PreOrderFunc, PostOrderFunc, +# AncestorFunc, fail); +# if record.stop then +# return infinity; +# fi; +# return data.best; + +# end); InstallMethod(DigraphRandomWalk, "for a digraph, a pos int and a non-negative int", From 711940aa855120ef2ea6dd608c4540aa2f82b777 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 17:51:20 +0100 Subject: [PATCH 45/54] Attempt 4: restoring DominatorTree --- gap/oper.gi | 188 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 152 insertions(+), 36 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 858d7402d..b4dc446ca 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2555,10 +2555,9 @@ 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, preorder_num_to_node, PreOrderFunc, record, parents, - node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, - pred, N, w, y, x, i, v, flags; - + 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 @@ -2566,32 +2565,36 @@ function(D, root) "argument (a digraph)"); fi; - preorder_num_to_node := []; - - PreOrderFunc := function(record, data) - Add(data, record.current); - end; - - flags := NewDFSFlagsLightweight(); - flags.use_preorder := true; - flags.use_parents := true; - flags.use_edge := true; - - record := NewDFSRecord(D, flags); - - ExecuteDFS(record, - preorder_num_to_node, - root, - PreOrderFunc, - fail, - fail, - fail); + node_to_preorder_num := []; + node_to_preorder_num[root] := 1; + preorder_num_to_node := [root]; - parents := record.parents; - node_to_preorder_num := record.preorder; + parent := []; + parent[root] := fail; - parents[root] := -1; + 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 := []; @@ -2601,7 +2604,7 @@ function(D, root) compress := function(v) local u; - u := parents[v]; + u := parent[v]; if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= node_to_preorder_num[lastlinked] then compress(u); @@ -2609,7 +2612,7 @@ function(D, root) < node_to_preorder_num[semi[label[v]]] then label[v] := label[u]; fi; - parents[v] := parents[u]; + parent[v] := parent[u]; fi; end; @@ -2629,8 +2632,7 @@ function(D, root) w := preorder_num_to_node[i]; for v in bucket[w] do y := eval(v); - if node_to_preorder_num[semi[y]] < - node_to_preorder_num[w] then + if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then idom[v] := y; else idom[v] := w; @@ -2638,16 +2640,15 @@ 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 + if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then semi[w] := semi[x]; fi; fi; od; - if parents[w] = semi[w] then - idom[w] := parents[w]; + if parent[w] = semi[w] then + idom[w] := parent[w]; else Add(bucket[semi[w]], w); fi; @@ -2667,6 +2668,121 @@ function(D, root) return rec(idom := idom, preorder := preorder_num_to_node); end); +# InstallMethod(DominatorTree, "for a digraph and a vertex", +# [IsDigraph, IsPosInt], +# function(D, root) +# local M, preorder_num_to_node, PreOrderFunc, record, parents, +# node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, +# pred, N, w, y, x, i, v, flags; + +# M := DigraphNrVertices(D); + +# if 0 = root or root > M then +# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", +# "argument (a digraph)"); +# fi; + +# preorder_num_to_node := []; + +# PreOrderFunc := function(record, data) +# Add(data, record.current); +# end; + +# flags := NewDFSFlagsLightweight(); +# flags.use_preorder := true; +# flags.use_parents := true; +# flags.use_edge := true; + +# record := NewDFSRecord(D, flags); + +# ExecuteDFS(record, +# preorder_num_to_node, +# root, +# PreOrderFunc, +# fail, +# fail, +# fail); + +# parents := record.parents; +# node_to_preorder_num := record.preorder; + +# parents[root] := -1; + +# semi := [1 .. M]; +# lastlinked := M + 1; +# label := []; +# bucket := List([1 .. M], x -> []); +# idom := []; +# idom[root] := root; + +# compress := function(v) +# local u; +# u := parents[v]; +# if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= +# node_to_preorder_num[lastlinked] then +# compress(u); +# if node_to_preorder_num[semi[label[u]]] +# < node_to_preorder_num[semi[label[v]]] then +# label[v] := label[u]; +# fi; +# parents[v] := parents[u]; +# fi; +# end; + +# eval := function(v) +# if lastlinked <= M and node_to_preorder_num[v] >= +# node_to_preorder_num[lastlinked] then +# compress(v); +# return label[v]; +# else +# return v; +# fi; +# end; + +# pred := InNeighbours(D); +# N := Length(preorder_num_to_node); +# for i in [N, N - 1 .. 2] do +# w := preorder_num_to_node[i]; +# for v in bucket[w] do +# y := eval(v); +# if node_to_preorder_num[semi[y]] < +# node_to_preorder_num[w] then +# idom[v] := y; +# else +# idom[v] := w; +# fi; +# od; +# bucket[w] := []; +# for v in pred[w] do +# 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]; +# fi; +# fi; +# od; +# if parents[w] = semi[w] then +# idom[w] := parents[w]; +# else +# Add(bucket[semi[w]], w); +# fi; +# lastlinked := w; +# label[w] := semi[w]; +# od; +# for v in bucket[root] do +# idom[v] := root; +# od; +# for i in [2 .. N] do +# w := preorder_num_to_node[i]; +# if idom[w] <> semi[w] then +# idom[w] := idom[semi[w]]; +# fi; +# od; +# idom[root] := fail; +# return rec(idom := idom, preorder := preorder_num_to_node); +# end); + InstallMethod(Dominators, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) From 22a8246cd201ffaf96587a66e0c4e0eac779fd04 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 17:56:15 +0100 Subject: [PATCH 46/54] Attempt 5: restoring VerticesReachableFrom int argument --- gap/oper.gi | 70 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index b4dc446ca..553b34ffd 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2399,45 +2399,59 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N, record, conf, data, AncestorFunc, PreOrderFunc; - + local N; 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; - conf := NewDFSFlagsLightweight(); + return VerticesReachableFrom(D, [root]); +end); + +# InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", +# [IsDigraph, IsPosInt], +# function(D, root) +# local N, record, conf, data, AncestorFunc, PreOrderFunc; - conf.use_edge := true; - conf.use_parents := true; +# 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, conf); - data := rec(result := [], root_reached := false); +# conf := NewDFSFlagsLightweight(); - PreOrderFunc := function(record, data) - if record.current <> root then - Add(data.result, record.current); - fi; - end; +# conf.use_edge := true; +# conf.use_parents := true; - AncestorFunc := function(record, data) - if record.child = root and not data.root_reached then - data.root_reached := true; - Add(data.result, root); - fi; - end; +# record := NewDFSRecord(D, conf); +# data := rec(result := [], root_reached := false); - ExecuteDFS(record, - data, - root, - PreOrderFunc, - fail, - AncestorFunc, - fail); - Sort(data.result); - return data.result; -end); +# PreOrderFunc := function(record, data) +# if record.current <> root then +# Add(data.result, record.current); +# fi; +# end; + +# AncestorFunc := function(record, data) +# if record.child = root and not data.root_reached then +# data.root_reached := true; +# Add(data.result, root); +# fi; +# end; + +# ExecuteDFS(record, +# data, +# root, +# PreOrderFunc, +# fail, +# AncestorFunc, +# fail); +# Sort(data.result); +# return data.result; +# end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", [IsDigraph, IsList], From 9676f16bef14e74f069638e1310cc6ec7f7517bb Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 18:00:08 +0100 Subject: [PATCH 47/54] Attempt 6: temporarily removing half the tests added in the commit where planarity started failing --- tst/standard/oper.tst | 66 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index a90854b56..d38246015 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3350,39 +3350,39 @@ gap> record := NewDFSRecord(gr);; gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); -# Stopping ExecuteDFS -gap> gr := CompleteDigraph(10000);; -gap> record := NewDFSRecord(gr);; -gap> data := rec(count := 0);; -gap> PreorderFunc := function(record, data) -> data.count := data.count + 1; -> if record.current = 5000 then -> record.stop := true; -> fi; -> end;; -gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 -gap> record_iter := NewDFSRecord(gr);; -gap> record_iter.config.iterative := true;; -gap> data := rec(count := 0);; -gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 -gap> data := ["postorder", "preorder"];; -gap> ForAll(data, x -> record_iter.(x) = record.(x)); -true -gap> record := NewDFSRecord(gr);; -gap> record.config.revisit := true;; -gap> record.config.iterative := true;; -gap> record.config.use_postorder := true;; -gap> data := rec(count := 0);; -gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 +# Stopping ExecuteDFS TODO uncomment +# gap> gr := CompleteDigraph(10000);; +# gap> record := NewDFSRecord(gr);; +# gap> data := rec(count := 0);; +# gap> PreorderFunc := function(record, data) +# > data.count := data.count + 1; +# > if record.current = 5000 then +# > record.stop := true; +# > fi; +# > end;; +# gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +# > fail); +# gap> data.count; +# 5000 +# gap> record_iter := NewDFSRecord(gr);; +# gap> record_iter.config.iterative := true;; +# gap> data := rec(count := 0);; +# gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, +# > fail); +# gap> data.count; +# 5000 +# gap> data := ["postorder", "preorder"];; +# gap> ForAll(data, x -> record_iter.(x) = record.(x)); +# true +# gap> record := NewDFSRecord(gr);; +# gap> record.config.revisit := true;; +# gap> record.config.iterative := true;; +# gap> record.config.use_postorder := true;; +# gap> data := rec(count := 0);; +# gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +# > fail); +# gap> data.count; +# 5000 # Checking results are consistent for different options - BinaryTree(5) gap> gr := BinaryTree(5);; From c6e65fc825743406637cad85bc75f19baa547ce7 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 18:09:13 +0100 Subject: [PATCH 48/54] Uncomment out potentially problematic iterative DFS tests --- tst/standard/oper.tst | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index d38246015..83bdff2f2 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3351,38 +3351,38 @@ gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); # Stopping ExecuteDFS TODO uncomment -# gap> gr := CompleteDigraph(10000);; # gap> record := NewDFSRecord(gr);; # gap> data := rec(count := 0);; -# gap> PreorderFunc := function(record, data) -# > data.count := data.count + 1; -# > if record.current = 5000 then -# > record.stop := true; -# > fi; -# > end;; # gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, # > fail); # gap> data.count; # 5000 -# gap> record_iter := NewDFSRecord(gr);; -# gap> record_iter.config.iterative := true;; -# gap> data := rec(count := 0);; -# gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, -# > fail); -# gap> data.count; -# 5000 -# gap> data := ["postorder", "preorder"];; # gap> ForAll(data, x -> record_iter.(x) = record.(x)); # true -# gap> record := NewDFSRecord(gr);; -# gap> record.config.revisit := true;; -# gap> record.config.iterative := true;; -# gap> record.config.use_postorder := true;; -# gap> data := rec(count := 0);; -# gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -# > fail); -# gap> data.count; -# 5000 +gap> gr := CompleteDigraph(10000);; +gap> PreorderFunc := function(record, data) +> data.count := data.count + 1; +> if record.current = 5000 then +> record.stop := true; +> fi; +> end;; +gap> record_iter := NewDFSRecord(gr);; +gap> record_iter.config.iterative := true;; +gap> data := rec(count := 0);; +gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +5000 +gap> data := ["postorder", "preorder"];; +gap> record := NewDFSRecord(gr);; +gap> record.config.revisit := true;; +gap> record.config.iterative := true;; +gap> record.config.use_postorder := true;; +gap> data := rec(count := 0);; +gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +5000 # Checking results are consistent for different options - BinaryTree(5) gap> gr := BinaryTree(5);; From ed6d496654b08388c576c14d2a78a887e8f8ebc7 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 18:14:56 +0100 Subject: [PATCH 49/54] Reduce size of graph to do recursive DFS on in seg faulting planarity / bliss test --- tst/standard/oper.tst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 83bdff2f2..99b10256c 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3350,30 +3350,30 @@ gap> record := NewDFSRecord(gr);; gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); -# Stopping ExecuteDFS TODO uncomment -# gap> record := NewDFSRecord(gr);; -# gap> data := rec(count := 0);; -# gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -# > fail); -# gap> data.count; -# 5000 -# gap> ForAll(data, x -> record_iter.(x) = record.(x)); -# true -gap> gr := CompleteDigraph(10000);; +# Stopping ExecuteDFS +gap> gr := CompleteDigraph(1000);; +gap> record := NewDFSRecord(gr);; +gap> data := rec(count := 0);; gap> PreorderFunc := function(record, data) > data.count := data.count + 1; -> if record.current = 5000 then +> if record.current = 500 then > record.stop := true; > fi; > end;; +gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, +> fail); +gap> data.count; +500 gap> record_iter := NewDFSRecord(gr);; gap> record_iter.config.iterative := true;; gap> data := rec(count := 0);; gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, > fail); gap> data.count; -5000 +500 gap> data := ["postorder", "preorder"];; +gap> ForAll(data, x -> record_iter.(x) = record.(x)); +true gap> record := NewDFSRecord(gr);; gap> record.config.revisit := true;; gap> record.config.iterative := true;; @@ -3382,7 +3382,7 @@ gap> data := rec(count := 0);; gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, > fail); gap> data.count; -5000 +500 # Checking results are consistent for different options - BinaryTree(5) gap> gr := BinaryTree(5);; From 804f863b552b9c797423eacaebb7f06dbbdd19e6 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 19:58:41 +0100 Subject: [PATCH 50/54] Change recursive DFS to use explicit gotos to avoid unoptimized tail call issues (hopefully fixes CI on large graphs using external bliss / planarity builds) --- gap/oper.gi | 592 ++++++++++++++---------------------------- src/dfs.c | 77 ++++-- tst/standard/oper.tst | 10 +- 3 files changed, 251 insertions(+), 428 deletions(-) diff --git a/gap/oper.gi b/gap/oper.gi index 553b34ffd..08dab8f1c 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1665,10 +1665,11 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local verts; + local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents, + flags; - verts := DigraphVertices(D); - if not (u in verts and v in verts) then + N := DigraphNrVertices(D); + if u > N or v > N then ErrorNoReturn("the 2nd and 3rd arguments and must be ", "vertices of the 1st argument ,"); elif IsDigraphEdge(D, u, v) then @@ -1681,91 +1682,67 @@ function(D, u, v) and DigraphConnectedComponents(D).id[u] <> DigraphConnectedComponents(D).id[v] then return fail; + elif OutDegreeOfVertex(D, u) = 0 + or (HasInNeighbours(D) and InDegreeOfVertex(D, v) = 0) then + return fail; + fi; + + flags := NewDFSFlagsLightweight(); + + flags.use_edge := true; + flags.use_parents := true; + + record := NewDFSRecord(D, flags); + + if u <> v then + # if v is reachable from u, then u is an ancestor of v, and so at some + # point v will be encountered for the first time, and PreOrderFunc will be + # called. + PreOrderFunc := function(record, _) + if record.current = v then + record.stop := true; + fi; + end; + AncestorFunc := fail; + else + # if u is reachable from u, then u will be encountered as an ancestor of + # itself, but PreOrderFunc won't be called (because u has already been + # discovered). + PreOrderFunc := fail; + AncestorFunc := function(record, _) + if record.child = v then + record.stop := true; + fi; + end; + fi; + + ExecuteDFS(record, + fail, + u, + PreOrderFunc, + fail, + AncestorFunc, + fail); + if not record.stop then + return fail; fi; - return DIGRAPH_PATH(OutNeighbours(D), u, v); -end); - -# InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", -# [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], -# function(D, u, v) -# local N, record, PreOrderFunc, AncestorFunc, nodes, edges, current, parents, -# flags; - -# 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 -# return [[u, v], [Position(OutNeighboursOfVertex(D, u), v)]]; -# elif HasIsTransitiveDigraph(D) and IsTransitiveDigraph(D) then -# # If it's a known transitive digraph, just check whether the edge exists -# return fail; -# # Glean information from WCC if we have it -# elif HasDigraphConnectedComponents(D) -# and DigraphConnectedComponents(D).id[u] <> -# DigraphConnectedComponents(D).id[v] then -# return fail; -# elif OutDegreeOfVertex(D, u) = 0 -# or (HasInNeighbours(D) and InDegreeOfVertex(D, v) = 0) then -# return fail; -# fi; - -# flags := NewDFSFlagsLightweight(); - -# flags.use_edge := true; -# flags.use_parents := true; - -# record := NewDFSRecord(D, flags); - -# if u <> v then -# # if v is reachable from u, then u is an ancestor of v, and so at some -# # point v will be encountered for the first time, and PreOrderFunc will be -# # called. -# PreOrderFunc := function(record, _) -# if record.current = v then -# record.stop := true; -# fi; -# end; -# AncestorFunc := fail; -# else -# # if u is reachable from u, then u will be encountered as an ancestor of -# # itself, but PreOrderFunc won't be called (because u has already been -# # discovered). -# PreOrderFunc := fail; -# AncestorFunc := function(record, _) -# if record.child = v then -# record.stop := true; -# fi; -# end; -# fi; - -# ExecuteDFS(record, -# fail, -# u, -# PreOrderFunc, -# fail, -# AncestorFunc, -# fail); -# if not record.stop then -# return fail; -# fi; -# nodes := [v]; -# edges := []; -# current := v; -# if u = v then -# # Go one back from v to the last node in the tree -# current := record.current; -# Add(nodes, current); -# Add(edges, Position(OutNeighboursOfVertex(D, current), u)); -# fi; -# # Follow the path from current (which is a descendant of u) back to u -# while current <> u do -# Add(edges, record.edge[current]); -# current := record.parents[current]; -# Add(nodes, current); -# od; -# return [Reversed(nodes), Reversed(edges)]; -# end); + nodes := [v]; + edges := []; + current := v; + if u = v then + # Go one back from v to the last node in the tree + current := record.current; + Add(nodes, current); + Add(edges, Position(OutNeighboursOfVertex(D, current), u)); + fi; + # Follow the path from current (which is a descendant of u) back to u + while current <> u do + Add(edges, record.edge[current]); + current := record.parents[current]; + Add(nodes, current); + od; + return [Reversed(nodes), Reversed(edges)]; +end); InstallMethod(IsDigraphPath, "for a digraph and list", [IsDigraph, IsList], @@ -2059,64 +2036,48 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local dist; + local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags; if not v in DigraphVertices(D) then ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", "argument ,"); fi; - dist := DIGRAPH_LONGEST_DIST_VERTEX(OutNeighbours(D), v); - if dist = -2 then - return infinity; - fi; - return dist; -end); - -# InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", -# [IsDigraphByOutNeighboursRep, IsPosInt], -# function(D, v) -# local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc, flags; -# if not v in DigraphVertices(D) then -# ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", -# "argument ,"); -# fi; + flags := NewDFSFlagsLightweight(); + flags.iterative := true; # revisit DFS must be iterative + flags.use_parents := true; + flags.revisit := true; # If found another edge to an already + # visited and backtracked on node, + # set to unvisited, and visit it -# flags := NewDFSFlagsLightweight(); -# flags.iterative := true; # revisit DFS must be iterative -# flags.use_parents := true; -# flags.revisit := true; # If found another edge to an already -# # visited and backtracked on node, -# # set to unvisited, and visit it + record := NewDFSRecord(D, flags); -# record := NewDFSRecord(D, flags); + data := rec(prev := -1, best := 0); -# data := rec(prev := -1, best := 0); - -# AncestorFunc := function(record, _) -# record.stop := true; -# end; + AncestorFunc := function(record, _) + record.stop := true; + end; -# PostOrderFunc := function(_, data) -# data.prev := data.prev - 1; -# end; + PostOrderFunc := function(_, data) + data.prev := data.prev - 1; + end; -# PreOrderFunc := function(_, data) -# data.prev := data.prev + 1; -# if data.prev > data.best then -# data.best := data.prev; -# fi; -# end; + PreOrderFunc := function(_, data) + data.prev := data.prev + 1; + if data.prev > data.best then + data.best := data.prev; + fi; + end; -# ExecuteDFS(record, data, v, -# PreOrderFunc, PostOrderFunc, -# AncestorFunc, fail); -# if record.stop then -# return infinity; -# fi; -# return data.best; + ExecuteDFS(record, data, v, + PreOrderFunc, PostOrderFunc, + AncestorFunc, fail); + if record.stop then + return infinity; + fi; + return data.best; -# end); +end); InstallMethod(DigraphRandomWalk, "for a digraph, a pos int and a non-negative int", @@ -2399,149 +2360,94 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N; - N := DigraphNrVertices(D); + local N, record, conf, data, AncestorFunc, PreOrderFunc; + N := DigraphNrVertices(D); if 0 = root or root > N then ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", "argument (a digraph)"); fi; - return VerticesReachableFrom(D, [root]); -end); + conf := NewDFSFlagsLightweight(); -# InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", -# [IsDigraph, IsPosInt], -# function(D, root) -# local N, record, conf, data, AncestorFunc, PreOrderFunc; + conf.use_edge := true; + conf.use_parents := true; -# 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, conf); + data := rec(result := [], root_reached := false); -# conf := NewDFSFlagsLightweight(); - -# conf.use_edge := true; -# conf.use_parents := true; - -# record := NewDFSRecord(D, conf); -# data := rec(result := [], root_reached := false); - -# PreOrderFunc := function(record, data) -# if record.current <> root then -# Add(data.result, record.current); -# fi; -# end; + PreOrderFunc := function(record, data) + if record.current <> root then + Add(data.result, record.current); + fi; + end; -# AncestorFunc := function(record, data) -# if record.child = root and not data.root_reached then -# data.root_reached := true; -# Add(data.result, root); -# fi; -# end; + AncestorFunc := function(record, data) + if record.child = root and not data.root_reached then + data.root_reached := true; + Add(data.result, root); + fi; + end; -# ExecuteDFS(record, -# data, -# root, -# PreOrderFunc, -# fail, -# AncestorFunc, -# fail); -# Sort(data.result); -# return data.result; -# end); + ExecuteDFS(record, + data, + root, + PreOrderFunc, + fail, + AncestorFunc, + fail); + Sort(data.result); + return data.result; +end); InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", [IsDigraph, IsList], function(D, roots) - local N, index, visited, queue_tail, queue, - root, element, neighbour, graph_out_neighbors, node_neighbours; - - N := DigraphNrVertices(D); - - for root in roots do - if not IsPosInt(N) or 0 = root or root > N then - ErrorNoReturn("an element of the 2nd argument ", - "(roots) is not a vertex of the 1st ", - "argument (a digraph)"); - fi; - od; - - visited := BlistList([1 .. N], []); + local record, flags, N, PreOrderFunc, AncestorFunc, + data; - graph_out_neighbors := OutNeighbors(D); - queue := EmptyPlist(N); - Append(queue, roots); - - queue_tail := Length(roots); - - index := 1; - while IsBound(queue[index]) do - element := queue[index]; - node_neighbours := graph_out_neighbors[element]; - for neighbour in node_neighbours do - if not visited[neighbour] then; - visited[neighbour] := true; - queue_tail := queue_tail + 1; - queue[queue_tail] := neighbour; - fi; - od; - index := index + 1; - od; - - return ListBlist([1 .. N], visited); -end); - -# InstallMethod(VerticesReachableFrom, "for a digraph and a list of vertices", -# [IsDigraph, IsList], -# function(D, roots) -# local record, flags, N, PreOrderFunc, AncestorFunc, -# data; - -# if (roots = []) then -# return []; -# fi; + if (roots = []) then + return []; + fi; -# N := DigraphNrVertices(D); + N := DigraphNrVertices(D); -# if (ForAny(roots, v -> v <= 0 or v > N)) then -# ErrorNoReturn("an element of the 2nd argument ", -# "(roots) is not a vertex of the 1st ", -# "argument (a digraph)"); -# fi; + if (ForAny(roots, v -> v <= 0 or v > N)) then + ErrorNoReturn("an element of the 2nd argument ", + "(roots) is not a vertex of the 1st ", + "argument (a digraph)"); + fi; -# data := rec(result := BlistList([1 .. N], [])); + data := rec(result := BlistList([1 .. N], [])); -# PreOrderFunc := function(record, data) -# if record.parents[record.current] <> record.current then -# data.result[record.current] := true; -# fi; -# end; + PreOrderFunc := function(record, data) + if record.parents[record.current] <> record.current then + data.result[record.current] := true; + fi; + end; -# AncestorFunc := function(record, data) -# data.result[record.child] := true; -# end; + AncestorFunc := function(record, data) + data.result[record.child] := true; + end; -# flags := NewDFSFlagsLightweight(); -# flags.use_edge := true; -# flags.use_parents := true; + flags := NewDFSFlagsLightweight(); + flags.use_edge := true; + flags.use_parents := true; -# flags.forest_specific := roots; + flags.forest_specific := roots; -# record := NewDFSRecord(D, flags); + record := NewDFSRecord(D, flags); -# ExecuteDFS(record, -# data, -# roots[1], -# PreOrderFunc, -# fail, -# AncestorFunc, -# fail); + ExecuteDFS(record, + data, + roots[1], + PreOrderFunc, + fail, + AncestorFunc, + fail); -# return ListBlist([1 .. N], data.result); -# end); + return ListBlist([1 .. N], data.result); +end); InstallMethod(IsOrderIdeal, "for a digraph and a list of vertices", [IsDigraph, IsList], @@ -2569,9 +2475,10 @@ InstallMethod(IsOrderFilter, "for a digraph and a list of vertices", InstallMethod(DominatorTree, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local M, node_to_preorder_num, preorder_num_to_node, parent, index, next, - current, succ, prev, n, semi, lastlinked, label, bucket, idom, - compress, eval, pred, N, w, y, x, i, v; + local M, preorder_num_to_node, PreOrderFunc, record, parents, + node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, + pred, N, w, y, x, i, v, flags; + M := DigraphNrVertices(D); if 0 = root or root > M then @@ -2579,36 +2486,32 @@ function(D, root) "argument (a digraph)"); fi; - node_to_preorder_num := []; - node_to_preorder_num[root] := 1; - preorder_num_to_node := [root]; + preorder_num_to_node := []; - parent := []; - parent[root] := fail; + PreOrderFunc := function(record, data) + Add(data, record.current); + end; - index := ListWithIdenticalEntries(M, 1); + flags := NewDFSFlagsLightweight(); + flags.use_preorder := true; + flags.use_parents := true; + flags.use_edge := true; + + record := NewDFSRecord(D, flags); + + ExecuteDFS(record, + preorder_num_to_node, + root, + PreOrderFunc, + fail, + fail, + fail); + + parents := record.parents; + node_to_preorder_num := record.preorder; + + parents[root] := -1; - next := 2; - current := root; - succ := OutNeighbours(D); - repeat - prev := current; - for i in [index[current] .. Length(succ[current])] do - n := succ[current][i]; - if not IsBound(node_to_preorder_num[n]) then - Add(preorder_num_to_node, n); - parent[n] := current; - index[current] := i + 1; - node_to_preorder_num[n] := next; - next := next + 1; - current := n; - break; - fi; - od; - if prev = current then - current := parent[current]; - fi; - until current = fail; semi := [1 .. M]; lastlinked := M + 1; label := []; @@ -2618,7 +2521,7 @@ function(D, root) compress := function(v) local u; - u := parent[v]; + u := parents[v]; if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= node_to_preorder_num[lastlinked] then compress(u); @@ -2626,7 +2529,7 @@ function(D, root) < node_to_preorder_num[semi[label[v]]] then label[v] := label[u]; fi; - parent[v] := parent[u]; + parents[v] := parents[u]; fi; end; @@ -2646,7 +2549,8 @@ function(D, root) w := preorder_num_to_node[i]; for v in bucket[w] do y := eval(v); - if node_to_preorder_num[semi[y]] < node_to_preorder_num[w] then + if node_to_preorder_num[semi[y]] < + node_to_preorder_num[w] then idom[v] := y; else idom[v] := w; @@ -2654,15 +2558,16 @@ function(D, root) od; bucket[w] := []; for v in pred[w] do - if IsBound(node_to_preorder_num[v]) then + if node_to_preorder_num[v] <> -1 then x := eval(v); - if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then + if node_to_preorder_num[semi[x]] < + node_to_preorder_num[semi[w]] then semi[w] := semi[x]; fi; fi; od; - if parent[w] = semi[w] then - idom[w] := parent[w]; + if parents[w] = semi[w] then + idom[w] := parents[w]; else Add(bucket[semi[w]], w); fi; @@ -2682,121 +2587,6 @@ function(D, root) return rec(idom := idom, preorder := preorder_num_to_node); end); -# InstallMethod(DominatorTree, "for a digraph and a vertex", -# [IsDigraph, IsPosInt], -# function(D, root) -# local M, preorder_num_to_node, PreOrderFunc, record, parents, -# node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, -# pred, N, w, y, x, i, v, flags; - -# M := DigraphNrVertices(D); - -# if 0 = root or root > M then -# ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", -# "argument (a digraph)"); -# fi; - -# preorder_num_to_node := []; - -# PreOrderFunc := function(record, data) -# Add(data, record.current); -# end; - -# flags := NewDFSFlagsLightweight(); -# flags.use_preorder := true; -# flags.use_parents := true; -# flags.use_edge := true; - -# record := NewDFSRecord(D, flags); - -# ExecuteDFS(record, -# preorder_num_to_node, -# root, -# PreOrderFunc, -# fail, -# fail, -# fail); - -# parents := record.parents; -# node_to_preorder_num := record.preorder; - -# parents[root] := -1; - -# semi := [1 .. M]; -# lastlinked := M + 1; -# label := []; -# bucket := List([1 .. M], x -> []); -# idom := []; -# idom[root] := root; - -# compress := function(v) -# local u; -# u := parents[v]; -# if u <> fail and lastlinked <= M and node_to_preorder_num[u] >= -# node_to_preorder_num[lastlinked] then -# compress(u); -# if node_to_preorder_num[semi[label[u]]] -# < node_to_preorder_num[semi[label[v]]] then -# label[v] := label[u]; -# fi; -# parents[v] := parents[u]; -# fi; -# end; - -# eval := function(v) -# if lastlinked <= M and node_to_preorder_num[v] >= -# node_to_preorder_num[lastlinked] then -# compress(v); -# return label[v]; -# else -# return v; -# fi; -# end; - -# pred := InNeighbours(D); -# N := Length(preorder_num_to_node); -# for i in [N, N - 1 .. 2] do -# w := preorder_num_to_node[i]; -# for v in bucket[w] do -# y := eval(v); -# if node_to_preorder_num[semi[y]] < -# node_to_preorder_num[w] then -# idom[v] := y; -# else -# idom[v] := w; -# fi; -# od; -# bucket[w] := []; -# for v in pred[w] do -# 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]; -# fi; -# fi; -# od; -# if parents[w] = semi[w] then -# idom[w] := parents[w]; -# else -# Add(bucket[semi[w]], w); -# fi; -# lastlinked := w; -# label[w] := semi[w]; -# od; -# for v in bucket[root] do -# idom[v] := root; -# od; -# for i in [2 .. N] do -# w := preorder_num_to_node[i]; -# if idom[w] <> semi[w] then -# idom[w] := idom[semi[w]]; -# fi; -# od; -# idom[root] := fail; -# return rec(idom := idom, preorder := preorder_num_to_node); -# end); - InstallMethod(Dominators, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) diff --git a/src/dfs.c b/src/dfs.c index 2360a7f6b..bf2d7b6ea 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -259,6 +259,13 @@ bool ExecuteDFSIter(Int start, struct dfs_args* args) { return true; } +/* + Main loop for iterative DFS called by ITER_FOREST and ExecuteDFSIter. + + ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function + if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc, + CrossFunc, or AncestorFunc. +*/ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { while (stack_size > 0) { Int current = INT_INTOBJ(STACK_POP(stack, stack_size)); @@ -308,44 +315,70 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { /* Recursive DFS Necessary record elements: edge, parents + goto used to force tail call optimization + + ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function + if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc, + CrossFunc, or AncestorFunc. */ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { +rec: if (idx == PREORDER_IDX) { // visit current ON_PREORDER(current, args); - // Start recursing on successors - return ExecuteDFSRec(current, parent, idx + 1, args); + + // Start recursing on successors of vertex , with parent + idx += 1; + goto rec; } - Obj succ = ELM_LIST(args -> neighbors, current); + Obj succ = ELM_LIST(args->neighbors, current); if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) - ON_BACKTRACK(current, parent, args); + ON_BACKTRACK(current, parent, args); - Int prev_idx = INT_INTOBJ(ELM_LIST(args -> edge, current)); - Int parents_parent = INT_INTOBJ(ELM_LIST(args -> parents, parent)); + Int prev_idx = INT_INTOBJ(ELM_LIST(args->edge, current)); + Int parents_parent = INT_INTOBJ(ELM_LIST(args->parents, parent)); - if (parent == current) return true; // At root + if (parent == current) { + return true; // At root + } + + // Continue exploration of 's successors - return ExecuteDFSRec(parent, parents_parent, prev_idx + 1, args); + current = parent; // Backtrack to parent of vertex + parent = parents_parent; // The parent is now the new vertex's + // previously assigned parent + idx = prev_idx + 1; // Index is the next successor to visit + // continuing previous exploration of + // 's successors + goto rec; } else { - Int v = INT_INTOBJ(ELM_LIST(succ, idx)); - bool visited = IS_VISITED(args, v); - - if (!visited) { - ON_ADD_SUCC(current, v, idx, args); - return ExecuteDFSRec(v, current, 0, args); - } else { - bool backtracked = (args -> dfs_conf -> use_postorder - || args -> dfs_conf -> partial_postorder) && - (IS_BACKTRACKED(args, v)); - ANCESTOR_CROSS(current, v, backtracked, args); - return ExecuteDFSRec(current, parent, idx + 1, args); // Skip - } + Int v = INT_INTOBJ(ELM_LIST(succ, idx)); + bool visited = IS_VISITED(args, v); + + if (!visited) { + ON_ADD_SUCC(current, v, idx, args); + + parent = current; // Explore successor v with parent + current = v; + idx = PREORDER_IDX; // Initial index to indicate v is being visited + + goto rec; + + } else { + bool backtracked = + (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) + && (IS_BACKTRACKED(args, v)); + ANCESTOR_CROSS(current, v, backtracked, args); + + idx += 1; // Skip this sucessor of + // since it has already been visited + goto rec; + } } } - Obj FuncExecuteDFS_C(Obj self, Obj args) { DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); Obj record = ELM_PLIST(args, 1); diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 99b10256c..a90854b56 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3351,26 +3351,26 @@ gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); # Stopping ExecuteDFS -gap> gr := CompleteDigraph(1000);; +gap> gr := CompleteDigraph(10000);; gap> record := NewDFSRecord(gr);; gap> data := rec(count := 0);; gap> PreorderFunc := function(record, data) > data.count := data.count + 1; -> if record.current = 500 then +> if record.current = 5000 then > record.stop := true; > fi; > end;; gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, > fail); gap> data.count; -500 +5000 gap> record_iter := NewDFSRecord(gr);; gap> record_iter.config.iterative := true;; gap> data := rec(count := 0);; gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, > fail); gap> data.count; -500 +5000 gap> data := ["postorder", "preorder"];; gap> ForAll(data, x -> record_iter.(x) = record.(x)); true @@ -3382,7 +3382,7 @@ gap> data := rec(count := 0);; gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, > fail); gap> data.count; -500 +5000 # Checking results are consistent for different options - BinaryTree(5) gap> gr := BinaryTree(5);; From aa8b88a63e4b2aab5b03a26620182b3a34e3ab77 Mon Sep 17 00:00:00 2001 From: Saffron Date: Sun, 8 Jun 2025 19:59:40 +0100 Subject: [PATCH 51/54] Fix spelling mistake --- src/dfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dfs.c b/src/dfs.c index bf2d7b6ea..c4153e0de 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -372,7 +372,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { && (IS_BACKTRACKED(args, v)); ANCESTOR_CROSS(current, v, backtracked, args); - idx += 1; // Skip this sucessor of + idx += 1; // Skip this successor of // since it has already been visited goto rec; } From c801686861ca412dcc010263bbec5d6698f93b18 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 2 Sep 2025 20:17:03 +0100 Subject: [PATCH 52/54] Add tests, add additional error handling, add comments, correct ArticulationPoints to use AddSet --- doc/attr.xml | 6 +- doc/oper.xml | 32 ++-- doc/z-chap4.xml | 4 +- gap/attr.gi | 19 +- gap/oper.gd | 4 +- gap/oper.gi | 89 ++++----- gap/prop.gi | 4 +- src/dfs.c | 436 +++++++++++++++++++++++++----------------- tst/standard/attr.tst | 8 +- tst/standard/oper.tst | 115 ++++++----- 10 files changed, 407 insertions(+), 310 deletions(-) diff --git a/doc/attr.xml b/doc/attr.xml index bb66a0b95..182934c9c 100644 --- a/doc/attr.xml +++ b/doc/attr.xml @@ -1142,15 +1142,15 @@ gap> ArticulationPoints(CycleDigraph(5)); [ ] gap> D := Digraph([[2, 7], [3, 5], [4], [2], [6], [1], []]);; gap> ArticulationPoints(D); -[ 2, 1 ] +[ 1, 2 ] gap> ArticulationPoints(ChainDigraph(5)); -[ 4, 3, 2 ] +[ 2, 3, 4 ] gap> ArticulationPoints(NullDigraph(5)); [ ] gap> D := ChainDigraph(IsMutableDigraph, 4); gap> ArticulationPoints(D); -[ 3, 2 ] +[ 2, 3 ] ]]> diff --git a/doc/oper.xml b/doc/oper.xml index 63d8abfc7..320078c62 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -2559,15 +2559,15 @@ true]]> Initial: false config - A configuration for DFS as defined using - or . This field should be set + A configuration for DFS as defined using + or . This field should be set by calling NewDFSRecord(graph, config) - where config was initially generated by - or .

+ where config was initially generated by + or .

Default when defined using with no config argument: A record as returned by - . + . @@ -2598,9 +2598,9 @@ gap> record.current; gap> record.graph; gap> d := CompleteDigraph(20);; -gap> flags := NewDFSFlagsLightweight();; +gap> flags := NewDFSConfigLightweight();; gap> record := NewDFSRecord(d, flags);; -gap> flags := NewDFSFlags();; +gap> flags := NewDFSConfig();; gap> flags.iterative := true;; gap> record := NewDFSRecord(d, flags);; gap> record.config.iterative; @@ -2610,9 +2610,9 @@ true <#/GAPDoc> -<#GAPDoc Label="NewDFSFlags"> +<#GAPDoc Label="NewDFSConfig"> - + A record. This function returns a DFSFlags record to be @@ -2624,9 +2624,9 @@ true example, record.config.use_postorder tells not to create the record.postorder field.

- The fields for both NewDFSFlags and + The fields for both NewDFSConfig and are described as follows, assuming config was returned - by or , + by or , and NewDFSRecord(d, config) is called for a digraph d, returning record:

@@ -2715,7 +2715,7 @@ Oper="ExecuteDFS"/> flags := NewDFSFlags();; +gap> flags := NewDFSConfig();; gap> flags; rec( forest := false, forest_specific := fail, iterative := false, revisit := false, use_edge := true, use_parents := true, @@ -2736,18 +2736,18 @@ fail <#/GAPDoc> -<#GAPDoc Label="NewDFSFlagsLightweight"> +<#GAPDoc Label="NewDFSConfigLightweight"> - + A record. This function returns a DFSFlags record to be used as the config field of a DFSRecord (see ). It - differs to the default config returned by see + differs to the default config returned by see since all of use_preorder, use_postorder, use_parents and use_edge are set to false. flags := NewDFSFlagsLightweight();; +gap> flags := NewDFSConfigLightweight();; gap> flags.use_preorder; false gap> flags.use_postorder; diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index c86cdce3f..1730e536e 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -21,8 +21,8 @@ <#Include Label="DigraphMaximalMatching"> <#Include Label="DigraphMaximumMatching"> <#Include Label="NewDFSRecord"> - <#Include Label="NewDFSFlags"> - <#Include Label="NewDFSFlagsLightweight"> + <#Include Label="NewDFSConfig"> + <#Include Label="NewDFSConfigLightweight"> <#Include Label="ExecuteDFS">

diff --git a/gap/attr.gi b/gap/attr.gi index 24523a6af..bca8efca7 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -50,20 +50,21 @@ function(D) copy := D; fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.use_preorder := true; flags.use_edge := true; flags.use_parents := true; PostOrderFunc := function(record, data) local child, current; + # Backtracking on edge (current -> child) child := record.child; current := record.parents[child]; # record.preorder[child] > record.preorder[current] then - if current <> child then - # stops the duplication of articulation_points + + if current <> child then # If not at the root node if current <> 1 and data.low[child] >= record.preorder[current] then - Add(data.articulation_points, current); + AddSet(data.articulation_points, current); fi; if data.low[child] = record.preorder[child] then Add(data.bridges, [current, child]); @@ -128,7 +129,7 @@ function(D) if data.counter = DigraphNrVertices(D) then connected := true; if data.nr_children > 1 then - Add(data.articulation_points, 1); + AddSet(data.articulation_points, 1); fi; if not IsEmpty(data.bridges) then data.orientation := fail; @@ -1073,7 +1074,7 @@ function(D) return []; fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.use_parents := true; flags.use_edge := true; @@ -3048,15 +3049,17 @@ function(D) data := List(DigraphVertices(C), x -> []); PreOrderFunc := function(record, data) + # If this is not the root if record.parents[record.current] <> record.current then Add(data[record.parents[record.current]], record.current); Add(data[record.current], record.parents[record.current]); fi; end; - conf := NewDFSFlagsLightweight(); + conf := NewDFSConfigLightweight(); conf.use_parents := true; - conf.iterative := true; + conf.use_edge := true; + conf.iterative := false; conf.forest := true; record := NewDFSRecord(C, conf); diff --git a/gap/oper.gd b/gap/oper.gd index 1b64acbd9..10a594924 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -158,6 +158,6 @@ DeclareOperation("PartialOrderDigraphMeetOfVertices", # 11. DFS DeclareOperation("NewDFSRecord", [IsDigraph]); DeclareOperation("NewDFSRecord", [IsDigraph, IsRecord]); -DeclareOperation("NewDFSFlags", []); -DeclareOperation("NewDFSFlagsLightweight", []); +DeclareOperation("NewDFSConfig", []); +DeclareOperation("NewDFSConfigLightweight", []); DeclareGlobalFunction("ExecuteDFS"); diff --git a/gap/oper.gi b/gap/oper.gi index 08dab8f1c..bcaf533cc 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1687,7 +1687,7 @@ function(D, u, v) return fail; fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.use_edge := true; flags.use_parents := true; @@ -2043,7 +2043,7 @@ function(D, v) "argument ,"); fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.iterative := true; # revisit DFS must be iterative flags.use_parents := true; flags.revisit := true; # If found another edge to an already @@ -2368,7 +2368,7 @@ function(D, root) "argument (a digraph)"); fi; - conf := NewDFSFlagsLightweight(); + conf := NewDFSConfigLightweight(); conf.use_edge := true; conf.use_parents := true; @@ -2430,7 +2430,7 @@ function(D, roots) data.result[record.child] := true; end; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.use_edge := true; flags.use_parents := true; @@ -2492,7 +2492,7 @@ function(D, root) Add(data, record.current); end; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.use_preorder := true; flags.use_parents := true; flags.use_edge := true; @@ -2828,7 +2828,7 @@ DIGRAPHS_DFSError := function() "NewDFSRecord,"); end; -DIGRAPHS_DFSFlagsBoolErr := function(flags, field) +DIGRAPHS_DFSFlagsBoolCheck := function(flags, field) if not IsBool(flags.(field)) then ErrorNoReturn("the 2nd argument (a record) should have a Bool ", "value for field ,", field); @@ -2841,23 +2841,16 @@ DIGRAPHS_DFS_CheckFlags := function(flags, graph) for bool_flag in ["iterative", "forest", "revisit", "use_parents", "use_edge", "use_postorder", "use_preorder"] do - DIGRAPHS_DFSFlagsBoolErr(flags, bool_flag); + DIGRAPHS_DFSFlagsBoolCheck(flags, bool_flag); od; - if (flags.forest_specific <> fail) and - (not (IsDenseList(flags.forest_specific) and - (IsEmpty(flags.forest_specific) - or IsPosInt(flags.forest_specific[1])))) then - ErrorNoReturn("the 2nd argument (a record) should have a Bool ", - "value for field ."); - fi; - if flags.forest_specific <> fail and - (ForAny(flags.forest_specific, n -> n < 1 - or n > DigraphNrVertices(graph))) then - ErrorNoReturn("the 2nd argument (a record) has elements in ", - ".forest_specific that are not vertices in the ", - "second argument ."); + (ForAny(flags.forest_specific, n -> (not IsPosInt(n)) or n < 0 + or n > DigraphNrVertices(graph)) or + (not IsDenseList(flags.forest_specific))) then + ErrorNoReturn("the 2nd argument (a record) must have a value for ", + ".forest_specific that is either fail or a dense ", + "list of vertices from the second argument ."); fi; end; @@ -2867,12 +2860,12 @@ DIGRAPHS_ExecuteDFSCheck := function(record) record_names := DIGRAPHS_DFSRecNames(); config_names := DIGRAPHS_DFSFlagNames(); - if not IsBound(record.config) then + if not IsRecord(record) then DIGRAPHS_DFSError(); - elif ForAny(config_names, n -> not IsBound(record.config.(n))) then - DIGRAPHS_DFSError(); elif ForAny(record_names, n -> not IsBound(record.(n))) then DIGRAPHS_DFSError(); + elif ForAny(config_names, n -> not IsBound(record.config.(n))) then + DIGRAPHS_DFSError(); elif record.config.forest_specific <> fail and not IsDenseList(record.config.forest_specific) then ErrorNoReturn("the 1st argument has a value of", @@ -2886,12 +2879,12 @@ end; InstallMethod(NewDFSRecord, "for a digraph", [IsDigraph], -Graph -> NewDFSRecord(Graph, NewDFSFlags())); +Graph -> NewDFSRecord(Graph, NewDFSConfig())); InstallMethod(NewDFSRecord, "for a digraph and a record", [IsDigraph, IsRecord], function(graph, conf) - local record, config_names, N; + local record, config_names, N, use_var, list_name, var_pair; N := DigraphNrVertices(graph); @@ -2910,32 +2903,30 @@ function(graph, conf) record.current := -1; record.stop := false; - if conf.use_preorder then - record.preorder := ListWithIdenticalEntries(N, -1); - else - record.preorder := fail; - fi; - if conf.use_parents then - record.parents := ListWithIdenticalEntries(N, -1); - else - record.parents := fail; - fi; - if conf.use_postorder then - record.postorder := ListWithIdenticalEntries(N, -1); - else - record.postorder := fail; - fi; - if conf.use_edge then - record.edge := ListWithIdenticalEntries(N, -1); - else - record.edge := fail; - fi; + for var_pair in [[conf.use_preorder, "preorder"], + [conf.use_postorder, "postorder"], + [conf.use_parents, "parents"], + [conf.use_edge, "edge"]] do + use_var := var_pair[1]; + list_name := var_pair[2]; + + if use_var then + record.(list_name) := ListWithIdenticalEntries(N, -1); + else + record.(list_name) := fail; + fi; + + continue; + od; record.config := conf; return record; end); -InstallMethod(NewDFSFlags, +# Default Configuration, recursive with all data provided in the record +# (preorder, postorder, parents, edges) + +InstallMethod(NewDFSConfig, "", [], function() local config; @@ -2951,11 +2942,13 @@ function() return config; end); -InstallMethod(NewDFSFlagsLightweight, +# Minimal Memory DFS Configuration (.config value) + +InstallMethod(NewDFSConfigLightweight, "", [], function() local config; - config := NewDFSFlags(); + config := NewDFSConfig(); config.iterative := false; config.use_postorder := false; config.use_preorder := false; diff --git a/gap/prop.gi b/gap/prop.gi index b97fd2125..00c4c7de5 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -245,7 +245,7 @@ function(D) return true; fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.iterative := true; record := NewDFSRecord(D, flags); @@ -394,7 +394,7 @@ function(D) return true; fi; - flags := NewDFSFlagsLightweight(); + flags := NewDFSConfigLightweight(); flags.iterative := true; record := NewDFSRecord(D, flags); diff --git a/src/dfs.c b/src/dfs.c index c4153e0de..ac62bb614 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -1,4 +1,4 @@ -/******************************************************************************* +/* ** *A dfs.c GAP package Digraphs Lea Racine ** James Mitchell @@ -10,17 +10,27 @@ ** *******************************************************************************/ -#include "dfs.h" - #include // for uint64_t #include // for NULL, free + +#include "dfs.h" #include "digraphs-debug.h" #include "safemalloc.h" // for safe_malloc Int DigraphNrVertices(Obj); Obj FuncOutNeighbours(Obj, Obj); -// Macros used for both recursive and iterative +// Iterative stack helpers + +#define STACK_PUSH(stack, size, val) AssPlist(stack, ++size, val) + +#define STACK_POP(stack, size) ELM_PLIST(stack, size--) + +// The index recursive DFS starts with (indicating to visit the current node) +#define PREORDER_IDX 0 + +// Call either PreorderFunc, PostorderFunc, CrossFunc or AncestorFunc, +// then return if the stop element was set #define CALL_CHECK_STOP(f, RNamStop, record, data) \ CALL_2ARGS(f, record, data); \ @@ -29,7 +39,15 @@ Obj FuncOutNeighbours(Obj, Obj); return false; \ } -#define GET_PREORDER(args, idx) \ +/* --- Macros for getting / setting partial / full lists in dfs_args struct + depending on dfs_args.conf + + e.g. use dfs_args.preorder if dfs_args.dfs_conf.use_preorder, otherwise + use dfs_args.partial_preorder --- */ + +// Preorder / Partial Preorder getters and setters + +#define GET_PREORDER(args, idx) \ args->dfs_conf->use_preorder ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) \ : args->preorder_partial[idx] @@ -47,14 +65,16 @@ Obj FuncOutNeighbours(Obj, Obj); args->preorder_partial[idx] = false; \ } -#define IS_VISITED(args, idx) \ - args->dfs_conf->use_preorder \ - ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) != -1 \ +#define IS_VISITED(args, idx) \ + args->dfs_conf->use_preorder \ + ? INT_INTOBJ(ELM_LIST(args->preorder, idx)) != -1 \ : args->preorder_partial[idx] +// --- Postorder / Partial Postorder getters and setters --- + #define GET_POSTORDER(args, idx) \ args->dfs_conf->use_postorder ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) \ - : args->postorder_partial[idx] + : args->postorder_partial[idx] #define SET_POSTORDER(args, idx) \ if (args->dfs_conf->use_postorder) { \ @@ -63,8 +83,8 @@ Obj FuncOutNeighbours(Obj, Obj); args->postorder_partial[idx] = true; \ } -#define IS_BACKTRACKED(args, idx) \ - args->dfs_conf->use_postorder \ +#define IS_BACKTRACKED(args, idx) \ + args->dfs_conf->use_postorder \ ? INT_INTOBJ(ELM_LIST(args->postorder, idx)) != -1 \ : args->postorder_partial[idx] @@ -77,16 +97,34 @@ Obj FuncOutNeighbours(Obj, Obj); args->PreorderFunc, args->RNamStop, args->record, args->data) \ } -#define ANCESTOR_CROSS(current, v, backtracked, args) \ +/* --- Macros for back edge, cross edge, successor adding, and backtracking + used by ExecuteDFSIter and ExecuteDFSRec --- */ + + +/* Back Edge / Cross Edges + + v is a visited node. The edge (current -> v) may be: + - a back edge (v is an ancestor of current) - if v has not been backtracked on + - a cross edge (v is neither an ancestor or a descendant of current) - if + v has been backtracked on and v was visited before current + + - Otherwise, a forward edge (v is a descendant of current) - Unused case + + + Set the child and current record elements for use in CrossFunc / AncestorFunc, + and call the appropriate function if (current -> v) is a back or cross edge +*/ + +#define ANCESTOR_CROSS(current, v, v_backtracked, args) \ if (args->CallAncestor || args->CallCross) { \ AssPRec(args->record, args->RNamChild, INTOBJ_INT(v)); \ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(current)); \ CHANGED_BAG(args->record); \ - if (args->CallAncestor && !backtracked) { \ + if (args->CallAncestor && !v_backtracked) { \ CALL_CHECK_STOP( \ args->AncestorFunc, args->RNamStop, args->record, args->data) \ } else if (args->CallCross \ - && (backtracked \ + && (v_backtracked \ && ((GET_PREORDER(args, v)) \ < (GET_PREORDER(args, current))))) { \ CALL_CHECK_STOP( \ @@ -95,13 +133,19 @@ Obj FuncOutNeighbours(Obj, Obj); CHANGED_BAG(args->record); \ } +/* Backtracking edge (parent -> current) + + Set a node as backtracked, then call the PostorderFunc if one exists + setting the current and child elements of the DFS record +*/ + #define ON_BACKTRACK(current, parent, args) \ if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) { \ SET_POSTORDER(args, current); \ CHANGED_BAG(args->record); \ } \ if (args->CallPostorder) { \ - /* When CallPostorder is true, use_parents must be true, parent is valid \ + /* When CallPostorder is true, use_parents must be true, parent is valid */ \ AssPRec(args->record, args->RNamChild, INTOBJ_INT(current)); \ AssPRec(args->record, args->RNamCurrent, INTOBJ_INT(parent)); \ @@ -111,6 +155,13 @@ Obj FuncOutNeighbours(Obj, Obj); CHANGED_BAG(args->record); \ } +/* Adding a successor succ of current. idx is the index + of succ in OutNeighbours[current] + + For iterative: When pushing succ to the stack + For recursive: Before recursing on succ +*/ + #define ON_ADD_SUCC(current, succ, idx, args) \ if (args->dfs_conf->use_parents) { \ AssPlist(args->parents, succ, INTOBJ_INT(current)); \ @@ -120,109 +171,109 @@ Obj FuncOutNeighbours(Obj, Obj); } \ CHANGED_BAG(args->record); -#define STACK_PUSH(stack, size, val) \ - AssPlist(stack, ++size, val) - -#define STACK_POP(stack, size) \ - ELM_PLIST(stack, size--) - -#define PREORDER_IDX 0 // The index recursive DFS starts with (indicating to - // visit the current node) - -#define RECURSE_FOREST(dfs_args, v) \ - bool visited = IS_VISITED(dfs_args, v); \ - if (!visited) { \ - if (dfs_args -> dfs_conf -> use_parents) { \ - AssPlist(dfs_args->parents, v, INTOBJ_INT(v)); \ - CHANGED_BAG(record); \ - } \ +#define RECURSE_FOREST(dfs_args, v) \ + bool visited = IS_VISITED(dfs_args, v); \ + if (!visited) { \ + if (dfs_args->dfs_conf->use_parents) { \ + AssPlist(dfs_args->parents, v, INTOBJ_INT(v)); \ + CHANGED_BAG(record); \ + } \ if (!ExecuteDFSRec(v, v, PREORDER_IDX, dfs_args)) { \ - CHANGED_BAG(record); \ + CHANGED_BAG(record); \ recordCleanup(dfs_args); \ - return record; \ - } \ + return record; \ + } \ } -#define ITER_FOREST(args, stack, v) \ - bool visited = IS_VISITED(args, v); \ - \ - if (!visited) { \ - if (args->dfs_conf->use_parents) { \ - AssPlist(args->parents, v, INTOBJ_INT(v)); \ - } \ - CHANGED_BAG(args->record); \ - AssPlist(stack, 1, INTOBJ_INT(v)); \ - \ - if (!iter_loop(stack, 1, args)) \ - return false; \ +#define ITER_FOREST(args, stack, v) \ + bool visited = IS_VISITED(args, v); \ + \ + if (!visited) { \ + if (args->dfs_conf->use_parents) { \ + AssPlist(args->parents, v, INTOBJ_INT(v)); \ + } \ + CHANGED_BAG(args->record); \ + AssPlist(stack, 1, INTOBJ_INT(v)); \ + \ + if (!iter_loop(stack, 1, args)) \ + return false; \ } void recordCleanup(struct dfs_args* args) { - struct dfs_config* dfs_conf = args -> dfs_conf; + struct dfs_config* dfs_conf = args->dfs_conf; - if (!dfs_conf -> use_preorder) { - free(args -> preorder_partial); + if (!dfs_conf->use_preorder) { + free(args->preorder_partial); } - if (dfs_conf -> partial_postorder) { - free(args -> postorder_partial); + if (dfs_conf->partial_postorder) { + free(args->postorder_partial); } } void parseConfig(struct dfs_args* args, Obj conf_record) { - struct dfs_config* conf = args -> dfs_conf; - conf -> iter = ElmPRec(conf_record, RNamName("iterative")) == True; - conf -> revisit = ElmPRec(conf_record, RNamName("revisit")) == True; - conf -> forest = ElmPRec(conf_record, RNamName("forest")) == True; - conf -> use_preorder = - ElmPRec(conf_record, RNamName("use_preorder")) == True; - conf -> use_postorder = - ElmPRec(conf_record, RNamName("use_postorder")) == True; - conf -> use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; - conf -> use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; - conf -> forest_specific = ElmPRec(conf_record, RNamName("forest_specific")); - conf -> partial_postorder = false; - - if (!conf -> iter && (!conf -> use_edge || !conf -> use_parents)) { + struct dfs_config* conf = args->dfs_conf; + conf->iter = ElmPRec(conf_record, RNamName("iterative")) == True; + conf->revisit = ElmPRec(conf_record, RNamName("revisit")) == True; + conf->forest = ElmPRec(conf_record, RNamName("forest")) == True; + conf->use_preorder = ElmPRec(conf_record, RNamName("use_preorder")) == True; + conf->use_postorder = ElmPRec(conf_record, RNamName("use_postorder")) == True; + conf->use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; + conf->use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; + conf->forest_specific = ElmPRec(conf_record, RNamName("forest_specific")); + conf->partial_postorder = false; + + if (!conf->iter && (!conf->use_edge || !conf->use_parents)) { ErrorQuit( "In a DFSRecord where the config flag iterative is false, use_edge and " - "use_parents must be true", 0L, 0L); + "use_parents must be true", + 0L, + 0L); } - if (conf -> revisit && !conf -> iter) { - ErrorQuit( - "In a DFSRecord where the config flag revisit is true, iterative " - "must also be true", 0L, 0L); + if (conf->revisit && !conf->iter) { + ErrorQuit("In a DFSRecord where the config flag revisit is true, iterative " + "must also be true", + 0L, + 0L); } - if ((args -> CallAncestor || args -> CallCross || conf -> revisit) && - !conf -> use_postorder) { - conf -> partial_postorder = true; + if ((args->CallAncestor || args->CallCross || conf->revisit) + && !conf->use_postorder) { + // If AncestorFunc or CrossFunc are called, we need to be able to + // detect whether a node has been backtracked + // likewise for being able to revisit nodes without infinite traversal + + conf->partial_postorder = true; } - if ((args -> CallPostorder && conf -> iter) && !conf -> use_parents) { + if ((args->CallPostorder && conf->iter) && !conf->use_parents) { // use_parents is always required for recursive (only check for iter) ErrorQuit( - "In a DFSRecord where a PostorderFunc exists, where the config flag " - "iter is true, the flag use_parents must also be true", 0L, 0L); + "In a DFSRecord where a PostorderFunc exists and the config flag " + "iter is true, the flag use_parents must also be true", + 0L, + 0L); } - if ((args -> CallPostorder && conf -> iter) && !conf -> use_postorder) { - conf -> partial_postorder = true; + if ((args->CallPostorder && conf->iter) && !conf->use_postorder) { + conf->partial_postorder = true; } - if (conf -> forest_specific != Fail && conf -> forest) { + if (conf->forest_specific != Fail && conf->forest) { ErrorQuit( "In a DFSRecord where the config flag forest_specific is not fail, " - "forest cannot also be true", 0L, 0L); + "forest cannot also be true", + 0L, + 0L); } - if (args -> CallCross && (!conf -> use_preorder)) { - ErrorQuit( - "In a DFSRecord where there is a CrossFunc, the config flag " - "use_preorder must be true", 0L, 0L); + if (args->CallCross && (!conf->use_preorder)) { + ErrorQuit("In a DFSRecord where there is a CrossFunc, the config flag " + "use_preorder must be true", + 0L, + 0L); } - } // Extreme examples are on the pull request #459 @@ -237,80 +288,85 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { */ bool ExecuteDFSIter(Int start, struct dfs_args* args) { - Int N = LEN_LIST(args -> neighbors); - Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); + Int N = LEN_LIST(args->neighbors); + Obj stack = NEW_PLIST(T_PLIST_CYC, N * 2); - Int stack_size = 1; + Int stack_size = 1; - AssPlist(stack, 1, INTOBJ_INT(start)); + AssPlist(stack, 1, INTOBJ_INT(start)); - if (!iter_loop(stack, stack_size, args)) return false; + if (!iter_loop(stack, stack_size, args)) + return false; - if (args -> dfs_conf -> forest) { - for (Int i = 1; i <= LEN_LIST(args -> neighbors); i++) { - ITER_FOREST(args, stack, i); - } - } else if (args -> dfs_conf -> forest_specific != Fail) { - Int len = LEN_LIST(args -> dfs_conf -> forest_specific); - for (Int i = 1; i <= len; i++) { - ITER_FOREST(args, stack, i); - } + if (args->dfs_conf->forest) { + for (Int i = 1; i <= LEN_LIST(args->neighbors); i++) { + ITER_FOREST(args, stack, i); + } + } else if (args->dfs_conf->forest_specific != Fail) { + Int len = LEN_LIST(args->dfs_conf->forest_specific); + for (Int i = 1; i <= len; i++) { + ITER_FOREST(args, stack, i); } - return true; + } + return true; } /* Main loop for iterative DFS called by ITER_FOREST and ExecuteDFSIter. ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function - if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc, - CrossFunc, or AncestorFunc. + if the record.stop attribute is set during a called PreOrderFunc, + PostOrderFunc, CrossFunc, or AncestorFunc. */ + bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { - while (stack_size > 0) { - Int current = INT_INTOBJ(STACK_POP(stack, stack_size)); - - if (current < 0) { - Int bt_on = current * -1; - Int parent = !args -> dfs_conf -> use_parents ? -1 : - INT_INTOBJ(ELM_LIST(args -> parents, bt_on)); - ON_BACKTRACK(bt_on, parent, args); - continue; - } else if (IS_VISITED(args, current)) { - continue; - } + while (stack_size > 0) { + Int current = INT_INTOBJ(STACK_POP(stack, stack_size)); + + if (current < 0) { // Backtrack node + Int bt_on = current * -1; + Int parent = !args->dfs_conf->use_parents + ? -1 + : INT_INTOBJ(ELM_LIST(args->parents, bt_on)); + ON_BACKTRACK(bt_on, parent, args); + continue; + } else if (IS_VISITED(args, current)) { + continue; + } - ON_PREORDER(current, args); // and push backtrack node - if (args -> dfs_conf -> use_postorder - || args -> dfs_conf -> partial_postorder - || args -> CallPostorder) { - STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); - } + ON_PREORDER(current, args); - Obj succ = ELM_LIST(args -> neighbors, current); - - for (Int i = LEN_LIST(succ); i > 0; i--) { - Int v = INT_INTOBJ(ELM_LIST(succ, i)); - - bool backtracked = (args -> dfs_conf -> use_postorder - || args -> dfs_conf -> partial_postorder) && - (IS_BACKTRACKED(args, v)); - bool revisit = (args -> dfs_conf -> revisit && backtracked); - if (revisit) { - UNSET_PREORDER(args, v); - CHANGED_BAG(args->record); - } - bool visited = IS_VISITED(args, v); - - if (!visited) { - ON_ADD_SUCC(current, v, i, args); - STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); - } else { - ANCESTOR_CROSS(current, v, backtracked, args); - } - } + if (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder + || args->CallPostorder) { // push backtrack node if needed + STACK_PUSH(stack, stack_size, INTOBJ_INT(current * -1)); } - return true; + + Obj succ = ELM_LIST(args->neighbors, current); + + // idx 1 at the top of the stack, + // (visit order consistent with recursive dfs) + for (Int i = LEN_LIST(succ); i > 0; i--) { + Int v = INT_INTOBJ(ELM_LIST(succ, i)); + + bool v_backtracked = + (args->dfs_conf->use_postorder || args->dfs_conf->partial_postorder) + && (IS_BACKTRACKED(args, v)); + bool revisit = (args->dfs_conf->revisit && v_backtracked); + if (revisit) { + UNSET_PREORDER(args, v); + CHANGED_BAG(args->record); + } + bool visited = IS_VISITED(args, v); + + if (!visited) { + ON_ADD_SUCC(current, v, i, args); + STACK_PUSH(stack, stack_size, INTOBJ_INT(v)); + } else { + ANCESTOR_CROSS(current, v, v_backtracked, args); + } + } + } + return true; } /* Recursive DFS @@ -318,23 +374,26 @@ bool iter_loop(Obj stack, Int stack_size, struct dfs_args* args) { goto used to force tail call optimization ON_PREORDER, ON_BACKTRACK and ANCESTOR_CROSS can return from this function - if the record.stop attribute is set during a called PreOrderFunc, PostOrderFunc, - CrossFunc, or AncestorFunc. + if the record.stop attribute is set during a called PreOrderFunc, + PostOrderFunc, CrossFunc, or AncestorFunc. */ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { -rec: +// "goto recurse" used to prevent stack overflows for deep trees regardless of +// optimisation level +recurse: if (idx == PREORDER_IDX) { // visit current ON_PREORDER(current, args); // Start recursing on successors of vertex , with parent idx += 1; - goto rec; + goto recurse; } - Obj succ = ELM_LIST(args->neighbors, current); + Obj successors = ELM_LIST(args->neighbors, current); - if (idx > LEN_LIST(succ)) { // Backtrack on current (all successors explored) + if (idx > LEN_LIST(successors)) { + // Backtrack on current (all successors explored) ON_BACKTRACK(current, parent, args); Int prev_idx = INT_INTOBJ(ELM_LIST(args->edge, current)); @@ -352,9 +411,10 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { idx = prev_idx + 1; // Index is the next successor to visit // continuing previous exploration of // 's successors - goto rec; + goto recurse; } else { - Int v = INT_INTOBJ(ELM_LIST(succ, idx)); + // Visit successor successors[idx] of current + Int v = INT_INTOBJ(ELM_LIST(successors, idx)); bool visited = IS_VISITED(args, v); if (!visited) { @@ -364,7 +424,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { current = v; idx = PREORDER_IDX; // Initial index to indicate v is being visited - goto rec; + goto recurse; } else { bool backtracked = @@ -374,7 +434,7 @@ bool ExecuteDFSRec(Int current, Int parent, Int idx, struct dfs_args* args) { idx += 1; // Skip this successor of // since it has already been visited - goto rec; + goto recurse; } } } @@ -390,48 +450,64 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Obj AncestorFunc = ELM_PLIST(args, 6); Obj CrossFunc = ELM_PLIST(args, 7); - DIGRAPHS_ASSERT(IS_PREC(record)); - DIGRAPHS_ASSERT(IS_INTOBJ(start)); + // Check if function args are fail or 2 argument functions +#define CHECK_FUNCTION(function) \ + if (!((IS_FUNC(function) && NARG_FUNC(function) == 2) \ + || function == Fail)) { \ + ErrorQuit("arguments 4-7 (PreorderFunc, PostorderFunc, AncestorFunc, " \ + "CrossFunc)" \ + "must be either a function taking arguments (record, data) or" \ + "Fail,", \ + 0L, \ + 0L); \ + } - PreorderFunc = !IS_FUNC(PreorderFunc) ? Fail : PreorderFunc; - PostorderFunc = !IS_FUNC(PostorderFunc) ? Fail : PostorderFunc; - AncestorFunc = !IS_FUNC(AncestorFunc) ? Fail : AncestorFunc; - CrossFunc = !IS_FUNC(CrossFunc) ? Fail : CrossFunc; + CHECK_FUNCTION(PreorderFunc); + CHECK_FUNCTION(PostorderFunc); + CHECK_FUNCTION(AncestorFunc); + CHECK_FUNCTION(CrossFunc); - Obj D = ElmPRec(record, RNamName("graph")); + Obj D = ElmPRec(record, RNamName("graph")); Obj outNeighbours = FuncOutNeighbours(self, D); - Int N = DigraphNrVertices(D); + Int N = DigraphNrVertices(D); - if (INT_INTOBJ(start) > N) { + if (!IS_INTOBJ(start) || INT_INTOBJ(start) > N || INT_INTOBJ(start) <= 0) { ErrorQuit( "the third argument must be a vertex in your graph,", 0L, 0L); } Int RNamStop = RNamName("stop"); - if (ElmPRec(record, RNamStop) == True) return record; + if (ElmPRec(record, RNamStop) == True) + return record; - Int preorder_num = 0; + Int preorder_num = 0; Int postorder_num = 0; struct dfs_config dfs_conf = {0}; struct dfs_args dfs_args_ = { - .dfs_conf = &dfs_conf, - .record = record, - .preorder_num = &preorder_num, - .postorder_num = &postorder_num, - .parents = ElmPRec(record, RNamName("parents")), - .postorder = ElmPRec(record, RNamName("postorder")), - .preorder = ElmPRec(record, RNamName("preorder")), - .edge = ElmPRec(record, RNamName("edge")), - .neighbors = outNeighbours, .data = data, - .PreorderFunc = PreorderFunc, .PostorderFunc = PostorderFunc, - .AncestorFunc = AncestorFunc, .CrossFunc = CrossFunc, - .RNamChild = RNamName("child"), .RNamCurrent = RNamName("current"), - .RNamStop = RNamStop, .CallPreorder = PreorderFunc != Fail, - .CallPostorder = PostorderFunc != Fail, - .CallAncestor = AncestorFunc != Fail, .CallCross = CrossFunc != Fail}; + .dfs_conf = &dfs_conf, + .record = record, + .preorder_num = &preorder_num, + .postorder_num = &postorder_num, + .parents = ElmPRec(record, RNamName("parents")), + .postorder = ElmPRec(record, RNamName("postorder")), + .preorder = ElmPRec(record, RNamName("preorder")), + .edge = ElmPRec(record, RNamName("edge")), + .neighbors = outNeighbours, + .data = data, + .PreorderFunc = PreorderFunc, + .PostorderFunc = PostorderFunc, + .AncestorFunc = AncestorFunc, + .CrossFunc = CrossFunc, + .RNamChild = RNamName("child"), + .RNamCurrent = RNamName("current"), + .RNamStop = RNamStop, + .CallPreorder = PreorderFunc != Fail, + .CallPostorder = PostorderFunc != Fail, + .CallAncestor = AncestorFunc != Fail, + .CallCross = CrossFunc != Fail}; parseConfig(&dfs_args_, config); @@ -453,7 +529,7 @@ Obj FuncExecuteDFS_C(Obj self, Obj args) { Int current = INT_INTOBJ(start); if (dfs_conf.iter || dfs_conf.revisit) { - ExecuteDFSIter(current, &dfs_args_); + ExecuteDFSIter(current, &dfs_args_); } else { if (dfs_conf.forest || (dfs_conf.forest_specific != Fail)) { // Initial DFS with specified start index diff --git a/tst/standard/attr.tst b/tst/standard/attr.tst index 53e3126d1..884a22fec 100644 --- a/tst/standard/attr.tst +++ b/tst/standard/attr.tst @@ -2044,11 +2044,11 @@ gap> StrongOrientation(DigraphSymmetricClosure(CycleDigraph(5))) > = CycleDigraph(5); true gap> ArticulationPoints(Digraph([[2, 7], [3, 5], [4], [2], [6], [1], []])); -[ 2, 1 ] +[ 1, 2 ] gap> StrongOrientation(Digraph([[2, 7], [3, 5], [4], [2], [6], [1], []])); Error, not yet implemented gap> ArticulationPoints(ChainDigraph(5)); -[ 4, 3, 2 ] +[ 2, 3, 4 ] gap> StrongOrientation(ChainDigraph(5)); Error, not yet implemented gap> ArticulationPoints(NullDigraph(5)); @@ -2098,7 +2098,7 @@ gap> ArticulationPoints(gr); gap> gr := Digraph([[2], [3], [], [3]]); gap> ArticulationPoints(gr); -[ 3, 2 ] +[ 2, 3 ] gap> IsConnectedDigraph(DigraphRemoveVertex(gr, 3)); false gap> IsConnectedDigraph(DigraphRemoveVertex(gr, 2)); @@ -2129,7 +2129,7 @@ gap> gr := DigraphFromSparse6String( > FIJONFQSplq]y@IwvbPKhMh}JGK?OLzW{agKKfRCtarqTGayQGb]rMIurapkxPG?RGcI]\ > IBtB_`EQKJ@LmxlL_?k^QieOkB|T"); -gap> Set(ArticulationPoints(gr)); +gap> ArticulationPoints(gr); [ 1, 3, 8, 11, 12, 15, 17, 18, 19, 21, 23, 27, 30, 36, 37, 41, 42, 46, 51, 52, 59, 60, 61, 63, 66, 68, 69, 73, 75, 76, 79, 84, 87 ] gap> IsDuplicateFree(last); diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index a90854b56..e901cc072 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3278,14 +3278,36 @@ gap> ExecuteDFS(record, [], 3, fail, fail, fail, > fail); Error, the 1st argument has a value of .config.forest_specifi\ c that is not fail or a dense list, - -# ExecuteDFS - Correctness -gap> record := NewDFSRecord(CompleteDigraph(10));; +gap> record := NewDFSRecord(d);; +gap> record.config.use_edge := false;; +gap> ExecuteDFS(record, [], 1, fail, fail, fail, +> fail);; +Error, In a DFSRecord where the config flag iterative is false, use_edge and u\ +se_parents must be true + +# ExecuteDFS - Correctness of standalone function +gap> gr := CompleteDigraph(10);; +gap> record := NewDFSRecord(gr);; +gap> record2 := NewDFSRecord(gr);; +gap> record2.config.iterative := true;; gap> ExecuteDFS(record, [], 2, fail, > fail, fail, fail); -gap> preorder_list := [];; +gap> ExecuteDFS(record2, [], 2, fail, +> fail, fail, fail); gap> record.preorder; [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ] +gap> record.postorder; +[ 9, 10, 8, 7, 6, 5, 4, 3, 2, 1 ] +gap> record.parents; +[ 2, 2, 1, 3, 4, 5, 6, 7, 8, 9 ] +gap> ForAll([record, record2], r -> +> ((r.edge = record.edge) and +> (r.parents = record.parents) and +> (r.preorder = record.preorder) and +> (r.postorder = record.postorder) and +> (IsDuplicateFree(r.postorder) and IsDuplicateFree(r.preorder))) +> ); +true gap> record := NewDFSRecord(CompleteDigraph(15));; gap> data := rec(cycle_vertex := 0);; gap> AncestorFunc := function(record, data) @@ -3349,60 +3371,62 @@ gap> gr := Digraph(List([1 .. 5], x -> [1 .. 5]));; gap> record := NewDFSRecord(gr);; gap> ExecuteDFS(record, data, 1, fail, fail, fail, > fail); +gap> gr := DigraphFromGraph6String("LCHK?p?O?c@`?_");; +gap> record := NewDFSRecord(gr);; +gap> PreorderFunc := function(record, data) +> data.visits := data.visits + 1; +> end;; +gap> PostorderFunc := function(record, data) +> data.backtracks := data.backtracks + 1; +> end;; +gap> data := rec(visits := 0, backtracks := 0);; +gap> ExecuteDFS(record, data, 1, PreorderFunc, PostorderFunc, fail, +> fail);; +gap> record.preorder; +[ 1, 7, 5, 2, 4, 6, 12, 3, 8, 9, 11, 10, 13 ] +gap> record.postorder; +[ 13, 4, 1, 12, 10, 9, 6, 11, 2, 3, 7, 8, 5 ] +gap> data.visits = 13; +true +gap> data.backtracks = 13; +true -# Stopping ExecuteDFS -gap> gr := CompleteDigraph(10000);; +# Stopping ExecuteDFS - Consistency Check across configurations +gap> gr := CompleteDigraph(10000);; # defaults, recursive gap> record := NewDFSRecord(gr);; -gap> data := rec(count := 0);; +gap> configs := [rec(forest := true), rec(forest_specific := DigraphVertices(gr)), +> rec(use_postorder := true), rec(use_preorder := true), rec(revisit := true, iterative := true)];; +gap> results := [];; gap> PreorderFunc := function(record, data) > data.count := data.count + 1; > if record.current = 5000 then > record.stop := true; > fi; > end;; -gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 -gap> record_iter := NewDFSRecord(gr);; -gap> record_iter.config.iterative := true;; -gap> data := rec(count := 0);; -gap> ExecuteDFS(record_iter, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 -gap> data := ["postorder", "preorder"];; -gap> ForAll(data, x -> record_iter.(x) = record.(x)); +> for conf in configs do +> data := rec(count := 0);; +> flags := NewDFSConfig();; +> for field in RecNames(conf) do +> flags.(field) := conf.(field); +> od; +> record := NewDFSRecord(gr, flags); +> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, fail); +> Add(results, data.count); +> od; +gap> ForAll(results, \x -> x = 5000); true -gap> record := NewDFSRecord(gr);; -gap> record.config.revisit := true;; -gap> record.config.iterative := true;; -gap> record.config.use_postorder := true;; -gap> data := rec(count := 0);; -gap> ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, -> fail); -gap> data.count; -5000 -# Checking results are consistent for different options - BinaryTree(5) +# BinaryTree(5) - ExecuteDFS Consistency check across configurations gap> gr := BinaryTree(5);; gap> record := NewDFSRecord(gr);; -gap> configs := [];; -gap> temp_conf := ShallowCopy(record.config);; # forest, recursive -gap> temp_conf.forest := true;; -gap> Add(configs, temp_conf);; -gap> temp_conf := ShallowCopy(record.config);; # forest_specific, recursive -gap> temp_conf.forest_specific := DigraphVertices(gr);; -gap> Add(configs, temp_conf);; -gap> temp_conf := ShallowCopy(configs[1]);; # forest, iterative -gap> temp_conf.iterative := true;; -gap> Add(configs, temp_conf);; -gap> temp_conf := ShallowCopy(configs[2]);; # forest_specific, iterative -gap> temp_conf.iterative := true;; -gap> Add(configs, temp_conf);; +gap> configs := [rec(forest := true), rec(forest_specific := DigraphVertices(gr)), +> rec(forest := true, iterative := true), rec(forest_specific := DigraphVertices(gr), iterative := true)];; gap> records := [];; gap> for conf in configs do -> record := NewDFSRecord(gr, conf);; +> record := NewDFSRecord(gr);; +> for field in RecNames(conf) do +> record.config.(field) := conf.(field); +> od; > ExecuteDFS(record, fail, 1, fail, fail, fail, fail);; > Add(records, record);; > od; @@ -3411,7 +3435,7 @@ gap> ForAll(records, r -> > (r.parents = records[1].parents) and > (r.preorder = records[1].preorder) and > (r.postorder = records[1].postorder) and -> (IsSet(r.postorder) and IsSet(r.preorder))) +> (IsDuplicateFree(r.postorder) and IsDuplicateFree(r.preorder))) > ); true @@ -3519,6 +3543,7 @@ gap> Unbind(PreorderFunc); gap> Unbind(AncestorFunc); gap> Unbind(CrossFunc); gap> Unbind(record); +gap> Unbind(record2); gap> Unbind(data); gap> Unbind(parents); gap> Unbind(edge); From b18a60e739270bd353c3afe99cf0c13dd64471f3 Mon Sep 17 00:00:00 2001 From: Saffron Date: Tue, 2 Sep 2025 20:57:20 +0100 Subject: [PATCH 53/54] Fix merge errors --- gap/attr.gi | 3 +-- src/dfs.c | 2 +- tst/standard/oper.tst | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gap/attr.gi b/gap/attr.gi index b426213de..227cf9540 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -129,11 +129,10 @@ function(D) if data.counter = DigraphNrVertices(D) then connected := true; if data.nr_children > 1 then - AddSet(data.articulation_points, 1); # 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); + AddSet(data.articulation_points, 1); fi; if not IsEmpty(data.bridges) then data.orientation := fail; diff --git a/src/dfs.c b/src/dfs.c index ac62bb614..a6f7a81b9 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -1,4 +1,4 @@ -/* +/******************************************************************************* ** *A dfs.c GAP package Digraphs Lea Racine ** James Mitchell diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 3159e3891..485268baa 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3410,7 +3410,7 @@ gap> data.backtracks = 13; true # Stopping ExecuteDFS - Consistency Check across configurations -gap> gr := CompleteDigraph(10000);; # defaults, recursive +gap> gr := CompleteDigraph(10000);; # defaults, recursive gap> record := NewDFSRecord(gr);; gap> configs := [rec(forest := true), rec(forest_specific := DigraphVertices(gr)), > rec(use_postorder := true), rec(use_preorder := true), rec(revisit := true, iterative := true)];; From cf1de237917ece8d031a5a99fcf7ce021edd1f8f Mon Sep 17 00:00:00 2001 From: Saffron Date: Wed, 10 Sep 2025 15:09:21 +0100 Subject: [PATCH 54/54] Make functions use recursive dfs when possible, correct documentation, move stack overflow test to extreme --- doc/oper.xml | 98 +++++++++++++++++++++---------------------- gap/oper.gi | 7 ++-- gap/prop.gi | 6 ++- src/dfs.c | 3 +- tst/extreme/oper.tst | 6 +++ tst/standard/oper.tst | 6 +-- 6 files changed, 67 insertions(+), 59 deletions(-) diff --git a/doc/oper.xml b/doc/oper.xml index 22736b6b0..126c9504f 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -2590,9 +2590,11 @@ true]]> postorder At each index, the postorder number (order in which the vertex is backtracked on) is stored. -

+ +

The record also stores a further 5 attributes: + current The current vertex that is being visited.

@@ -2633,6 +2635,7 @@ true]]> . +

When this function is called as NewDFSRecord(record, conf), this function returns a with the @@ -2678,16 +2681,17 @@ true A record. - This function returns a DFSFlags record to be + This function returns a DFSConfig record to be used as the config field of a DFSRecord. - called as NewDFSRecord(record, config) should be used to set + called + as NewDFSRecord(record, config) should be used to set config (as shown by the below example), since fields such as record.config.use_postorder change the behaviour of . For example, record.config.use_postorder tells not to create the record.postorder field.

- The fields for both NewDFSConfig and + The fields for both and are described as follows, assuming config was returned by or , and NewDFSRecord(d, config) is @@ -2697,32 +2701,28 @@ true use_preorder When config.use_preorder is true, record.preorder - is fail, and not used during the procedure - (when called with first argument record).

+ is fail, and not used during the procedure.

Default: true use_postorder When config.use_postorder is true, record.postorder - is fail, and not used during the procedure - (when called with first argument record).

+ is fail, and not used during the procedure.

Default: true use_parents When config.use_parents is true, record.parents - is fail, and not used during the procedure - (when called with first argument record).

+ is fail, and not used during the procedure.

Default: true use_edge When config.use_edge is true, record.edge - is fail, and not used during the procedure - (when called with first argument record).

+ is fail, and not used during the procedure.

Default: true @@ -2737,25 +2737,22 @@ true revisit - Allows nodes to be revited during if they + Allows vertices to be revisited during if they are encountered as a successor, and have been backtracked in the current DFS tree. When config.revisit - is true, config.iterative must also be true. - Requires config.iterative to be true.

+ is true, config.iterative must also be true.

Default: false forest - Ensures all nodes are visited during . . - is first ran with a start node of record.start. For each + is first ran with a start vertex of record.start. For each v in DigraphVertices(record.graph), - if v has not been visited in any DFS traversals yet (if - config.use_preorder then this is the case when - record.preorder[v] is -1), a - DFS traversal with a start node of v is called until - all nodes in DigraphVertices(record.graph) + if v has not been visited in any DFS traversals yet, a + DFS traversal with a start vertex of v is called until + all vertices in DigraphVertices(record.graph) have been visited (traversing all disconnected components). Requires config.forest_specific to be fail.

@@ -2770,7 +2767,7 @@ Oper="ExecuteDFS"/> This is achieved in the same way as config.forest but instead of ensuring all vertices from DigraphVertices(record.graph) are visited, the same behaviour exists for all vertices - from config.forest_specific (if it is not fail). + in config.forest_specific. Requires config.forest to be false.

Default: fail @@ -2804,11 +2801,10 @@ fail A record. - This function returns a DFSFlags record to be + This function returns a DFSConfig record to be used as the config field of a DFSRecord (see ). It differs to the default config returned by see - since all of use_preorder, use_postorder, use_parents and - use_edge are set to false. + since both config.use_preorder, config.use_postorder are set to false. flags := NewDFSConfigLightweight();; gap> flags.use_preorder; @@ -2816,23 +2812,21 @@ false gap> flags.use_postorder; false gap> flags.use_edge; -false +true gap> flags.use_parents; -false +true gap> flags.iterative := true; true gap> d := BinaryTree(2);; gap> record := NewDFSRecord(d, flags);; -gap> record.config.use_parents; -false gap> record.preorder; fail gap> record.postorder; fail gap> record.edge; -fail +[ -1, -1, -1 ] gap> record.parents; -fail +[ -1, -1, -1 ] ]]> @@ -2844,7 +2838,7 @@ fail Arg="record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc"/> 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 + 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: @@ -2855,33 +2849,38 @@ fail start The vertex where the depth first search begins. PreOrderFunc - This function is called when a vertex is first visited. This vertex - is stored in record.current. A vertex can be backtracked - more than once when record.config.revisit is true. + A function taking two arguments record, data. + This function is called when a vertex is first visited. This vertex + is stored in record.current. A vertex can be visited + more than once when record.config.revisit is true. PostOrderFunc - This function is called when a vertex has no more unvisited children - causing us to backtrack. This vertex is stored in record.child and its parent is stored - in record.current. A vertex can be visited - more than once when record.config.revisit is true.

+ A function taking two arguments record, data. + This function is called when a vertex has no more unvisited children + causing us to backtrack. This vertex is stored in record.child and its parent is stored + in record.current. A vertex can be backtracked + more than once when record.config.revisit is true.

When record.PostOrderFunc is not fail, then record.config.use_parents - must be true (recursive DFS requires record.config.use_parents - in general). + must be true. 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 + A function taking two arguments record, data. + This function is called when [record.current, + record.child] is an edge and record.child + is an ancestor of record.current. An ancestor here means that record.child is on the same branch as record.current but was visited prior to - record.current (record.current has been backtracked + record.current (record.child has not been backtracked on). Equivalently, [record.current, record.child] is a back edge. CrossFunc - This function is called when [record.current, - record.child] is an edge and record.child has been visited before record.current - and it is not an ancestor of record.current. Equivalently, [record.current, - record.child] is a cross edge.

+ A function taking two arguments record, data. + 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 (record.child has been backtracked). + Equivalently, [record.current, record.child] is a cross edge.

When record.CrossFunc is not fail, record.config.use_preorder must be true (to determine visit order). @@ -2893,7 +2892,6 @@ fail Note that this function only performs a depth first search on the vertices reachable from start unless record.config.forest is true or record.config.forest_specific is a list of vertices to visit. - It is also important to note that all functions passed need to accept arguments record and data. Finally, for the start vertex, its parent is itself and the PreOrderFunc will be called on it. This is also the case for the start vertex of any additional searches required when record.config.forest is true or diff --git a/gap/oper.gi b/gap/oper.gi index 07437bc2d..92565633f 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -2061,6 +2061,7 @@ function(D, v) flags := NewDFSConfigLightweight(); flags.iterative := true; # revisit DFS must be iterative flags.use_parents := true; + flags.use_edge := false; flags.revisit := true; # If found another edge to an already # visited and backtracked on node, # set to unvisited, and visit it @@ -2980,8 +2981,8 @@ function() config.iterative := false; config.use_postorder := false; config.use_preorder := false; - config.use_parents := false; - config.use_edge := false; + config.use_parents := true; # Must be true to use recursive dfs + config.use_edge := true; return config; end); @@ -3003,4 +3004,4 @@ function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, DIGRAPHS_ExecuteDFSCheck(record); ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, CrossFunc); -end); \ No newline at end of file +end); diff --git a/gap/prop.gi b/gap/prop.gi index b5f4d4676..760f1e682 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -251,7 +251,8 @@ function(D) fi; flags := NewDFSConfigLightweight(); - flags.iterative := true; + flags.use_edge := true; + flags.use_parents := true; record := NewDFSRecord(D, flags); @@ -400,7 +401,8 @@ function(D) fi; flags := NewDFSConfigLightweight(); - flags.iterative := true; + flags.use_edge := true; + flags.use_parents := true; record := NewDFSRecord(D, flags); record.config.forest := true; diff --git a/src/dfs.c b/src/dfs.c index a6f7a81b9..f80fbe03a 100644 --- a/src/dfs.c +++ b/src/dfs.c @@ -221,7 +221,8 @@ void parseConfig(struct dfs_args* args, Obj conf_record) { conf->use_parents = ElmPRec(conf_record, RNamName("use_parents")) == True; conf->use_edge = ElmPRec(conf_record, RNamName("use_edge")) == True; conf->forest_specific = ElmPRec(conf_record, RNamName("forest_specific")); - conf->partial_postorder = false; + conf->partial_postorder = false; // Updated when parsing the config (whether + // to use a bitset for backtracked vertices) if (!conf->iter && (!conf->use_edge || !conf->use_parents)) { ErrorQuit( diff --git a/tst/extreme/oper.tst b/tst/extreme/oper.tst index 82b9aeb31..d0e2cd930 100644 --- a/tst/extreme/oper.tst +++ b/tst/extreme/oper.tst @@ -173,6 +173,12 @@ gap> SortedList(DigraphLayers(gr, 6)[2]); gap> SortedList(DigraphLayers(gr, 1500)[2]); [ 81, 82, 169, 253, 254 ] +# ExecuteDFS recursive stack overflow check +gap> gr := CompleteDigraph(10000);; # defaults, recursive +gap> record := NewDFSRecord(gr);; +gap> record.config.iterative := false;; +gap> ExecuteDFS(record, fail, 1, fail, fail, fail, fail); + # DIGRAPHS_UnbindVariables gap> Unbind(d); gap> Unbind(gr); diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 485268baa..12464ec12 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -3410,14 +3410,14 @@ gap> data.backtracks = 13; true # Stopping ExecuteDFS - Consistency Check across configurations -gap> gr := CompleteDigraph(10000);; # defaults, recursive +gap> gr := CompleteDigraph(1000);; # defaults, recursive gap> record := NewDFSRecord(gr);; gap> configs := [rec(forest := true), rec(forest_specific := DigraphVertices(gr)), > rec(use_postorder := true), rec(use_preorder := true), rec(revisit := true, iterative := true)];; gap> results := [];; gap> PreorderFunc := function(record, data) > data.count := data.count + 1; -> if record.current = 5000 then +> if record.current = 500 then > record.stop := true; > fi; > end;; @@ -3431,7 +3431,7 @@ gap> PreorderFunc := function(record, data) > ExecuteDFS(record, data, 1, PreorderFunc, fail, fail, fail); > Add(results, data.count); > od; -gap> ForAll(results, \x -> x = 5000); +gap> ForAll(results, \x -> x = 500); true # BinaryTree(5) - ExecuteDFS Consistency check across configurations