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 }