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.
- curve_generation/__init__.py +9 -0
- curve_generation/bezier_curve.py +131 -0
- curve_generation/bspline_curve.py +271 -0
- curve_generation/cubic_spline.py +128 -0
- curve_generation/curve.py +64 -0
- curve_generation/dubins_curve.py +348 -0
- curve_generation/fem_pos_smooth.py +114 -0
- curve_generation/polynomial_curve.py +226 -0
- curve_generation/reeds_shepp.py +736 -0
- global_planner/__init__.py +3 -0
- global_planner/evolutionary_search/__init__.py +4 -0
- global_planner/evolutionary_search/aco.py +186 -0
- global_planner/evolutionary_search/evolutionary_search.py +87 -0
- global_planner/evolutionary_search/pso.py +356 -0
- global_planner/graph_search/__init__.py +28 -0
- global_planner/graph_search/a_star.py +124 -0
- global_planner/graph_search/d_star.py +291 -0
- global_planner/graph_search/d_star_lite.py +188 -0
- global_planner/graph_search/dijkstra.py +77 -0
- global_planner/graph_search/gbfs.py +78 -0
- global_planner/graph_search/graph_search.py +87 -0
- global_planner/graph_search/jps.py +165 -0
- global_planner/graph_search/lazy_theta_star.py +114 -0
- global_planner/graph_search/lpa_star.py +230 -0
- global_planner/graph_search/s_theta_star.py +133 -0
- global_planner/graph_search/theta_star.py +171 -0
- global_planner/graph_search/voronoi.py +200 -0
- global_planner/sample_search/__init__.py +6 -0
- global_planner/sample_search/informed_rrt.py +152 -0
- global_planner/sample_search/rrt.py +151 -0
- global_planner/sample_search/rrt_connect.py +147 -0
- global_planner/sample_search/rrt_star.py +77 -0
- global_planner/sample_search/sample_search.py +135 -0
- local_planner/__init__.py +19 -0
- local_planner/apf.py +144 -0
- local_planner/ddpg.py +630 -0
- local_planner/dqn.py +687 -0
- local_planner/dwa.py +212 -0
- local_planner/local_planner.py +262 -0
- local_planner/lqr.py +146 -0
- local_planner/mpc.py +214 -0
- local_planner/pid.py +158 -0
- local_planner/rpp.py +147 -0
- python_motion_planning-1.0.dist-info/LICENSE +674 -0
- python_motion_planning-1.0.dist-info/METADATA +873 -0
- python_motion_planning-1.0.dist-info/RECORD +65 -0
- python_motion_planning-1.0.dist-info/WHEEL +5 -0
- python_motion_planning-1.0.dist-info/top_level.txt +4 -0
- utils/__init__.py +19 -0
- utils/agent/__init__.py +0 -0
- utils/agent/agent.py +135 -0
- utils/environment/__init__.py +0 -0
- utils/environment/env.py +134 -0
- utils/environment/node.py +85 -0
- utils/environment/point2d.py +96 -0
- utils/environment/pose2d.py +91 -0
- utils/helper/__init__.py +3 -0
- utils/helper/math_helper.py +65 -0
- utils/planner/__init__.py +0 -0
- utils/planner/control_factory.py +31 -0
- utils/planner/curve_factory.py +29 -0
- utils/planner/planner.py +40 -0
- utils/planner/search_factory.py +51 -0
- utils/plot/__init__.py +0 -0
- 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
|
+
|