gonum.org/v1/gonum@v0.14.0/graph/path/dynamic/dstarlite.go (about)

     1  // Copyright ©2014 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  	"container/heap"
     9  	"fmt"
    10  	"math"
    11  
    12  	"gonum.org/v1/gonum/graph"
    13  	"gonum.org/v1/gonum/graph/path"
    14  	"gonum.org/v1/gonum/graph/simple"
    15  )
    16  
    17  // DStarLite implements the D* Lite dynamic re-planning path search algorithm.
    18  //
    19  //	doi:10.1109/tro.2004.838026 and ISBN:0-262-51129-0 pp476-483
    20  type DStarLite struct {
    21  	s, t *dStarLiteNode
    22  	last *dStarLiteNode
    23  
    24  	model       WorldModel
    25  	queue       dStarLiteQueue
    26  	keyModifier float64
    27  
    28  	weight    path.Weighting
    29  	heuristic path.Heuristic
    30  }
    31  
    32  // WorldModel is a mutable weighted directed graph that returns nodes identified
    33  // by id number.
    34  type WorldModel interface {
    35  	graph.WeightedBuilder
    36  	graph.WeightedDirected
    37  }
    38  
    39  // NewDStarLite returns a new DStarLite planner for the path from s to t in g using the
    40  // heuristic h. The world model, m, is used to store shortest path information during path
    41  // planning. The world model must be an empty graph when NewDStarLite is called.
    42  //
    43  // If h is nil, the DStarLite will use the g.HeuristicCost method if g implements
    44  // path.HeuristicCoster, falling back to path.NullHeuristic otherwise. If the graph does not
    45  // implement graph.Weighter, path.UniformCost is used. NewDStarLite will panic if g has
    46  // a negative edge weight.
    47  func NewDStarLite(s, t graph.Node, g graph.Graph, h path.Heuristic, m WorldModel) *DStarLite {
    48  	/*
    49  	   procedure Initialize()
    50  	   {02”} U = ∅;
    51  	   {03”} k_m = 0;
    52  	   {04”} for all s ∈ S rhs(s) = g(s) = ∞;
    53  	   {05”} rhs(s_goal) = 0;
    54  	   {06”} U.Insert(s_goal, [h(s_start, s_goal); 0]);
    55  	*/
    56  
    57  	d := &DStarLite{
    58  		s: newDStarLiteNode(s),
    59  		t: newDStarLiteNode(t), // badKey is overwritten below.
    60  
    61  		model: m,
    62  
    63  		heuristic: h,
    64  	}
    65  	d.t.rhs = 0
    66  
    67  	/*
    68  		procedure Main()
    69  		{29”} s_last = s_start;
    70  		{30”} Initialize();
    71  	*/
    72  	d.last = d.s
    73  
    74  	if wg, ok := g.(graph.Weighted); ok {
    75  		d.weight = wg.Weight
    76  	} else {
    77  		d.weight = path.UniformCost(g)
    78  	}
    79  	if d.heuristic == nil {
    80  		if g, ok := g.(path.HeuristicCoster); ok {
    81  			d.heuristic = g.HeuristicCost
    82  		} else {
    83  			d.heuristic = path.NullHeuristic
    84  		}
    85  	}
    86  
    87  	d.queue.insert(d.t, key{d.heuristic(s, t), 0})
    88  
    89  	nodes := g.Nodes()
    90  	for nodes.Next() {
    91  		n := nodes.Node()
    92  		switch n.ID() {
    93  		case d.s.ID():
    94  			d.model.AddNode(d.s)
    95  		case d.t.ID():
    96  			d.model.AddNode(d.t)
    97  		default:
    98  			d.model.AddNode(newDStarLiteNode(n))
    99  		}
   100  	}
   101  	model := d.model.Nodes()
   102  	for model.Next() {
   103  		u := model.Node()
   104  		uid := u.ID()
   105  		to := g.From(uid)
   106  		for to.Next() {
   107  			v := to.Node()
   108  			vid := v.ID()
   109  			w := edgeWeight(d.weight, uid, vid)
   110  			if w < 0 {
   111  				panic("D* Lite: negative edge weight")
   112  			}
   113  			d.model.SetWeightedEdge(simple.WeightedEdge{F: u, T: d.model.Node(vid), W: w})
   114  		}
   115  	}
   116  
   117  	/*
   118  		procedure Main()
   119  		{31”} ComputeShortestPath();
   120  	*/
   121  	d.findShortestPath()
   122  
   123  	return d
   124  }
   125  
   126  // edgeWeight is a helper function that returns the weight of the edge between
   127  // two connected nodes, u and v, using the provided weight function. It panics
   128  // if there is no edge between u and v.
   129  func edgeWeight(weight path.Weighting, uid, vid int64) float64 {
   130  	w, ok := weight(uid, vid)
   131  	if !ok {
   132  		panic("D* Lite: unexpected invalid weight")
   133  	}
   134  	return w
   135  }
   136  
   137  // keyFor is the CalculateKey procedure in the D* Lite papers.
   138  func (d *DStarLite) keyFor(s *dStarLiteNode) key {
   139  	/*
   140  	   procedure CalculateKey(s)
   141  	   {01”} return [min(g(s), rhs(s)) + h(s_start, s) + k_m; min(g(s), rhs(s))];
   142  	*/
   143  	k := key{1: math.Min(s.g, s.rhs)}
   144  	k[0] = k[1] + d.heuristic(d.s.Node, s.Node) + d.keyModifier
   145  	return k
   146  }
   147  
   148  // update is the UpdateVertex procedure in the D* Lite papers.
   149  func (d *DStarLite) update(u *dStarLiteNode) {
   150  	/*
   151  	   procedure UpdateVertex(u)
   152  	   {07”} if (g(u) != rhs(u) AND u ∈ U) U.Update(u,CalculateKey(u));
   153  	   {08”} else if (g(u) != rhs(u) AND u /∈ U) U.Insert(u,CalculateKey(u));
   154  	   {09”} else if (g(u) = rhs(u) AND u ∈ U) U.Remove(u);
   155  	*/
   156  	inQueue := u.inQueue()
   157  	switch {
   158  	case inQueue && u.g != u.rhs:
   159  		d.queue.update(u, d.keyFor(u))
   160  	case !inQueue && u.g != u.rhs:
   161  		d.queue.insert(u, d.keyFor(u))
   162  	case inQueue && u.g == u.rhs:
   163  		d.queue.remove(u)
   164  	}
   165  }
   166  
   167  // findShortestPath is the ComputeShortestPath procedure in the D* Lite papers.
   168  func (d *DStarLite) findShortestPath() {
   169  	/*
   170  	   procedure ComputeShortestPath()
   171  	   {10”} while (U.TopKey() < CalculateKey(s_start) OR rhs(s_start) > g(s_start))
   172  	   {11”} u = U.Top();
   173  	   {12”} k_old = U.TopKey();
   174  	   {13”} k_new = CalculateKey(u);
   175  	   {14”} if(k_old < k_new)
   176  	   {15”}   U.Update(u, k_new);
   177  	   {16”} else if (g(u) > rhs(u))
   178  	   {17”}   g(u) = rhs(u);
   179  	   {18”}   U.Remove(u);
   180  	   {19”}   for all s ∈ Pred(u)
   181  	   {20”}     if (s != s_goal) rhs(s) = min(rhs(s), c(s, u) + g(u));
   182  	   {21”}     UpdateVertex(s);
   183  	   {22”} else
   184  	   {23”}   g_old = g(u);
   185  	   {24”}   g(u) = ∞;
   186  	   {25”}   for all s ∈ Pred(u) ∪ {u}
   187  	   {26”}     if (rhs(s) = c(s, u) + g_old)
   188  	   {27”}       if (s != s_goal) rhs(s) = min s'∈Succ(s)(c(s, s') + g(s'));
   189  	   {28”}     UpdateVertex(s);
   190  	*/
   191  	for d.queue.Len() != 0 { // We use d.queue.Len since d.queue does not return an infinite key when empty.
   192  		u := d.queue.top()
   193  		if !u.key.less(d.keyFor(d.s)) && d.s.rhs <= d.s.g {
   194  			break
   195  		}
   196  		uid := u.ID()
   197  		switch kNew := d.keyFor(u); {
   198  		case u.key.less(kNew):
   199  			d.queue.update(u, kNew)
   200  		case u.g > u.rhs:
   201  			u.g = u.rhs
   202  			d.queue.remove(u)
   203  			from := d.model.To(uid)
   204  			for from.Next() {
   205  				s := from.Node().(*dStarLiteNode)
   206  				sid := s.ID()
   207  				if sid != d.t.ID() {
   208  					s.rhs = math.Min(s.rhs, edgeWeight(d.model.Weight, sid, uid)+u.g)
   209  				}
   210  				d.update(s)
   211  			}
   212  		default:
   213  			gOld := u.g
   214  			u.g = math.Inf(1)
   215  			for _, _s := range append(graph.NodesOf(d.model.To(uid)), u) {
   216  				s := _s.(*dStarLiteNode)
   217  				sid := s.ID()
   218  				if s.rhs == edgeWeight(d.model.Weight, sid, uid)+gOld {
   219  					if s.ID() != d.t.ID() {
   220  						s.rhs = math.Inf(1)
   221  						to := d.model.From(sid)
   222  						for to.Next() {
   223  							t := to.Node()
   224  							tid := t.ID()
   225  							s.rhs = math.Min(s.rhs, edgeWeight(d.model.Weight, sid, tid)+t.(*dStarLiteNode).g)
   226  						}
   227  					}
   228  				}
   229  				d.update(s)
   230  			}
   231  		}
   232  	}
   233  }
   234  
   235  // Step performs one movement step along the best path towards the goal.
   236  // It returns false if no further progression toward the goal can be
   237  // achieved, either because the goal has been reached or because there
   238  // is no path.
   239  func (d *DStarLite) Step() bool {
   240  	/*
   241  	   procedure Main()
   242  	   {32”} while (s_start != s_goal)
   243  	   {33”} // if (rhs(s_start) = ∞) then there is no known path
   244  	   {34”}   s_start = argmin s'∈Succ(s_start)(c(s_start, s') + g(s'));
   245  	*/
   246  	if d.s.ID() == d.t.ID() {
   247  		return false
   248  	}
   249  	if math.IsInf(d.s.rhs, 1) {
   250  		return false
   251  	}
   252  
   253  	// We use rhs comparison to break ties
   254  	// between coequally weighted nodes.
   255  	rhs := math.Inf(1)
   256  	min := math.Inf(1)
   257  
   258  	var next *dStarLiteNode
   259  	dsid := d.s.ID()
   260  	to := d.model.From(dsid)
   261  	for to.Next() {
   262  		s := to.Node().(*dStarLiteNode)
   263  		w := edgeWeight(d.model.Weight, dsid, s.ID()) + s.g
   264  		if w < min || (w == min && s.rhs < rhs) {
   265  			next = s
   266  			min = w
   267  			rhs = s.rhs
   268  		}
   269  	}
   270  	d.s = next
   271  
   272  	/*
   273  	   procedure Main()
   274  	   {35”}   Move to s_start;
   275  	*/
   276  	return true
   277  }
   278  
   279  // MoveTo moves to n in the world graph.
   280  func (d *DStarLite) MoveTo(n graph.Node) {
   281  	d.last = d.s
   282  	d.s = d.model.Node(n.ID()).(*dStarLiteNode)
   283  	d.keyModifier += d.heuristic(d.last, d.s)
   284  }
   285  
   286  // UpdateWorld updates or adds edges in the world graph. UpdateWorld will
   287  // panic if changes include a negative edge weight.
   288  func (d *DStarLite) UpdateWorld(changes []graph.Edge) {
   289  	/*
   290  	   procedure Main()
   291  	   {36”}   Scan graph for changed edge costs;
   292  	   {37”}   if any edge costs changed
   293  	   {38”}     k_m = k_m + h(s_last, s_start);
   294  	   {39”}     s_last = s_start;
   295  	   {40”}     for all directed edges (u, v) with changed edge costs
   296  	   {41”}       c_old = c(u, v);
   297  	   {42”}       Update the edge cost c(u, v);
   298  	   {43”}       if (c_old > c(u, v))
   299  	   {44”}         if (u != s_goal) rhs(u) = min(rhs(u), c(u, v) + g(v));
   300  	   {45”}       else if (rhs(u) = c_old + g(v))
   301  	   {46”}         if (u != s_goal) rhs(u) = min s'∈Succ(u)(c(u, s') + g(s'));
   302  	   {47”}       UpdateVertex(u);
   303  	   {48”}     ComputeShortestPath()
   304  	*/
   305  	if len(changes) == 0 {
   306  		return
   307  	}
   308  	d.keyModifier += d.heuristic(d.last, d.s)
   309  	d.last = d.s
   310  	for _, e := range changes {
   311  		from := e.From()
   312  		fid := from.ID()
   313  		to := e.To()
   314  		tid := to.ID()
   315  		c, _ := d.weight(fid, tid)
   316  		if c < 0 {
   317  			panic("D* Lite: negative edge weight")
   318  		}
   319  		cOld, _ := d.model.Weight(fid, tid)
   320  		u := d.worldNodeFor(from)
   321  		v := d.worldNodeFor(to)
   322  		d.model.SetWeightedEdge(simple.WeightedEdge{F: u, T: v, W: c})
   323  		uid := u.ID()
   324  		if cOld > c {
   325  			if uid != d.t.ID() {
   326  				u.rhs = math.Min(u.rhs, c+v.g)
   327  			}
   328  		} else if u.rhs == cOld+v.g {
   329  			if uid != d.t.ID() {
   330  				u.rhs = math.Inf(1)
   331  				to := d.model.From(uid)
   332  				for to.Next() {
   333  					t := to.Node()
   334  					u.rhs = math.Min(u.rhs, edgeWeight(d.model.Weight, uid, t.ID())+t.(*dStarLiteNode).g)
   335  				}
   336  			}
   337  		}
   338  		d.update(u)
   339  	}
   340  	d.findShortestPath()
   341  }
   342  
   343  func (d *DStarLite) worldNodeFor(n graph.Node) *dStarLiteNode {
   344  	switch w := d.model.Node(n.ID()).(type) {
   345  	case *dStarLiteNode:
   346  		return w
   347  	case graph.Node:
   348  		panic(fmt.Sprintf("D* Lite: illegal world model node type: %T", w))
   349  	default:
   350  		return newDStarLiteNode(n)
   351  	}
   352  }
   353  
   354  // Here returns the current location.
   355  func (d *DStarLite) Here() graph.Node {
   356  	return d.s.Node
   357  }
   358  
   359  // Path returns the path from the current location to the goal and the
   360  // weight of the path.
   361  func (d *DStarLite) Path() (p []graph.Node, weight float64) {
   362  	u := d.s
   363  	p = []graph.Node{u.Node}
   364  	for u.ID() != d.t.ID() {
   365  		if math.IsInf(u.rhs, 1) {
   366  			return nil, math.Inf(1)
   367  		}
   368  
   369  		// We use stored rhs comparison to break
   370  		// ties between calculated rhs-coequal nodes.
   371  		rhsMin := math.Inf(1)
   372  		min := math.Inf(1)
   373  		var (
   374  			next *dStarLiteNode
   375  			cost float64
   376  		)
   377  		uid := u.ID()
   378  		to := d.model.From(uid)
   379  		for to.Next() {
   380  			v := to.Node().(*dStarLiteNode)
   381  			vid := v.ID()
   382  			w := edgeWeight(d.model.Weight, uid, vid)
   383  			if rhs := w + v.g; rhs < min || (rhs == min && v.rhs < rhsMin) {
   384  				next = v
   385  				min = rhs
   386  				rhsMin = v.rhs
   387  				cost = w
   388  			}
   389  		}
   390  		if next == nil {
   391  			return nil, math.NaN()
   392  		}
   393  		u = next
   394  		weight += cost
   395  		p = append(p, u.Node)
   396  	}
   397  	return p, weight
   398  }
   399  
   400  /*
   401  The pseudocode uses the following functions to manage the priority
   402  queue:
   403  
   404        * U.Top() returns a vertex with the smallest priority of all
   405          vertices in priority queue U.
   406        * U.TopKey() returns the smallest priority of all vertices in
   407          priority queue U. (If is empty, then U.TopKey() returns [∞;∞].)
   408        * U.Pop() deletes the vertex with the smallest priority in
   409          priority queue U and returns the vertex.
   410        * U.Insert(s, k) inserts vertex s into priority queue with
   411          priority k.
   412        * U.Update(s, k) changes the priority of vertex s in priority
   413          queue U to k. (It does nothing if the current priority of vertex
   414          s already equals k.)
   415        * Finally, U.Remove(s) removes vertex s from priority queue U.
   416  */
   417  
   418  // key is a D* Lite priority queue key.
   419  type key [2]float64
   420  
   421  // badKey is a poisoned key. Testing for a bad key uses NaN inequality.
   422  var badKey = key{math.NaN(), math.NaN()}
   423  
   424  func (k key) isBadKey() bool { return k != k }
   425  
   426  // less returns whether k is less than other. From ISBN:0-262-51129-0 pp476-483:
   427  //
   428  //	k ≤ k' iff k₁ < k'₁ OR (k₁ == k'₁ AND k₂ ≤ k'₂)
   429  func (k key) less(other key) bool {
   430  	if k.isBadKey() || other.isBadKey() {
   431  		panic("D* Lite: poisoned key")
   432  	}
   433  	return k[0] < other[0] || (k[0] == other[0] && k[1] < other[1])
   434  }
   435  
   436  // dStarLiteNode adds D* Lite accounting to a graph.Node.
   437  type dStarLiteNode struct {
   438  	graph.Node
   439  	key key
   440  	idx int
   441  	rhs float64
   442  	g   float64
   443  }
   444  
   445  // newDStarLiteNode returns a dStarLite node that is in a legal state
   446  // for existence outside the DStarLite priority queue.
   447  func newDStarLiteNode(n graph.Node) *dStarLiteNode {
   448  	return &dStarLiteNode{
   449  		Node: n,
   450  		rhs:  math.Inf(1),
   451  		g:    math.Inf(1),
   452  		key:  badKey,
   453  		idx:  -1,
   454  	}
   455  }
   456  
   457  // inQueue returns whether the node is in the queue.
   458  func (q *dStarLiteNode) inQueue() bool {
   459  	return q.idx >= 0
   460  }
   461  
   462  // dStarLiteQueue is a D* Lite priority queue.
   463  type dStarLiteQueue []*dStarLiteNode
   464  
   465  func (q dStarLiteQueue) Less(i, j int) bool {
   466  	return q[i].key.less(q[j].key)
   467  }
   468  
   469  func (q dStarLiteQueue) Swap(i, j int) {
   470  	q[i], q[j] = q[j], q[i]
   471  	q[i].idx = i
   472  	q[j].idx = j
   473  }
   474  
   475  func (q dStarLiteQueue) Len() int {
   476  	return len(q)
   477  }
   478  
   479  func (q *dStarLiteQueue) Push(x interface{}) {
   480  	n := x.(*dStarLiteNode)
   481  	n.idx = len(*q)
   482  	*q = append(*q, n)
   483  }
   484  
   485  func (q *dStarLiteQueue) Pop() interface{} {
   486  	n := (*q)[len(*q)-1]
   487  	n.idx = -1
   488  	*q = (*q)[:len(*q)-1]
   489  	return n
   490  }
   491  
   492  // top returns the top node in the queue. Note that instead of
   493  // returning a key [∞;∞] when q is empty, the caller checks for
   494  // an empty queue by calling q.Len.
   495  func (q dStarLiteQueue) top() *dStarLiteNode {
   496  	return q[0]
   497  }
   498  
   499  // insert puts the node u into the queue with the key k.
   500  func (q *dStarLiteQueue) insert(u *dStarLiteNode, k key) {
   501  	u.key = k
   502  	heap.Push(q, u)
   503  }
   504  
   505  // update updates the node in the queue identified by id with the key k.
   506  func (q *dStarLiteQueue) update(n *dStarLiteNode, k key) {
   507  	n.key = k
   508  	heap.Fix(q, n.idx)
   509  }
   510  
   511  // remove removes the node identified by id from the queue.
   512  func (q *dStarLiteQueue) remove(n *dStarLiteNode) {
   513  	heap.Remove(q, n.idx)
   514  	n.key = badKey
   515  	n.idx = -1
   516  }