gonum.org/v1/gonum@v0.14.0/graph/path/a_star.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 path 6 7 import ( 8 "container/heap" 9 10 "gonum.org/v1/gonum/graph" 11 "gonum.org/v1/gonum/graph/internal/set" 12 "gonum.org/v1/gonum/graph/traverse" 13 ) 14 15 // AStar finds the A*-shortest path from s to t in g using the heuristic h. The path and 16 // its cost are returned in a Shortest along with paths and costs to all nodes explored 17 // during the search. The number of expanded nodes is also returned. This value may help 18 // with heuristic tuning. 19 // 20 // The path will be the shortest path if the heuristic is admissible. A heuristic is 21 // admissible if for any node, n, in the graph, the heuristic estimate of the cost of 22 // the path from n to t is less than or equal to the true cost of that path. 23 // 24 // If h is nil, AStar will use the g.HeuristicCost method if g implements HeuristicCoster, 25 // falling back to NullHeuristic otherwise. If the graph does not implement Weighted, 26 // UniformCost is used. AStar will panic if g has an A*-reachable negative edge weight. 27 func AStar(s, t graph.Node, g traverse.Graph, h Heuristic) (path Shortest, expanded int) { 28 if g, ok := g.(graph.Graph); ok { 29 if g.Node(s.ID()) == nil || g.Node(t.ID()) == nil { 30 return Shortest{from: s}, 0 31 } 32 } 33 var weight Weighting 34 if wg, ok := g.(Weighted); ok { 35 weight = wg.Weight 36 } else { 37 weight = UniformCost(g) 38 } 39 if h == nil { 40 if g, ok := g.(HeuristicCoster); ok { 41 h = g.HeuristicCost 42 } else { 43 h = NullHeuristic 44 } 45 } 46 47 path = newShortestFrom(s, []graph.Node{s, t}) 48 tid := t.ID() 49 50 visited := make(set.Int64s) 51 open := &aStarQueue{indexOf: make(map[int64]int)} 52 heap.Push(open, aStarNode{node: s, gscore: 0, fscore: h(s, t)}) 53 54 for open.Len() != 0 { 55 u := heap.Pop(open).(aStarNode) 56 uid := u.node.ID() 57 i := path.indexOf[uid] 58 expanded++ 59 60 if uid == tid { 61 break 62 } 63 64 visited.Add(uid) 65 to := g.From(u.node.ID()) 66 for to.Next() { 67 v := to.Node() 68 vid := v.ID() 69 if visited.Has(vid) { 70 continue 71 } 72 j, ok := path.indexOf[vid] 73 if !ok { 74 j = path.add(v) 75 } 76 77 w, ok := weight(u.node.ID(), vid) 78 if !ok { 79 panic("path: A* unexpected invalid weight") 80 } 81 if w < 0 { 82 panic("path: A* negative edge weight") 83 } 84 g := u.gscore + w 85 if n, ok := open.node(vid); !ok { 86 path.set(j, g, i) 87 heap.Push(open, aStarNode{node: v, gscore: g, fscore: g + h(v, t)}) 88 } else if g < n.gscore { 89 path.set(j, g, i) 90 open.update(vid, g, g+h(v, t)) 91 } 92 } 93 } 94 95 return path, expanded 96 } 97 98 // NullHeuristic is an admissible, consistent heuristic that will not speed up computation. 99 func NullHeuristic(_, _ graph.Node) float64 { 100 return 0 101 } 102 103 // aStarNode adds A* accounting to a graph.Node. 104 type aStarNode struct { 105 node graph.Node 106 gscore float64 107 fscore float64 108 } 109 110 // aStarQueue is an A* priority queue. 111 type aStarQueue struct { 112 indexOf map[int64]int 113 nodes []aStarNode 114 } 115 116 func (q *aStarQueue) Less(i, j int) bool { 117 return q.nodes[i].fscore < q.nodes[j].fscore 118 } 119 120 func (q *aStarQueue) Swap(i, j int) { 121 q.indexOf[q.nodes[i].node.ID()] = j 122 q.indexOf[q.nodes[j].node.ID()] = i 123 q.nodes[i], q.nodes[j] = q.nodes[j], q.nodes[i] 124 } 125 126 func (q *aStarQueue) Len() int { 127 return len(q.nodes) 128 } 129 130 func (q *aStarQueue) Push(x interface{}) { 131 n := x.(aStarNode) 132 q.indexOf[n.node.ID()] = len(q.nodes) 133 q.nodes = append(q.nodes, n) 134 } 135 136 func (q *aStarQueue) Pop() interface{} { 137 n := q.nodes[len(q.nodes)-1] 138 q.nodes = q.nodes[:len(q.nodes)-1] 139 delete(q.indexOf, n.node.ID()) 140 return n 141 } 142 143 func (q *aStarQueue) update(id int64, g, f float64) { 144 i, ok := q.indexOf[id] 145 if !ok { 146 return 147 } 148 q.nodes[i].gscore = g 149 q.nodes[i].fscore = f 150 heap.Fix(q, i) 151 } 152 153 func (q *aStarQueue) node(id int64) (aStarNode, bool) { 154 loc, ok := q.indexOf[id] 155 if ok { 156 return q.nodes[loc], true 157 } 158 return aStarNode{}, false 159 }