ida_star.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import time
  2. from pathfinding.core.heuristic import manhatten, octile
  3. from pathfinding.core.diagonal_movement import DiagonalMovement
  4. from pathfinding.core.node import Node
  5. from .finder import Finder, TIME_LIMIT, MAX_RUNS
  6. class IDAStarFinder(Finder):
  7. """
  8. Iterative Deeping A Star (IDA*) path-finder.
  9. Recursion based on:
  10. http://www.apl.jhu.edu/~hall/AI-Programming/IDA-Star.html
  11. Path retracing based on:
  12. V. Nageshwara Rao, Vipin Kumar and K. Ramesh
  13. "A Parallel Implementation of Iterative-Deeping-A*", January 1987.
  14. ftp://ftp.cs.utexas.edu/.snapshot/hourly.1/pub/AI-Lab/tech-reports/
  15. UT-AI-TR-87-46.pdf
  16. based on the JavaScript implementation by Gerard Meier
  17. (www.gerardmeier.com)
  18. """
  19. def __init__(self, heuristic=None, weight=1,
  20. diagonal_movement=DiagonalMovement.never,
  21. time_limit=TIME_LIMIT,
  22. max_runs=MAX_RUNS,
  23. track_recursion=True):
  24. super(IDAStarFinder, self).__init__(
  25. heuristic=heuristic, weight=weight,
  26. diagonal_movement=diagonal_movement,
  27. weighted=False,
  28. time_limit=time_limit,
  29. max_runs=max_runs)
  30. self.track_recursion = track_recursion
  31. if not heuristic:
  32. if diagonal_movement == DiagonalMovement.never:
  33. self.heuristic = manhatten
  34. else:
  35. # When diagonal movement is allowed the manhattan heuristic is
  36. # not admissible it should be octile instead
  37. self.heuristic = octile
  38. def search(self, node, g, cutoff, path, depth, end, grid):
  39. self.runs += 1
  40. self.keep_running()
  41. self.nodes_visited += 1
  42. f = g + self.apply_heuristic(node, end) * self.weight
  43. # We've searched too deep for this iteration.
  44. if f > cutoff:
  45. return f
  46. if node == end:
  47. if len(path) < depth:
  48. path += [None] * (depth - len(path) + 1)
  49. path[depth] = node
  50. return node
  51. neighbors = self.find_neighbors(grid, node)
  52. # Sort the neighbors, gives nicer paths. But, this deviates
  53. # from the original algorithm - so I left it out
  54. # TODO: make this an optional parameter
  55. # def sort_neighbors(a, b):
  56. # return self.apply_heuristic(a, end) - \
  57. # self.apply_heuristic(b, end)
  58. # sorted(neighbors, sort_neighbors)
  59. min_t = float('inf')
  60. for neighbor in neighbors:
  61. if self.track_recursion:
  62. # Retain a copy for visualisation. Due to recursion, this
  63. # node may be part of other paths too.
  64. neighbor.retain_count += 1
  65. neighbor.tested = True
  66. t = self.search(neighbor, g + self.calc_cost(node, neighbor),
  67. cutoff, path, depth + 1, end, grid)
  68. if isinstance(t, Node):
  69. if len(path) < depth:
  70. path += [None] * (depth - len(path) + 1)
  71. path[depth] = node
  72. return t
  73. # Decrement count, then determine whether it's actually closed.
  74. if self.track_recursion:
  75. neighbor.retain_count -= 1
  76. if neighbor.retain_count == 0:
  77. neighbor.tested = False
  78. if t < min_t:
  79. min_t = t
  80. return min_t
  81. def find_path(self, start, end, grid):
  82. self.start_time = time.time() # execution time limitation
  83. self.runs = 0 # count number of iterations
  84. self.nodes_visited = 0 # for statistics
  85. # initial search depth, given the typical heuristic contraints,
  86. # there should be no cheaper route possible.
  87. cutoff = self.apply_heuristic(start, end)
  88. while True:
  89. path = []
  90. # search till cut-off depth:
  91. t = self.search(start, 0, cutoff, path, 0, end, grid)
  92. if isinstance(t, bool) and not t:
  93. # only when an error occured we return "False"
  94. break
  95. # If t is a node, it's also the end node. Route is now
  96. # populated with a valid path to the end node.
  97. if isinstance(t, Node):
  98. return [(node.x, node.y) for node in path], self.runs
  99. # Try again, this time with a deeper cut-off. The t score
  100. # is the closest we got to the end node.
  101. cutoff = t
  102. return [], self.runs