View Javadoc

1   package cz.cuni.amis.utils.astar;
2   
3   import java.util.Collection;
4   import java.util.Iterator;
5   
6   import cz.cuni.amis.utils.SafeEquals;
7   
8   /**
9    * ========================================================
10   * This file holds implementation of generic A* algorithm,
11   * better refered to as A* Machine according to 
12   * Dan Higgins, Generic A* Pathfind, AI Gaming Wisdom, 2002
13   * ========================================================
14   * <p><p>
15   * 
16   * What is A*<p>
17   * ----------<p>
18   * A* is space-search algorithm using a custom-built heuristic. It's an improved
19   * version of well-known Dijkstra algorithm which is used to find the shortest
20   * path in weighted graphs. Instead of picking the node with the smallest path
21   * from the start node it chooses node which seems to be on the shortest path
22   * to the goal node (and this guess is based on provided heuristic).
23   * <p><p>
24   * Note<p>
25   * ----<p>
26   * Insted of weights we speak about cost of the edges. 
27   * <p><p>
28   * Limitation of A*<p>
29   * ----------------<p>
30   * 1) A* doesn't work over graphs with negative edge costs.<p>
31   * 2) heuristic has to be correct -> it has to be lower estimation of the cost to
32   *    the goal node (in 2D, 3D an euklidian metric will do the job).<p>
33   * <p>
34   * First we have to specify some interfaces for A*<p>
35   * -----------------------------------------------<p>
36   * we will need a few things: <p>
37   *                            Open List Class<p> 
38   *                            Close List Class<p>
39   *                            Goal (which can tell us about extra costs, when work is done, etc)<p>
40   *                            Map  (which tells us about travel cost between nodes and can return
41   *                                  node's neighbours)<p>
42   * <p>                                 
43   * Note about Nodes<p>
44   * ----------------<p>
45   * Note that we don't need to have a Node interface so you're free to have
46   * any nodes you want (POJOs). But implementation of A* requires the nodes to have
47   * hashCode() and equals() implemented correctly, which should be a good practice!
48   * (Note that also means you can't have two nodes which are equals in the map!)
49   * <p><p>
50   * Idea behind AStarGoal / AStarMap<p>
51   * --------------------------------<p>
52   * Usually you will have only one world / state space representation but you need to
53   * change the cost of edges between nodes according to let say creature for which you
54   * search the path.
55   * <p><p>
56   * Imagine the situation with the lake / human / fish.
57   * Human may swim across the lake but it's faster to run around it (so you need to give the edges between
58   * water tiles an extra cost).<p>
59   * Fish can swim really fast but can't get out of the water (so you need to forbid tiles around the lake
60   * and give the edges between the lakes' tiles an negative extra cost).<p>
61   * <p>
62   * So the AStarMap will represent the world with the lake with default cost of the edges.
63   * AStarGoal may change the edges cost / forbid some nodes completely. So you will
64   * implement one goal for a human and another for a fish.
65   * <p><p>
66   * Note about the speed<p>
67   * --------------------<p>
68   * Speed of algorithm is based upon the speed of AStarOpenList and AStarCloseList.
69   */
70  public class AStar<NODE> {
71  		
72  	/**
73  	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
74  	 * itself towards goal that is described by {@link AStarGoal}. Note that {@link AStarGoal} also contains a heuristic {@link AStarHeuristic}.
75  	 * <p><p>
76  	 * {@link AStarMap} provides informations about node neighbours and edge costs,
77  	 * while {@link AStarGoal} contains the definition of goal node and extra cost / extra info
78  	 * about map nodes.
79  	 * <p><p>
80  	 * You may also specify maxIterations - "how long the A* should search" equals
81  	 * to number of evaluated nodes. If it's 0 then A* won't even start! If it is < 0 it will run until the 'goal' is found
82  	 * all nodes are evaluated.
83  	 * 
84  	 * @param map
85  	 * @param start
86  	 * @param goal
87  	 * @param iterationsMax maximum of iterations to be made by algorithm during the search (negative number == infinite)
88  	 */
89  	public static <NODE> AStarResult<NODE> aStar(AStarMap<NODE> map, NODE start, AStarGoal<NODE> goal, long iterationsMax){
90  		
91  		// NOTE: values of the estimated cost is maintained in AStarResult
92  		// AS HEAP: we're using AStarHeap with AStarHeapComparator which is
93  		//          using data from AStarResult.estimatedCost ...
94  		//          that means you have to first alter AStarResult.estimatedCost
95  		//          before adding / decreasing key in AStarHeap
96  		
97  		AStarResult<NODE> result = new AStarResult<NODE>();
98  		
99  		AStarHeap<NODE> open = new AStarHeap<NODE>(new AStarHeapComparator<NODE>(result.estimatedCost), 64);
100 		result.openList = open;
101 		Collection<NODE> close = result.closeList;
102 		
103 		goal.setCloseList(result.closeList);
104 		goal.setOpenList(result.openList);
105 				
106 		result.startNode = start;
107 		
108 		result.putCostToNode(result.startNode, 0);
109 		result.putEstimatedCostToNode(result.startNode, goal.getEstimatedDistanceToGoal(result.startNode));
110 		open.add(result.startNode);		
111 		
112 		NODE node, nextNode;
113 		Collection<NODE> neighbours;
114 		Iterator<NODE> nodeIter;
115 		int nodePathCost, nextNodePathCost, travelCost, extraCost, estimatedPathCost,
116 		     newNextNodePathCost;
117 		
118 		while ((!open.empty()) && 
119 			   ((iterationsMax <= 0) || (result.interations < iterationsMax))
120 			  ){
121 			++result.interations; // new iteratrion begin
122 			
123 			node = open.getMin();
124 			
125 			if (node == null){ // failure
126 				result.success = false;
127 				break;
128 			}
129 			
130 			open.deleteMin();			
131 			
132             if (goal.isGoalReached(node)){ // we've reached the goal HURRAY!
133             	result.goalNode = node;
134             	result.success = true;
135             	break;
136             }
137             
138             nodePathCost = result.getCostToNode(node);
139             
140             neighbours = map.getNodeNeighbours(node);
141             nodeIter = neighbours.iterator();
142             
143             while (nodeIter.hasNext()){
144             	nextNode = nodeIter.next();
145            		// iterate over all of the neighbours node
146 			    // and evaluate them one by one
147             	
148             	if (!goal.isNodeOpened(nextNode)){  // stepping to this node is forbidden, skip it
149             		continue;
150             	}
151             	
152             	travelCost = map.getEdgeCost(node, nextNode);
153             	extraCost = goal.getExtraCost(node, nextNode);
154             	
155             	nextNodePathCost = result.getCostToNode(nextNode);
156             	if (nextNodePathCost == -1){ 
157             		// we've never touched nextNode
158             		nextNodePathCost = nodePathCost + travelCost + extraCost;
159             		if (nextNodePathCost < 0) nextNodePathCost = 0;
160             		result.putCostToNode(nextNode, nextNodePathCost);
161             		result.putPreviousNode(nextNode, node);
162             		
163             		estimatedPathCost = nextNodePathCost + goal.getEstimatedDistanceToGoal(nextNode);
164             		result.putEstimatedCostToNode(nextNode, estimatedPathCost);
165             		
166             		open.add(nextNode);
167             		continue;
168             	} else {                     
169             		// we've already touched the nextNode                     
170             		newNextNodePathCost = nodePathCost + travelCost + extraCost;
171             		if (newNextNodePathCost < 0) newNextNodePathCost = 0;
172             		if (newNextNodePathCost < nextNodePathCost){            			
173             			estimatedPathCost = newNextNodePathCost + goal.getEstimatedDistanceToGoal(nextNode);
174             			result.putCostToNode(nextNode, newNextNodePathCost);
175             			result.putEstimatedCostToNode(nextNode, estimatedPathCost);
176             			if (close.contains(nextNode)){
177             				close.remove(nextNode);            				
178             				open.add(nextNode);
179             			} else 
180             				if (open.contains(nextNode)){
181             					open.decreaseKey(node);
182             				} else {
183             					open.add(nextNode);
184             				}
185             		}
186                 	// if estimatedCost is higher or equal, we don't have to take any actions
187             		continue;
188             	}
189             }            
190             close.add(node);
191 		}
192 		
193 		return result;
194 	}
195 
196 	/**
197 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
198 	 * itself towards goal that is described by {@link AStarGoal}. Note that {@link AStarGoal} also contains a heuristic {@link AStarHeuristic}.
199 	 * <p><p>
200 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
201 	 * while {@link AStarGoal} contains the definition of goal node and extra cost / extra info
202 	 * about map nodes.
203 	 * <p><p>
204 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
205 	 * 
206 	 * @param <NODE>
207 	 * @param map
208 	 * @param start
209 	 * @param goal
210 	 * @return
211 	 */
212 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final NODE start, final AStarGoal<NODE> goal) {
213 		return aStar(map, start, goal, -1);
214 	}
215 	
216 	/**
217 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
218 	 * itself towards 'goal' using heuristic and extra costs defined by {@link AStarEvaluator}.
219 	 * <p><p>
220 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
221 	 * while {@link AStarEvaluator} contains definition of the heuristic + extra edge info about map nodes.
222 	 * <p><p>
223 	 * You may also specify maxIterations - "how long the A* should search" equals
224 	 * to number of evaluated nodes. If it's 0 then A* won't even start! If it is < 0 it will run until the 'goal' is found
225 	 * all nodes are evaluated.
226 	 * <p><p>
227 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
228 	 * to recognized whether the current evaluated node is the same as the goal or not!
229 	 * 
230 	 * @param <NODE>
231 	 * @param map
232 	 * @param evaluator
233 	 * @param start
234 	 * @param goal
235 	 * @param maxIterations maximum of iterations to be made by algorithm during the search (negative number == infinite)
236 	 * @return
237 	 */
238 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarEvaluator<NODE> evaluator, final NODE start, final NODE goal, int maxIterations) {
239 		return
240 			aStar(
241 				map, 
242 				start, 
243 					new AStarGoal<NODE>() {
244 			
245 						@Override
246 						public boolean isGoalReached(NODE actualNode) {
247 							return SafeEquals.equals(actualNode, goal);
248 						}
249 			
250 						@Override
251 						public void setCloseList(Collection<NODE> closeList) {
252 							// NOT NEEDED
253 						}
254 			
255 						@Override
256 						public void setOpenList(Collection<NODE> openList) {
257 							// NOT NEEDED
258 						}
259 			
260 						@Override
261 						public int getExtraCost(NODE nodeFrom, NODE nodeTo) {
262 							return evaluator.getExtraCost(nodeFrom, nodeTo);
263 						}
264 			
265 						@Override
266 						public boolean isNodeOpened(NODE node) {
267 							return evaluator.isNodeOpened(node);
268 						}
269 			
270 						@Override
271 						public int getEstimatedDistanceToGoal(NODE node) {
272 							return evaluator.getEstimatedDistanceToGoal(node);
273 						}
274 					
275 					},
276 				maxIterations
277 		);
278 	}
279 		
280 	/**
281 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
282 	 * itself towards 'goal' using heuristic and extra costs defined by {@link AStarEvaluator}.
283 	 * <p><p>
284 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
285 	 * while {@link AStarEvaluator} contains definition of the heuristic + extra edge info about map nodes.
286 	 * <p><p>
287 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
288 	 * <p><p>
289 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
290 	 * to recognized whether the current evaluated node is the same as the goal or not!
291 	 * 
292 	 * @param <NODE>
293 	 * @param map
294 	 * @param evaluator
295 	 * @param start
296 	 * @param goal
297 	 * @return
298 	 */
299 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarEvaluator<NODE> evaluator, final NODE start, final NODE goal) {
300 		return aStar(map, evaluator, start, goal, -1);
301 	}
302 	
303 	/**
304 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
305 	 * itself towards 'goal' using heuristic defined by {@link AStarHeuristic}.
306 	 * <p><p>
307 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
308 	 * while {@link AStarHeuristic} contains definition of the heuristic.
309 	 * <p><p>
310 	 * You may also specify maxIterations - "how long the A* should search" equals
311 	 * to number of evaluated nodes. If it's 0 then A* won't even start! If it is < 0 it will run until the 'goal' is found
312 	 * all nodes are evaluated.
313 	 * <p><p>
314 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
315 	 * to recognized whether the current evaluated node is the same as the goal or not!
316 	 * 
317 	 * @param <NODE>
318 	 * @param map
319 	 * @param evaluator
320 	 * @param start
321 	 * @param goal
322 	 * @param maxIterations
323 	 * @return
324 	 */
325 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarHeuristic<NODE> heuristic, final NODE start, final NODE goal, int maxIterations) {
326 		return aStar(map, start, new AStarGoal<NODE>() {
327 
328 			@Override
329 			public boolean isGoalReached(NODE actualNode) {
330 				return SafeEquals.equals(actualNode, goal);
331 			}
332 
333 			@Override
334 			public void setCloseList(Collection<NODE> closeList) {
335 				// NOT NEEDED
336 			}
337 
338 			@Override
339 			public void setOpenList(Collection<NODE> openList) {
340 				// NOT NEEDED
341 			}
342 
343 			@Override
344 			public int getExtraCost(NODE nodeFrom, NODE nodeTo) {
345 				return 0;
346 			}
347 
348 			@Override
349 			public boolean isNodeOpened(NODE node) {
350 				return true;
351 			}
352 
353 			@Override
354 			public int getEstimatedDistanceToGoal(NODE node) {
355 				return heuristic.getEstimatedDistanceToGoal(node);
356 			}
357 			
358 		},
359 		maxIterations);
360 	}
361 		
362 	/**
363 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
364 	 * itself towards 'goal' using heuristic defined by {@link AStarHeuristic}.
365 	 * <p><p>
366 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
367 	 * while {@link AStarHeuristic} contains definition of the heuristic.
368 	 * <p><p>
369 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
370 	 * <p><p>
371 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
372 	 * to recognized whether the current evaluated node is the same as the goal or not!
373 	 * 
374 	 * @param <NODE>
375 	 * @param map
376 	 * @param heuristic
377 	 * @param start
378 	 * @param goal
379 	 * @return
380 	 */
381 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarHeuristic<NODE> heuristic, final NODE start, final NODE goal) {
382 		return aStar(map, heuristic, start, goal, -1);
383 	}
384 
385 }