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