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,124 @@
1
+ """
2
+ @file: a_star.py
3
+ @breif: 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
+
13
+ class AStar(GraphSearcher):
14
+ """
15
+ Class for A* motion planning.
16
+
17
+ Parameters:
18
+ start (tuple): start point coordinate
19
+ goal (tuple): goal point coordinate
20
+ env (Env): environment
21
+ heuristic_type (str): heuristic function type
22
+
23
+ Examples:
24
+ >>> import python_motion_planning as pmp
25
+ >>> planner = pmp.AStar((5, 5), (45, 25), pmp.Grid(51, 31))
26
+ >>> cost, path, expand = planner.plan() # planning results only
27
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
28
+ >>> planner.run() # run both planning and animation
29
+
30
+ References:
31
+ [1] A Formal Basis for the heuristic Determination of Minimum Cost Paths
32
+ """
33
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
34
+ super().__init__(start, goal, env, heuristic_type)
35
+
36
+ def __str__(self) -> str:
37
+ return "A*"
38
+
39
+ def plan(self) -> tuple:
40
+ """
41
+ A* motion plan function.
42
+
43
+ Returns:
44
+ cost (float): path cost
45
+ path (list): planning path
46
+ expand (list): all nodes that planner has searched
47
+ """
48
+ # OPEN list (priority queue) and CLOSED list (hash table)
49
+ OPEN = []
50
+ heapq.heappush(OPEN, self.start)
51
+ CLOSED = dict()
52
+
53
+ while OPEN:
54
+ node = heapq.heappop(OPEN)
55
+
56
+ # exists in CLOSED list
57
+ if node.current in CLOSED:
58
+ continue
59
+
60
+ # goal found
61
+ if node == self.goal:
62
+ CLOSED[node.current] = node
63
+ cost, path = self.extractPath(CLOSED)
64
+ return cost, path, list(CLOSED.values())
65
+
66
+ for node_n in self.getNeighbor(node):
67
+ # exists in CLOSED list
68
+ if node_n.current in CLOSED:
69
+ continue
70
+
71
+ node_n.parent = node.current
72
+ node_n.h = self.h(node_n, self.goal)
73
+
74
+ # goal found
75
+ if node_n == self.goal:
76
+ heapq.heappush(OPEN, node_n)
77
+ break
78
+
79
+ # update OPEN list
80
+ heapq.heappush(OPEN, node_n)
81
+
82
+ CLOSED[node.current] = node
83
+ return [], [], []
84
+
85
+ def getNeighbor(self, node: Node) -> list:
86
+ """
87
+ Find neighbors of node.
88
+
89
+ Parameters:
90
+ node (Node): current node
91
+
92
+ Returns:
93
+ neighbors (list): neighbors of current node
94
+ """
95
+ return [node + motion for motion in self.motions
96
+ if not self.isCollision(node, node + motion)]
97
+
98
+ def extractPath(self, closed_list: dict) -> tuple:
99
+ """
100
+ Extract the path based on the CLOSED list.
101
+
102
+ Parameters:
103
+ closed_list (dict): CLOSED list
104
+
105
+ Returns:
106
+ cost (float): the cost of planned path
107
+ path (list): the planning path
108
+ """
109
+ cost = 0
110
+ node = closed_list[self.goal.current]
111
+ path = [node.current]
112
+ while node != self.start:
113
+ node_parent = closed_list[node.parent]
114
+ cost += self.dist(node, node_parent)
115
+ node = node_parent
116
+ path.append(node.current)
117
+ return cost, path
118
+
119
+ def run(self):
120
+ """
121
+ Running both planning and animation.
122
+ """
123
+ cost, path, expand = self.plan()
124
+ self.plot.animation(path, str(self), cost, expand)
@@ -0,0 +1,291 @@
1
+ """
2
+ @file: d_star.py
3
+ @breif: Dynamic A* motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ from .graph_search import GraphSearcher
8
+ from python_motion_planning.utils import Env, Node
9
+
10
+
11
+ class DNode(Node):
12
+ """
13
+ Class for D* nodes.
14
+
15
+ Parameters:
16
+ current (tuple): current coordinate
17
+ parent (tuple): coordinate of parent node
18
+ t (str): state of node, including `NEW` `OPEN` and `CLOSED`
19
+ h (float): cost from goal to current node
20
+ k (float): minimum cost from goal to current node in history
21
+ """
22
+ def __init__(self, current: tuple, parent: tuple, t: str, h: float, k: float) -> None:
23
+ self.current = current
24
+ self.parent = parent
25
+ self.t = t
26
+ self.h = h
27
+ self.k = k
28
+
29
+ def __add__(self, node):
30
+ return DNode((self.x + node.x, self.y + node.y),
31
+ self.parent, self.t, self.h + node.h, self.k)
32
+
33
+ def __str__(self) -> str:
34
+ return "----------\ncurrent:{}\nparent:{}\nt:{}\nh:{}\nk:{}\n----------" \
35
+ .format(self.current, self.parent, self.t, self.h, self.k)
36
+
37
+ class DStar(GraphSearcher):
38
+ """
39
+ Class for D* motion planning.
40
+
41
+ Parameters:
42
+ start (tuple): start point coordinate
43
+ goal (tuple): goal point coordinate
44
+ env (Env): environment
45
+
46
+ Examples:
47
+ >>> import python_motion_planning as pmp
48
+ >>> planner = pmp.DStar((5, 5), (45, 25), pmp.Grid(51, 31))
49
+ >>> cost, path, _ = planner.plan() # planning results only
50
+ >>> planner.plot.animation(path, str(planner), cost) # animation
51
+ >>> planner.run() # run both planning and animation
52
+
53
+ References:
54
+ [1]Optimal and Efficient Path Planning for Partially-Known Environments
55
+ """
56
+ def __init__(self, start: tuple, goal: tuple, env: Env) -> None:
57
+ super().__init__(start, goal, env, None)
58
+ self.start = DNode(start, None, 'NEW', float('inf'), float("inf"))
59
+ self.goal = DNode(goal, None, 'NEW', 0, float('inf'))
60
+ # allowed motions
61
+ self.motions = [DNode(motion.current, None, None, motion.g, 0) for motion in self.env.motions]
62
+ # OPEN list and EXPAND list
63
+ self.OPEN = []
64
+ self.EXPAND = []
65
+ # record history infomation of map grids
66
+ self.map = {s: DNode(s, None, 'NEW', float("inf"), float("inf")) for s in self.env.grid_map}
67
+ self.map[self.goal.current] = self.goal
68
+ self.map[self.start.current] = self.start
69
+ # intialize OPEN list
70
+ self.insert(self.goal, 0)
71
+
72
+ def __str__(self) -> str:
73
+ return "Dynamic A*(D*)"
74
+
75
+ def plan(self) -> tuple:
76
+ """
77
+ D* static motion planning function.
78
+
79
+ Returns:
80
+ cost (float): path cost
81
+ path (list): planning path
82
+ _ (None): None
83
+ """
84
+ while True:
85
+ self.processState()
86
+ if self.start.t == 'CLOSED':
87
+ break
88
+ cost, path = self.extractPath(self.map)
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) -> None:
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
+ if (x, y) not in self.obstacles:
114
+ print("Add obstacle at: ({}, {})".format(x, y))
115
+ # update obstacles
116
+ self.obstacles.add((x, y))
117
+ self.env.update(self.obstacles)
118
+
119
+ # move from start to goal, replan locally when meeting collisions
120
+ node = self.start
121
+ self.EXPAND, path, cost = [], [], 0
122
+ while node != self.goal:
123
+ node_parent = self.map[node.parent]
124
+ if self.isCollision(node, node_parent):
125
+ self.modify(node, node_parent)
126
+ continue
127
+ path.append(node.current)
128
+ cost += self.cost(node, node_parent)
129
+ node = node_parent
130
+
131
+ self.plot.clean()
132
+ self.plot.animation(path, str(self), cost, self.EXPAND)
133
+
134
+ self.plot.update()
135
+
136
+ def extractPath(self, closed_list: dict) -> tuple:
137
+ """
138
+ Extract the path based on the CLOSED list.
139
+
140
+ Parameters:
141
+ closed_list (dict): CLOSED list
142
+
143
+ Returns:
144
+ cost (float): the cost of planning path
145
+ path (list): the planning path
146
+ """
147
+ cost = 0
148
+ node = self.start
149
+ path = [node.current]
150
+ while node != self.goal:
151
+ node_parent = closed_list[node.parent]
152
+ cost += self.cost(node, node_parent)
153
+ node = node_parent
154
+ path.append(node.current)
155
+
156
+ return cost, path
157
+
158
+ def processState(self) -> float:
159
+ """
160
+ Broadcast dynamic obstacle information.
161
+
162
+ Returns:
163
+ min_k (float): minimum k value of map
164
+ """
165
+ # get node in OPEN list with min k value
166
+ node = self.min_state
167
+ self.EXPAND.append(node)
168
+
169
+ if node is None:
170
+ return -1
171
+
172
+ # record the min k value of this iteration
173
+ k_old = self.min_k
174
+ # move node from OPEN list to CLOSED list
175
+ self.delete(node)
176
+
177
+ # k_min < h[x] --> x: RAISE state (try to reduce k value by neighbor)
178
+ if k_old < node.h:
179
+ for node_n in self.getNeighbor(node):
180
+ if node_n.h <= k_old and node.h > node_n.h + self.cost(node, node_n):
181
+ # update h_value and choose parent
182
+ node.parent = node_n.current
183
+ node.h = node_n.h + self.cost(node, node_n)
184
+
185
+ # k_min >= h[x] -- > x: LOWER state (cost reductions)
186
+ if k_old == node.h:
187
+ for node_n in self.getNeighbor(node):
188
+ if node_n.t == 'NEW' or \
189
+ (node_n.parent == node.current and node_n.h != node.h + self.cost(node, node_n)) or \
190
+ (node_n.parent != node.current and node_n.h > node.h + self.cost(node, node_n)):
191
+ # Condition:
192
+ # 1) t[node_n] == 'NEW': not visited
193
+ # 2) node_n's parent: cost reduction
194
+ # 3) node_n find a better parent
195
+ node_n.parent = node.current
196
+ self.insert(node_n, node.h + self.cost(node, node_n))
197
+ else:
198
+ for node_n in self.getNeighbor(node):
199
+ if node_n.t == 'NEW' or \
200
+ (node_n.parent == node.current and node_n.h != node.h + self.cost(node, node_n)):
201
+ # Condition:
202
+ # 1) t[node_n] == 'NEW': not visited
203
+ # 2) node_n's parent: cost reduction
204
+ node_n.parent = node.current
205
+ self.insert(node_n, node.h + self.cost(node, node_n))
206
+ else:
207
+ if node_n.parent != node.current and \
208
+ node_n.h > node.h + self.cost(node, node_n):
209
+ # Condition: LOWER happened in OPEN list (s), s should be explored again
210
+ self.insert(node, node.h)
211
+ else:
212
+ if node_n.parent != node.current and \
213
+ node.h > node_n.h + self.cost(node, node_n) and \
214
+ node_n.t == 'CLOSED' and \
215
+ node_n.h > k_old:
216
+ # Condition: LOWER happened in CLOSED list (s_n), s_n should be explored again
217
+ self.insert(node_n, node_n.h)
218
+ return self.min_k
219
+
220
+ @property
221
+ def min_state(self) -> DNode:
222
+ """
223
+ Choose the node with the minimum k value in OPEN list.
224
+ """
225
+ if not self.OPEN:
226
+ return None
227
+ return min(self.OPEN, key=lambda node: node.k)
228
+
229
+ @property
230
+ def min_k(self) -> float:
231
+ """
232
+ Choose the minimum k value for nodes in OPEN list.
233
+ """
234
+ return self.min_state.k
235
+
236
+ def insert(self, node: DNode, h_new: float) -> None:
237
+ """
238
+ Insert node into OPEN list.
239
+
240
+ Parameters:
241
+ node (DNode): the node to insert
242
+ h_new (float): new or better cost to come value
243
+ """
244
+ if node.t == 'NEW': node.k = h_new
245
+ elif node.t == 'OPEN': node.k = min(node.k, h_new)
246
+ elif node.t == 'CLOSED': node.k = min(node.h, h_new)
247
+ node.h, node.t = h_new, 'OPEN'
248
+ self.OPEN.append(node)
249
+
250
+ def delete(self, node: DNode) -> None:
251
+ """
252
+ Delete node from OPEN list.
253
+
254
+ Parameters:
255
+ node (DNode): the node to delete
256
+ """
257
+ if node.t == 'OPEN':
258
+ node.t = 'CLOSED'
259
+ self.OPEN.remove(node)
260
+
261
+ def modify(self, node: DNode, node_parent: DNode) -> None:
262
+ """
263
+ Start processing from node.
264
+
265
+ Parameters:
266
+ node (DNode): the node to modify
267
+ node_parent (DNode): the parent node of `node`
268
+ """
269
+ if node.t == 'CLOSED':
270
+ self.insert(node, node_parent.h + self.cost(node, node_parent))
271
+ while True:
272
+ k_min = self.processState()
273
+ if k_min >= node.h:
274
+ break
275
+
276
+ def getNeighbor(self, node: DNode) -> list:
277
+ """
278
+ Find neighbors of node.
279
+
280
+ Parameters:
281
+ node (DNode): current node
282
+
283
+ Returns:
284
+ neighbors (list): neighbors of current node
285
+ """
286
+ neighbors = []
287
+ for motion in self.motions:
288
+ n = self.map[(node + motion).current]
289
+ if not self.isCollision(node, n):
290
+ neighbors.append(n)
291
+ return neighbors
@@ -0,0 +1,188 @@
1
+ """
2
+ @file: d_star_lite.py
3
+ @breif: D* Lite 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 .lpa_star import LPAStar, LNode
11
+ from python_motion_planning.utils import Env
12
+
13
+
14
+ class DStarLite(LPAStar):
15
+ """
16
+ Class for D* Lite motion planning.
17
+
18
+ Parameters:
19
+ start (tuple): start point coordinate
20
+ goal (tuple): goal point coordinate
21
+ env (Env): environment
22
+ heuristic_type (str): heuristic function type
23
+
24
+ Examples:
25
+ >>> import python_motion_planning as pmp
26
+ >>> planner = pmp.DStarLite((5, 5), (45, 25), pmp.Grid(51, 31))
27
+ >>> cost, path, _ = planner.plan() # planning results only
28
+ >>> planner.plot.animation(path, str(planner), cost) # animation
29
+ >>> planner.run() # run both planning and animation
30
+
31
+ References:
32
+ [1] D* Lite
33
+ """
34
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
35
+ GraphSearcher.__init__(self, start, goal, env, heuristic_type)
36
+ # start and goal
37
+ self.start = LNode(start, float('inf'), float('inf'), None)
38
+ self.goal = LNode(goal, float('inf'), 0.0, None)
39
+ # correction
40
+ self.km = 0
41
+ # OPEN set and expand zone
42
+ self.U, self.EXPAND = [], []
43
+
44
+ # intialize global information, record history infomation of map grids
45
+ self.map = {s: LNode(s, float('inf'), float('inf'), None) for s in self.env.grid_map}
46
+ self.map[self.goal.current] = self.goal
47
+ self.map[self.start.current] = self.start
48
+ # OPEN set with priority
49
+ self.goal.key = self.calculateKey(self.goal)
50
+ heapq.heappush(self.U, self.goal)
51
+
52
+ def __str__(self) -> str:
53
+ return "D* Lite"
54
+
55
+ def OnPress(self, event) -> None:
56
+ """
57
+ Mouse button callback function.
58
+
59
+ Parameters:
60
+ event (MouseEvent): mouse event
61
+ """
62
+ x, y = int(event.xdata), int(event.ydata)
63
+ if x < 0 or x > self.env.x_range - 1 or y < 0 or y > self.env.y_range - 1:
64
+ print("Please choose right area!")
65
+ else:
66
+ print("Change position: x = {}, y = {}".format(x, y))
67
+
68
+ cur_start, new_start = self.start, self.start
69
+ update_start = True
70
+ cost, count = 0, 0
71
+ path = [self.start.current]
72
+ self.EXPAND = []
73
+
74
+ while cur_start != self.goal:
75
+ neighbors = [node_n for node_n in self.getNeighbor(cur_start)
76
+ if not self.isCollision(cur_start, node_n)]
77
+ next_node = min(neighbors, key=lambda n: n.g)
78
+ path.append(next_node.current)
79
+ cost += self.cost(cur_start, next_node)
80
+ count += 1
81
+ cur_start = next_node
82
+
83
+ if update_start:
84
+ update_start = False
85
+ self.km = self.h(cur_start, new_start)
86
+ new_start = cur_start
87
+
88
+ node_change = self.map[(x, y)]
89
+ if (x, y) not in self.obstacles:
90
+ self.obstacles.add((x, y))
91
+ else:
92
+ self.obstacles.remove((x, y))
93
+ self.updateVertex(node_change)
94
+
95
+ self.env.update(self.obstacles)
96
+ for node_n in self.getNeighbor(node_change):
97
+ self.updateVertex(node_n)
98
+
99
+ self.computeShortestPath()
100
+
101
+ # animation
102
+ self.plot.clean()
103
+ self.plot.animation(path, str(self), cost, self.EXPAND)
104
+ self.plot.update()
105
+
106
+ def computeShortestPath(self) -> None:
107
+ """
108
+ Perceived dynamic obstacle information to optimize global path.
109
+ """
110
+ while True:
111
+ node = min(self.U, key=lambda node: node.key)
112
+ if node.key >= self.calculateKey(self.start) and \
113
+ self.start.rhs == self.start.g:
114
+ break
115
+
116
+ self.U.remove(node)
117
+ self.EXPAND.append(node)
118
+
119
+ # affected by obstacles
120
+ if node.key < self.calculateKey(node):
121
+ node.key = self.calculateKey(node)
122
+ heapq.heappush(self.U, node)
123
+ # Locally over-consistent -> Locally consistent
124
+ elif node.g > node.rhs:
125
+ node.g = node.rhs
126
+ for node_n in self.getNeighbor(node):
127
+ self.updateVertex(node_n)
128
+ # Locally under-consistent -> Locally over-consistent
129
+ else:
130
+ node.g = float("inf")
131
+ self.updateVertex(node)
132
+ for node_n in self.getNeighbor(node):
133
+ self.updateVertex(node_n)
134
+
135
+ def updateVertex(self, node: LNode) -> None:
136
+ """
137
+ Update the status and the current cost to node and it's neighbor.
138
+
139
+ Parameters:
140
+ node (LNode): the node to be updated
141
+ """
142
+ # greed correction(reverse searching)
143
+ if node != self.goal:
144
+ node.rhs = min([node_n.g + self.cost(node_n, node)
145
+ for node_n in self.getNeighbor(node)])
146
+
147
+ if node in self.U:
148
+ self.U.remove(node)
149
+
150
+ # Locally unconsistent nodes should be added into OPEN set (set U)
151
+ if node.g != node.rhs:
152
+ node.key = self.calculateKey(node)
153
+ heapq.heappush(self.U, node)
154
+
155
+ def calculateKey(self, node: LNode) -> list:
156
+ """
157
+ Calculate priority of node.
158
+
159
+ Parameters:
160
+ node (LNode): the node to be calculated
161
+
162
+ Returns:
163
+ key (list): the priority of node
164
+ """
165
+ return [min(node.g, node.rhs) + self.h(node, self.start) + self.km,
166
+ min(node.g, node.rhs)]
167
+
168
+ def extractPath(self) -> tuple:
169
+ """
170
+ Extract the path based on greedy policy.
171
+
172
+ Returns:
173
+ cost (float): the cost of planning path
174
+ path (list): the planning path
175
+ """
176
+ node = self.start
177
+ path = [node.current]
178
+ cost, count = 0, 0
179
+ while node != self.goal:
180
+ neighbors = [node_n for node_n in self.getNeighbor(node) if not self.isCollision(node, node_n)]
181
+ next_node = min(neighbors, key=lambda n: n.g)
182
+ path.append(next_node.current)
183
+ cost += self.cost(node, next_node)
184
+ node = next_node
185
+ count += 1
186
+ if count == 1000:
187
+ return cost, []
188
+ return cost, list(path)
@@ -0,0 +1,77 @@
1
+ """
2
+ @file: dijkstra.py
3
+ @breif: Dijkstra 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 Dijkstra(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 "Dijkstra"
19
+
20
+ def plan(self) -> tuple:
21
+ """
22
+ Class for Dijkstra motion planning.
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.Dijkstra((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 = 0
67
+
68
+ # goal found
69
+ if node_n == self.goal:
70
+ heapq.heappush(OPEN, node_n)
71
+ break
72
+
73
+ # update OPEN set
74
+ heapq.heappush(OPEN, node_n)
75
+
76
+ CLOSED[node.current] = node
77
+ return [], [], []