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   * <p><p>
71   * Use amis-path-finding library instead, see svn://artemis.ms.mff.cuni.cz/pogamut/trunk/project/Utils/AmisPathFinding
72   */
73  @Deprecated
74  public class AStar<NODE> {
75  		
76  	/**
77  	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
78  	 * itself towards goal that is described by {@link AStarGoal}. Note that {@link AStarGoal} also contains a heuristic {@link AStarHeuristic}.
79  	 * <p><p>
80  	 * {@link AStarMap} provides informations about node neighbours and edge costs,
81  	 * while {@link AStarGoal} contains the definition of goal node and extra cost / extra info
82  	 * about map nodes.
83  	 * <p><p>
84  	 * You may also specify maxIterations - "how long the A* should search" equals
85  	 * 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
86  	 * all nodes are evaluated.
87  	 * 
88  	 * @param map
89  	 * @param start
90  	 * @param goal
91  	 * @param iterationsMax maximum of iterations to be made by algorithm during the search (negative number == infinite)
92  	 */
93  	public static <NODE> AStarResult<NODE> aStar(AStarMap<NODE> map, NODE start, AStarGoal<NODE> goal, long iterationsMax){
94  		
95  		// NOTE: values of the estimated cost is maintained in AStarResult
96  		// AS HEAP: we're using AStarHeap with AStarHeapComparator which is
97  		//          using data from AStarResult.estimatedCost ...
98  		//          that means you have to first alter AStarResult.estimatedCost
99  		//          before adding / decreasing key in AStarHeap
100 		
101 		AStarResult<NODE> result = new AStarResult<NODE>();
102 		
103 		AStarHeap<NODE> open = new AStarHeap<NODE>(new AStarHeapComparator<NODE>(result.estimatedCost), 64);
104 		result.openList = open;
105 		Collection<NODE> close = result.closeList;
106 		
107 		goal.setCloseList(result.closeList);
108 		goal.setOpenList(result.openList);
109 				
110 		result.startNode = start;
111 		
112 		result.putCostToNode(result.startNode, 0);
113 		result.putEstimatedCostToNode(result.startNode, goal.getEstimatedDistanceToGoal(result.startNode));
114 		open.add(result.startNode);		
115 		
116 		NODE node, nextNode;
117 		Collection<NODE> neighbours;
118 		Iterator<NODE> nodeIter;
119 		int nodePathCost, nextNodePathCost, travelCost, extraCost, estimatedPathCost,
120 		     newNextNodePathCost;
121 		
122 		while ((!open.empty()) && 
123 			   ((iterationsMax <= 0) || (result.interations < iterationsMax))
124 			  ){
125 			++result.interations; // new iteratrion begin
126 			
127 			node = open.getMin();
128 			
129 			if (node == null){ // failure
130 				result.success = false;
131 				break;
132 			}
133 			
134 			open.deleteMin();			
135 			
136             if (goal.isGoalReached(node)){ // we've reached the goal HURRAY!
137             	result.goalNode = node;
138             	result.success = true;
139             	break;
140             }
141             
142             nodePathCost = result.getCostToNode(node);
143             
144             neighbours = map.getNodeNeighbours(node);
145             nodeIter = neighbours.iterator();
146             
147             while (nodeIter.hasNext()){
148             	nextNode = nodeIter.next();
149            		// iterate over all of the neighbours node
150 			    // and evaluate them one by one
151             	
152             	if (!goal.isNodeOpened(nextNode)){  // stepping to this node is forbidden, skip it
153             		continue;
154             	}
155             	
156             	travelCost = map.getEdgeCost(node, nextNode);
157             	extraCost = goal.getExtraCost(node, nextNode);
158             	
159             	nextNodePathCost = result.getCostToNode(nextNode);
160             	if (nextNodePathCost == -1){ 
161             		// we've never touched nextNode
162             		nextNodePathCost = nodePathCost + travelCost + extraCost;
163             		if (nextNodePathCost < 0) nextNodePathCost = 0;
164             		result.putCostToNode(nextNode, nextNodePathCost);
165             		result.putPreviousNode(nextNode, node);
166             		
167             		estimatedPathCost = nextNodePathCost + goal.getEstimatedDistanceToGoal(nextNode);
168             		result.putEstimatedCostToNode(nextNode, estimatedPathCost);
169             		
170             		open.add(nextNode);
171             		continue;
172             	} else {                     
173             		// we've already touched the nextNode                     
174             		newNextNodePathCost = nodePathCost + travelCost + extraCost;
175             		if (newNextNodePathCost < 0) newNextNodePathCost = 0;
176             		if (newNextNodePathCost < nextNodePathCost){            			
177             			estimatedPathCost = newNextNodePathCost + goal.getEstimatedDistanceToGoal(nextNode);
178             			result.putCostToNode(nextNode, newNextNodePathCost);
179             			result.putEstimatedCostToNode(nextNode, estimatedPathCost);
180             			if (close.contains(nextNode)){
181             				close.remove(nextNode);            				
182             				open.add(nextNode);
183             			} else 
184             				if (open.contains(nextNode)){
185             					open.decreaseKey(node);
186             				} else {
187             					open.add(nextNode);
188             				}
189             		}
190                 	// if estimatedCost is higher or equal, we don't have to take any actions
191             		continue;
192             	}
193             }            
194             close.add(node);
195 		}
196 		
197 		return result;
198 	}
199 
200 	/**
201 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
202 	 * itself towards goal that is described by {@link AStarGoal}. Note that {@link AStarGoal} also contains a heuristic {@link AStarHeuristic}.
203 	 * <p><p>
204 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
205 	 * while {@link AStarGoal} contains the definition of goal node and extra cost / extra info
206 	 * about map nodes.
207 	 * <p><p>
208 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
209 	 * 
210 	 * @param <NODE>
211 	 * @param map
212 	 * @param start
213 	 * @param goal
214 	 * @return
215 	 */
216 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final NODE start, final AStarGoal<NODE> goal) {
217 		return aStar(map, start, goal, -1);
218 	}
219 	
220 	/**
221 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
222 	 * itself towards 'goal' using heuristic and extra costs defined by {@link AStarEvaluator}.
223 	 * <p><p>
224 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
225 	 * while {@link AStarEvaluator} contains definition of the heuristic + extra edge info about map nodes.
226 	 * <p><p>
227 	 * You may also specify maxIterations - "how long the A* should search" equals
228 	 * 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
229 	 * all nodes are evaluated.
230 	 * <p><p>
231 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
232 	 * to recognized whether the current evaluated node is the same as the goal or not!
233 	 * 
234 	 * @param <NODE>
235 	 * @param map
236 	 * @param evaluator
237 	 * @param start
238 	 * @param goal
239 	 * @param maxIterations maximum of iterations to be made by algorithm during the search (negative number == infinite)
240 	 * @return
241 	 */
242 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarEvaluator<NODE> evaluator, final NODE start, final NODE goal, int maxIterations) {
243 		return
244 			aStar(
245 				map, 
246 				start, 
247 					new AStarGoal<NODE>() {
248 			
249 						@Override
250 						public boolean isGoalReached(NODE actualNode) {
251 							return SafeEquals.equals(actualNode, goal);
252 						}
253 			
254 						@Override
255 						public void setCloseList(Collection<NODE> closeList) {
256 							// NOT NEEDED
257 						}
258 			
259 						@Override
260 						public void setOpenList(Collection<NODE> openList) {
261 							// NOT NEEDED
262 						}
263 			
264 						@Override
265 						public int getExtraCost(NODE nodeFrom, NODE nodeTo) {
266 							return evaluator.getExtraCost(nodeFrom, nodeTo);
267 						}
268 			
269 						@Override
270 						public boolean isNodeOpened(NODE node) {
271 							return evaluator.isNodeOpened(node);
272 						}
273 			
274 						@Override
275 						public int getEstimatedDistanceToGoal(NODE node) {
276 							return evaluator.getEstimatedDistanceToGoal(node);
277 						}
278 					
279 					},
280 				maxIterations
281 		);
282 	}
283 		
284 	/**
285 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
286 	 * itself towards 'goal' using heuristic and extra costs defined by {@link AStarEvaluator}.
287 	 * <p><p>
288 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
289 	 * while {@link AStarEvaluator} contains definition of the heuristic + extra edge info about map nodes.
290 	 * <p><p>
291 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
292 	 * <p><p>
293 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
294 	 * to recognized whether the current evaluated node is the same as the goal or not!
295 	 * 
296 	 * @param <NODE>
297 	 * @param map
298 	 * @param evaluator
299 	 * @param start
300 	 * @param goal
301 	 * @return
302 	 */
303 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarEvaluator<NODE> evaluator, final NODE start, final NODE goal) {
304 		return aStar(map, evaluator, start, goal, -1);
305 	}
306 	
307 	/**
308 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
309 	 * itself towards 'goal' using heuristic defined by {@link AStarHeuristic}.
310 	 * <p><p>
311 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
312 	 * while {@link AStarHeuristic} contains definition of the heuristic.
313 	 * <p><p>
314 	 * You may also specify maxIterations - "how long the A* should search" equals
315 	 * 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
316 	 * all nodes are evaluated.
317 	 * <p><p>
318 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
319 	 * to recognized whether the current evaluated node is the same as the goal or not!
320 	 * 
321 	 * @param <NODE>
322 	 * @param map
323 	 * @param evaluator
324 	 * @param start
325 	 * @param goal
326 	 * @param maxIterations
327 	 * @return
328 	 */
329 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarHeuristic<NODE> heuristic, final NODE start, final NODE goal, int maxIterations) {
330 		return aStar(map, start, new AStarGoal<NODE>() {
331 
332 			@Override
333 			public boolean isGoalReached(NODE actualNode) {
334 				return SafeEquals.equals(actualNode, goal);
335 			}
336 
337 			@Override
338 			public void setCloseList(Collection<NODE> closeList) {
339 				// NOT NEEDED
340 			}
341 
342 			@Override
343 			public void setOpenList(Collection<NODE> openList) {
344 				// NOT NEEDED
345 			}
346 
347 			@Override
348 			public int getExtraCost(NODE nodeFrom, NODE nodeTo) {
349 				return 0;
350 			}
351 
352 			@Override
353 			public boolean isNodeOpened(NODE node) {
354 				return true;
355 			}
356 
357 			@Override
358 			public int getEstimatedDistanceToGoal(NODE node) {
359 				return heuristic.getEstimatedDistanceToGoal(node);
360 			}
361 			
362 		},
363 		maxIterations);
364 	}
365 		
366 	/**
367 	 * Method performing an AStar search over graph defined inside {@link AStarMap} starting from 'start' node driving
368 	 * itself towards 'goal' using heuristic defined by {@link AStarHeuristic}.
369 	 * <p><p>
370 	 * {@link AStarMap} provides informations about node neighbours and edge costs,
371 	 * while {@link AStarHeuristic} contains definition of the heuristic.
372 	 * <p><p>
373 	 * This method performs 'unbounded' AStar search, i.e., it does not limit a number of iterations the algorithm will perform.
374 	 * <p><p>
375 	 * <b>WARNING</b>: Class that is used for NODE must have correctly defined {@link Object#equals(Object)} because it will be used
376 	 * to recognized whether the current evaluated node is the same as the goal or not!
377 	 * 
378 	 * @param <NODE>
379 	 * @param map
380 	 * @param heuristic
381 	 * @param start
382 	 * @param goal
383 	 * @return
384 	 */
385 	public static <NODE> AStarResult<NODE> aStar(final AStarMap<NODE> map, final AStarHeuristic<NODE> heuristic, final NODE start, final NODE goal) {
386 		return aStar(map, heuristic, start, goal, -1);
387 	}
388 
389 }