| // Copyright ©2017 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package path |
| |
| import ( |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "math" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "golang.org/x/exp/rand" |
| |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/graph/encoding" |
| "gonum.org/v1/gonum/graph/encoding/dot" |
| "gonum.org/v1/gonum/graph/graphs/gen" |
| "gonum.org/v1/gonum/graph/iterator" |
| "gonum.org/v1/gonum/graph/simple" |
| "gonum.org/v1/gonum/graph/topo" |
| ) |
| |
| var slta = flag.Bool("slta", false, "specify DominatorsSLT benchmark") |
| |
| func BenchmarkDominators(b *testing.B) { |
| testdata := filepath.FromSlash("./testdata/flow") |
| |
| fis, err := ioutil.ReadDir(testdata) |
| if err != nil { |
| b.Fatalf("failed to open control flow testdata: %v", err) |
| } |
| for _, fi := range fis { |
| name := fi.Name() |
| ext := filepath.Ext(name) |
| if ext != ".dot" { |
| continue |
| } |
| test := name[:len(name)-len(ext)] |
| |
| data, err := ioutil.ReadFile(filepath.Join(testdata, name)) |
| if err != nil { |
| b.Errorf("failed to open control flow case: %v", err) |
| continue |
| } |
| g := &labeled{DirectedGraph: simple.NewDirectedGraph()} |
| err = dot.Unmarshal(data, g) |
| if err != nil { |
| b.Errorf("failed to unmarshal graph data: %v", err) |
| continue |
| } |
| want := g.root |
| if want == nil { |
| b.Error("no entry node label for graph") |
| continue |
| } |
| |
| if *slta { |
| b.Run(test, func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| d := DominatorsSLT(g.root, g) |
| if got := d.Root(); got.ID() != want.ID() { |
| b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) |
| } |
| } |
| }) |
| } else { |
| b.Run(test, func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| d := Dominators(g.root, g) |
| if got := d.Root(); got.ID() != want.ID() { |
| b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), want.ID()) |
| } |
| } |
| }) |
| } |
| } |
| } |
| |
| type labeled struct { |
| *simple.DirectedGraph |
| |
| root *node |
| } |
| |
| func (g *labeled) NewNode() graph.Node { |
| return &node{Node: g.DirectedGraph.NewNode(), g: g} |
| } |
| |
| func (g *labeled) SetEdge(e graph.Edge) { |
| if e.To().ID() == e.From().ID() { |
| // Do not attempt to add self edges. |
| return |
| } |
| g.DirectedGraph.SetEdge(e) |
| } |
| |
| type node struct { |
| graph.Node |
| name string |
| g *labeled |
| } |
| |
| func (n *node) SetDOTID(id string) { |
| n.name = id |
| } |
| |
| func (n *node) SetAttribute(attr encoding.Attribute) error { |
| if attr.Key != "label" { |
| return nil |
| } |
| switch attr.Value { |
| default: |
| if attr.Value != `"{%0}"` && !strings.HasPrefix(attr.Value, `"{%0|`) { |
| return nil |
| } |
| fallthrough |
| case "entry", "root": |
| if n.g.root != nil { |
| return fmt.Errorf("set root for graph with existing root: old=%q new=%q", n.g.root.name, n.name) |
| } |
| n.g.root = n |
| } |
| return nil |
| } |
| |
| func BenchmarkRandomGraphDominators(b *testing.B) { |
| tests := []struct { |
| name string |
| g func() *simple.DirectedGraph |
| }{ |
| {name: "gnm-n=1e3-m=1e3", g: gnm(1e3, 1e3)}, |
| {name: "gnm-n=1e3-m=3e3", g: gnm(1e3, 3e3)}, |
| {name: "gnm-n=1e3-m=1e4", g: gnm(1e3, 1e4)}, |
| {name: "gnm-n=1e3-m=3e4", g: gnm(1e3, 3e4)}, |
| |
| {name: "gnm-n=1e4-m=1e4", g: gnm(1e4, 1e4)}, |
| {name: "gnm-n=1e4-m=3e4", g: gnm(1e4, 3e4)}, |
| {name: "gnm-n=1e4-m=1e5", g: gnm(1e4, 1e5)}, |
| {name: "gnm-n=1e4-m=3e5", g: gnm(1e4, 3e5)}, |
| |
| {name: "gnm-n=1e5-m=1e5", g: gnm(1e5, 1e5)}, |
| {name: "gnm-n=1e5-m=3e5", g: gnm(1e5, 3e5)}, |
| {name: "gnm-n=1e5-m=1e6", g: gnm(1e5, 1e6)}, |
| {name: "gnm-n=1e5-m=3e6", g: gnm(1e5, 3e6)}, |
| |
| {name: "gnm-n=1e6-m=1e6", g: gnm(1e6, 1e6)}, |
| {name: "gnm-n=1e6-m=3e6", g: gnm(1e6, 3e6)}, |
| {name: "gnm-n=1e6-m=1e7", g: gnm(1e6, 1e7)}, |
| {name: "gnm-n=1e6-m=3e7", g: gnm(1e6, 3e7)}, |
| |
| {name: "dup-n=1e3-d=0.8-a=0.1", g: duplication(1e3, 0.8, 0.1, math.NaN())}, |
| {name: "dup-n=1e3-d=0.5-a=0.2", g: duplication(1e3, 0.5, 0.2, math.NaN())}, |
| |
| {name: "dup-n=1e4-d=0.8-a=0.1", g: duplication(1e4, 0.8, 0.1, math.NaN())}, |
| {name: "dup-n=1e4-d=0.5-a=0.2", g: duplication(1e4, 0.5, 0.2, math.NaN())}, |
| |
| {name: "dup-n=1e5-d=0.8-a=0.1", g: duplication(1e5, 0.8, 0.1, math.NaN())}, |
| {name: "dup-n=1e5-d=0.5-a=0.2", g: duplication(1e5, 0.5, 0.2, math.NaN())}, |
| } |
| |
| for _, test := range tests { |
| rnd := rand.New(rand.NewSource(1)) |
| g := test.g() |
| |
| // Guess a maximally expensive entry to the graph. |
| sort, err := topo.Sort(g) |
| root := sort[0] |
| if root == nil { |
| // If we did not get a node in the first position |
| // then there must be an unorderable set of nodes |
| // in the first position of the error. Pick one |
| // of the nodes at random. |
| unordered := err.(topo.Unorderable) |
| root = unordered[0][rnd.Intn(len(unordered[0]))] |
| } |
| if root == nil { |
| b.Error("no entry node label for graph") |
| continue |
| } |
| |
| if len(sort) > 1 { |
| // Ensure that the graph has a complete path |
| // through the sorted nodes. |
| |
| // unordered will only be accessed if there is |
| // a sort element that is nil, in which case |
| // unordered will contain a set of nodes from |
| // an SCC. |
| unordered, _ := err.(topo.Unorderable) |
| |
| var ui int |
| for i, v := range sort[1:] { |
| u := sort[i] |
| if u == nil { |
| u = unordered[ui][rnd.Intn(len(unordered[ui]))] |
| ui++ |
| } |
| if v == nil { |
| v = unordered[ui][rnd.Intn(len(unordered[ui]))] |
| } |
| if !g.HasEdgeFromTo(u.ID(), v.ID()) { |
| g.SetEdge(g.NewEdge(u, v)) |
| } |
| } |
| } |
| |
| b.Run(test.name, func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| d := Dominators(root, g) |
| if got := d.Root(); got.ID() != root.ID() { |
| b.Fatalf("unexpected root node: got:%d want:%d", got.ID(), root.ID()) |
| } |
| } |
| }) |
| } |
| } |
| |
| // gnm returns a directed G(n,m) Erdõs-Rényi graph. |
| func gnm(n, m int) func() *simple.DirectedGraph { |
| return func() *simple.DirectedGraph { |
| dg := simple.NewDirectedGraph() |
| err := gen.Gnm(dg, n, m, rand.New(rand.NewSource(1))) |
| if err != nil { |
| panic(err) |
| } |
| return dg |
| } |
| } |
| |
| // duplication returns an edge-induced directed subgraph of a |
| // duplication graph. |
| func duplication(n int, delta, alpha, sigma float64) func() *simple.DirectedGraph { |
| return func() *simple.DirectedGraph { |
| g := undirected{simple.NewDirectedGraph()} |
| rnd := rand.New(rand.NewSource(1)) |
| err := gen.Duplication(g, n, delta, alpha, sigma, rnd) |
| if err != nil { |
| panic(err) |
| } |
| for _, e := range graph.EdgesOf(g.Edges()) { |
| if rnd.Intn(2) == 0 { |
| g.RemoveEdge(e.From().ID(), e.To().ID()) |
| } |
| } |
| return g.DirectedGraph |
| } |
| } |
| |
| type undirected struct { |
| *simple.DirectedGraph |
| } |
| |
| func (g undirected) From(id int64) graph.Nodes { |
| return iterator.NewOrderedNodes(append( |
| graph.NodesOf(g.DirectedGraph.From(id)), |
| graph.NodesOf(g.DirectedGraph.To(id))...)) |
| } |
| |
| func (g undirected) HasEdgeBetween(xid, yid int64) bool { |
| return g.DirectedGraph.HasEdgeFromTo(xid, yid) |
| } |
| |
| func (g undirected) EdgeBetween(xid, yid int64) graph.Edge { |
| return g.DirectedGraph.Edge(xid, yid) |
| } |
| |
| func (g undirected) SetEdge(e graph.Edge) { |
| g.DirectedGraph.SetEdge(e) |
| g.DirectedGraph.SetEdge(g.DirectedGraph.NewEdge(e.To(), e.From())) |
| } |