python-motion-planning 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. curve_generation/__init__.py +9 -0
  2. curve_generation/bezier_curve.py +131 -0
  3. curve_generation/bspline_curve.py +271 -0
  4. curve_generation/cubic_spline.py +128 -0
  5. curve_generation/curve.py +64 -0
  6. curve_generation/dubins_curve.py +348 -0
  7. curve_generation/fem_pos_smooth.py +114 -0
  8. curve_generation/polynomial_curve.py +226 -0
  9. curve_generation/reeds_shepp.py +736 -0
  10. global_planner/__init__.py +3 -0
  11. global_planner/evolutionary_search/__init__.py +4 -0
  12. global_planner/evolutionary_search/aco.py +186 -0
  13. global_planner/evolutionary_search/evolutionary_search.py +87 -0
  14. global_planner/evolutionary_search/pso.py +356 -0
  15. global_planner/graph_search/__init__.py +28 -0
  16. global_planner/graph_search/a_star.py +124 -0
  17. global_planner/graph_search/d_star.py +291 -0
  18. global_planner/graph_search/d_star_lite.py +188 -0
  19. global_planner/graph_search/dijkstra.py +77 -0
  20. global_planner/graph_search/gbfs.py +78 -0
  21. global_planner/graph_search/graph_search.py +87 -0
  22. global_planner/graph_search/jps.py +165 -0
  23. global_planner/graph_search/lazy_theta_star.py +114 -0
  24. global_planner/graph_search/lpa_star.py +230 -0
  25. global_planner/graph_search/s_theta_star.py +133 -0
  26. global_planner/graph_search/theta_star.py +171 -0
  27. global_planner/graph_search/voronoi.py +200 -0
  28. global_planner/sample_search/__init__.py +6 -0
  29. global_planner/sample_search/informed_rrt.py +152 -0
  30. global_planner/sample_search/rrt.py +151 -0
  31. global_planner/sample_search/rrt_connect.py +147 -0
  32. global_planner/sample_search/rrt_star.py +77 -0
  33. global_planner/sample_search/sample_search.py +135 -0
  34. local_planner/__init__.py +19 -0
  35. local_planner/apf.py +144 -0
  36. local_planner/ddpg.py +630 -0
  37. local_planner/dqn.py +687 -0
  38. local_planner/dwa.py +212 -0
  39. local_planner/local_planner.py +262 -0
  40. local_planner/lqr.py +146 -0
  41. local_planner/mpc.py +214 -0
  42. local_planner/pid.py +158 -0
  43. local_planner/rpp.py +147 -0
  44. python_motion_planning-1.0.dist-info/LICENSE +674 -0
  45. python_motion_planning-1.0.dist-info/METADATA +873 -0
  46. python_motion_planning-1.0.dist-info/RECORD +65 -0
  47. python_motion_planning-1.0.dist-info/WHEEL +5 -0
  48. python_motion_planning-1.0.dist-info/top_level.txt +4 -0
  49. utils/__init__.py +19 -0
  50. utils/agent/__init__.py +0 -0
  51. utils/agent/agent.py +135 -0
  52. utils/environment/__init__.py +0 -0
  53. utils/environment/env.py +134 -0
  54. utils/environment/node.py +85 -0
  55. utils/environment/point2d.py +96 -0
  56. utils/environment/pose2d.py +91 -0
  57. utils/helper/__init__.py +3 -0
  58. utils/helper/math_helper.py +65 -0
  59. utils/planner/__init__.py +0 -0
  60. utils/planner/control_factory.py +31 -0
  61. utils/planner/curve_factory.py +29 -0
  62. utils/planner/planner.py +40 -0
  63. utils/planner/search_factory.py +51 -0
  64. utils/plot/__init__.py +0 -0
  65. utils/plot/plot.py +274 -0
@@ -0,0 +1,78 @@
1
+ """
2
+ @file: gbfs.py
3
+ @breif: Greedy Best First Search motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq
8
+
9
+ from .a_star import AStar
10
+ from python_motion_planning.utils import Env
11
+
12
+
13
+ class GBFS(AStar):
14
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
15
+ super().__init__(start, goal, env, heuristic_type)
16
+
17
+ def __str__(self) -> str:
18
+ return "Greedy Best First Search(GBFS)"
19
+
20
+ def plan(self) -> tuple:
21
+ """
22
+ Class for Greedy Best First Search.
23
+
24
+ Parameters:
25
+ start (tuple): start point coordinate
26
+ goal (tuple): goal point coordinate
27
+ env (Env): environment
28
+ heuristic_type (str): heuristic function type
29
+
30
+ Examples:
31
+ >>> import python_motion_planning as pmp
32
+ >>> planner = pmp.GBFS((5, 5), (45, 25), pmp.Grid(51, 31))
33
+ >>> cost, path, expand = planner.plan() # planning results only
34
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
35
+ >>> planner.run() # run both planning and animation
36
+ """
37
+ # OPEN list (priority queue) and CLOSED list (hash table)
38
+ OPEN = []
39
+ heapq.heappush(OPEN, self.start)
40
+ CLOSED = dict()
41
+
42
+ while OPEN:
43
+ node = heapq.heappop(OPEN)
44
+
45
+ # exists in CLOSED list
46
+ if node.current in CLOSED:
47
+ continue
48
+
49
+ # goal found
50
+ if node == self.goal:
51
+ CLOSED[node.current] = node
52
+ cost, path = self.extractPath(CLOSED)
53
+ return cost, path, list(CLOSED.values())
54
+
55
+ for node_n in self.getNeighbor(node):
56
+
57
+ # hit the obstacle
58
+ if node_n.current in self.obstacles:
59
+ continue
60
+
61
+ # exists in CLOSED list
62
+ if node_n.current in CLOSED:
63
+ continue
64
+
65
+ node_n.parent = node.current
66
+ node_n.h = self.h(node_n, self.goal)
67
+ node_n.g = 0
68
+
69
+ # goal found
70
+ if node_n == self.goal:
71
+ heapq.heappush(OPEN, node_n)
72
+ break
73
+
74
+ # update OPEN set
75
+ heapq.heappush(OPEN, node_n)
76
+
77
+ CLOSED[node.current] = node
78
+ return [], [], []
@@ -0,0 +1,87 @@
1
+ """
2
+ @file: graph_search.py
3
+ @breif: Base class for planner based on graph searching
4
+ @author: Winter
5
+ @update: 2023.1.13
6
+ """
7
+ import math
8
+ from python_motion_planning.utils import Env, Node, Planner
9
+
10
+
11
+ class GraphSearcher(Planner):
12
+ """
13
+ Base class for planner based on graph searching.
14
+
15
+ Parameters:
16
+ start (tuple): start point coordinate
17
+ goal (tuple): goal point coordinate
18
+ env (Env): environment
19
+ heuristic_type (str): heuristic function type
20
+ """
21
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str="euclidean") -> None:
22
+ super().__init__(start, goal, env)
23
+ # heuristic type
24
+ self.heuristic_type = heuristic_type
25
+ # allowed motions
26
+ self.motions = self.env.motions
27
+ # obstacles
28
+ self.obstacles = self.env.obstacles
29
+
30
+ def h(self, node: Node, goal: Node) -> float:
31
+ """
32
+ Calculate heuristic.
33
+
34
+ Parameters:
35
+ node (Node): current node
36
+ goal (Node): goal node
37
+
38
+ Returns:
39
+ h (float): heuristic function value of node
40
+ """
41
+ if self.heuristic_type == "manhattan":
42
+ return abs(goal.x - node.x) + abs(goal.y - node.y)
43
+ elif self.heuristic_type == "euclidean":
44
+ return math.hypot(goal.x - node.x, goal.y - node.y)
45
+
46
+ def cost(self, node1: Node, node2: Node) -> float:
47
+ """
48
+ Calculate cost for this motion.
49
+
50
+ Parameters:
51
+ node1 (Node): node 1
52
+ node2 (Node): node 2
53
+
54
+ Returns:
55
+ cost (float): cost of this motion
56
+ """
57
+ if self.isCollision(node1, node2):
58
+ return float("inf")
59
+ return self.dist(node1, node2)
60
+
61
+ def isCollision(self, node1: Node, node2: Node) -> bool:
62
+ """
63
+ Judge collision when moving from node1 to node2.
64
+
65
+ Parameters:
66
+ node1 (Node): node 1
67
+ node2 (Node): node 2
68
+
69
+ Returns:
70
+ collision (bool): True if collision exists else False
71
+ """
72
+ if node1.current in self.obstacles or node2.current in self.obstacles:
73
+ return True
74
+
75
+ x1, y1 = node1.x, node1.y
76
+ x2, y2 = node2.x, node2.y
77
+
78
+ if x1 != x2 and y1 != y2:
79
+ if x2 - x1 == y1 - y2:
80
+ s1 = (min(x1, x2), min(y1, y2))
81
+ s2 = (max(x1, x2), max(y1, y2))
82
+ else:
83
+ s1 = (min(x1, x2), max(y1, y2))
84
+ s2 = (max(x1, x2), min(y1, y2))
85
+ if s1 in self.obstacles or s2 in self.obstacles:
86
+ return True
87
+ return False
@@ -0,0 +1,165 @@
1
+ """
2
+ @file: jps.py
3
+ @breif: Jump Point Search motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq
8
+
9
+ from .a_star import AStar
10
+ from python_motion_planning.utils import Env, Node
11
+
12
+ class JPS(AStar):
13
+ """
14
+ Class for JPS motion planning.
15
+
16
+ Parameters:
17
+ start (tuple): start point coordinate
18
+ goal (tuple): goal point coordinate
19
+ env (Env): environment
20
+ heuristic_type (str): heuristic function type
21
+
22
+ Examples:
23
+ >>> import python_motion_planning as pmp
24
+ >>> planner = pmp.JPS((5, 5), (45, 25), pmp.Grid(51, 31))
25
+ >>> cost, path, expand = planner.plan() # planning results only
26
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
27
+ >>> planner.run() # run both planning and animation
28
+
29
+ References:
30
+ [1] Online Graph Pruning for Pathfinding On Grid Maps
31
+ """
32
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
33
+ super().__init__(start, goal, env, heuristic_type)
34
+
35
+ def __str__(self) -> str:
36
+ return "Jump Point Search(JPS)"
37
+
38
+ def plan(self) -> tuple:
39
+ """
40
+ JPS motion plan function.
41
+
42
+ Returns:
43
+ cost (float): path cost
44
+ path (list): planning path
45
+ expand (list): all nodes that planner has searched
46
+ """
47
+ # OPEN list (priority queue) and CLOSED list (hash table)
48
+ OPEN = []
49
+ heapq.heappush(OPEN, self.start)
50
+ CLOSED = dict()
51
+
52
+ while OPEN:
53
+ node = heapq.heappop(OPEN)
54
+
55
+ # exists in CLOSED list
56
+ if node.current in CLOSED:
57
+ continue
58
+
59
+ # goal found
60
+ if node == self.goal:
61
+ CLOSED[node.current] = node
62
+ cost, path = self.extractPath(CLOSED)
63
+ return cost, path, list(CLOSED.values())
64
+
65
+ jp_list = []
66
+ for motion in self.motions:
67
+ jp = self.jump(node, motion)
68
+ # exists and not in CLOSED list
69
+ if jp and jp.current not in CLOSED:
70
+ jp.parent = node.current
71
+ jp.h = self.h(jp, self.goal)
72
+ jp_list.append(jp)
73
+
74
+ for jp in jp_list:
75
+ # update OPEN list
76
+ heapq.heappush(OPEN, jp)
77
+
78
+ # goal found
79
+ if jp == self.goal:
80
+ break
81
+
82
+ CLOSED[node.current] = node
83
+ return [], [], []
84
+
85
+ def jump(self, node: Node, motion: Node):
86
+ """
87
+ Jumping search recursively.
88
+
89
+ Parameters:
90
+ node (Node): current node
91
+ motion (Node): the motion that current node executes
92
+
93
+ Returns:
94
+ jump_point (Node): jump point or None if searching fails
95
+ """
96
+ # explore a new node
97
+ new_node = node + motion
98
+ new_node.parent = node.current
99
+ new_node.h = self.h(new_node, self.goal)
100
+
101
+ # hit the obstacle
102
+ if new_node.current in self.obstacles:
103
+ return None
104
+
105
+ # goal found
106
+ if new_node == self.goal:
107
+ return new_node
108
+
109
+ # diagonal
110
+ if motion.x and motion.y:
111
+ # if exists jump point at horizontal or vertical
112
+ x_dir = Node((motion.x, 0), None, 1, None)
113
+ y_dir = Node((0, motion.y), None, 1, None)
114
+ if self.jump(new_node, x_dir) or self.jump(new_node, y_dir):
115
+ return new_node
116
+
117
+ # if exists forced neighbor
118
+ if self.detectForceNeighbor(new_node, motion):
119
+ return new_node
120
+ else:
121
+ return self.jump(new_node, motion)
122
+
123
+ def detectForceNeighbor(self, node, motion):
124
+ """
125
+ Detect forced neighbor of node.
126
+
127
+ Parameters:
128
+ node (Node): current node
129
+ motion (Node): the motion that current node executes
130
+
131
+ Returns:
132
+ flag (bool): True if current node has forced neighbor else Flase
133
+ """
134
+ x, y = node.current
135
+ x_dir, y_dir = motion.current
136
+
137
+ # horizontal
138
+ if x_dir and not y_dir:
139
+ if (x, y + 1) in self.obstacles and \
140
+ (x + x_dir, y + 1) not in self.obstacles:
141
+ return True
142
+ if (x, y - 1) in self.obstacles and \
143
+ (x + x_dir, y - 1) not in self.obstacles:
144
+ return True
145
+
146
+ # vertical
147
+ if not x_dir and y_dir:
148
+ if (x + 1, y) in self.obstacles and \
149
+ (x + 1, y + y_dir) not in self.obstacles:
150
+ return True
151
+ if (x - 1, y) in self.obstacles and \
152
+ (x - 1, y + y_dir) not in self.obstacles:
153
+ return True
154
+
155
+ # diagonal
156
+ if x_dir and y_dir:
157
+ if (x - x_dir, y) in self.obstacles and \
158
+ (x - x_dir, y + y_dir) not in self.obstacles:
159
+ return True
160
+ if (x, y - y_dir) in self.obstacles and \
161
+ (x + x_dir, y - y_dir) not in self.obstacles:
162
+ return True
163
+
164
+ return False
165
+
@@ -0,0 +1,114 @@
1
+ """
2
+ @file: lazy_theta_star.py
3
+ @breif: Lazy Theta* motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq
8
+
9
+ from .theta_star import ThetaStar
10
+ from python_motion_planning.utils import Env, Node
11
+
12
+ class LazyThetaStar(ThetaStar):
13
+ """
14
+ Class for Lazy Theta* motion planning.
15
+
16
+ Parameters:
17
+ start (tuple): start point coordinate
18
+ goal (tuple): goal point coordinate
19
+ env (Env): environment
20
+ heuristic_type (str): heuristic function type
21
+
22
+ Examples:
23
+ >>> import python_motion_planning as pmp
24
+ >>> planner = pmp.LazyThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
25
+ >>> cost, path, expand = planner.plan()
26
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
27
+ >>> planner.run() # run both planning and animation
28
+
29
+ References:
30
+ [1] Lazy Theta*: Any-Angle Path Planning and Path Length Analysis in 3D
31
+ """
32
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
33
+ super().__init__(start, goal, env, heuristic_type)
34
+
35
+ def __str__(self) -> str:
36
+ return "Lazy Theta*"
37
+
38
+ def plan(self) -> tuple:
39
+ """
40
+ Lazy Theta* motion plan function.
41
+
42
+ Returns:
43
+ cost (float): path cost
44
+ path (list): planning path
45
+ expand (list): all nodes that planner has searched
46
+ """
47
+ # OPEN list (priority queue) and CLOSED list (hash table)
48
+ OPEN = []
49
+ heapq.heappush(OPEN, self.start)
50
+ CLOSED = dict()
51
+
52
+ while OPEN:
53
+ node = heapq.heappop(OPEN)
54
+
55
+ # set vertex: path 1
56
+ node_p = CLOSED.get(node.parent)
57
+ if node_p:
58
+ if not self.lineOfSight(node_p, node):
59
+ node.g = float("inf")
60
+ for node_n in self.getNeighbor(node):
61
+ if node_n.current in CLOSED:
62
+ node_n = CLOSED[node_n.current]
63
+ if node.g > node_n.g + self.dist(node_n, node):
64
+ node.g = node_n.g + self.dist(node_n, node)
65
+ node.parent = node_n.current
66
+
67
+ # exists in CLOSED list
68
+ if node.current in CLOSED:
69
+ continue
70
+
71
+ # goal found
72
+ if node == self.goal:
73
+ CLOSED[node.current] = node
74
+ cost, path = self.extractPath(CLOSED)
75
+ return cost, path, list(CLOSED.values())
76
+
77
+ for node_n in self.getNeighbor(node):
78
+ # exists in CLOSED list
79
+ if node_n.current in CLOSED:
80
+ continue
81
+
82
+ # path1
83
+ node_n.parent = node.current
84
+ node_n.h = self.h(node_n, self.goal)
85
+
86
+ node_p = CLOSED.get(node.parent)
87
+
88
+ if node_p:
89
+ # path2
90
+ self.updateVertex(node_p, node_n)
91
+
92
+ # goal found
93
+ if node_n == self.goal:
94
+ heapq.heappush(OPEN, node_n)
95
+ break
96
+
97
+ # update OPEN list
98
+ heapq.heappush(OPEN, node_n)
99
+
100
+ CLOSED[node.current] = node
101
+ return [], [], []
102
+
103
+ def updateVertex(self, node_p: Node, node_c: Node) -> None:
104
+ """
105
+ Update extend node information with current node's parent node.
106
+
107
+ Parameters:
108
+ node_p (Node): parent node
109
+ node_c (Node): current node
110
+ """
111
+ # path 2
112
+ if node_p.g + self.dist(node_c, node_p) <= node_c.g:
113
+ node_c.g = node_p.g + self.dist(node_c, node_p)
114
+ node_c.parent = node_p.current
@@ -0,0 +1,230 @@
1
+ """
2
+ @file: lpa_star.py
3
+ @breif: Lifelong Planning A* motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq
8
+
9
+ from .graph_search import GraphSearcher
10
+ from python_motion_planning.utils import Env, Node
11
+
12
+ class LNode(Node):
13
+ """
14
+ Class for LPA* nodes.
15
+
16
+ Parameters:
17
+ current (tuple): current coordinate
18
+ g (float): minimum cost moving from start(predict)
19
+ rhs (float): minimum cost moving from start(value)
20
+ key (list): priority
21
+ """
22
+ def __init__(self, current: tuple, g: float, rhs: float, key: list) -> None:
23
+ self.current = current
24
+ self.g = g
25
+ self.rhs = rhs
26
+ self.key = key
27
+
28
+ def __add__(self, node):
29
+ return LNode((self.x + node.x, self.y + node.y),
30
+ self.g, self.rhs, self.key)
31
+
32
+ def __lt__(self, node) -> bool:
33
+ return self.key < node.key
34
+
35
+ def __str__(self) -> str:
36
+ return "----------\ncurrent:{}\ng:{}\nrhs:{}\nkey:{}\n----------" \
37
+ .format(self.current, self.g, self.rhs, self.key)
38
+
39
+ class LPAStar(GraphSearcher):
40
+ """
41
+ Class for LPA* motion planning.
42
+
43
+ Parameters:
44
+ start (tuple): start point coordinate
45
+ goal (tuple): goal point coordinate
46
+ env (Env): environment
47
+ heuristic_type (str): heuristic function type
48
+
49
+ Examples:
50
+ >>> import python_motion_planning as pmp
51
+ >>> planner = pmp.LPAStar((5, 5), (45, 25), pmp.Grid(51, 31))
52
+ >>> cost, path, _ = planner.plan() # planning results only
53
+ >>> planner.plot.animation(path, str(planner), cost) # animation
54
+ >>> planner.run() # run both planning and animation
55
+
56
+ References:
57
+ [1] Lifelong Planning A*
58
+ """
59
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
60
+ super().__init__(start, goal, env, heuristic_type)
61
+ # start and goal
62
+ self.start = LNode(start, float('inf'), 0.0, None)
63
+ self.goal = LNode(goal, float('inf'), float('inf'), None)
64
+ # OPEN set and expand zone
65
+ self.U, self.EXPAND = [], []
66
+
67
+ # intialize global information, record history infomation of map grids
68
+ self.map = {s: LNode(s, float('inf'), float('inf'), None) for s in self.env.grid_map}
69
+ self.map[self.goal.current] = self.goal
70
+ self.map[self.start.current] = self.start
71
+ # OPEN set with priority
72
+ self.start.key = self.calculateKey(self.start)
73
+ heapq.heappush(self.U, self.start)
74
+
75
+ def __str__(self) -> str:
76
+ return "Lifelong Planning A*"
77
+
78
+ def plan(self) -> tuple:
79
+ """
80
+ LPA* dynamic motion planning function.
81
+
82
+ Returns:
83
+ cost (float): path cost
84
+ path (list): planning path
85
+ _ (None): None
86
+ """
87
+ self.computeShortestPath()
88
+ cost, path = self.extractPath()
89
+ return cost, path, None
90
+
91
+ def run(self) -> None:
92
+ """
93
+ Running both plannig and animation.
94
+ """
95
+ # static planning
96
+ cost, path, _ = self.plan()
97
+
98
+ # animation
99
+ self.plot.connect('button_press_event', self.OnPress)
100
+ self.plot.animation(path, str(self), cost=cost)
101
+
102
+ def OnPress(self, event):
103
+ """
104
+ Mouse button callback function.
105
+
106
+ Parameters:
107
+ event (MouseEvent): mouse event
108
+ """
109
+ x, y = int(event.xdata), int(event.ydata)
110
+ if x < 0 or x > self.env.x_range - 1 or y < 0 or y > self.env.y_range - 1:
111
+ print("Please choose right area!")
112
+ else:
113
+ print("Change position: x = {}, y = {}".format(x, y))
114
+ self.EXPAND = []
115
+ node_change = self.map[(x, y)]
116
+
117
+ if (x, y) not in self.obstacles:
118
+ self.obstacles.add((x, y))
119
+ else:
120
+ self.obstacles.remove((x, y))
121
+ self.updateVertex(node_change)
122
+
123
+ self.env.update(self.obstacles)
124
+
125
+ for node_n in self.getNeighbor(node_change):
126
+ self.updateVertex(node_n)
127
+
128
+ cost, path, _ = self.plan()
129
+
130
+ # animation
131
+ self.plot.clean()
132
+ self.plot.animation(path, str(self), cost, self.EXPAND)
133
+ self.plot.update()
134
+
135
+ def computeShortestPath(self) -> None:
136
+ """
137
+ Perceived dynamic obstacle information to optimize global path.
138
+ """
139
+ while True:
140
+ node = min(self.U, key=lambda node: node.key)
141
+ if node.key >= self.calculateKey(self.goal) and \
142
+ self.goal.rhs == self.goal.g:
143
+ break
144
+
145
+ self.U.remove(node)
146
+ self.EXPAND.append(node)
147
+
148
+ # Locally over-consistent -> Locally consistent
149
+ if node.g > node.rhs:
150
+ node.g = node.rhs
151
+ # Locally under-consistent -> Locally over-consistent
152
+ else:
153
+ node.g = float("inf")
154
+ self.updateVertex(node)
155
+
156
+ for node_n in self.getNeighbor(node):
157
+ self.updateVertex(node_n)
158
+
159
+ def updateVertex(self, node: LNode) -> None:
160
+ """
161
+ Update the status and the current cost to node and it's neighbor.
162
+
163
+ Parameters:
164
+ node (LNode): current node
165
+ """
166
+ # greed correction
167
+ if node != self.start:
168
+ node.rhs = min([node_n.g + self.cost(node_n, node)
169
+ for node_n in self.getNeighbor(node)])
170
+
171
+ if node in self.U:
172
+ self.U.remove(node)
173
+
174
+ # Locally unconsistent nodes should be added into OPEN set (set U)
175
+ if node.g != node.rhs:
176
+ node.key = self.calculateKey(node)
177
+ heapq.heappush(self.U, node)
178
+
179
+ def calculateKey(self, node: LNode) -> list:
180
+ """
181
+ Calculate priority of node.
182
+
183
+ Parameters:
184
+ node (LNode): current node
185
+
186
+ Returns:
187
+ key (list): priority of node
188
+ """
189
+ return [min(node.g, node.rhs) + self.h(node, self.goal),
190
+ min(node.g, node.rhs)]
191
+
192
+ def getNeighbor(self, node: LNode) -> list:
193
+ """
194
+ Find neighbors of node.
195
+
196
+ Parameters:
197
+ node (LNode): current node
198
+
199
+ Returns:
200
+ neighbors (list): neighbors of node
201
+ """
202
+ neighbors = []
203
+ for motion in self.motions:
204
+ n = self.map[(node + motion).current]
205
+ if n.current not in self.obstacles:
206
+ neighbors.append(n)
207
+ return neighbors
208
+
209
+ def extractPath(self):
210
+ """
211
+ Extract the path based on greedy policy.
212
+
213
+ Return:
214
+ cost (float): the cost of planning path
215
+ path (list): the planning path
216
+ """
217
+ node = self.goal
218
+ path = [node.current]
219
+ cost, count = 0, 0
220
+ while node != self.start:
221
+ neighbors = [node_n for node_n in self.getNeighbor(node) if not self.isCollision(node, node_n)]
222
+ next_node = min(neighbors, key=lambda n: n.g)
223
+ path.append(next_node.current)
224
+ cost += self.cost(node, next_node)
225
+ node = next_node
226
+ count += 1
227
+ if count == 1000:
228
+ return cost, []
229
+ return cost, list(reversed(path))
230
+