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,348 @@
1
+ """
2
+ @file: dubins_curve.py
3
+ @breif: Dubins curve generation
4
+ @author: Winter
5
+ @update: 2023.5.31
6
+ """
7
+ import math
8
+ import numpy as np
9
+
10
+ from scipy.spatial.transform import Rotation as Rot
11
+ from python_motion_planning.utils import Plot
12
+ from .curve import Curve
13
+
14
+ class Dubins(Curve):
15
+ """
16
+ Class for Dubins curve generation.
17
+
18
+ Parameters:
19
+ step (float): Simulation or interpolation size
20
+ max_curv (float): The maximum curvature of the curve
21
+
22
+ Examples:
23
+ >>> from python_motion_planning.curve_generation import Dubins
24
+ >>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
25
+ >>> generator = Dubins(step, max_curv)
26
+ >>> generator.run(points)
27
+
28
+ References:
29
+ [1] On curves of minimal length with a constraint on average curvature, and with prescribed initial and terminal positions and tangents
30
+ """
31
+ def __init__(self, step: float, max_curv: float) -> None:
32
+ super().__init__(step)
33
+ self.max_curv = max_curv
34
+
35
+ def __str__(self) -> str:
36
+ return "Dubins Curve"
37
+
38
+ def LSL(self, alpha: float, beta: float, dist: float):
39
+ """
40
+ Left-Straight-Left generation mode.
41
+
42
+ Parameters:
43
+ alpha (float): Initial pose of (0, 0, alpha)
44
+ beta (float): Goal pose of (dist, 0, beta)
45
+ dist (float): The distance between the initial and goal pose
46
+
47
+ Returns:
48
+ t (float): Moving lenght of segments
49
+ p (float): Moving lenght of segments
50
+ q (float): Moving lenght of segments
51
+ mode (list): Motion mode
52
+ """
53
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
54
+
55
+ p_lsl = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_a - sin_b)
56
+ if p_lsl < 0:
57
+ return None, None, None, ["L", "S", "L"]
58
+ else:
59
+ p_lsl = math.sqrt(p_lsl)
60
+
61
+ t_lsl = self.mod2pi(-alpha + math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
62
+ q_lsl = self.mod2pi(beta - math.atan2(cos_b - cos_a, dist + sin_a - sin_b))
63
+
64
+ return t_lsl, p_lsl, q_lsl, ["L", "S", "L"]
65
+
66
+ def RSR(self, alpha: float, beta: float, dist: float):
67
+ """
68
+ Right-Straight-Right generation mode.
69
+
70
+ Parameters:
71
+ alpha (float): Initial pose of (0, 0, alpha)
72
+ beta (float): Goal pose of (dist, 0, beta)
73
+
74
+ Returns:
75
+ t (float): Moving lenght of segments
76
+ p (float): Moving lenght of segments
77
+ q (float): Moving lenght of segments
78
+ mode (list): Motion mode
79
+ """
80
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
81
+
82
+ p_rsr = 2 + dist ** 2 - 2 * cos_a_b + 2 * dist * (sin_b - sin_a)
83
+ if p_rsr < 0:
84
+ return None, None, None, ["R", "S", "R"]
85
+ else:
86
+ p_rsr = math.sqrt(p_rsr)
87
+
88
+ t_rsr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
89
+ q_rsr = self.mod2pi(-beta + math.atan2(cos_a - cos_b, dist - sin_a + sin_b))
90
+
91
+ return t_rsr, p_rsr, q_rsr, ["R", "S", "R"]
92
+
93
+ def LSR(self, alpha: float, beta: float, dist: float):
94
+ """
95
+ Left-Straight-Right generation mode.
96
+
97
+ Parameters:
98
+ alpha (float): Initial pose of (0, 0, alpha)
99
+ beta (float): Goal pose of (dist, 0, beta)
100
+ dist (float): The distance between the initial and goal pose
101
+
102
+ Returns:
103
+ t (float): Moving lenght of segments
104
+ p (float): Moving lenght of segments
105
+ q (float): Moving lenght of segments
106
+ mode (list): Motion mode
107
+ """
108
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
109
+
110
+ p_lsr = -2 + dist ** 2 + 2 * cos_a_b + 2 * dist * (sin_a + sin_b)
111
+ if p_lsr < 0:
112
+ return None, None, None, ["L", "S", "R"]
113
+ else:
114
+ p_lsr = math.sqrt(p_lsr)
115
+
116
+ t_lsr = self.mod2pi(-alpha + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
117
+ q_lsr = self.mod2pi(-beta + math.atan2(-cos_a - cos_b, dist + sin_a + sin_b) - math.atan2(-2.0, p_lsr))
118
+
119
+ return t_lsr, p_lsr, q_lsr, ["L", "S", "R"]
120
+
121
+
122
+ def RSL(self, alpha: float, beta: float, dist: float):
123
+ """
124
+ Right-Straight-Left generation mode.
125
+
126
+ Parameters:
127
+ alpha (float): Initial pose of (0, 0, alpha)
128
+ beta (float): Goal pose of (dist, 0, beta)
129
+ dist (float): The distance between the initial and goal pose
130
+
131
+ Returns:
132
+ t (float): Moving lenght of segments
133
+ p (float): Moving lenght of segments
134
+ q (float): Moving lenght of segments
135
+ mode (list): Motion mode
136
+ """
137
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
138
+
139
+ p_rsl = -2 + dist ** 2 + 2 * cos_a_b - 2 * dist * (sin_a + sin_b)
140
+ if p_rsl < 0:
141
+ return None, None, None, ["R", "S", "L"]
142
+ else:
143
+ p_rsl = math.sqrt(p_rsl)
144
+
145
+ t_rsl = self.mod2pi(alpha - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
146
+ q_rsl = self.mod2pi(beta - math.atan2(cos_a + cos_b, dist - sin_a - sin_b) + math.atan2(2.0, p_rsl))
147
+
148
+ return t_rsl, p_rsl, q_rsl, ["R", "S", "L"]
149
+
150
+
151
+ def RLR(self, alpha: float, beta: float, dist: float):
152
+ """
153
+ Right-Left-Right generation mode.
154
+
155
+ Parameters:
156
+ alpha (float): Initial pose of (0, 0, alpha)
157
+ beta (float): Goal pose of (dist, 0, beta)
158
+ dist (float): The distance between the initial and goal pose
159
+
160
+ Returns:
161
+ t (float): Moving lenght of segments
162
+ p (float): Moving lenght of segments
163
+ q (float): Moving lenght of segments
164
+ mode (list): Motion mode
165
+ """
166
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
167
+
168
+ p_rlr = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
169
+ if abs(p_rlr) > 1.0:
170
+ return None, None, None, ["R", "L", "R"]
171
+ else:
172
+ p_rlr = self.mod2pi(2 * math.pi - math.acos(p_rlr))
173
+
174
+ t_rlr = self.mod2pi(alpha - math.atan2(cos_a - cos_b, dist - sin_a + sin_b) + p_rlr / 2.0)
175
+ q_rlr = self.mod2pi(alpha - beta - t_rlr + p_rlr)
176
+
177
+ return t_rlr, p_rlr, q_rlr, ["R", "L", "R"]
178
+
179
+ def LRL(self, alpha: float, beta: float, dist: float):
180
+ """
181
+ Left-Right-Left generation mode.
182
+
183
+ Parameters:
184
+ alpha (float): Initial pose of (0, 0, alpha)
185
+ beta (float): Goal pose of (dist, 0, beta)
186
+ dist (float): The distance between the initial and goal pose
187
+
188
+ Returns:
189
+ t (float): Moving lenght of segments
190
+ p (float): Moving lenght of segments
191
+ q (float): Moving lenght of segments
192
+ mode (list): Motion mode
193
+ """
194
+ sin_a, sin_b, cos_a, cos_b, _, cos_a_b = self.trigonometric(alpha, beta)
195
+
196
+ p_lrl = (6.0 - dist ** 2 + 2.0 * cos_a_b + 2.0 * dist * (sin_a - sin_b)) / 8.0
197
+ if abs(p_lrl) > 1.0:
198
+ return None, None, None, ["L", "R", "L"]
199
+ else:
200
+ p_lrl = self.mod2pi(2 * math.pi - math.acos(p_lrl))
201
+
202
+ t_lrl = self.mod2pi(-alpha + math.atan2(-cos_a + cos_b, dist + sin_a - sin_b) + p_lrl / 2.0)
203
+ q_lrl = self.mod2pi(beta - alpha - t_lrl + p_lrl)
204
+
205
+ return t_lrl, p_lrl, q_lrl, ["L", "R", "L"]
206
+
207
+ def interpolate(self, mode: str, length: float, init_pose: tuple):
208
+ """
209
+ Planning path interpolation.
210
+
211
+ Parameters:
212
+ mode (str): motion, e.g., L, S, R
213
+ length (float): Single step motion path length
214
+ init_pose (tuple): Initial pose (x, y, yaw)
215
+
216
+ Returns:
217
+ new_pose (tuple): New pose (new_x, new_y, new_yaw) after moving
218
+ """
219
+ x, y, yaw = init_pose
220
+
221
+ if mode == "S":
222
+ new_x = x + length / self.max_curv * math.cos(yaw)
223
+ new_y = y + length / self.max_curv * math.sin(yaw)
224
+ new_yaw = yaw
225
+ elif mode == "L":
226
+ new_x = x + (math.sin(yaw + length) - math.sin(yaw)) / self.max_curv
227
+ new_y = y - (math.cos(yaw + length) - math.cos(yaw)) / self.max_curv
228
+ new_yaw = yaw + length
229
+ elif mode == "R":
230
+ new_x = x - (math.sin(yaw - length) - math.sin(yaw)) / self.max_curv
231
+ new_y = y + (math.cos(yaw - length) - math.cos(yaw)) / self.max_curv
232
+ new_yaw = yaw - length
233
+ else:
234
+ raise NotImplementedError
235
+
236
+ return new_x, new_y, new_yaw
237
+
238
+ def generation(self, start_pose: tuple, goal_pose: tuple):
239
+ """
240
+ Generate the Dubins Curve.
241
+
242
+ Parameters:
243
+ start_pose (tuple): Initial pose (x, y, yaw)
244
+ goal_pose (tuple): Target pose (x, y, yaw)
245
+
246
+ Returns:
247
+ best_cost (float): Best planning path length
248
+ best_mode (list): Best motion modes
249
+ x_list (list): Trajectory of x
250
+ y_list (list): Trajectory of y
251
+ yaw_list (list): Trajectory of yaw
252
+ """
253
+ sx, sy, syaw = start_pose
254
+ gx, gy, gyaw = goal_pose
255
+
256
+ # coordinate transformation
257
+ gx, gy = gx - sx, gy - sy
258
+ theta = self.mod2pi(math.atan2(gy, gx))
259
+ dist = math.hypot(gx, gy) * self.max_curv
260
+ alpha = self.mod2pi(syaw - theta)
261
+ beta = self.mod2pi(gyaw - theta)
262
+
263
+ # select the best motion
264
+ planners = [self.LSL, self.RSR, self.LSR, self.RSL, self.RLR, self.LRL]
265
+ best_t, best_p, best_q, best_mode, best_cost = None, None, None, None, float("inf")
266
+
267
+ for planner in planners:
268
+ t, p, q, mode = planner(alpha, beta, dist)
269
+ if t is None:
270
+ continue
271
+ cost = (abs(t) + abs(p) + abs(q))
272
+ if best_cost > cost:
273
+ best_t, best_p, best_q, best_mode, best_cost = t, p, q, mode, cost
274
+
275
+ # interpolation
276
+ segments = [best_t, best_p, best_q]
277
+ points_num = int(sum(segments) / self.step) + len(segments) + 3
278
+ x_list = [0.0 for _ in range(points_num)]
279
+ y_list = [0.0 for _ in range(points_num)]
280
+ yaw_list = [alpha for _ in range(points_num)]
281
+
282
+ i = 0
283
+ for mode_, seg_length in zip(best_mode, segments):
284
+ # path increment
285
+ d_length = self.step if seg_length > 0.0 else -self.step
286
+ x, y, yaw = x_list[i], y_list[i], yaw_list[i]
287
+ # current path length
288
+ length = d_length
289
+ while abs(length) <= abs(seg_length):
290
+ i += 1
291
+ x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, length, (x, y, yaw))
292
+ length += d_length
293
+ i += 1
294
+ x_list[i], y_list[i], yaw_list[i] = self.interpolate(mode_, seg_length, (x, y, yaw))
295
+
296
+ # failed
297
+ if len(x_list) <= 1:
298
+ return None, None, [], [], []
299
+
300
+ # remove unused data
301
+ while len(x_list) >= 1 and x_list[-1] == 0.0:
302
+ x_list.pop()
303
+ y_list.pop()
304
+ yaw_list.pop()
305
+
306
+ # coordinate transformation
307
+ rot = Rot.from_euler('z', theta).as_matrix()[0:2, 0:2]
308
+ converted_xy = rot @ np.stack([x_list, y_list])
309
+ x_list = converted_xy[0, :] + sx
310
+ y_list = converted_xy[1, :] + sy
311
+ yaw_list = [self.pi2pi(i_yaw + theta) for i_yaw in yaw_list]
312
+
313
+ return best_cost, best_mode, x_list, y_list, yaw_list
314
+
315
+ def run(self, points: list):
316
+ """
317
+ Running both generation and animation.
318
+
319
+ Parameters:
320
+ points (list[tuple]): path points
321
+ """
322
+ assert len(points) >= 2, "Number of points should be at least 2."
323
+ import matplotlib.pyplot as plt
324
+
325
+ # generation
326
+ path_x, path_y, path_yaw = [], [], []
327
+ for i in range(len(points) - 1):
328
+ _, _, x_list, y_list, yaw_list = self.generation(
329
+ (points[i][0], points[i][1], np.deg2rad(points[i][2])),
330
+ (points[i + 1][0], points[i + 1][1], np.deg2rad(points[i + 1][2])))
331
+
332
+ for j in range(len(x_list)):
333
+ path_x.append(x_list[j])
334
+ path_y.append(y_list[j])
335
+ path_yaw.append(yaw_list[j])
336
+
337
+ # animation
338
+ plt.figure("curve generation")
339
+ plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
340
+ for x, y, theta in points:
341
+ Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
342
+
343
+ plt.axis("equal")
344
+ plt.title(str(self))
345
+ plt.show()
346
+
347
+
348
+
@@ -0,0 +1,114 @@
1
+ """
2
+ @file: fem_pos_smooth.py
3
+ @breif: Fem-Pos Smoother
4
+ @author: Winter
5
+ @update: 2024.3.23
6
+ """
7
+ import osqp
8
+ import numpy as np
9
+ from scipy import sparse
10
+
11
+ from .curve import Curve
12
+
13
+ class FemPosSmoother(Curve):
14
+ """
15
+ Class for Fem-pos smoother.
16
+
17
+ Parameters:
18
+
19
+ Examples:
20
+ >>> from python_motion_planning.curve_generation import FemPosSmoother
21
+ >>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
22
+ >>> generator = FemPosSmoother(w_smooth, w_length, w_ref, dx_l, dx_u, dy_l, dy_u)
23
+ >>> generator.run(points)
24
+ """
25
+ def __init__(self, w_smooth: float, w_length: float, w_ref: float,
26
+ dx_l: float, dx_u: float, dy_l: float, dy_u: float) -> None:
27
+ super().__init__(0.1)
28
+ self.w_smooth = w_smooth
29
+ self.w_length = w_length
30
+ self.w_ref = w_ref
31
+ self.dx_l = dx_l
32
+ self.dx_u = dx_u
33
+ self.dy_l = dy_l
34
+ self.dy_u = dy_u
35
+
36
+ def __str__(self) -> str:
37
+ return "Fem-pos Smoother"
38
+
39
+ def generation(self, start_pose: tuple, goal_pose: tuple):
40
+ pass
41
+
42
+ def run(self, points: list, display: bool = True):
43
+ """
44
+ Running both generation and animation.
45
+
46
+ Parameters:
47
+ points (list[tuple]): path points
48
+ """
49
+ assert len(points) >= 3, "Number of points should be at least 3."
50
+ if len(points[0]) == 3:
51
+ points = [(ix, iy) for ix, iy, _ in points]
52
+ import matplotlib.pyplot as plt
53
+
54
+ n = len(points)
55
+ P = np.zeros((2 * n, 2 * n))
56
+
57
+ X = np.eye(2) * self.w_smooth
58
+ Y = np.eye(2) * self.w_length
59
+ Z = np.eye(2) * self.w_ref
60
+
61
+ P[0:2, 0:2] = X + Y + Z
62
+ P[0:2, 2:4] = -2 * X - Y
63
+ P[2:4, 2:4] = 5 * X + 2 * Y + Z
64
+ P[2 * n - 2:2 * n, 2 * n - 2:2 * n] = X + Y + Z
65
+ P[2 * n - 4:2 * n - 2, 2 * n - 4:2 * n - 2] = 5 * X + 2 * Y + Z
66
+ P[2 * n - 4:2 * n - 2, 2 * n - 2:2 * n] = -2 * X - Y
67
+
68
+ for i in range(2, n - 2):
69
+ P[2 * i:2 * i + 2, 2 * i:2 * i + 2] = 6 * X + 2 * Y + Z
70
+ for i in range(2, n - 1):
71
+ P[2 * i - 2:2 * i, 2 * i:2 * i + 2] = -4 * X - Y
72
+ for i in range(2, n):
73
+ P[2 * i - 4:2 * i - 2, 2 * i:2 * i + 2] = X
74
+
75
+ A_I = np.eye(2 * n)
76
+ g = np.zeros((2 * n, 1))
77
+ lower = np.zeros((2 * n, 1))
78
+ upper = np.zeros((2 * n, 1))
79
+ for i, p in enumerate(points):
80
+ g[2 * i], g[2 * i + 1] = -2 * self.w_ref * p[0], -2 * self.w_ref * p[1]
81
+ lower[2 * i], lower[2 * i + 1] = p[0] - self.dx_l, p[1] - self.dy_l
82
+ upper[2 * i], upper[2 * i + 1] = p[0] + self.dx_u, p[1] + self.dy_u
83
+
84
+ # solve
85
+ solver = osqp.OSQP()
86
+ solver.setup(sparse.csc_matrix(P), g, sparse.csc_matrix(A_I), lower, upper, verbose=False)
87
+ res = solver.solve()
88
+ opt = res.x
89
+
90
+ path_x, path_y = [], []
91
+ for i in range(n):
92
+ path_x.append(opt[2 * i])
93
+ path_y.append(opt[2 * i + 1])
94
+
95
+ if display:
96
+ plt.figure("curve generation")
97
+ plt.plot(path_x, path_y, linewidth=2, c="#ff0000", marker="o", label="smooth path")
98
+ raw_x, raw_y = [], []
99
+ for i, (x, y) in enumerate(points):
100
+ # label = "bounding box" if i == 0 else None
101
+ # plt.gca().add_patch(
102
+ # plt.Rectangle(xy=(x - self.dx_l, y - self.dy_l), width=self.dx_u + self.dx_l,
103
+ # height=self.dy_u + self.dy_l, color='red', linestyle="--", fill=False, label=label)
104
+ # )
105
+ raw_x.append(x)
106
+ raw_y.append(y)
107
+ plt.plot(raw_x, raw_y, linewidth=2, c="#1f77b4", marker="x", label="raw path")
108
+ # plt.axis("equal")
109
+ plt.legend()
110
+ plt.title(str(self))
111
+
112
+ plt.show()
113
+
114
+ return [(ix, iy) for (ix, iy) in zip(path_x, path_y)]
@@ -0,0 +1,226 @@
1
+ """
2
+ @file: polynomial_curve.py
3
+ @breif: Polynomial curve generation
4
+ @author: Winter
5
+ @update: 2023.7.25
6
+ """
7
+ import math
8
+ import numpy as np
9
+
10
+ from python_motion_planning.utils import Plot
11
+ from .curve import Curve
12
+
13
+ class Polynomial(Curve):
14
+ """
15
+ Class for polynomial curve generation(Quintic).
16
+
17
+ Parameters:
18
+ step (float): Simulation or interpolation size
19
+ max_acc (float): Maximum acceleration
20
+ max_jerk (float): Maximum jerk
21
+
22
+ Examples:
23
+ >>> from python_motion_planning.curve_generation import Polynomial
24
+ >>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
25
+ >>> generator = Polynomial(step, max_acc, max_jerk)
26
+ >>> generator.run(points)
27
+ """
28
+ def __init__(self, step: float, max_acc: float, max_jerk: float) -> None:
29
+ super().__init__(step)
30
+ self.max_acc = max_acc
31
+ self.max_jerk = max_jerk
32
+ self.dt = 0.1
33
+ self.t_min = 1
34
+ self.t_max = 30
35
+
36
+ def __str__(self) -> str:
37
+ return "Quintic Polynomial Curve"
38
+
39
+ class Poly:
40
+ """
41
+ Polynomial interpolation solver
42
+ """
43
+ def __init__(self, state0: tuple, state1: tuple, t: float) -> None:
44
+ x0, v0, a0 = state0
45
+ xt, vt, at = state1
46
+
47
+ A = np.array([[t ** 3, t ** 4, t ** 5],
48
+ [3 * t ** 2, 4 * t ** 3, 5 * t ** 4],
49
+ [6 * t, 12 * t ** 2, 20 * t ** 3]])
50
+ b = np.array([xt - x0 - v0 * t - a0 * t ** 2 / 2,
51
+ vt - v0 - a0 * t,
52
+ at - a0])
53
+ X = np.linalg.solve(A, b)
54
+
55
+ # Quintic polynomial coefficient
56
+ self.p0 = x0
57
+ self.p1 = v0
58
+ self.p2 = a0 / 2.0
59
+ self.p3 = X[0]
60
+ self.p4 = X[1]
61
+ self.p5 = X[2]
62
+
63
+ def x(self, t):
64
+ return self.p0 + self.p1 * t + self.p2 * t ** 2 + \
65
+ self.p3 * t ** 3 + self.p4 * t ** 4 + self.p5 * t ** 5
66
+
67
+ def dx(self, t):
68
+ return self.p1 + 2 * self.p2 * t + 3 * self.p3 * t ** 2 + \
69
+ 4 * self.p4 * t ** 3 + 5 * self.p5 * t ** 4
70
+
71
+ def ddx(self, t):
72
+ return 2 * self.p2 + 6 * self.p3 * t + 12 * self.p4 * t ** 2 + 20 * self.p5 * t ** 3
73
+
74
+ def dddx(self, t):
75
+ return 6 * self.p3 + 24 * self.p4 * t + 60 * self.p5 * t ** 2
76
+
77
+ class Trajectory:
78
+ """
79
+ Polynomial interpolation solver
80
+ """
81
+ def __init__(self):
82
+ self.clear()
83
+
84
+ def clear(self):
85
+ self.time = []
86
+ self.x = []
87
+ self.y = []
88
+ self.yaw = []
89
+ self.v = []
90
+ self.a = []
91
+ self.jerk = []
92
+
93
+ @property
94
+ def size(self):
95
+ assert len(self.time) == len(self.x) and \
96
+ len(self.x) == len(self.y) and \
97
+ len(self.y) == len(self.yaw) and \
98
+ len(self.yaw) == len(self.v) and \
99
+ len(self.v) == len(self.a) and \
100
+ len(self.a) == len(self.jerk), \
101
+ "Unequal dimensions of each attribute, this should not happen."
102
+ return len(self.time)
103
+
104
+ def generation(self, start_pose: tuple, goal_pose: tuple):
105
+ """
106
+ Generate the polynomial Curve.
107
+
108
+ Parameters:
109
+ start_pose (tuple): Initial pose (x, y, yaw)
110
+ goal_pose (tuple): Target pose (x, y, yaw)
111
+
112
+ Returns:
113
+ traj (Traj): The first trajectory that satisfies the acceleration and jerk constraint
114
+ """
115
+ sx, sy, syaw, sv, sa = start_pose
116
+ gx, gy, gyaw, gv, ga = goal_pose
117
+
118
+ sv_x = sv * math.cos(syaw)
119
+ sv_y = sv * math.sin(syaw)
120
+ gv_x = gv * math.cos(gyaw)
121
+ gv_y = gv * math.sin(gyaw)
122
+
123
+ sa_x = sa * math.cos(syaw)
124
+ sa_y = sa * math.sin(syaw)
125
+ ga_x = ga * math.cos(gyaw)
126
+ ga_y = ga * math.sin(gyaw)
127
+
128
+ traj = self.Trajectory()
129
+
130
+ for T in np.arange(self.t_min, self.t_max, self.step):
131
+ x_psolver = self.Poly((sx, sv_x, sa_x), (gx, gv_x, ga_x), T)
132
+ y_psolver = self.Poly((sy, sv_y, sa_y), (gy, gv_y, ga_y), T)
133
+
134
+ for t in np.arange(0.0, T + self.dt, self.dt):
135
+ traj.time.append(t)
136
+ traj.x.append(x_psolver.x(t))
137
+ traj.y.append(y_psolver.x(t))
138
+
139
+ vx = x_psolver.dx(t)
140
+ vy = y_psolver.dx(t)
141
+ traj.v.append(math.hypot(vx, vy))
142
+ traj.yaw.append(math.atan2(vy, vx))
143
+
144
+ ax = x_psolver.ddx(t)
145
+ ay = y_psolver.ddx(t)
146
+ a = math.hypot(ax, ay)
147
+ if len(traj.v) >= 2 and traj.v[-1] - traj.v[-2] < 0.0:
148
+ a *= -1
149
+ traj.a.append(a)
150
+
151
+ jx = x_psolver.dddx(t)
152
+ jy = y_psolver.dddx(t)
153
+ j = math.hypot(jx, jy)
154
+ if len(traj.a) >= 2 and traj.a[-1] - traj.a[-2] < 0.0:
155
+ j *= -1
156
+ traj.jerk.append(j)
157
+
158
+ if max(np.abs(traj.a)) <= self.max_acc and \
159
+ max(np.abs(traj.jerk)) <= self.max_jerk:
160
+ return traj
161
+ else:
162
+ traj.clear()
163
+
164
+ return traj
165
+
166
+ def run(self, points: list):
167
+ """
168
+ Running both generation and animation.
169
+
170
+ Parameters:
171
+ points (list[tuple]): path points
172
+ """
173
+ assert len(points) >= 2, "Number of points should be at least 2."
174
+ import matplotlib.pyplot as plt
175
+
176
+ # generate velocity and acceleration constraints heuristically
177
+ v = [0]
178
+ for i in range(len(points) - 1):
179
+ v.append(1.0)
180
+
181
+ a = [(v[i + 1] - v[i]) / 5 for i in range(len(points) - 1)]
182
+ a.append(0)
183
+
184
+ # generate curve
185
+ path_x, path_y, path_yaw = [], [], []
186
+ for i in range(len(points) - 1):
187
+ path = self.generation(
188
+ (points[i][0], points[i][1], np.deg2rad(points[i][2]), v[i], a[i]),
189
+ (points[i + 1][0], points[i + 1][1], np.deg2rad(points[i + 1][2]), v[i + 1], a[i + 1])
190
+ )
191
+
192
+ for j in range(path.size):
193
+ path_x.append(path.x[j])
194
+ path_y.append(path.y[j])
195
+ path_yaw.append(path.yaw[j])
196
+
197
+ # animation
198
+ plt.figure("curve generation")
199
+
200
+ # # static
201
+ # plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
202
+ # for x, y, theta in points:
203
+ # Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
204
+
205
+ # plt.axis("equal")
206
+ # plt.title(str(self))
207
+
208
+ # dynamic
209
+ plt.ion()
210
+ for i in range(len(path_x)):
211
+ plt.clf()
212
+ plt.gcf().canvas.mpl_connect('key_release_event',
213
+ lambda event: [exit(0) if event.key == 'escape' else None])
214
+ plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
215
+ for x, y, theta in points:
216
+ Plot.plotArrow(x, y, np.deg2rad(theta), 2, 'blueviolet')
217
+ Plot.plotCar(path_x[i], path_y[i], path_yaw[i], 1.5, 3, "black")
218
+ plt.axis("equal")
219
+ plt.title(str(self))
220
+ plt.draw()
221
+ plt.pause(0.001)
222
+
223
+ plt.show()
224
+
225
+
226
+