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,65 @@
1
+ """
2
+ @file: math_helper.py
3
+ @breif: Contains common/commonly used math function
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.5.20
6
+ """
7
+ import math
8
+
9
+ class MathHelper:
10
+ @staticmethod
11
+ def circleSegmentIntersection(p1: tuple, p2: tuple, r: float) -> list:
12
+ x1, x2 = p1[0], p2[0]
13
+ y1, y2 = p1[1], p2[1]
14
+
15
+ dx, dy = x2 - x1, y2 - y1
16
+ dr2 = dx * dx + dy * dy
17
+ D = x1 * y2 - x2 * y1
18
+
19
+ # the first element is the point within segment
20
+ d1 = x1 * x1 + y1 * y1
21
+ d2 = x2 * x2 + y2 * y2
22
+ dd = d2 - d1
23
+
24
+ delta_2 = r * r * dr2 - D * D
25
+ if delta_2 < 0: # no intersection
26
+ return []
27
+
28
+ delta = math.sqrt(delta_2)
29
+ if (delta == 0):
30
+ return [(D * dy / dr2, -D * dx / dr2)]
31
+ else: # delta > 0
32
+ return [
33
+ ((D * dy + math.copysign(1.0, dd) * dx * delta) / dr2,
34
+ (-D * dx + math.copysign(1.0, dd) * dy * delta) / dr2),
35
+ ((D * dy - math.copysign(1.0, dd) * dx * delta) / dr2,
36
+ (-D * dx - math.copysign(1.0, dd) * dy * delta) / dr2)
37
+ ]
38
+
39
+ @staticmethod
40
+ def closestPointOnLine(a: tuple, b: tuple, p: tuple = (0.0, 0.0)) -> tuple:
41
+ """
42
+ Find the closest intersection point (foot of a perpendicular) between point p and the line ab.
43
+
44
+ Parameters:
45
+ a (tuple): point a of the line
46
+ b (tuple): point b of the line
47
+ p (tuple): point p to find the closest intersection point
48
+
49
+ References:
50
+ [1] method 2 of https://www.youtube.com/watch?v=TPDgB6136ZE
51
+ """
52
+ ap = (p[0] - a[0], p[1] - a[1])
53
+ ab = (b[0] - a[0], b[1] - a[1])
54
+ af_coef = (ap[0] * ab[0] + ap[1] * ab[1]) / (ab[0] ** 2 + ab[1] ** 2)
55
+ af = (af_coef * ab[0], af_coef * ab[1])
56
+ f = (a[0] + af[0], a[1] + af[1])
57
+ return f
58
+
59
+ @staticmethod
60
+ def clamp(val: float, min_val: float, max_val: float) -> float:
61
+ if val < min_val:
62
+ val = min_val
63
+ if val > max_val :
64
+ val = max_val
65
+ return val
File without changes
@@ -0,0 +1,31 @@
1
+ """
2
+ @file: control_factory.py
3
+ @breif: Facotry class for local planner.
4
+ @author: Winter
5
+ @update: 2023.10.24
6
+ """
7
+ from python_motion_planning.local_planner import *
8
+
9
+ class ControlFactory(object):
10
+ def __init__(self) -> None:
11
+ pass
12
+
13
+ def __call__(self, planner_name, **config):
14
+ if planner_name == "dwa":
15
+ return DWA(**config)
16
+ elif planner_name == "pid":
17
+ return PID(**config)
18
+ elif planner_name == "apf":
19
+ return APF(**config)
20
+ elif planner_name == "rpp":
21
+ return RPP(**config)
22
+ elif planner_name == "lqr":
23
+ return LQR(**config)
24
+ elif planner_name == "mpc":
25
+ return MPC(**config)
26
+ elif planner_name == "ddpg":
27
+ return DDPG(actor_load_path="models/actor_best_example.pth",
28
+ critic_load_path="models/critic_best_example.pth",
29
+ **config)
30
+ else:
31
+ raise ValueError("The `planner_name` must be set correctly.")
@@ -0,0 +1,29 @@
1
+ """
2
+ @file: curve_factory.py
3
+ @breif: Facotry class for curve generation.
4
+ @author: Winter
5
+ @update: 2023.7.25
6
+ """
7
+ from python_motion_planning.curve_generation import *
8
+
9
+ class CurveFactory(object):
10
+ def __init__(self) -> None:
11
+ pass
12
+
13
+ def __call__(self, curve_name, **config):
14
+ if curve_name == "dubins":
15
+ return Dubins(**config)
16
+ elif curve_name == "bezier":
17
+ return Bezier(**config)
18
+ elif curve_name == "polynomial":
19
+ return Polynomial(**config)
20
+ elif curve_name == "reeds_shepp":
21
+ return ReedsShepp(**config)
22
+ elif curve_name == "cubic_spline":
23
+ return CubicSpline(**config)
24
+ elif curve_name == "bspline":
25
+ return BSpline(**config)
26
+ elif curve_name == "fem_pos_smoother":
27
+ return FemPosSmoother(**config)
28
+ else:
29
+ raise ValueError("The `curve_name` must be set correctly.")
@@ -0,0 +1,40 @@
1
+ """
2
+ @file: planner.py
3
+ @breif: Abstract class for planner
4
+ @author: Winter
5
+ @update: 2023.1.17
6
+ """
7
+ import math
8
+ from abc import abstractmethod, ABC
9
+ from ..environment.env import Env, Node
10
+ from ..plot.plot import Plot
11
+
12
+ class Planner(ABC):
13
+ def __init__(self, start: tuple, goal: tuple, env: Env) -> None:
14
+ # plannig start and goal
15
+ self.start = Node(start, start, 0, 0)
16
+ self.goal = Node(goal, goal, 0, 0)
17
+ # environment
18
+ self.env = env
19
+ # graph handler
20
+ self.plot = Plot(start, goal, env)
21
+
22
+ def dist(self, node1: Node, node2: Node) -> float:
23
+ return math.hypot(node2.x - node1.x, node2.y - node1.y)
24
+
25
+ def angle(self, node1: Node, node2: Node) -> float:
26
+ return math.atan2(node2.y - node1.y, node2.x - node1.x)
27
+
28
+ @abstractmethod
29
+ def plan(self):
30
+ '''
31
+ Interface for planning.
32
+ '''
33
+ pass
34
+
35
+ @abstractmethod
36
+ def run(self):
37
+ '''
38
+ Interface for running both plannig and animation.
39
+ '''
40
+ pass
@@ -0,0 +1,51 @@
1
+ """
2
+ @file: search_factory.py
3
+ @breif: Factory class for global planner.
4
+ @author: Winter
5
+ @update: 2023.3.2
6
+ """
7
+ from python_motion_planning.global_planner import *
8
+
9
+ class SearchFactory(object):
10
+ def __init__(self) -> None:
11
+ pass
12
+
13
+ def __call__(self, planner_name, **config):
14
+ if planner_name == "a_star":
15
+ return AStar(**config)
16
+ elif planner_name == "dijkstra":
17
+ return Dijkstra(**config)
18
+ elif planner_name == "gbfs":
19
+ return GBFS(**config)
20
+ elif planner_name == "jps":
21
+ return JPS(**config)
22
+ elif planner_name == "d_star":
23
+ return DStar(**config)
24
+ elif planner_name == "lpa_star":
25
+ return LPAStar(**config)
26
+ elif planner_name == "d_star_lite":
27
+ return DStarLite(**config)
28
+ elif planner_name == "voronoi":
29
+ return VoronoiPlanner(**config)
30
+ elif planner_name == "theta_star":
31
+ return ThetaStar(**config)
32
+ elif planner_name == "lazy_theta_star":
33
+ return LazyThetaStar(**config)
34
+ elif planner_name == "s_theta_star":
35
+ return SThetaStar(**config)
36
+ elif planner_name == "anya":
37
+ return Anya(**config)
38
+ elif planner_name == "rrt":
39
+ return RRT(**config)
40
+ elif planner_name == "rrt_connect":
41
+ return RRTConnect(**config)
42
+ elif planner_name == "rrt_star":
43
+ return RRTStar(**config)
44
+ elif planner_name == "informed_rrt":
45
+ return InformedRRT(**config)
46
+ elif planner_name == "aco":
47
+ return ACO(**config)
48
+ elif planner_name == "pso":
49
+ return PSO(**config)
50
+ else:
51
+ raise ValueError("The `planner_name` must be set correctly.")
utils/plot/__init__.py ADDED
File without changes
utils/plot/plot.py ADDED
@@ -0,0 +1,274 @@
1
+ """
2
+ Plot tools 2D
3
+ @author: huiming zhou
4
+ """
5
+ import numpy as np
6
+ import matplotlib
7
+ import matplotlib.pyplot as plt
8
+ import matplotlib.patches as patches
9
+
10
+ from ..environment.env import Env, Grid, Map, Node
11
+
12
+
13
+ class Plot:
14
+ def __init__(self, start, goal, env: Env):
15
+ self.start = Node(start, start, 0, 0)
16
+ self.goal = Node(goal, goal, 0, 0)
17
+ self.env = env
18
+ self.fig = plt.figure("planning")
19
+ self.ax = self.fig.add_subplot()
20
+
21
+ def animation(self, path: list, name: str, cost: float = None, expand: list = None, history_pose: list = None,
22
+ predict_path: list = None, lookahead_pts: list = None, cost_curve: list = None,
23
+ ellipse: np.ndarray = None) -> None:
24
+ name = name + "\ncost: " + str(cost) if cost else name
25
+ self.plotEnv(name)
26
+ if expand is not None:
27
+ self.plotExpand(expand)
28
+ if history_pose is not None:
29
+ self.plotHistoryPose(history_pose, predict_path, lookahead_pts)
30
+ if path is not None:
31
+ self.plotPath(path)
32
+
33
+ if cost_curve:
34
+ plt.figure("cost curve")
35
+ self.plotCostCurve(cost_curve, name)
36
+
37
+ if ellipse is not None:
38
+ self.plotEllipse(ellipse)
39
+
40
+ plt.show()
41
+
42
+ def plotEnv(self, name: str) -> None:
43
+ '''
44
+ Plot environment with static obstacles.
45
+
46
+ Parameters
47
+ ----------
48
+ name: Algorithm name or some other information
49
+ '''
50
+ plt.plot(self.start.x, self.start.y, marker="s", color="#ff0000")
51
+ plt.plot(self.goal.x, self.goal.y, marker="s", color="#1155cc")
52
+
53
+ if isinstance(self.env, Grid):
54
+ obs_x = [x[0] for x in self.env.obstacles]
55
+ obs_y = [x[1] for x in self.env.obstacles]
56
+ plt.plot(obs_x, obs_y, "sk")
57
+
58
+ if isinstance(self.env, Map):
59
+ ax = self.fig.add_subplot()
60
+ # boundary
61
+ for (ox, oy, w, h) in self.env.boundary:
62
+ ax.add_patch(patches.Rectangle(
63
+ (ox, oy), w, h,
64
+ edgecolor='black',
65
+ facecolor='black',
66
+ fill=True
67
+ )
68
+ )
69
+ # rectangle obstacles
70
+ for (ox, oy, w, h) in self.env.obs_rect:
71
+ ax.add_patch(patches.Rectangle(
72
+ (ox, oy), w, h,
73
+ edgecolor='black',
74
+ facecolor='gray',
75
+ fill=True
76
+ )
77
+ )
78
+ # circle obstacles
79
+ for (ox, oy, r) in self.env.obs_circ:
80
+ ax.add_patch(patches.Circle(
81
+ (ox, oy), r,
82
+ edgecolor='black',
83
+ facecolor='gray',
84
+ fill=True
85
+ )
86
+ )
87
+
88
+ plt.title(name)
89
+ plt.axis("equal")
90
+
91
+ def plotExpand(self, expand: list) -> None:
92
+ '''
93
+ Plot expanded grids using in graph searching.
94
+
95
+ Parameters
96
+ ----------
97
+ expand: Expanded grids during searching
98
+ '''
99
+ if self.start in expand:
100
+ expand.remove(self.start)
101
+ if self.goal in expand:
102
+ expand.remove(self.goal)
103
+
104
+ count = 0
105
+ if isinstance(self.env, Grid):
106
+ for x in expand:
107
+ count += 1
108
+ plt.plot(x.x, x.y, color="#dddddd", marker='s')
109
+ plt.gcf().canvas.mpl_connect('key_release_event',
110
+ lambda event: [exit(0) if event.key == 'escape' else None])
111
+ if count < len(expand) / 3: length = 20
112
+ elif count < len(expand) * 2 / 3: length = 30
113
+ else: length = 40
114
+ if count % length == 0: plt.pause(0.001)
115
+
116
+ if isinstance(self.env, Map):
117
+ for x in expand:
118
+ count += 1
119
+ if x.parent:
120
+ plt.plot([x.parent[0], x.x], [x.parent[1], x.y],
121
+ color="#dddddd", linestyle="-")
122
+ plt.gcf().canvas.mpl_connect('key_release_event',
123
+ lambda event:
124
+ [exit(0) if event.key == 'escape' else None])
125
+ if count % 10 == 0:
126
+ plt.pause(0.001)
127
+
128
+ plt.pause(0.01)
129
+
130
+ def plotPath(self, path: list, path_color: str='#13ae00', path_style: str="-") -> None:
131
+ '''
132
+ Plot path in global planning.
133
+
134
+ Parameters
135
+ ----------
136
+ path: Path found in global planning
137
+ '''
138
+ path_x = [path[i][0] for i in range(len(path))]
139
+ path_y = [path[i][1] for i in range(len(path))]
140
+ plt.plot(path_x, path_y, path_style, linewidth='2', color=path_color)
141
+ plt.plot(self.start.x, self.start.y, marker="s", color="#ff0000")
142
+ plt.plot(self.goal.x, self.goal.y, marker="s", color="#1155cc")
143
+
144
+ def plotAgent(self, pose: tuple, radius: float=1) -> None:
145
+ '''
146
+ Plot agent with specifical pose.
147
+
148
+ Parameters
149
+ ----------
150
+ pose: Pose of agent
151
+ radius: Radius of agent
152
+ '''
153
+ x, y, theta = pose
154
+ ref_vec = np.array([[radius / 2], [0]])
155
+ rot_mat = np.array([[np.cos(theta), -np.sin(theta)],
156
+ [np.sin(theta), np.cos(theta)]])
157
+ end_pt = rot_mat @ ref_vec + np.array([[x], [y]])
158
+
159
+ try:
160
+ self.ax.artists.pop()
161
+ for art in self.ax.get_children():
162
+ if isinstance(art, matplotlib.patches.FancyArrow):
163
+ art.remove()
164
+ except:
165
+ pass
166
+
167
+ self.ax.arrow(x, y, float(end_pt[0]) - x, float(end_pt[1]) - y,
168
+ width=0.1, head_width=0.40, color="r")
169
+ circle = plt.Circle((x, y), radius, color="r", fill=False)
170
+ self.ax.add_artist(circle)
171
+
172
+ def plotHistoryPose(self, history_pose, predict_path=None, lookahead_pts=None) -> None:
173
+ lookahead_handler = None
174
+ for i, pose in enumerate(history_pose):
175
+ if i < len(history_pose) - 1:
176
+ plt.plot([history_pose[i][0], history_pose[i + 1][0]],
177
+ [history_pose[i][1], history_pose[i + 1][1]], c="#13ae00")
178
+ if predict_path is not None:
179
+ plt.plot(predict_path[i][:, 0], predict_path[i][:, 1], c="#ddd")
180
+ i += 1
181
+
182
+ # agent
183
+ self.plotAgent(pose)
184
+
185
+ # lookahead
186
+ if lookahead_handler is not None:
187
+ lookahead_handler.remove()
188
+ if lookahead_pts is not None:
189
+ try:
190
+ lookahead_handler = self.ax.scatter(lookahead_pts[i][0], lookahead_pts[i][1], c="b")
191
+ except:
192
+ lookahead_handler = self.ax.scatter(lookahead_pts[-1][0], lookahead_pts[-1][1], c="b")
193
+
194
+ plt.gcf().canvas.mpl_connect('key_release_event',
195
+ lambda event: [exit(0) if event.key == 'escape' else None])
196
+ if i % 5 == 0: plt.pause(0.03)
197
+
198
+ def plotCostCurve(self, cost_list: list, name: str) -> None:
199
+ '''
200
+ Plot cost curve with epochs using in evolutionary searching.
201
+
202
+ Parameters
203
+ ----------
204
+ cost_list: Cost with epochs
205
+ name: Algorithm name or some other information
206
+ '''
207
+ plt.plot(cost_list, color="b")
208
+ plt.xlabel("epochs")
209
+ plt.ylabel("cost value")
210
+ plt.title(name)
211
+ plt.grid()
212
+
213
+ def plotEllipse(self, ellipse: np.ndarray, color: str = 'darkorange', linestyle: str = '--', linewidth: float = 2):
214
+ plt.plot(ellipse[0, :], ellipse[1, :], linestyle=linestyle, color=color, linewidth=linewidth)
215
+
216
+ def connect(self, name: str, func) -> None:
217
+ self.fig.canvas.mpl_connect(name, func)
218
+
219
+ def clean(self):
220
+ plt.cla()
221
+
222
+ def update(self):
223
+ self.fig.canvas.draw_idle()
224
+
225
+ @staticmethod
226
+ def plotArrow(x, y, theta, length, color):
227
+ angle = np.deg2rad(30)
228
+ d = 0.5 * length
229
+ w = 2
230
+
231
+ x_start, y_start = x, y
232
+ x_end = x + length * np.cos(theta)
233
+ y_end = y + length * np.sin(theta)
234
+
235
+ theta_hat_L = theta + np.pi - angle
236
+ theta_hat_R = theta + np.pi + angle
237
+
238
+ x_hat_start = x_end
239
+ x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L)
240
+ x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R)
241
+
242
+ y_hat_start = y_end
243
+ y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L)
244
+ y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R)
245
+
246
+ plt.plot([x_start, x_end], [y_start, y_end], color=color, linewidth=w)
247
+ plt.plot([x_hat_start, x_hat_end_L], [y_hat_start, y_hat_end_L], color=color, linewidth=w)
248
+ plt.plot([x_hat_start, x_hat_end_R], [y_hat_start, y_hat_end_R], color=color, linewidth=w)
249
+
250
+ @staticmethod
251
+ def plotCar(x, y, theta, width, length, color):
252
+ theta_B = np.pi + theta
253
+
254
+ xB = x + length / 4 * np.cos(theta_B)
255
+ yB = y + length / 4 * np.sin(theta_B)
256
+
257
+ theta_BL = theta_B + np.pi / 2
258
+ theta_BR = theta_B - np.pi / 2
259
+
260
+ x_BL = xB + width / 2 * np.cos(theta_BL) # Bottom-Left vertex
261
+ y_BL = yB + width / 2 * np.sin(theta_BL)
262
+ x_BR = xB + width / 2 * np.cos(theta_BR) # Bottom-Right vertex
263
+ y_BR = yB + width / 2 * np.sin(theta_BR)
264
+
265
+ x_FL = x_BL + length * np.cos(theta) # Front-Left vertex
266
+ y_FL = y_BL + length * np.sin(theta)
267
+ x_FR = x_BR + length * np.cos(theta) # Front-Right vertex
268
+ y_FR = y_BR + length * np.sin(theta)
269
+
270
+ plt.plot([x_BL, x_BR, x_FR, x_FL, x_BL],
271
+ [y_BL, y_BR, y_FR, y_FL, y_BL],
272
+ linewidth=1, color=color)
273
+
274
+ Plot.plotArrow(x, y, theta, length / 2, color)