1
+ """
2
+ A*
3
+
4
+ Author: Timothy Moore
5
+
6
+ A* is an informed search algorithm, or a best-first search,
7
+ meaning that it solves problems by searching among all possible
8
+ paths to the solution (goal) for the one that incurs the smallest
9
+ cost (least distance travelled, shortest time, etc.), and among
10
+ these paths it first considers the ones that appear to lead most
11
+ quickly to the solution.
12
+ https://en.wikipedia.org/wiki/A*_search_algorithm
13
+ """
1
14
import heapq
15
+ import inspect
16
+
2
17
3
18
class OneDirectionalAStar (object ):
4
19
"""AStar object
@@ -28,7 +43,7 @@ class OneDirectionalAStar(object):
28
43
#
29
44
# You would expand your side of center and find the top and bottom at
30
45
# (5 + 12) - WORSE than just going to them. This is the case where we
31
- # would NOT add the path base->center->top to the open list because
46
+ # would NOT add the path base->center->top to the _open list because
32
47
# (for these weights) it will never be better than base->top.
33
48
#
34
49
# You would also add the new node (55 + 0) or the destination.
@@ -37,14 +52,15 @@ class OneDirectionalAStar(object):
37
52
# river with a cost of (18 + 12).
38
53
#
39
54
# You expand the top node on the other side of the river next and find
40
- # one of the neighbors is already on the open list (the destination)
55
+ # one of the neighbors is already on the _open list (the destination)
41
56
# at a score of (55 + 0), but your cost to get there is (30 + 0). This
42
57
# is where you would REPLACE the old path with yourself.
43
58
44
59
def __init__ (self ):
45
60
pass
46
-
47
- def reverse_path (self , node ):
61
+
62
+ @staticmethod
63
+ def reverse_path (node ):
48
64
"""
49
65
Walks backward from an end node to the start
50
66
node and reconstructs a path. Meant for internal
@@ -75,7 +91,7 @@ def find_path(self, graph, start, end, heuristic_fn):
75
91
- The distance between nodes are small
76
92
- There are too many nodes for an exhaustive search
77
93
to ever be feasible.
78
- - The world is mostly open (ie there are many paths
94
+ - The world is mostly _open (ie there are many paths
79
95
from the start to the end that are acceptable)
80
96
- Execution speed is more important than accuracy.
81
97
The best way to do this is to make the heuristic slightly
@@ -94,21 +110,21 @@ def find_path(self, graph, start, end, heuristic_fn):
94
110
"""
95
111
96
112
# It starts off very similiar to Dijkstra. However, we will need to lookup
97
- # nodes in the open list before. There can be thousands of nodes in the open
113
+ # nodes in the _open list before. There can be thousands of nodes in the _open
98
114
# list and any unordered search is too expensive, so we trade some memory usage for
99
115
# more consistent performance by maintaining a dictionary (O(1) lookup) between
100
116
# vertices and their nodes.
101
- open_lookup = {}
102
- open = []
117
+ _open_lookup = {}
118
+ _open = []
103
119
closed = set ()
104
120
105
121
# We require a bit more information on each node than Dijkstra
106
122
# and we do slightly more calculation, so the heuristic must
107
123
# prune enough nodes to offset those costs. In practice this
108
- # is almost always the case if their are any large open areas
124
+ # is almost always the case if their are any large _open areas
109
125
# (nodes with many connected nodes).
110
126
111
- # Rather than simply expanding nodes that are on the open list
127
+ # Rather than simply expanding nodes that are on the _open list
112
128
# based on how close they are to the start, we will expand based
113
129
# on how much distance we predict is between the start and end
114
130
# node IF we go through that parent. That is a combination of
@@ -120,15 +136,19 @@ def find_path(self, graph, start, end, heuristic_fn):
120
136
121
137
counter = 0
122
138
heur = heuristic_fn (graph , start , end )
123
- open_lookup [start ] = { 'vertex' : start , 'dist_start_to_here' : 0 , 'pred_dist_here_to_end' : heur , 'pred_total_dist' : heur , 'parent' : None }
124
- heapq .heappush (open , (heur , counter , start ))
139
+ _open_lookup [start ] = {'vertex' : start ,
140
+ 'dist_start_to_here' : 0 ,
141
+ 'pred_dist_here_to_end' : heur ,
142
+ 'pred_total_dist' : heur ,
143
+ 'parent' : None }
144
+ heapq .heappush (_open , (heur , counter , start ))
125
145
counter += 1
126
146
127
- while len (open ) > 0 :
128
- current = heapq .heappop (open )
147
+ while len (_open ) > 0 :
148
+ current = heapq .heappop (_open )
129
149
current_vertex = current [2 ]
130
- current_dict = open_lookup [current_vertex ]
131
- del open_lookup [current_vertex ]
150
+ current_dict = _open_lookup [current_vertex ]
151
+ del _open_lookup [current_vertex ]
132
152
closed .update (current_vertex )
133
153
134
154
if current_vertex == end :
@@ -142,8 +162,10 @@ def find_path(self, graph, start, end, heuristic_fn):
142
162
# node first.
143
163
continue
144
164
145
- cost_start_to_neighbor = current_dict ['dist_start_to_here' ] + graph .get_edge_weight (current_vertex , neighbor )
146
- neighbor_from_lookup = open_lookup .get (neighbor , None ) # avoid searching twice
165
+ cost_start_to_neighbor = current_dict ['dist_start_to_here' ] \
166
+ + graph .get_edge_weight (current_vertex , neighbor )
167
+ # avoid searching twice
168
+ neighbor_from_lookup = _open_lookup .get (neighbor , None )
147
169
if neighbor_from_lookup is not None :
148
170
# If our heuristic is NOT consistent or the grid is NOT uniform,
149
171
# it is possible that there is a better path to a neighbor of a
@@ -158,50 +180,49 @@ def find_path(self, graph, start, end, heuristic_fn):
158
180
if cost_start_to_neighbor < old_dist_start_to_neighbor :
159
181
pred_dist_neighbor_to_end = neighbor_from_lookup ['pred_dist_here_to_end' ]
160
182
pred_total_dist_through_neighbor_to_end = cost_start_to_neighbor + pred_dist_neighbor_to_end
161
- # Note, we've already shown that neighbor (the vector) is already in the open list,
183
+ # Note, we've already shown that neighbor (the vector) is already in the _open list,
162
184
# but unfortunately we don't know where and we have to do a slow lookup to fix the
163
185
# key its sorting by to the new predicted total distance.
164
186
165
187
# In case we're using a fancy debugger we want to search in user-code so when
166
188
# this lookup freezes we can see how much longer its going to take.
167
189
found = None
168
- for i in range (0 , len (open )):
169
- if open [i ][2 ] == neighbor :
190
+ for i in range (0 , len (_open )):
191
+ if _open [i ][2 ] == neighbor :
170
192
found = i
171
193
break
172
194
if found is None :
173
- raise Exception ('A vertex is in the open lookup but not in open. This is impossible, please submit an issue + include the graph!' )
174
- # todo I'm not certain about the performance characteristics of doing this with heapq, nor if
175
- # it would be better to delete heapify and push or rather than replace
176
- open [i ] = (pred_total_dist_through_neighbor_to_end , counter , neighbor )
195
+ raise Exception ('A vertex is in the _open lookup but not in _open. '
196
+ 'This is impossible, please submit an issue + include the graph!' )
197
+ # TODO: I'm not certain about the performance characteristics of doing this with heapq, nor if
198
+ # TODO: it would be better to delete heapify and push or rather than replace
199
+
200
+ # TODO: Local variable 'i' could be referenced before assignment
201
+ _open [i ] = (pred_total_dist_through_neighbor_to_end , counter , neighbor )
177
202
counter += 1
178
- heapq .heapify (open )
179
- open_lookup [neighbor ] = { 'vertex' : neighbor ,
203
+ heapq .heapify (_open )
204
+ _open_lookup [neighbor ] = {'vertex' : neighbor ,
180
205
'dist_start_to_here' : cost_start_to_neighbor ,
181
206
'pred_dist_here_to_end' : pred_dist_neighbor_to_end ,
182
207
'pred_total_dist' : pred_total_dist_through_neighbor_to_end ,
183
- 'parent' : current_dict }
208
+ 'parent' : current_dict }
184
209
continue
185
-
186
-
210
+
187
211
# We've found the first possible way to the path!
188
212
pred_dist_neighbor_to_end = heuristic_fn (graph , neighbor , end )
189
213
pred_total_dist_through_neighbor_to_end = cost_start_to_neighbor + pred_dist_neighbor_to_end
190
- heapq .heappush (open , (pred_total_dist_through_neighbor_to_end , counter , neighbor ))
191
- open_lookup [neighbor ] = { 'vertex' : neighbor ,
214
+ heapq .heappush (_open , (pred_total_dist_through_neighbor_to_end , counter , neighbor ))
215
+ _open_lookup [neighbor ] = {'vertex' : neighbor ,
192
216
'dist_start_to_here' : cost_start_to_neighbor ,
193
217
'pred_dist_here_to_end' : pred_dist_neighbor_to_end ,
194
218
'pred_total_dist' : pred_total_dist_through_neighbor_to_end ,
195
- 'parent' : current_dict }
219
+ 'parent' : current_dict }
196
220
197
221
return None
198
222
199
223
@staticmethod
200
- def get_code (self ):
224
+ def get_code ():
201
225
"""
202
226
returns the code for the current class
203
227
"""
204
228
return inspect .getsource (OneDirectionalAStar )
205
-
206
-
207
-
0 commit comments