gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/graph/traverse/traverse_test.go (about)

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package traverse
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"slices"
    11  	"strings"
    12  	"testing"
    13  
    14  	"gonum.org/v1/gonum/graph"
    15  	"gonum.org/v1/gonum/graph/graphs/gen"
    16  	"gonum.org/v1/gonum/graph/simple"
    17  	"gonum.org/v1/gonum/internal/order"
    18  )
    19  
    20  var (
    21  	// batageljZaversnikGraph is the example graph from
    22  	// figure 1 of http://arxiv.org/abs/cs/0310049v1
    23  	batageljZaversnikGraph = []intset{
    24  		0: nil,
    25  
    26  		1: linksTo(2, 3),
    27  		2: linksTo(4),
    28  		3: linksTo(4),
    29  		4: linksTo(5),
    30  		5: nil,
    31  
    32  		6:  linksTo(7, 8, 14),
    33  		7:  linksTo(8, 11, 12, 14),
    34  		8:  linksTo(14),
    35  		9:  linksTo(11),
    36  		10: linksTo(11),
    37  		11: linksTo(12),
    38  		12: linksTo(18),
    39  		13: linksTo(14, 15),
    40  		14: linksTo(15, 17),
    41  		15: linksTo(16, 17),
    42  		16: nil,
    43  		17: linksTo(18, 19, 20),
    44  		18: linksTo(19, 20),
    45  		19: linksTo(20),
    46  		20: nil,
    47  	}
    48  
    49  	// wpBronKerboschGraph is the example given in the Bron-Kerbosch article on wikipedia (renumbered).
    50  	// http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858
    51  	wpBronKerboschGraph = []intset{
    52  		0: linksTo(1, 4),
    53  		1: linksTo(2, 4),
    54  		2: linksTo(3),
    55  		3: linksTo(4, 5),
    56  		4: nil,
    57  		5: nil,
    58  	}
    59  
    60  	// g1595 is the graph shown in https://github.com/gonum/gonum/issues/1595 with the addition
    61  	// of an unconnected 0 node (due to limitations of intset).
    62  	g1595 = []intset{
    63  		0: nil,
    64  		1: linksTo(2, 4),
    65  		2: linksTo(3),
    66  		3: nil,
    67  		4: nil,
    68  	}
    69  )
    70  
    71  var breadthFirstTests = []struct {
    72  	g     []intset
    73  	from  graph.Node
    74  	edge  func(graph.Edge) bool
    75  	until func(graph.Node, int) bool
    76  	final map[graph.Node]bool
    77  	want  [][]int64
    78  }{
    79  	{
    80  		g:     wpBronKerboschGraph,
    81  		from:  simple.Node(1),
    82  		final: map[graph.Node]bool{nil: true},
    83  		want: [][]int64{
    84  			{1},
    85  			{0, 2, 4},
    86  			{3},
    87  			{5},
    88  		},
    89  	},
    90  	{
    91  		g: wpBronKerboschGraph,
    92  		edge: func(e graph.Edge) bool {
    93  			// Do not traverse an edge between 3 and 5.
    94  			return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
    95  		},
    96  		from:  simple.Node(1),
    97  		final: map[graph.Node]bool{nil: true},
    98  		want: [][]int64{
    99  			{1},
   100  			{0, 2, 4},
   101  			{3},
   102  		},
   103  	},
   104  	{
   105  		g:     wpBronKerboschGraph,
   106  		from:  simple.Node(1),
   107  		until: func(n graph.Node, _ int) bool { return n == simple.Node(3) },
   108  		final: map[graph.Node]bool{simple.Node(3): true},
   109  		want: [][]int64{
   110  			{1},
   111  			{0, 2, 4},
   112  		},
   113  	},
   114  	{
   115  		g:     batageljZaversnikGraph,
   116  		from:  simple.Node(13),
   117  		final: map[graph.Node]bool{nil: true},
   118  		want: [][]int64{
   119  			{13},
   120  			{14, 15},
   121  			{6, 7, 8, 16, 17},
   122  			{11, 12, 18, 19, 20},
   123  			{9, 10},
   124  		},
   125  	},
   126  	{
   127  		g:     batageljZaversnikGraph,
   128  		from:  simple.Node(13),
   129  		until: func(_ graph.Node, d int) bool { return d > 2 },
   130  		final: map[graph.Node]bool{
   131  			simple.Node(11): true,
   132  			simple.Node(12): true,
   133  			simple.Node(18): true,
   134  			simple.Node(19): true,
   135  			simple.Node(20): true,
   136  		},
   137  		want: [][]int64{
   138  			{13},
   139  			{14, 15},
   140  			{6, 7, 8, 16, 17},
   141  		},
   142  	},
   143  }
   144  
   145  func TestBreadthFirst(t *testing.T) {
   146  	for i, test := range breadthFirstTests {
   147  		g := simple.NewUndirectedGraph()
   148  		for u, e := range test.g {
   149  			// Add nodes that are not defined by an edge.
   150  			if g.Node(int64(u)) == nil {
   151  				g.AddNode(simple.Node(u))
   152  			}
   153  			for v := range e {
   154  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   155  			}
   156  		}
   157  		w := BreadthFirst{
   158  			Traverse: test.edge,
   159  		}
   160  		var got [][]int64
   161  		final := w.Walk(g, test.from, func(n graph.Node, d int) bool {
   162  			if test.until != nil && test.until(n, d) {
   163  				return true
   164  			}
   165  			if d >= len(got) {
   166  				got = append(got, []int64(nil))
   167  			}
   168  			got[d] = append(got[d], n.ID())
   169  			return false
   170  		})
   171  		if !test.final[final] {
   172  			t.Errorf("unexpected final node for test %d:\ngot:  %v\nwant: %v", i, final, test.final)
   173  		}
   174  		for _, l := range got {
   175  			slices.Sort(l)
   176  		}
   177  		if !reflect.DeepEqual(got, test.want) {
   178  			t.Errorf("unexpected BFS level structure for test %d:\ngot:  %v\nwant: %v", i, got, test.want)
   179  		}
   180  	}
   181  }
   182  
   183  var depthFirstTests = []struct {
   184  	g     []intset
   185  	from  graph.Node
   186  	edge  func(graph.Edge) bool
   187  	until func(graph.Node) bool
   188  	final map[graph.Node]bool
   189  	want  [][]int64
   190  }{
   191  	{
   192  		g:     wpBronKerboschGraph,
   193  		from:  simple.Node(1),
   194  		final: map[graph.Node]bool{nil: true},
   195  		want: [][]int64{
   196  			{1, 0, 4, 3, 5, 2},
   197  			{1, 0, 4, 3, 2, 5},
   198  			{1, 2, 3, 4, 0, 5},
   199  			{1, 2, 3, 5, 4, 0},
   200  			{1, 4, 0, 3, 5, 2},
   201  			{1, 4, 0, 3, 2, 5},
   202  			{1, 4, 3, 2, 5, 0},
   203  			{1, 4, 3, 5, 2, 0},
   204  		},
   205  	},
   206  	{
   207  		g: wpBronKerboschGraph,
   208  		edge: func(e graph.Edge) bool {
   209  			// Do not traverse an edge between 3 and 5.
   210  			return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
   211  		},
   212  		from:  simple.Node(1),
   213  		final: map[graph.Node]bool{nil: true},
   214  		want: [][]int64{
   215  			{1, 0, 4, 3, 2},
   216  			{1, 2, 3, 4, 0},
   217  			{1, 4, 0, 3, 2},
   218  			{1, 4, 3, 2, 0},
   219  		},
   220  	},
   221  	{
   222  		g:     wpBronKerboschGraph,
   223  		from:  simple.Node(1),
   224  		until: func(n graph.Node) bool { return n == simple.Node(3) },
   225  		final: map[graph.Node]bool{simple.Node(3): true},
   226  	},
   227  	{
   228  		g:     batageljZaversnikGraph,
   229  		from:  simple.Node(0),
   230  		final: map[graph.Node]bool{nil: true},
   231  		want:  [][]int64{{0}},
   232  	},
   233  	{
   234  		g:     batageljZaversnikGraph,
   235  		from:  simple.Node(3),
   236  		final: map[graph.Node]bool{nil: true},
   237  		want: [][]int64{
   238  			{3, 1, 2, 4, 5},
   239  			{3, 4, 2, 1, 5},
   240  			{3, 4, 5, 2, 1},
   241  		},
   242  	},
   243  	{
   244  		g:     g1595,
   245  		from:  simple.Node(1),
   246  		final: map[graph.Node]bool{nil: true},
   247  		want: [][]int64{
   248  			{1, 4, 2, 3},
   249  			{1, 2, 3, 4},
   250  		},
   251  	},
   252  }
   253  
   254  func TestDepthFirst(t *testing.T) {
   255  	for i, test := range depthFirstTests {
   256  		g := simple.NewUndirectedGraph()
   257  		for u, e := range test.g {
   258  			// Add nodes that are not defined by an edge.
   259  			if g.Node(int64(u)) == nil {
   260  				g.AddNode(simple.Node(u))
   261  			}
   262  			for v := range e {
   263  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   264  			}
   265  		}
   266  		w := DepthFirst{
   267  			Traverse: test.edge,
   268  		}
   269  		var got []int64
   270  		final := w.Walk(g, test.from, func(n graph.Node) bool {
   271  			if test.until != nil && test.until(n) {
   272  				return true
   273  			}
   274  			got = append(got, n.ID())
   275  			return false
   276  		})
   277  		if !test.final[final] {
   278  			t.Errorf("unexpected final node for test %d:\ngot:  %v\nwant: %v", i, final, test.final)
   279  		}
   280  		var ok bool
   281  		for _, want := range test.want {
   282  			if reflect.DeepEqual(got, want) {
   283  				ok = true
   284  				break
   285  			}
   286  		}
   287  		if test.want != nil && !ok {
   288  			var wanted strings.Builder
   289  			for _, w := range test.want {
   290  				fmt.Fprintf(&wanted, "     %v\n", w)
   291  			}
   292  			t.Errorf("unexpected DFS traversed nodes for test %d:\ngot: %v\nwant one of:\n%s", i, got, &wanted)
   293  		}
   294  	}
   295  }
   296  
   297  var walkAllTests = []struct {
   298  	g    []intset
   299  	edge func(graph.Edge) bool
   300  	want [][]int64
   301  }{
   302  	{
   303  		g: batageljZaversnikGraph,
   304  		want: [][]int64{
   305  			{0},
   306  			{1, 2, 3, 4, 5},
   307  			{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
   308  		},
   309  	},
   310  	{
   311  		g: batageljZaversnikGraph,
   312  		edge: func(e graph.Edge) bool {
   313  			// Do not traverse an edge between 3 and 5.
   314  			return (e.From().ID() != 4 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 4)
   315  		},
   316  		want: [][]int64{
   317  			{0},
   318  			{1, 2, 3, 4},
   319  			{5},
   320  			{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
   321  		},
   322  	},
   323  }
   324  
   325  func TestWalkAll(t *testing.T) {
   326  	for i, test := range walkAllTests {
   327  		g := simple.NewUndirectedGraph()
   328  
   329  		for u, e := range test.g {
   330  			if g.Node(int64(u)) == nil {
   331  				g.AddNode(simple.Node(u))
   332  			}
   333  			for v := range e {
   334  				if g.Node(int64(v)) == nil {
   335  					g.AddNode(simple.Node(v))
   336  				}
   337  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   338  			}
   339  		}
   340  		type walker interface {
   341  			WalkAll(g graph.Undirected, before, after func(), during func(graph.Node))
   342  		}
   343  		for _, w := range []walker{
   344  			&BreadthFirst{},
   345  			&DepthFirst{},
   346  		} {
   347  			var (
   348  				c  []graph.Node
   349  				cc [][]graph.Node
   350  			)
   351  			switch w := w.(type) {
   352  			case *BreadthFirst:
   353  				w.Traverse = test.edge
   354  			case *DepthFirst:
   355  				w.Traverse = test.edge
   356  			default:
   357  				panic(fmt.Sprintf("bad walker type: %T", w))
   358  			}
   359  			during := func(n graph.Node) {
   360  				c = append(c, n)
   361  			}
   362  			after := func() {
   363  				cc = append(cc, []graph.Node(nil))
   364  				cc[len(cc)-1] = append(cc[len(cc)-1], c...)
   365  				c = c[:0]
   366  			}
   367  			w.WalkAll(g, nil, after, during)
   368  
   369  			got := make([][]int64, len(cc))
   370  			for j, c := range cc {
   371  				ids := make([]int64, len(c))
   372  				for k, n := range c {
   373  					ids[k] = n.ID()
   374  				}
   375  				slices.Sort(ids)
   376  				got[j] = ids
   377  			}
   378  			order.BySliceValues(got)
   379  			if !reflect.DeepEqual(got, test.want) {
   380  				t.Errorf("unexpected connected components for test %d using %T:\ngot: %v\nwant:%v", i, w, got, test.want)
   381  			}
   382  		}
   383  	}
   384  }
   385  
   386  // intset is an integer set.
   387  type intset map[int]struct{}
   388  
   389  func linksTo(i ...int) intset {
   390  	if len(i) == 0 {
   391  		return nil
   392  	}
   393  	s := make(intset)
   394  	for _, v := range i {
   395  		s[v] = struct{}{}
   396  	}
   397  	return s
   398  }
   399  
   400  var (
   401  	gnpUndirected_10_tenth   = gnpUndirected(10, 0.1)
   402  	gnpUndirected_100_tenth  = gnpUndirected(100, 0.1)
   403  	gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1)
   404  	gnpUndirected_10_half    = gnpUndirected(10, 0.5)
   405  	gnpUndirected_100_half   = gnpUndirected(100, 0.5)
   406  	gnpUndirected_1000_half  = gnpUndirected(1000, 0.5)
   407  )
   408  
   409  func gnpUndirected(n int, p float64) graph.Undirected {
   410  	g := simple.NewUndirectedGraph()
   411  	err := gen.Gnp(g, n, p, nil)
   412  	if err != nil {
   413  		panic(fmt.Sprintf("traverse: bad test: %v", err))
   414  	}
   415  	return g
   416  }
   417  
   418  func benchmarkWalkAllBreadthFirst(b *testing.B, g graph.Undirected) {
   419  	n := g.Nodes().Len()
   420  	b.ResetTimer()
   421  	var bft BreadthFirst
   422  	for i := 0; i < b.N; i++ {
   423  		bft.WalkAll(g, nil, nil, nil)
   424  	}
   425  	if len(bft.visited) != n {
   426  		b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(bft.visited))
   427  	}
   428  }
   429  
   430  func BenchmarkWalkAllBreadthFirstGnp_10_tenth(b *testing.B) {
   431  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_tenth)
   432  }
   433  func BenchmarkWalkAllBreadthFirstGnp_100_tenth(b *testing.B) {
   434  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_tenth)
   435  }
   436  func BenchmarkWalkAllBreadthFirstGnp_1000_tenth(b *testing.B) {
   437  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_tenth)
   438  }
   439  func BenchmarkWalkAllBreadthFirstGnp_10_half(b *testing.B) {
   440  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_half)
   441  }
   442  func BenchmarkWalkAllBreadthFirstGnp_100_half(b *testing.B) {
   443  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_half)
   444  }
   445  func BenchmarkWalkAllBreadthFirstGnp_1000_half(b *testing.B) {
   446  	benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_half)
   447  }
   448  
   449  func benchmarkWalkAllDepthFirst(b *testing.B, g graph.Undirected) {
   450  	n := g.Nodes().Len()
   451  	b.ResetTimer()
   452  	var dft DepthFirst
   453  	for i := 0; i < b.N; i++ {
   454  		dft.WalkAll(g, nil, nil, nil)
   455  	}
   456  	if len(dft.visited) != n {
   457  		b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(dft.visited))
   458  	}
   459  }
   460  
   461  func BenchmarkWalkAllDepthFirstGnp_10_tenth(b *testing.B) {
   462  	benchmarkWalkAllDepthFirst(b, gnpUndirected_10_tenth)
   463  }
   464  func BenchmarkWalkAllDepthFirstGnp_100_tenth(b *testing.B) {
   465  	benchmarkWalkAllDepthFirst(b, gnpUndirected_100_tenth)
   466  }
   467  func BenchmarkWalkAllDepthFirstGnp_1000_tenth(b *testing.B) {
   468  	benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_tenth)
   469  }
   470  func BenchmarkWalkAllDepthFirstGnp_10_half(b *testing.B) {
   471  	benchmarkWalkAllDepthFirst(b, gnpUndirected_10_half)
   472  }
   473  func BenchmarkWalkAllDepthFirstGnp_100_half(b *testing.B) {
   474  	benchmarkWalkAllDepthFirst(b, gnpUndirected_100_half)
   475  }
   476  func BenchmarkWalkAllDepthFirstGnp_1000_half(b *testing.B) {
   477  	benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_half)
   478  }