diff --git a/algorithms/algorithms.go b/algorithms/algorithms.go index 7715f01..b35b4b1 100644 --- a/algorithms/algorithms.go +++ b/algorithms/algorithms.go @@ -1,6 +1,5 @@ // This package implements various algorithms to compute Generalized Hypertree Decompositions as well as -// the more restricted set of Hypertree Deocmpositions. - +// the more restricted set of Hypertree Decompositions. package algorithms import "github.com/cem-okulmus/BalancedGo/lib" diff --git a/algorithms/balsepGlobal.go b/algorithms/balsepGlobal.go index 00d87f8..f986fb9 100644 --- a/algorithms/balsepGlobal.go +++ b/algorithms/balsepGlobal.go @@ -1,7 +1,6 @@ package algorithms import ( - "log" "reflect" "runtime" @@ -93,7 +92,7 @@ func rerooting(H lib.Graph, balsep lib.Edges, subtrees []lib.Decomp) lib.Decomp for _, s := range subtrees { // fmt.Println("H ", H, "balsep ", balsep, "comp ", s.Graph) s.Root = s.Root.Reroot(rerootNode) - log.Printf("Rerooted Decomp: %v\n", s) + // log.Printf("Rerooted Decomp: %v\n", s) output.Children = append(output.Children, s.Root.Children...) } // log.Println("H: ", H, "output: ", output) diff --git a/algorithms/balsepHybrid.go b/algorithms/balsepHybrid.go index 4615862..090801b 100644 --- a/algorithms/balsepHybrid.go +++ b/algorithms/balsepHybrid.go @@ -111,13 +111,15 @@ func (b BalSepHybrid) findDecomp(currentDepth int, H lib.Graph) lib.Decomp { // Base case handling //stop if there are at most two special edges left - if comps[i].Len() <= 2 { + if comps[i].Len() <= 1 { + comps[i].Special = append(comps[i].Special, SepSpecial) ch <- baseCaseSmart(b.Graph, comps[i]) return } //Early termination - if comps[i].Edges.Len() <= b.K && len(comps[i].Special) == 1 { + if comps[i].Edges.Len() <= b.K && len(comps[i].Special) == 0 { + comps[i].Special = append(comps[i].Special, SepSpecial) ch <- earlyTermination(comps[i]) return } @@ -126,12 +128,12 @@ func (b BalSepHybrid) findDecomp(currentDepth int, H lib.Graph) lib.Decomp { det.cache.Init() result := det.findDecomp(comps[i], balsep.Vertices()) - if !reflect.DeepEqual(result, lib.Decomp{}) && currentDepth == 0 { + if !reflect.DeepEqual(result, lib.Decomp{}) { result.SkipRerooting = true } else { // comps[i].Special = append(comps[i].Special, SepSpecial) - // res2 := b.findDecompBalSep(1000, comps[i]) - // if !reflect.DeepEqual(res2, Decomp{}) { + // res2 := b.findDecomp(1000, comps[i]) + // if !reflect.DeepEqual(res2, lib.Decomp{}) { // fmt.Println("Result, ", res2) // fmt.Println("H: ", comps[i], "balsep ", balsep) // log.Panicln("Something is rotten in the state of this program") diff --git a/algorithms/detKDecomp.go b/algorithms/detKDecomp.go index bf1be55..a79af16 100644 --- a/algorithms/detKDecomp.go +++ b/algorithms/detKDecomp.go @@ -18,6 +18,8 @@ type DetKDecomp struct { // SetWidth sets the current width parameter of the algorithm func (d *DetKDecomp) SetWidth(K int) { + d.cache.Reset() // reset the cache as the new width might invalidate any old results + d.K = K } @@ -181,9 +183,9 @@ OUTER: d.cache.AddNegative(sepActual, comps[i]) // log.Printf("detK REJECTING %v: couldn't decompose %v \n", - // Graph{Edges: sepActual}, comps[i]) - // log.Printf("\n\nCurrent oldSep: %v\n", PrintVertices(oldSep)) - // log.Printf("Current SubGraph: %v ( %v edges)\n", H, H.Edges.Len(), H.Edges.Hash()) + // lib.Graph{Edges: sepActual}, comps[i]) + // log.Printf("\n\nCurrent oldSep: %v\n", lib.PrintVertices(oldSep)) + // log.Printf("Current SubGraph: %v ( %v edges) %v\n", H, H.Edges.Len(), H.Edges.Hash()) if d.SubEdge { if sepSub == nil { @@ -209,8 +211,8 @@ OUTER: } } } - // log.Printf("Sub Sep chosen: %vof %v \n", Graph{Edges: sepActual}, - // Graph{Edges: sepActualOrigin}) + // log.Printf("Sub Sep chosen: %vof %v \n", lib.Graph{Edges: sepActual}, + // lib.Graph{Edges: sepActualOrigin}) continue subEdges } diff --git a/algorithms/logKDecomp.go b/algorithms/logKDecomp.go index fd9876d..79d1855 100644 --- a/algorithms/logKDecomp.go +++ b/algorithms/logKDecomp.go @@ -27,6 +27,8 @@ type decompInt struct { // SetWidth sets the current width parameter of the algorithm func (l *LogKDecomp) SetWidth(K int) { + l.cache.Reset() // reset the cache as the new width might invalidate any old results + l.K = K } diff --git a/algorithms/logkHybrid.go b/algorithms/logkHybrid.go index 1ba04a0..c4144f6 100644 --- a/algorithms/logkHybrid.go +++ b/algorithms/logkHybrid.go @@ -90,6 +90,8 @@ func (l *LogKHybrid) ETimesKDivAvgEdgePred(H lib.Graph, K int) bool { // SetWidth sets the current width parameter of the algorithm func (l *LogKHybrid) SetWidth(K int) { + l.cache.Reset() // reset the cache as the new width might invalidate any old results + l.K = K } diff --git a/balanced.go b/balanced.go index 6d6a0b7..6c53414 100644 --- a/balanced.go +++ b/balanced.go @@ -12,7 +12,6 @@ // In addition to this, there is also a tool subdirectory in the repository which is intended to support functionality // not directly related to the computation of decompositions, such as changing the formatting of hypergraphs, or fixing // a faulty decomposition. - package main import ( @@ -412,7 +411,8 @@ func main() { if *globalBal { global := &algo.BalSepGlobal{ - K: *width, Graph: parsedGraph, + K: *width, + Graph: parsedGraph, BalFactor: BalFactor, } solver = global diff --git a/lib/base.go b/lib/base.go index 60cf3a8..e14dfc1 100644 --- a/lib/base.go +++ b/lib/base.go @@ -1,6 +1,5 @@ // This package provides various functions, data structures and methods to aid in the design of algorithms to -// compute structural decomposition methodds. - +// compute structural decomposition methods. package lib import ( diff --git a/lib/cache.go b/lib/cache.go index 879f9ca..98681df 100644 --- a/lib/cache.go +++ b/lib/cache.go @@ -30,6 +30,18 @@ func (c *Cache) CopyRef(other *Cache) { other.once.Do(func() {}) // if cache is copied, it's assumed to already be initialised, so once is pre-fired here } +// Reset will throw out all saved cache entries +func (c *Cache) Reset() { + if c.cacheMux == nil { + return // don't do anything if cache wasn't initialised yet + } + c.cacheMux.Lock() + defer c.cacheMux.Unlock() + + c.cache = make(map[uint64]*compCache) + +} + // Init needs to be called to initialise the cache func (c *Cache) Init() { c.once.Do(c.initFunction) @@ -45,8 +57,8 @@ func (c *Cache) initFunction() { // Len returns the number of bindings in the cache func (c *Cache) Len() int { - c.cacheMux.Lock() - defer c.cacheMux.Unlock() + c.cacheMux.RLock() + defer c.cacheMux.RUnlock() return len(c.cache) } diff --git a/lib/edge.go b/lib/edge.go index a0a897c..e267ea4 100644 --- a/lib/edge.go +++ b/lib/edge.go @@ -6,6 +6,7 @@ import ( "math" "reflect" "sort" + "strconv" "sync" ) @@ -295,3 +296,24 @@ func (e *Edges) Diff(other Edges) Edges { return NewEdges(output) } + +// FullString always prints the list of vertices of an edge, even if the edge is named +func (e Edge) FullStringInt() string { + var buffer bytes.Buffer + mutex.RLock() + defer mutex.RUnlock() + if e.Name > 0 { + buffer.WriteString("E" + strconv.Itoa(e.Name)) + } + buffer.WriteString(" (") + for i, n := range e.Vertices { + var s string + s = fmt.Sprintf("V%v", n) + buffer.WriteString(s) + if i != len(e.Vertices)-1 { + buffer.WriteString(", ") + } + } + buffer.WriteString(")") + return buffer.String() +} diff --git a/lib/graph.go b/lib/graph.go index d8937c7..2608a17 100644 --- a/lib/graph.go +++ b/lib/graph.go @@ -303,3 +303,18 @@ func (g Graph) GetBIP() int { return output } + +//ToHyperBench format transforms the graph structure to a string in HyperBench Format. This is only relevant for generated instances with no existing string representation. Using this with a parsed graph is not the target use case, only used for internal testing +func (g Graph) ToHyberBenchFormat() string { + var buffer bytes.Buffer + + for i, e := range g.Edges.Slice() { + buffer.WriteString(e.FullStringInt()) + if i != g.Edges.Len()-1 { + buffer.WriteString(",\n") + } + } + + buffer.WriteString(".") + return buffer.String() +} diff --git a/lib/parser.go b/lib/parser.go index cd95259..2c4ffad 100644 --- a/lib/parser.go +++ b/lib/parser.go @@ -132,7 +132,7 @@ type parseEdgePACE struct { } type parseGraphPACEInfo struct { - Vertices int `"p htd":Begin @(Number) ` + Vertices int `"p htd" @(Number) ` Edges int `@(Number) "\n"` } @@ -149,7 +149,7 @@ func GetGraphPACE(s string) Graph { Comment = ("c" | "//") { "\u0000"…"\uffff"-"\n" } Newline. Begin = "p htd" . Number = ("." | digit | "_"){"." | digit | stuff } . - Whitespace = " " | "\t" | "\n" | "\r" . + Whitespace = " " | "\t" | "\r" . stuff = ":" | "@" | ";" | "-" | "_" . Punct = "!"…"/" . Newline = "\n" . diff --git a/test/algorithm_test.go b/test/algorithm_test.go new file mode 100644 index 0000000..db5a632 --- /dev/null +++ b/test/algorithm_test.go @@ -0,0 +1,275 @@ +package tests + +import ( + "fmt" + "log" + "math/rand" + "reflect" + "testing" + "time" + + algo "github.com/cem-okulmus/BalancedGo/algorithms" + "github.com/cem-okulmus/BalancedGo/lib" +) + +func solve(solver algo.Algorithm, graph lib.Graph, hinget *lib.Hingetree) lib.Decomp { + var output lib.Decomp + + if hinget != nil { + output = hinget.DecompHinge(solver, graph) + } else { + output = solver.FindDecomp() + } + + return output +} + +type solverDecomp struct { + solver algo.Algorithm + decomp lib.Decomp +} + +func TestAlgo(t *testing.T) { + + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + + graphInitial, encoding := getRandomGraph(10) + // graph := lib.GetGraphPACE(graphFirst.ToPACE()) + var graph lib.Graph + graph = graphInitial + width := r.Intn(6) + 1 + BalFactor := 2 + + heuristicRand := r.Intn(4) + + // Preprocessing on graph + order1 := lib.GetDegreeOrder(graph.Edges) + order2 := lib.GetMaxSepOrder(graph.Edges) + order3 := lib.GetMSCOrder(graph.Edges) + order4 := lib.GetEdgeDegreeOrder(graph.Edges) + + switch heuristicRand { + case 1: + graph.Edges = order1 + break + case 2: + graph.Edges = order2 + break + case 3: + graph.Edges = order3 + break + case 4: + graph.Edges = order4 + break + } + + var removalMap map[int][]int + var ops []lib.GYÖReduct + graph, removalMap, _ = graph.TypeCollapse() + graph, ops = graph.GYÖReduct() + + hinget := lib.GetHingeTree(graph) + + var algoTestsHD []algo.Algorithm + var algoTestsGHD []algo.Algorithm + + balDet := &algo.BalSepHybrid{ + K: width, + Graph: graph, + BalFactor: BalFactor, + Depth: 1, + } + algoTestsGHD = append(algoTestsGHD, balDet) + + seqBalDet := &algo.BalSepHybridSeq{ + K: width, + Graph: graph, + BalFactor: BalFactor, + Depth: 1, + } + + algoTestsGHD = append(algoTestsGHD, seqBalDet) + + det := &algo.DetKDecomp{ + K: width, + Graph: graph, + BalFactor: BalFactor, + SubEdge: false, + } + + algoTestsHD = append(algoTestsHD, det) + + localBIP := &algo.DetKDecomp{ + K: width, + Graph: graph, + BalFactor: BalFactor, + SubEdge: true, + } + + algoTestsGHD = append(algoTestsGHD, localBIP) + + logK := &algo.LogKDecomp{ + Graph: graph, + K: width, + BalFactor: BalFactor, + } + + algoTestsHD = append(algoTestsHD, logK) + + meta := r.Intn(300) + logkHybridChoice := r.Intn(4) + + logKHyb := &algo.LogKHybrid{ + Graph: graph, + K: width, + BalFactor: BalFactor, + } + logKHyb.Size = meta + + var pred algo.HybridPredicate + + switch logkHybridChoice { + case 0: + pred = logKHyb.NumberEdgesPred + case 1: + pred = logKHyb.SumEdgesPred + case 2: + pred = logKHyb.ETimesKDivAvgEdgePred + case 3: + pred = logKHyb.OneRoundPred + + } + logKHyb.Predicate = pred // set the predicate to use + + algoTestsHD = append(algoTestsHD, logKHyb) + + global := &algo.BalSepGlobal{ + K: width, + Graph: graph, + BalFactor: BalFactor, + } + + algoTestsGHD = append(algoTestsGHD, global) + + local := &algo.BalSepLocal{ + K: width, + Graph: graph, + BalFactor: BalFactor, + } + + algoTestsGHD = append(algoTestsGHD, local) + + // test out all algorithms + + first := true + prevAnswer := false + prevAlgo := "" + var out lib.Decomp + prevDecomp := lib.Decomp{} + + for _, algorithm := range algoTestsHD { + + out = solve(algorithm, graph, &hinget) + + if !reflect.DeepEqual(out, lib.Decomp{}) || (len(ops) > 0 && graph.Edges.Len() == 0) { + var result bool + out.Root, result = out.Root.RestoreGYÖ(ops) + if !result { + fmt.Println("Partial decomp:", out.Root) + log.Panicln("GYÖ reduction failed") + } + out.Root, result = out.Root.RestoreTypes(removalMap) + if !result { + fmt.Println("Partial decomp:", out.Root) + log.Panicln("Type Collapse reduction failed") + } + } + if !reflect.DeepEqual(out, lib.Decomp{}) { + out.Graph = graphInitial + } + + answer := out.Correct(graphInitial) + if answer { + if out.CheckWidth() > width { + t.Errorf("Out decomp of higher width than required: %v, width %v", out, width) + } + } + + if !first && answer != prevAnswer { + + fmt.Println("GraphInitial ", graphInitial.ToHyberBenchFormat()) + fmt.Println("Graph ", graph) + fmt.Println("Width: ", width) + + fmt.Println("Current algo ", algorithm.Name(), "answer: ", answer, " and decomp: ", out) + fmt.Println("Current algo ", prevAlgo, "answer: ", prevAnswer, " and decomp: ", prevDecomp) + + t.Errorf("Found disagreement among the algorithms: %v %v", algorithm.Name(), prevAlgo) + } + + prevAnswer = answer + prevAlgo = algorithm.Name() + prevDecomp = out + first = false + } + + first = true + prevAnswer = false + prevAlgo = "" + prevDecomp = lib.Decomp{} + + for _, algorithm := range algoTestsGHD { + + out = solve(algorithm, graph, &hinget) + + if !reflect.DeepEqual(out, lib.Decomp{}) || (len(ops) > 0 && graph.Edges.Len() == 0) { + var result bool + out.Root, result = out.Root.RestoreGYÖ(ops) + if !result { + fmt.Println("Partial decomp:", out.Root) + log.Panicln("GYÖ reduction failed") + } + out.Root, result = out.Root.RestoreTypes(removalMap) + if !result { + fmt.Println("Partial decomp:", out.Root) + log.Panicln("Type Collapse reduction failed") + } + } + if !reflect.DeepEqual(out, lib.Decomp{}) { + out.Graph = graphInitial + } + + answer := out.Correct(graphInitial) + + if answer { + if out.CheckWidth() > width { + t.Errorf("Out decomp of higher width than required: %v, width %v", out, width) + } + } + + if !first && answer != prevAnswer { + + fmt.Println("GraphInitial ", graphInitial.ToHyberBenchFormat()) + fmt.Println("Graph ", graph) + fmt.Println("Width: ", width) + + fmt.Println("Current algo ", algorithm.Name(), "answer: ", answer, " and decomp: ", out) + fmt.Println("Current algo ", prevAlgo, "answer: ", prevAnswer, " and decomp: ", prevDecomp) + + t.Errorf("Found disagreement among the algorithms: %v %v", algorithm.Name(), prevAlgo) + } + + prevAnswer = answer + prevAlgo = algorithm.Name() + prevDecomp = out + first = false + } + + //produce a GML + gml := out.ToGML() + if !reflect.DeepEqual(out, lib.Decomp{}) { + lib.GetDecompGML(gml, graphInitial, encoding) + } + +} diff --git a/test/cache_test.go b/test/cache_test.go index 3417d18..293553f 100644 --- a/test/cache_test.go +++ b/test/cache_test.go @@ -10,6 +10,8 @@ import ( "github.com/cem-okulmus/BalancedGo/lib" ) +var EDGE int + //getRandomEdge will produce a random Edge func getRandomEdge(size int) lib.Edge { s := rand.NewSource(time.Now().UnixNano()) @@ -17,22 +19,22 @@ func getRandomEdge(size int) lib.Edge { arity := r.Intn(size) + 1 var vertices []int - name := r.Intn(size * 10) + name := r.Intn(size*10) + EDGE + 1 + EDGE = name for i := 0; i < arity; i++ { - vertices = append(vertices, r.Intn(size*10)+i) + + vertices = append(vertices, r.Intn(size*10)+i+1) } return lib.Edge{Name: name, Vertices: vertices} } //getRandomGraph will produce a random Graph -func getRandomGraph(size int) lib.Graph { +func getRandomGraph(size int) (lib.Graph, map[string]int) { s := rand.NewSource(time.Now().UnixNano()) r := rand.New(s) - card := r.Intn(size) + 1 - special := r.Intn(size) var edges []lib.Edge var SpEdges []lib.Edges @@ -41,11 +43,10 @@ func getRandomGraph(size int) lib.Graph { edges = append(edges, getRandomEdge(size)) } - for i := 0; i < special; i++ { - SpEdges = append(SpEdges, getRandomEdges(size)) - } + outString := lib.Graph{Edges: lib.NewEdges(edges), Special: SpEdges}.ToHyberBenchFormat() + parsedGraph, pGraph := lib.GetGraph(outString) - return lib.Graph{Edges: lib.NewEdges(edges), Special: SpEdges} + return parsedGraph, pGraph.Encoding } //getRandomEdges will produce a random Edges struct @@ -77,7 +78,7 @@ func getRandomSep(g lib.Graph, size int) lib.Edges { } func TestCache(t *testing.T) { - randomGraph := getRandomGraph(100) + randomGraph, _ := getRandomGraph(100) randomSep := getRandomSep(randomGraph, 10) randomSep2 := getRandomSep(randomGraph, 10) randomSep3 := getRandomSep(randomGraph, 10) diff --git a/test/cover_test.go b/test/cover_test.go index 5072d94..8741e93 100644 --- a/test/cover_test.go +++ b/test/cover_test.go @@ -13,7 +13,7 @@ func TestCover(t *testing.T) { s := rand.NewSource(time.Now().UnixNano()) r := rand.New(s) - graph := getRandomGraph(100) + graph, _ := getRandomGraph(100) k := r.Intn(10) + 1 @@ -40,7 +40,7 @@ func shuffle(input lib.Edges) lib.Edges { //TestCover2 also checks if Cover works for non-empty Conn func TestCover2(t *testing.T) { - graph := getRandomGraph(100) + graph, _ := getRandomGraph(100) oldSep := getRandomSep(graph, 10) conn := lib.Inter(oldSep.Vertices(), graph.Vertices()) diff --git a/test/hash_test.go b/test/hash_test.go index 5f23343..c418807 100644 --- a/test/hash_test.go +++ b/test/hash_test.go @@ -1,5 +1,4 @@ // This package implements various black box unit tests - package tests import ( diff --git a/test/search_test.go b/test/search_test.go index 3a3cf29..09677a6 100644 --- a/test/search_test.go +++ b/test/search_test.go @@ -17,7 +17,7 @@ func TestSearchBal(t *testing.T) { s := rand.NewSource(time.Now().UnixNano()) r := rand.New(s) - randGraph := getRandomGraph(20) + randGraph, _ := getRandomGraph(20) k := r.Intn(5) + 1 combinParallel := lib.SplitCombin(randGraph.Edges.Len(), k, runtime.GOMAXPROCS(-1), false) @@ -71,7 +71,7 @@ func TestSearchPar(t *testing.T) { s := rand.NewSource(time.Now().UnixNano()) r := rand.New(s) - randGraph := getRandomGraph(30) + randGraph, _ := getRandomGraph(30) k := r.Intn(5) + 1 prevSep := getRandomSep(randGraph, k) diff --git a/test/subedge_test.go b/test/subedge_test.go index 9598c35..05c3a40 100644 --- a/test/subedge_test.go +++ b/test/subedge_test.go @@ -10,7 +10,7 @@ import ( //TestSubedge creates a random scenario for the SepSub struct and checks if some subedges are being produced func TestSubedge(t *testing.T) { - graph := getRandomGraph(20) + graph, _ := getRandomGraph(20) sep := getRandomSep(graph, 10) test := lib.GetSepSub(graph.Edges, sep, sep.Len()) diff --git a/tools/HyperParse/hyperParse.go b/tools/HyperParse/hyperParse.go index 355cb27..569b78e 100644 --- a/tools/HyperParse/hyperParse.go +++ b/tools/HyperParse/hyperParse.go @@ -1,6 +1,5 @@ // This package implements a basic tool to change the formatting of hypergraphs, as well as restore // various errors in an input GHD and also compute various statistics for an input hypergraph. - package main import ( @@ -32,10 +31,9 @@ func pruneTree(n *lib.Node) { if i > len(n.Children) { fmt.Println("Current node", n) fmt.Println("Children: ", n.Children) - fmt.Println("Index: ", c) - log.Panicln("the fuck?") + log.Panicln("error with pruning?") } pruneTree(&c) if lib.Subset(c.Bag, n.Bag) { @@ -122,9 +120,7 @@ func main() { // Performing GYÖ reduction reducedGraph, _ = reducedGraph.GYÖReduct() - hinget := lib.GetHingeTree(reducedGraph) - reducedGraph = hinget.GetLargestGraph() sumEdgeSizes := 0 @@ -152,7 +148,6 @@ func main() { } if *decompPath != "" { - dis, err2 := ioutil.ReadFile(*decompPath) if err2 != nil { panic(err) @@ -164,7 +159,6 @@ func main() { } defer f.Close() - decomp := lib.GetDecompGML(string(dis), parsedGraph, parseGraph.Encoding) if decomp.Correct(parsedGraph) { @@ -181,8 +175,6 @@ func main() { os.Exit(0) } - // fmt.Println("parsed Decomp ", decomp) - var removalMap map[int][]int // Performing Type Collapse reducedGraph, removalMap, _ = parsedGraph.TypeCollapse() @@ -192,15 +184,8 @@ func main() { // Performing GYÖ reduction reducedGraph, ops = reducedGraph.GYÖReduct() - decomp.Graph = reducedGraph - // parsedGraph = reducedGraph - // fmt.Println("Graph after GYÖ:") - // fmt.Println(reducedGraph) - // fmt.Println("Reductions:") - // fmt.Print(ops, "\n\n") - if !decomp.Correct(reducedGraph) { log.Panicln("decomp isn't correct decomp of reduced graph") }