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  }