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 }