blob: a9b81a326f2c4d51b98207d8a77a8340ed829d54 [file] [log] [blame]
// Copyright ©2015 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 traverse
import (
"fmt"
"reflect"
"sort"
"testing"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/graphs/gen"
"gonum.org/v1/gonum/graph/internal/ordered"
"gonum.org/v1/gonum/graph/simple"
)
var (
// batageljZaversnikGraph is the example graph from
// figure 1 of http://arxiv.org/abs/cs/0310049v1
batageljZaversnikGraph = []intset{
0: nil,
1: linksTo(2, 3),
2: linksTo(4),
3: linksTo(4),
4: linksTo(5),
5: nil,
6: linksTo(7, 8, 14),
7: linksTo(8, 11, 12, 14),
8: linksTo(14),
9: linksTo(11),
10: linksTo(11),
11: linksTo(12),
12: linksTo(18),
13: linksTo(14, 15),
14: linksTo(15, 17),
15: linksTo(16, 17),
16: nil,
17: linksTo(18, 19, 20),
18: linksTo(19, 20),
19: linksTo(20),
20: nil,
}
// wpBronKerboschGraph is the example given in the Bron-Kerbosch article on wikipedia (renumbered).
// http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858
wpBronKerboschGraph = []intset{
0: linksTo(1, 4),
1: linksTo(2, 4),
2: linksTo(3),
3: linksTo(4, 5),
4: nil,
5: nil,
}
)
var breadthFirstTests = []struct {
g []intset
from graph.Node
edge func(graph.Edge) bool
until func(graph.Node, int) bool
final map[graph.Node]bool
want [][]int64
}{
{
g: wpBronKerboschGraph,
from: simple.Node(1),
final: map[graph.Node]bool{nil: true},
want: [][]int64{
{1},
{0, 2, 4},
{3},
{5},
},
},
{
g: wpBronKerboschGraph,
edge: func(e graph.Edge) bool {
// Do not traverse an edge between 3 and 5.
return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
},
from: simple.Node(1),
final: map[graph.Node]bool{nil: true},
want: [][]int64{
{1},
{0, 2, 4},
{3},
},
},
{
g: wpBronKerboschGraph,
from: simple.Node(1),
until: func(n graph.Node, _ int) bool { return n == simple.Node(3) },
final: map[graph.Node]bool{simple.Node(3): true},
want: [][]int64{
{1},
{0, 2, 4},
},
},
{
g: batageljZaversnikGraph,
from: simple.Node(13),
final: map[graph.Node]bool{nil: true},
want: [][]int64{
{13},
{14, 15},
{6, 7, 8, 16, 17},
{11, 12, 18, 19, 20},
{9, 10},
},
},
{
g: batageljZaversnikGraph,
from: simple.Node(13),
until: func(_ graph.Node, d int) bool { return d > 2 },
final: map[graph.Node]bool{
simple.Node(11): true,
simple.Node(12): true,
simple.Node(18): true,
simple.Node(19): true,
simple.Node(20): true,
},
want: [][]int64{
{13},
{14, 15},
{6, 7, 8, 16, 17},
},
},
}
func TestBreadthFirst(t *testing.T) {
for i, test := range breadthFirstTests {
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if g.Node(int64(u)) == nil {
g.AddNode(simple.Node(u))
}
for v := range e {
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
w := BreadthFirst{
EdgeFilter: test.edge,
}
var got [][]int64
final := w.Walk(g, test.from, func(n graph.Node, d int) bool {
if test.until != nil && test.until(n, d) {
return true
}
if d >= len(got) {
got = append(got, []int64(nil))
}
got[d] = append(got[d], n.ID())
return false
})
if !test.final[final] {
t.Errorf("unexepected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
}
for _, l := range got {
sort.Sort(ordered.Int64s(l))
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("unexepected BFS level structure for test %d:\ngot: %v\nwant: %v", i, got, test.want)
}
}
}
var depthFirstTests = []struct {
g []intset
from graph.Node
edge func(graph.Edge) bool
until func(graph.Node) bool
final map[graph.Node]bool
want []int64
}{
{
g: wpBronKerboschGraph,
from: simple.Node(1),
final: map[graph.Node]bool{nil: true},
want: []int64{0, 1, 2, 3, 4, 5},
},
{
g: wpBronKerboschGraph,
edge: func(e graph.Edge) bool {
// Do not traverse an edge between 3 and 5.
return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
},
from: simple.Node(1),
final: map[graph.Node]bool{nil: true},
want: []int64{0, 1, 2, 3, 4},
},
{
g: wpBronKerboschGraph,
from: simple.Node(1),
until: func(n graph.Node) bool { return n == simple.Node(3) },
final: map[graph.Node]bool{simple.Node(3): true},
},
{
g: batageljZaversnikGraph,
from: simple.Node(0),
final: map[graph.Node]bool{nil: true},
want: []int64{0},
},
{
g: batageljZaversnikGraph,
from: simple.Node(3),
final: map[graph.Node]bool{nil: true},
want: []int64{1, 2, 3, 4, 5},
},
{
g: batageljZaversnikGraph,
from: simple.Node(13),
final: map[graph.Node]bool{nil: true},
want: []int64{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
},
}
func TestDepthFirst(t *testing.T) {
for i, test := range depthFirstTests {
g := simple.NewUndirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if g.Node(int64(u)) == nil {
g.AddNode(simple.Node(u))
}
for v := range e {
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
w := DepthFirst{
EdgeFilter: test.edge,
}
var got []int64
final := w.Walk(g, test.from, func(n graph.Node) bool {
if test.until != nil && test.until(n) {
return true
}
got = append(got, n.ID())
return false
})
if !test.final[final] {
t.Errorf("unexepected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
}
sort.Sort(ordered.Int64s(got))
if test.want != nil && !reflect.DeepEqual(got, test.want) {
t.Errorf("unexepected DFS traversed nodes for test %d:\ngot: %v\nwant: %v", i, got, test.want)
}
}
}
var walkAllTests = []struct {
g []intset
edge func(graph.Edge) bool
want [][]int64
}{
{
g: batageljZaversnikGraph,
want: [][]int64{
{0},
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
},
},
{
g: batageljZaversnikGraph,
edge: func(e graph.Edge) bool {
// Do not traverse an edge between 3 and 5.
return (e.From().ID() != 4 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 4)
},
want: [][]int64{
{0},
{1, 2, 3, 4},
{5},
{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
},
},
}
func TestWalkAll(t *testing.T) {
for i, test := range walkAllTests {
g := simple.NewUndirectedGraph()
for u, e := range test.g {
if g.Node(int64(u)) == nil {
g.AddNode(simple.Node(u))
}
for v := range e {
if g.Node(int64(v)) == nil {
g.AddNode(simple.Node(v))
}
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
}
}
type walker interface {
WalkAll(g graph.Undirected, before, after func(), during func(graph.Node))
}
for _, w := range []walker{
&BreadthFirst{},
&DepthFirst{},
} {
var (
c []graph.Node
cc [][]graph.Node
)
switch w := w.(type) {
case *BreadthFirst:
w.EdgeFilter = test.edge
case *DepthFirst:
w.EdgeFilter = test.edge
default:
panic(fmt.Sprintf("bad walker type: %T", w))
}
during := func(n graph.Node) {
c = append(c, n)
}
after := func() {
cc = append(cc, []graph.Node(nil))
cc[len(cc)-1] = append(cc[len(cc)-1], c...)
c = c[:0]
}
w.WalkAll(g, nil, after, during)
got := make([][]int64, len(cc))
for j, c := range cc {
ids := make([]int64, len(c))
for k, n := range c {
ids[k] = n.ID()
}
sort.Sort(ordered.Int64s(ids))
got[j] = ids
}
sort.Sort(ordered.BySliceValues(got))
if !reflect.DeepEqual(got, test.want) {
t.Errorf("unexpected connected components for test %d using %T:\ngot: %v\nwant:%v", i, w, got, test.want)
}
}
}
}
// intset is an integer set.
type intset map[int]struct{}
func linksTo(i ...int) intset {
if len(i) == 0 {
return nil
}
s := make(intset)
for _, v := range i {
s[v] = struct{}{}
}
return s
}
var (
gnpUndirected_10_tenth = gnpUndirected(10, 0.1)
gnpUndirected_100_tenth = gnpUndirected(100, 0.1)
gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1)
gnpUndirected_10_half = gnpUndirected(10, 0.5)
gnpUndirected_100_half = gnpUndirected(100, 0.5)
gnpUndirected_1000_half = gnpUndirected(1000, 0.5)
)
func gnpUndirected(n int, p float64) graph.Undirected {
g := simple.NewUndirectedGraph()
gen.Gnp(g, n, p, nil)
return g
}
func benchmarkWalkAllBreadthFirst(b *testing.B, g graph.Undirected) {
n := g.Nodes().Len()
b.ResetTimer()
var bft BreadthFirst
for i := 0; i < b.N; i++ {
bft.WalkAll(g, nil, nil, nil)
}
if len(bft.visited) != n {
b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(bft.visited))
}
}
func BenchmarkWalkAllBreadthFirstGnp_10_tenth(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_tenth)
}
func BenchmarkWalkAllBreadthFirstGnp_100_tenth(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_tenth)
}
func BenchmarkWalkAllBreadthFirstGnp_1000_tenth(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_tenth)
}
func BenchmarkWalkAllBreadthFirstGnp_10_half(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_half)
}
func BenchmarkWalkAllBreadthFirstGnp_100_half(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_half)
}
func BenchmarkWalkAllBreadthFirstGnp_1000_half(b *testing.B) {
benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_half)
}
func benchmarkWalkAllDepthFirst(b *testing.B, g graph.Undirected) {
n := g.Nodes().Len()
b.ResetTimer()
var dft DepthFirst
for i := 0; i < b.N; i++ {
dft.WalkAll(g, nil, nil, nil)
}
if len(dft.visited) != n {
b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(dft.visited))
}
}
func BenchmarkWalkAllDepthFirstGnp_10_tenth(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_10_tenth)
}
func BenchmarkWalkAllDepthFirstGnp_100_tenth(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_100_tenth)
}
func BenchmarkWalkAllDepthFirstGnp_1000_tenth(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_tenth)
}
func BenchmarkWalkAllDepthFirstGnp_10_half(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_10_half)
}
func BenchmarkWalkAllDepthFirstGnp_100_half(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_100_half)
}
func BenchmarkWalkAllDepthFirstGnp_1000_half(b *testing.B) {
benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_half)
}