gonum.org/v1/gonum@v0.14.0/graph/path/dynamic/dstarlite_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 dynamic 6 7 import ( 8 "bytes" 9 "flag" 10 "fmt" 11 "math" 12 "reflect" 13 "strings" 14 "testing" 15 16 "gonum.org/v1/gonum/graph" 17 "gonum.org/v1/gonum/graph/path" 18 "gonum.org/v1/gonum/graph/path/internal/testgraphs" 19 "gonum.org/v1/gonum/graph/simple" 20 ) 21 22 var ( 23 debug = flag.Bool("debug", false, "write path progress for failing dynamic case tests") 24 vdebug = flag.Bool("vdebug", false, "write path progress for all dynamic case tests (requires test.v)") 25 maxWide = flag.Int("maxwidth", 5, "maximum width grid to dump for debugging") 26 ) 27 28 func TestDStarLiteNullHeuristic(t *testing.T) { 29 t.Parallel() 30 for _, test := range testgraphs.ShortestPathTests { 31 // Skip zero-weight cycles. 32 if strings.HasPrefix(test.Name, "zero-weight") { 33 continue 34 } 35 36 g := test.Graph() 37 for _, e := range test.Edges { 38 g.SetWeightedEdge(e) 39 } 40 41 var ( 42 d *DStarLite 43 44 panicked bool 45 ) 46 func() { 47 defer func() { 48 panicked = recover() != nil 49 }() 50 d = NewDStarLite(test.Query.From(), test.Query.To(), g.(graph.Graph), path.NullHeuristic, simple.NewWeightedDirectedGraph(0, math.Inf(1))) 51 }() 52 if panicked || test.HasNegativeWeight { 53 if !test.HasNegativeWeight { 54 t.Errorf("%q: unexpected panic", test.Name) 55 } 56 if !panicked { 57 t.Errorf("%q: expected panic for negative edge weight", test.Name) 58 } 59 continue 60 } 61 62 p, weight := d.Path() 63 64 if !math.IsInf(weight, 1) && p[0].ID() != test.Query.From().ID() { 65 t.Fatalf("%q: unexpected from node ID: got:%d want:%d", test.Name, p[0].ID(), test.Query.From().ID()) 66 } 67 if weight != test.Weight { 68 t.Errorf("%q: unexpected weight from Between: got:%f want:%f", 69 test.Name, weight, test.Weight) 70 } 71 72 var got []int64 73 for _, n := range p { 74 got = append(got, n.ID()) 75 } 76 ok := len(got) == 0 && len(test.WantPaths) == 0 77 for _, sp := range test.WantPaths { 78 if reflect.DeepEqual(got, sp) { 79 ok = true 80 break 81 } 82 } 83 if !ok { 84 t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", 85 test.Name, p, test.WantPaths) 86 } 87 } 88 } 89 90 var dynamicDStarLiteTests = []struct { 91 g *testgraphs.Grid 92 radius float64 93 all bool 94 diag, unit bool 95 remember []bool 96 modify func(*testgraphs.LimitedVisionGrid) 97 98 heuristic func(dx, dy float64) float64 99 100 s, t graph.Node 101 102 want []graph.Node 103 weight float64 104 wantedPaths map[int64][]graph.Node 105 }{ 106 { 107 // This is the example shown in figures 6 and 7 of doi:10.1109/tro.2004.838026. 108 g: testgraphs.NewGridFrom( 109 "...", 110 ".*.", 111 ".*.", 112 ".*.", 113 "...", 114 ), 115 radius: 1.5, 116 all: true, 117 diag: true, 118 unit: true, 119 remember: []bool{false, true}, 120 121 heuristic: func(dx, dy float64) float64 { 122 return math.Max(math.Abs(dx), math.Abs(dy)) 123 }, 124 125 s: simple.Node(3), 126 t: simple.Node(14), 127 128 want: []graph.Node{ 129 simple.Node(3), 130 simple.Node(6), 131 simple.Node(9), 132 simple.Node(13), 133 simple.Node(14), 134 }, 135 weight: 4, 136 }, 137 { 138 // This is a small example that has the property that the first corner 139 // may be taken incorrectly at 90° or correctly at 45° because the 140 // calculated rhs values of 12 and 17 are tied when moving from node 141 // 16, and the grid is small enough to examine by a dump. 142 g: testgraphs.NewGridFrom( 143 ".....", 144 "...*.", 145 "**.*.", 146 "...*.", 147 ), 148 radius: 1.5, 149 all: true, 150 diag: true, 151 remember: []bool{false, true}, 152 153 heuristic: func(dx, dy float64) float64 { 154 return math.Max(math.Abs(dx), math.Abs(dy)) 155 }, 156 157 s: simple.Node(15), 158 t: simple.Node(14), 159 160 want: []graph.Node{ 161 simple.Node(15), 162 simple.Node(16), 163 simple.Node(12), 164 simple.Node(7), 165 simple.Node(3), 166 simple.Node(9), 167 simple.Node(14), 168 }, 169 weight: 7.242640687119285, 170 wantedPaths: map[int64][]graph.Node{ 171 12: {simple.Node(12), simple.Node(7), simple.Node(3), simple.Node(9), simple.Node(14)}, 172 }, 173 }, 174 { 175 // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 176 // with the exception that diagonal edge weights are calculated with the hypot 177 // function instead of a step count and only allowing information to be known 178 // from exploration. 179 g: testgraphs.NewGridFrom( 180 "..................", 181 "..................", 182 "..................", 183 "..................", 184 "..................", 185 "..................", 186 "....*.*...........", 187 "*****.***.........", 188 "......*...........", 189 "......***.........", 190 "......*...........", 191 "......*...........", 192 "......*...........", 193 "*****.*...........", 194 "......*...........", 195 ), 196 radius: 1.5, 197 all: true, 198 diag: true, 199 remember: []bool{false, true}, 200 201 heuristic: func(dx, dy float64) float64 { 202 return math.Max(math.Abs(dx), math.Abs(dy)) 203 }, 204 205 s: simple.Node(253), 206 t: simple.Node(122), 207 208 want: []graph.Node{ 209 simple.Node(253), 210 simple.Node(254), 211 simple.Node(255), 212 simple.Node(256), 213 simple.Node(239), 214 simple.Node(221), 215 simple.Node(203), 216 simple.Node(185), 217 simple.Node(167), 218 simple.Node(149), 219 simple.Node(131), 220 simple.Node(113), 221 simple.Node(96), 222 223 // The following section depends 224 // on map iteration order. 225 nil, 226 nil, 227 nil, 228 nil, 229 nil, 230 nil, 231 nil, 232 233 simple.Node(122), 234 }, 235 weight: 21.242640687119287, 236 }, 237 { 238 // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 239 // with the exception that diagonal edge weights are calculated with the hypot 240 // function instead of a step count, not closing the exit and only allowing 241 // information to be known from exploration. 242 g: testgraphs.NewGridFrom( 243 "..................", 244 "..................", 245 "..................", 246 "..................", 247 "..................", 248 "..................", 249 "....*.*...........", 250 "*****.***.........", 251 "..................", // Keep open. 252 "......***.........", 253 "......*...........", 254 "......*...........", 255 "......*...........", 256 "*****.*...........", 257 "......*...........", 258 ), 259 radius: 1.5, 260 all: true, 261 diag: true, 262 remember: []bool{false, true}, 263 264 heuristic: func(dx, dy float64) float64 { 265 return math.Max(math.Abs(dx), math.Abs(dy)) 266 }, 267 268 s: simple.Node(253), 269 t: simple.Node(122), 270 271 want: []graph.Node{ 272 simple.Node(253), 273 simple.Node(254), 274 simple.Node(255), 275 simple.Node(256), 276 simple.Node(239), 277 simple.Node(221), 278 simple.Node(203), 279 simple.Node(185), 280 simple.Node(167), 281 simple.Node(150), 282 simple.Node(151), 283 simple.Node(152), 284 285 // The following section depends 286 // on map iteration order. 287 nil, 288 nil, 289 nil, 290 nil, 291 nil, 292 293 simple.Node(122), 294 }, 295 weight: 18.656854249492383, 296 }, 297 { 298 // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 299 // with the exception that diagonal edge weights are calculated with the hypot 300 // function instead of a step count, the exit is closed at a distance and 301 // information is allowed to be known from exploration. 302 g: testgraphs.NewGridFrom( 303 "..................", 304 "..................", 305 "..................", 306 "..................", 307 "..................", 308 "..................", 309 "....*.*...........", 310 "*****.***.........", 311 "........*.........", 312 "......***.........", 313 "......*...........", 314 "......*...........", 315 "......*...........", 316 "*****.*...........", 317 "......*...........", 318 ), 319 radius: 1.5, 320 all: true, 321 diag: true, 322 remember: []bool{false, true}, 323 324 heuristic: func(dx, dy float64) float64 { 325 return math.Max(math.Abs(dx), math.Abs(dy)) 326 }, 327 328 s: simple.Node(253), 329 t: simple.Node(122), 330 331 want: []graph.Node{ 332 simple.Node(253), 333 simple.Node(254), 334 simple.Node(255), 335 simple.Node(256), 336 simple.Node(239), 337 simple.Node(221), 338 simple.Node(203), 339 simple.Node(185), 340 simple.Node(167), 341 simple.Node(150), 342 simple.Node(151), 343 simple.Node(150), 344 simple.Node(131), 345 simple.Node(113), 346 simple.Node(96), 347 348 // The following section depends 349 // on map iteration order. 350 nil, 351 nil, 352 nil, 353 nil, 354 nil, 355 nil, 356 nil, 357 358 simple.Node(122), 359 }, 360 weight: 24.07106781186548, 361 }, 362 { 363 // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 364 // with the exception that diagonal edge weights are calculated with the hypot 365 // function instead of a step count. 366 g: testgraphs.NewGridFrom( 367 "..................", 368 "..................", 369 "..................", 370 "..................", 371 "..................", 372 "..................", 373 "....*.*...........", 374 "*****.***.........", 375 "......*...........", // Forget this wall. 376 "......***.........", 377 "......*...........", 378 "......*...........", 379 "......*...........", 380 "*****.*...........", 381 "......*...........", 382 ), 383 radius: 1.5, 384 all: true, 385 diag: true, 386 remember: []bool{true}, 387 388 modify: func(l *testgraphs.LimitedVisionGrid) { 389 all := l.Grid.AllVisible 390 l.Grid.AllVisible = false 391 for _, n := range graph.NodesOf(l.Nodes()) { 392 id := n.ID() 393 l.Known[id] = l.Grid.Node(id) == nil 394 } 395 l.Grid.AllVisible = all 396 397 const ( 398 wallRow = 8 399 wallCol = 6 400 ) 401 l.Known[l.NodeAt(wallRow, wallCol).ID()] = false 402 403 // Check we have a correctly modified representation. 404 nodes := graph.NodesOf(l.Nodes()) 405 for _, u := range nodes { 406 uid := u.ID() 407 for _, v := range nodes { 408 vid := v.ID() 409 if l.HasEdgeBetween(uid, vid) != l.Grid.HasEdgeBetween(uid, vid) { 410 ur, uc := l.RowCol(uid) 411 vr, vc := l.RowCol(vid) 412 if (ur == wallRow && uc == wallCol) || (vr == wallRow && vc == wallCol) { 413 if !l.HasEdgeBetween(uid, vid) { 414 panic(fmt.Sprintf("expected to believe edge between %v (%d,%d) and %v (%d,%d) is passable", 415 u, v, ur, uc, vr, vc)) 416 } 417 continue 418 } 419 panic(fmt.Sprintf("disagreement about edge between %v (%d,%d) and %v (%d,%d): got:%t want:%t", 420 u, v, ur, uc, vr, vc, l.HasEdgeBetween(uid, vid), l.Grid.HasEdgeBetween(uid, vid))) 421 } 422 } 423 } 424 }, 425 426 heuristic: func(dx, dy float64) float64 { 427 return math.Max(math.Abs(dx), math.Abs(dy)) 428 }, 429 430 s: simple.Node(253), 431 t: simple.Node(122), 432 433 want: []graph.Node{ 434 simple.Node(253), 435 simple.Node(254), 436 simple.Node(255), 437 simple.Node(256), 438 simple.Node(239), 439 simple.Node(221), 440 simple.Node(203), 441 simple.Node(185), 442 simple.Node(167), 443 simple.Node(149), 444 simple.Node(131), 445 simple.Node(113), 446 simple.Node(96), 447 448 // The following section depends 449 // on map iteration order. 450 nil, 451 nil, 452 nil, 453 nil, 454 nil, 455 nil, 456 nil, 457 458 simple.Node(122), 459 }, 460 weight: 21.242640687119287, 461 }, 462 { 463 g: testgraphs.NewGridFrom( 464 "*..*", 465 "**.*", 466 "**.*", 467 "**.*", 468 ), 469 radius: 1, 470 all: true, 471 diag: false, 472 remember: []bool{false, true}, 473 474 heuristic: func(dx, dy float64) float64 { 475 return math.Hypot(dx, dy) 476 }, 477 478 s: simple.Node(1), 479 t: simple.Node(14), 480 481 want: []graph.Node{ 482 simple.Node(1), 483 simple.Node(2), 484 simple.Node(6), 485 simple.Node(10), 486 simple.Node(14), 487 }, 488 weight: 4, 489 }, 490 { 491 g: testgraphs.NewGridFrom( 492 "*..*", 493 "**.*", 494 "**.*", 495 "**.*", 496 ), 497 radius: 1.5, 498 all: true, 499 diag: true, 500 remember: []bool{false, true}, 501 502 heuristic: func(dx, dy float64) float64 { 503 return math.Hypot(dx, dy) 504 }, 505 506 s: simple.Node(1), 507 t: simple.Node(14), 508 509 want: []graph.Node{ 510 simple.Node(1), 511 simple.Node(6), 512 simple.Node(10), 513 simple.Node(14), 514 }, 515 weight: math.Sqrt2 + 2, 516 }, 517 { 518 g: testgraphs.NewGridFrom( 519 "...", 520 ".*.", 521 ".*.", 522 ".*.", 523 ".*.", 524 ), 525 radius: 1, 526 all: true, 527 diag: false, 528 remember: []bool{false, true}, 529 530 heuristic: func(dx, dy float64) float64 { 531 return math.Hypot(dx, dy) 532 }, 533 534 s: simple.Node(6), 535 t: simple.Node(14), 536 537 want: []graph.Node{ 538 simple.Node(6), 539 simple.Node(9), 540 simple.Node(12), 541 simple.Node(9), 542 simple.Node(6), 543 simple.Node(3), 544 simple.Node(0), 545 simple.Node(1), 546 simple.Node(2), 547 simple.Node(5), 548 simple.Node(8), 549 simple.Node(11), 550 simple.Node(14), 551 }, 552 weight: 12, 553 }, 554 } 555 556 func TestDStarLiteDynamic(t *testing.T) { 557 t.Parallel() 558 for i, test := range dynamicDStarLiteTests { 559 for _, remember := range test.remember { 560 l := &testgraphs.LimitedVisionGrid{ 561 Grid: test.g, 562 VisionRadius: test.radius, 563 Location: test.s, 564 } 565 if remember { 566 l.Known = make(map[int64]bool) 567 } 568 569 l.Grid.AllVisible = test.all 570 571 l.Grid.AllowDiagonal = test.diag 572 l.Grid.UnitEdgeWeight = test.unit 573 574 if test.modify != nil { 575 test.modify(l) 576 } 577 578 got := []graph.Node{test.s} 579 l.MoveTo(test.s) 580 581 heuristic := func(a, b graph.Node) float64 { 582 ax, ay := l.XY(a.ID()) 583 bx, by := l.XY(b.ID()) 584 return test.heuristic(ax-bx, ay-by) 585 } 586 587 world := simple.NewWeightedDirectedGraph(0, math.Inf(1)) 588 d := NewDStarLite(test.s, test.t, l, heuristic, world) 589 var ( 590 dp *dumper 591 buf bytes.Buffer 592 ) 593 _, c := l.Grid.Dims() 594 if c <= *maxWide && (*debug || *vdebug) { 595 dp = &dumper{ 596 w: &buf, 597 598 dStarLite: d, 599 grid: l, 600 } 601 } 602 603 dp.dump(true) 604 dp.printEdges("Initial world knowledge: %s\n\n", simpleWeightedEdgesOf(l, graph.EdgesOf(world.Edges()))) 605 for d.Step() { 606 changes, _ := l.MoveTo(d.Here()) 607 got = append(got, l.Location) 608 d.UpdateWorld(changes) 609 dp.dump(true) 610 if wantedPath, ok := test.wantedPaths[l.Location.ID()]; ok { 611 gotPath, _ := d.Path() 612 if !samePath(gotPath, wantedPath) { 613 t.Errorf("unexpected intermediate path estimation for test %d %s memory:\ngot: %v\nwant:%v", 614 i, memory(remember), gotPath, wantedPath) 615 } 616 } 617 dp.printEdges("Edges changing after last step:\n%s\n\n", simpleWeightedEdgesOf(l, changes)) 618 } 619 620 if weight := weightOf(got, l.Grid); !samePath(got, test.want) || weight != test.weight { 621 t.Errorf("unexpected path for test %d %s memory got weight:%v want weight:%v:\ngot: %v\nwant:%v", 622 i, memory(remember), weight, test.weight, got, test.want) 623 b, err := l.Render(got) 624 t.Errorf("path taken (err:%v):\n%s", err, b) 625 if c <= *maxWide && (*debug || *vdebug) { 626 t.Error(buf.String()) 627 } 628 } else if c <= *maxWide && *vdebug { 629 t.Logf("Test %d:\n%s", i, buf.String()) 630 } 631 } 632 } 633 } 634 635 type memory bool 636 637 func (m memory) String() string { 638 if m { 639 return "with" 640 } 641 return "without" 642 } 643 644 // samePath compares two paths for equality ignoring nodes that are nil. 645 func samePath(a, b []graph.Node) bool { 646 if len(a) != len(b) { 647 return false 648 } 649 for i, e := range a { 650 if e == nil || b[i] == nil { 651 continue 652 } 653 if e.ID() != b[i].ID() { 654 return false 655 } 656 } 657 return true 658 } 659 660 // weightOf return the weight of the path in g. 661 func weightOf(path []graph.Node, g graph.Weighted) float64 { 662 var w float64 663 if len(path) > 1 { 664 for p, n := range path[1:] { 665 ew, ok := g.Weight(path[p].ID(), n.ID()) 666 if !ok { 667 return math.Inf(1) 668 } 669 w += ew 670 } 671 } 672 return w 673 } 674 675 // simpleWeightedEdgesOf returns the weighted edges in g corresponding to the given edges. 676 func simpleWeightedEdgesOf(g graph.Weighted, edges []graph.Edge) []simple.WeightedEdge { 677 w := make([]simple.WeightedEdge, len(edges)) 678 for i, e := range edges { 679 w[i].F = e.From() 680 w[i].T = e.To() 681 ew, _ := g.Weight(e.From().ID(), e.To().ID()) 682 w[i].W = ew 683 } 684 return w 685 }