func TestHarderAStar(t *testing.T) { tg := graph.NewTileGraph(3, 3, true) path, cost, _ := graph.AStar(graph.GonumNode(0), graph.GonumNode(8), tg, nil, nil) if math.Abs(cost-4.0) > .00001 || !graph.IsPath(path, tg) { t.Error("Non-optimal or impossible path found for 3x3 grid") } tg = graph.NewTileGraph(1000, 1000, true) path, cost, _ = graph.AStar(graph.GonumNode(00), graph.GonumNode(999*1000+999), tg, nil, nil) if !graph.IsPath(path, tg) || cost != 1998.0 { t.Error("Non-optimal or impossible path found for 100x100 grid; cost:", cost, "path:\n"+tg.PathString(path)) } }
func TestSimpleAStar(t *testing.T) { tg, err := graph.GenerateTileGraph("▀ ▀\n▀▀ ▀\n▀▀ ▀\n▀▀ ▀") if err != nil { t.Fatal("Couldn't generate tilegraph") } path, cost, _ := graph.AStar(graph.GonumNode(1), graph.GonumNode(14), tg, nil, nil) if math.Abs(cost-4.0) > .00001 { t.Errorf("A* reports incorrect cost for simple tilegraph search") } if path == nil { t.Fatalf("A* fails to find path for for simple tilegraph search") } else { correctPath := []int{1, 2, 6, 10, 14} if len(path) != len(correctPath) { t.Fatalf("Astar returns wrong length path for simple tilegraph search") } for i, node := range path { if node.ID() != correctPath[i] { t.Errorf("Astar returns wrong path at step", i, "got:", node, "actual:", correctPath[i]) } } } }
// Transform uses graphs to add network interfaces to Seeds func Transform(orig *seed.Seed) (*seed.Seed, error) { // build graph g := seedGraph.SeedAsGraph(orig) orig.Name = strings.Title(orig.Name) + "Server" for inputName, input := range orig.Collections { if input.Type != seed.CollectionInput { continue } input.Key = append([]string{"@address"}, input.Key...) start, ok := g.NodeFor(inputName) if !ok { panic("could not find node for " + inputName) } for outputName, output := range orig.Collections { if output.Type != seed.CollectionOutput { continue } goal, ok := g.NodeFor(outputName) if !ok { panic("could not find node for " + outputName) } path, cost, _ := graph.AStar(start, goal, g, cost, nil) if math.IsInf(cost, 0) { // there is no path (that does not go through a table) continue } outputAddress := outputName + "_addr" previousCollection := inputName for _, node := range path[:len(path)] { // unbox graph.internalNode; node = g.GetNode(node.ID()) switch node := node.(type) { case seedGraph.CollectionNode: previousCollection = node.Name switch node.Collection.Type { case seed.CollectionInput, seed.CollectionScratch: node.Collection.Key = prependIfNotExists(node.Collection.Key, outputAddress) case seed.CollectionOutput: node.Collection.Key = prependIfNotExists(node.Collection.Key, "@"+outputAddress) case seed.CollectionChannel, seed.CollectionTable: panic("should not encounter these collection types") default: panic(fmt.Sprintf("unhandled type: %d", node.Collection.Type)) } case seedGraph.RuleNode: exists := false for _, expression := range node.Rule.Intension { switch expression := expression.(type) { case seed.QualifiedColumn: if expression.Column == outputAddress { // if a reference to the outputAddress already exists (i.e., from being added by another flow) // then add a constraint to make the rows match up node.Rule.Predicate = append(node.Rule.Predicate, seed.Constraint{ Left: expression, Right: seed.QualifiedColumn{ Collection: previousCollection, Column: outputAddress, }, }) exists = true } case seed.MapFunction, seed.ReduceFunction: continue default: panic(fmt.Sprintf("unhandled type: %v", reflect.TypeOf(expression).String())) } } if !exists { // add to the projection node.Rule.Intension = append([]seed.Expression{seed.QualifiedColumn{ Collection: previousCollection, Column: outputAddress, }}, node.Rule.Intension...) } default: panic(fmt.Sprintf("unhandled type: %v", reflect.TypeOf(node).String())) } } } } // change inputs and outputs to channels for _, collection := range orig.Collections { switch collection.Type { case seed.CollectionInput, seed.CollectionOutput: collection.Type = seed.CollectionChannel case seed.CollectionScratch, seed.CollectionTable, seed.CollectionChannel: // no-op default: panic(collection.Type) } } // rules supplying channels must be asynchronous for _, rule := range orig.Rules { collectionName := rule.Supplies collection, ok := orig.Collections[collectionName] if !ok { // should never happen panic(collectionName) } if collection.Type == seed.CollectionChannel { rule.Operation = "<~" } } return orig, nil }