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
local_planner/mpc.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: mpc.py
|
|
3
|
+
@breif: Model Predicted Control (MPC) motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.25
|
|
6
|
+
"""
|
|
7
|
+
import osqp
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy import sparse
|
|
10
|
+
|
|
11
|
+
from .local_planner import LocalPlanner
|
|
12
|
+
from python_motion_planning.utils import Env
|
|
13
|
+
|
|
14
|
+
class MPC(LocalPlanner):
|
|
15
|
+
"""
|
|
16
|
+
Class for Model Predicted Control (MPC) 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
|
+
**params: other parameters can be found in the parent class LocalPlanner
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> from python_motion_planning.utils import Grid
|
|
27
|
+
>>> from python_motion_planning.local_planner import MPC
|
|
28
|
+
>>> start = (5, 5, 0)
|
|
29
|
+
>>> goal = (45, 25, 0)
|
|
30
|
+
>>> env = Grid(51, 31)
|
|
31
|
+
>>> planner = MPC(start, goal, env)
|
|
32
|
+
>>> planner.run()
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean", **params) -> None:
|
|
35
|
+
super().__init__(start, goal, env, heuristic_type, **params)
|
|
36
|
+
# MPC parameters
|
|
37
|
+
self.p = 12
|
|
38
|
+
self.m = 8
|
|
39
|
+
self.Q = np.diag([0.8, 0.8, 0.5])
|
|
40
|
+
self.R = np.diag([2, 2])
|
|
41
|
+
self.u_min = np.array([[self.params["MIN_V"]], [self.params["MIN_W"]]])
|
|
42
|
+
self.u_max = np.array([[self.params["MAX_V"]], [self.params["MAX_W"]]])
|
|
43
|
+
# self.du_min = np.array([[self.params["MIN_V"]], [self.params["MIN_W"]]])
|
|
44
|
+
self.du_min = np.array([[self.params["MIN_V_INC"]], [self.params["MIN_W_INC"]]])
|
|
45
|
+
self.du_max = np.array([[self.params["MAX_V_INC"]], [self.params["MAX_W_INC"]]])
|
|
46
|
+
|
|
47
|
+
# global planner
|
|
48
|
+
g_start = (start[0], start[1])
|
|
49
|
+
g_goal = (goal[0], goal[1])
|
|
50
|
+
self.g_planner = {"planner_name": "a_star", "start": g_start, "goal": g_goal, "env": env}
|
|
51
|
+
self.path = self.g_path[::-1]
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return "Model Predicted Control (MPC)"
|
|
55
|
+
|
|
56
|
+
def plan(self):
|
|
57
|
+
"""
|
|
58
|
+
MPC motion plan function.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
flag (bool): planning successful if true else failed
|
|
62
|
+
pose_list (list): history poses of robot
|
|
63
|
+
"""
|
|
64
|
+
dt = self.params["TIME_STEP"]
|
|
65
|
+
u_p = (0, 0)
|
|
66
|
+
for _ in range(self.params["MAX_ITERATION"]):
|
|
67
|
+
# break until goal reached
|
|
68
|
+
if self.reachGoal(tuple(self.robot.state.squeeze(axis=1)[0:3]), self.goal):
|
|
69
|
+
return True, self.robot.history_pose
|
|
70
|
+
|
|
71
|
+
# get the particular point on the path at the lookahead distance
|
|
72
|
+
lookahead_pt, theta_trj, kappa = self.getLookaheadPoint()
|
|
73
|
+
|
|
74
|
+
# calculate velocity command
|
|
75
|
+
e_theta = self.regularizeAngle(self.robot.theta - self.goal[2])
|
|
76
|
+
if not self.shouldMoveToGoal(self.robot.position, self.goal):
|
|
77
|
+
if not self.shouldRotateToPath(abs(e_theta)):
|
|
78
|
+
u = np.array([[0], [0]])
|
|
79
|
+
else:
|
|
80
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
81
|
+
else:
|
|
82
|
+
e_theta = self.regularizeAngle(
|
|
83
|
+
self.angle(self.robot.position, lookahead_pt) - self.robot.theta
|
|
84
|
+
)
|
|
85
|
+
if self.shouldRotateToPath(abs(e_theta)):
|
|
86
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
87
|
+
else:
|
|
88
|
+
s = (self.robot.px, self.robot.py, self.robot.theta) # current state
|
|
89
|
+
s_d = (lookahead_pt[0], lookahead_pt[1], theta_trj) # desired state
|
|
90
|
+
u_r = (self.robot.v, self.robot.v * kappa) # refered input
|
|
91
|
+
u, u_p = self.mpcControl(s, s_d, u_r, u_p)
|
|
92
|
+
|
|
93
|
+
# feed into robotic kinematic
|
|
94
|
+
self.robot.kinematic(u, dt)
|
|
95
|
+
|
|
96
|
+
return False, None
|
|
97
|
+
|
|
98
|
+
def run(self):
|
|
99
|
+
"""
|
|
100
|
+
Running both plannig and animation.
|
|
101
|
+
"""
|
|
102
|
+
_, history_pose = self.plan()
|
|
103
|
+
if not history_pose:
|
|
104
|
+
raise ValueError("Path not found and planning failed!")
|
|
105
|
+
|
|
106
|
+
path = np.array(history_pose)[:, 0:2]
|
|
107
|
+
cost = np.sum(np.sqrt(np.sum(np.diff(path, axis=0)**2, axis=1, keepdims=True)))
|
|
108
|
+
self.plot.plotPath(self.path, path_color="r", path_style="--")
|
|
109
|
+
self.plot.animation(path, str(self), cost, history_pose=history_pose)
|
|
110
|
+
|
|
111
|
+
def mpcControl(self, s: tuple, s_d: tuple, u_r: tuple, u_p: tuple) -> np.ndarray:
|
|
112
|
+
"""
|
|
113
|
+
Execute MPC control process.
|
|
114
|
+
|
|
115
|
+
Parameters:
|
|
116
|
+
s (tuple): current state
|
|
117
|
+
s_d (tuple): desired state
|
|
118
|
+
u_r (tuple): refered control
|
|
119
|
+
u_p (tuple): previous control error
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
u (np.ndarray): control vector
|
|
123
|
+
"""
|
|
124
|
+
dim_u, dim_x = 2, 3
|
|
125
|
+
dt = self.params["TIME_STEP"]
|
|
126
|
+
|
|
127
|
+
# state vector (5 x 1)
|
|
128
|
+
x = np.array([
|
|
129
|
+
[s[0] - s_d[0]],
|
|
130
|
+
[s[1] - s_d[1]],
|
|
131
|
+
[s[2] - s_d[2]],
|
|
132
|
+
[u_p[0]],
|
|
133
|
+
[u_p[1]],
|
|
134
|
+
])
|
|
135
|
+
|
|
136
|
+
# original state matrix
|
|
137
|
+
A = np.identity(3)
|
|
138
|
+
A[0, 2] = -u_r[0] * np.sin(s_d[2]) * dt
|
|
139
|
+
A[1, 2] = u_r[0] * np.cos(s_d[2]) * dt
|
|
140
|
+
|
|
141
|
+
# original control matrix
|
|
142
|
+
B = np.zeros((3, 2))
|
|
143
|
+
B[0, 0] = np.cos(s_d[2]) * dt
|
|
144
|
+
B[1, 0] = np.sin(s_d[2]) * dt
|
|
145
|
+
B[2, 1] = dt
|
|
146
|
+
|
|
147
|
+
# state matrix (5 x 5)
|
|
148
|
+
A = np.concatenate((A, B), axis=1)
|
|
149
|
+
temp = np.concatenate((np.zeros((dim_u, dim_x)), np.identity(dim_u)), axis=1)
|
|
150
|
+
A = np.concatenate((A, temp), axis=0)
|
|
151
|
+
|
|
152
|
+
# control matrix (5 x 2)
|
|
153
|
+
B = np.concatenate((B, np.identity(dim_u)), axis=0)
|
|
154
|
+
|
|
155
|
+
# output matrix (3 x 5)
|
|
156
|
+
C = np.concatenate((np.identity(dim_x), np.zeros((dim_x, dim_u))), axis=1)
|
|
157
|
+
|
|
158
|
+
# mpc state matrix (3p x 5)
|
|
159
|
+
S_x = C @ A
|
|
160
|
+
for i in range(1, self.p):
|
|
161
|
+
S_x = np.concatenate((S_x, C @ np.linalg.matrix_power(A, i + 1)), axis=0)
|
|
162
|
+
|
|
163
|
+
# mpc control matrix (3p x 2m)
|
|
164
|
+
S_u_rows = []
|
|
165
|
+
for i in range(self.p):
|
|
166
|
+
S_u_row = C @ np.linalg.matrix_power(A, i) @ B
|
|
167
|
+
for j in range(1, self.m):
|
|
168
|
+
if j <= i:
|
|
169
|
+
S_u_row = np.concatenate((
|
|
170
|
+
S_u_row, C @ np.linalg.matrix_power(A, i - j) @ B
|
|
171
|
+
), axis=1)
|
|
172
|
+
else:
|
|
173
|
+
S_u_row = np.concatenate((S_u_row, np.zeros((dim_x, dim_u))), axis=1)
|
|
174
|
+
S_u_rows.append(S_u_row)
|
|
175
|
+
S_u = np.vstack(S_u_rows)
|
|
176
|
+
|
|
177
|
+
# optimization
|
|
178
|
+
Yr = np.zeros((3 * self.p, 1)) # (3p x 1)
|
|
179
|
+
Q = np.kron(np.identity(self.p), self.Q) # (3p x 3p)
|
|
180
|
+
R = np.kron(np.identity(self.m), self.R) # (2m x 2m)
|
|
181
|
+
H = S_u.T @ Q @ S_u + R # (2m x 2m)
|
|
182
|
+
g = S_u.T @ Q @ (S_x @ x - Yr) # (2m x 1)
|
|
183
|
+
|
|
184
|
+
# constriants
|
|
185
|
+
I = np.eye(2 * self.m)
|
|
186
|
+
A_I = np.kron(np.tril(np.ones((self.m, self.m))), np.diag([1, 1]))
|
|
187
|
+
U_min = np.kron(np.ones((self.m, 1)), self.u_min)
|
|
188
|
+
U_max = np.kron(np.ones((self.m, 1)), self.u_max)
|
|
189
|
+
U_k_1 = np.kron(np.ones((self.m, 1)), np.array([[u_p[0]], [u_p[1]]]))
|
|
190
|
+
|
|
191
|
+
# boundary
|
|
192
|
+
dU_min = np.kron(np.ones((self.m, 1)), self.du_min)
|
|
193
|
+
dU_max = np.kron(np.ones((self.m, 1)), self.du_max)
|
|
194
|
+
|
|
195
|
+
# solve
|
|
196
|
+
solver = osqp.OSQP()
|
|
197
|
+
H = sparse.csc_matrix(H)
|
|
198
|
+
A = sparse.csc_matrix(np.vstack([A_I, I]))
|
|
199
|
+
l = np.vstack([U_min - U_k_1, dU_min])
|
|
200
|
+
u = np.vstack([U_max - U_k_1, dU_max])
|
|
201
|
+
solver.setup(H, g, A, l, u, verbose=False)
|
|
202
|
+
res = solver.solve()
|
|
203
|
+
dU_opt = res.x[:, None]
|
|
204
|
+
|
|
205
|
+
# first element
|
|
206
|
+
du = dU_opt[0:2]
|
|
207
|
+
|
|
208
|
+
# real control
|
|
209
|
+
u = du + np.array([[u_p[0]], [u_p[1]]]) + np.array([[u_r[0]], [u_r[1]]])
|
|
210
|
+
|
|
211
|
+
return np.array([
|
|
212
|
+
[self.linearRegularization(float(u[0]))],
|
|
213
|
+
[self.angularRegularization(float(u[1]))]
|
|
214
|
+
]), (float(u[0]) - u_r[0], float(u[1]) - u_r[1])
|
local_planner/pid.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: pid.py
|
|
3
|
+
@breif: PID motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.25
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
from .local_planner import LocalPlanner
|
|
11
|
+
from python_motion_planning.utils import Env, MathHelper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PID(LocalPlanner):
|
|
15
|
+
"""
|
|
16
|
+
Class for PID 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
|
+
**params: other parameters can be found in the parent class LocalPlanner
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> from python_motion_planning.utils import Grid
|
|
27
|
+
>>> from python_motion_planning.local_planner import PID
|
|
28
|
+
>>> start = (5, 5, 0)
|
|
29
|
+
>>> goal = (45, 25, 0)
|
|
30
|
+
>>> env = Grid(51, 31)
|
|
31
|
+
>>> planner = PID(start, goal, env)
|
|
32
|
+
>>> planner.run()
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean",
|
|
35
|
+
k_v_p: float = 1.00, k_v_i: float = 0.10, k_v_d: float = 0.10,
|
|
36
|
+
k_w_p: float = 1.00, k_w_i: float = 0.10, k_w_d: float = 0.10,
|
|
37
|
+
k_theta: float = 0.75, **params) -> None:
|
|
38
|
+
super().__init__(start, goal, env, heuristic_type, MIN_LOOKAHEAD_DIST=0.75, **params)
|
|
39
|
+
# PID parameters
|
|
40
|
+
self.e_w, self.i_w = 0.0, 0.0
|
|
41
|
+
self.e_v, self.i_v = 0.0, 0.0
|
|
42
|
+
self.k_v_p, self.k_v_i, self.k_v_d = k_v_p, k_v_i, k_v_d
|
|
43
|
+
self.k_w_p, self.k_w_i, self.k_w_d = k_w_p, k_w_i, k_w_d
|
|
44
|
+
self.k_theta = k_theta
|
|
45
|
+
|
|
46
|
+
# global planner
|
|
47
|
+
g_start = (start[0], start[1])
|
|
48
|
+
g_goal = (goal[0], goal[1])
|
|
49
|
+
self.g_planner = {"planner_name": "a_star", "start": g_start, "goal": g_goal, "env": env}
|
|
50
|
+
self.path = self.g_path[::-1]
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
return "PID Planner"
|
|
54
|
+
|
|
55
|
+
def plan(self):
|
|
56
|
+
"""
|
|
57
|
+
PID motion plan function.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
flag (bool): planning successful if true else failed
|
|
61
|
+
pose_list (list): history poses of robot
|
|
62
|
+
"""
|
|
63
|
+
dt = self.params["TIME_STEP"]
|
|
64
|
+
for _ in range(self.params["MAX_ITERATION"]):
|
|
65
|
+
# break until goal reached
|
|
66
|
+
if self.reachGoal(tuple(self.robot.state.squeeze(axis=1)[0:3]), self.goal):
|
|
67
|
+
return True, self.robot.history_pose
|
|
68
|
+
|
|
69
|
+
# find next tracking point
|
|
70
|
+
lookahead_pt, theta_trj, _ = self.getLookaheadPoint()
|
|
71
|
+
|
|
72
|
+
# desired angle
|
|
73
|
+
theta_err = self.angle(self.robot.position, lookahead_pt)
|
|
74
|
+
if abs(theta_err - theta_trj) > np.pi:
|
|
75
|
+
if theta_err > theta_trj:
|
|
76
|
+
theta_trj += 2 * np.pi
|
|
77
|
+
else:
|
|
78
|
+
theta_err += 2 * np.pi
|
|
79
|
+
theta_d = self.k_theta * theta_err + (1 - self.k_theta) * theta_trj
|
|
80
|
+
|
|
81
|
+
# calculate velocity command
|
|
82
|
+
e_theta = self.regularizeAngle(self.robot.theta - self.goal[2])
|
|
83
|
+
if not self.shouldMoveToGoal(self.robot.position, self.goal):
|
|
84
|
+
if not self.shouldRotateToPath(abs(e_theta)):
|
|
85
|
+
u = np.array([[0], [0]])
|
|
86
|
+
else:
|
|
87
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
88
|
+
else:
|
|
89
|
+
e_theta = self.regularizeAngle(theta_d - self.robot.theta)
|
|
90
|
+
if self.shouldRotateToPath(abs(e_theta)):
|
|
91
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
92
|
+
else:
|
|
93
|
+
v_d = self.dist(lookahead_pt, self.robot.position) / dt
|
|
94
|
+
u = np.array([[self.linearRegularization(v_d)], [self.angularRegularization(e_theta / dt)]])
|
|
95
|
+
|
|
96
|
+
# feed into robotic kinematic
|
|
97
|
+
self.robot.kinematic(u, dt)
|
|
98
|
+
|
|
99
|
+
return False, None
|
|
100
|
+
|
|
101
|
+
def run(self):
|
|
102
|
+
"""
|
|
103
|
+
Running both plannig and animation.
|
|
104
|
+
"""
|
|
105
|
+
_, history_pose = self.plan()
|
|
106
|
+
if not history_pose:
|
|
107
|
+
raise ValueError("Path not found and planning failed!")
|
|
108
|
+
|
|
109
|
+
path = np.array(history_pose)[:, 0:2]
|
|
110
|
+
cost = np.sum(np.sqrt(np.sum(np.diff(path, axis=0)**2, axis=1, keepdims=True)))
|
|
111
|
+
self.plot.plotPath(self.path, path_color="r", path_style="--")
|
|
112
|
+
self.plot.animation(path, str(self), cost, history_pose=history_pose)
|
|
113
|
+
|
|
114
|
+
def linearRegularization(self, v_d: float) -> float:
|
|
115
|
+
"""
|
|
116
|
+
Linear velocity controller with pid.
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
v_d (float): reference velocity input
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
v (float): control velocity output
|
|
123
|
+
"""
|
|
124
|
+
e_v = v_d - self.robot.v
|
|
125
|
+
self.i_v += e_v * self.params["TIME_STEP"]
|
|
126
|
+
d_v = (e_v - self.e_v) / self.params["TIME_STEP"]
|
|
127
|
+
self.e_v = e_v
|
|
128
|
+
|
|
129
|
+
v_inc = self.k_v_p * e_v + self.k_v_i * self.i_v + self.k_v_d * d_v
|
|
130
|
+
v_inc = MathHelper.clamp(v_inc, self.params["MIN_V_INC"], self.params["MAX_V_INC"])
|
|
131
|
+
|
|
132
|
+
v = self.robot.v + v_inc
|
|
133
|
+
v = MathHelper.clamp(v, self.params["MIN_V"], self.params["MAX_V"])
|
|
134
|
+
|
|
135
|
+
return v
|
|
136
|
+
|
|
137
|
+
def angularRegularization(self, w_d: float) -> float:
|
|
138
|
+
"""
|
|
139
|
+
Angular velocity controller with pid.
|
|
140
|
+
|
|
141
|
+
Parameters:
|
|
142
|
+
w_d (float): reference angular input
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
w (float): control angular velocity output
|
|
146
|
+
"""
|
|
147
|
+
e_w = w_d - self.robot.w
|
|
148
|
+
self.i_w += e_w * self.params["TIME_STEP"]
|
|
149
|
+
d_w = (e_w - self.e_w) / self.params["TIME_STEP"]
|
|
150
|
+
self.e_w = e_w
|
|
151
|
+
|
|
152
|
+
w_inc = self.k_w_p * e_w + self.k_w_i * self.i_w + self.k_w_d * d_w
|
|
153
|
+
w_inc = MathHelper.clamp(w_inc, self.params["MIN_W_INC"], self.params["MAX_W_INC"])
|
|
154
|
+
|
|
155
|
+
w = self.robot.w + w_inc
|
|
156
|
+
w = MathHelper.clamp(w, self.params["MIN_W"], self.params["MAX_W"])
|
|
157
|
+
|
|
158
|
+
return w
|
local_planner/rpp.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: rpp.py
|
|
3
|
+
@breif: Regulated Pure Pursuit (RPP) motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.5.21
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.spatial.distance import cdist
|
|
10
|
+
|
|
11
|
+
from .local_planner import LocalPlanner
|
|
12
|
+
from python_motion_planning.utils import Env
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RPP(LocalPlanner):
|
|
16
|
+
"""
|
|
17
|
+
Class for RPP motion planning.
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
start (tuple): start point coordinate
|
|
21
|
+
goal (tuple): goal point coordinate
|
|
22
|
+
env (Env): environment
|
|
23
|
+
heuristic_type (str): heuristic function type
|
|
24
|
+
**params: other parameters can be found in the parent class LocalPlanner
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> from python_motion_planning.utils import Grid
|
|
28
|
+
>>> from python_motion_planning.local_planner import RPP
|
|
29
|
+
>>> start = (5, 5, 0)
|
|
30
|
+
>>> goal = (45, 25, 0)
|
|
31
|
+
>>> env = Grid(51, 31)
|
|
32
|
+
>>> planner = RPP(start, goal, env)
|
|
33
|
+
>>> planner.run()
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean", **params) -> None:
|
|
36
|
+
super().__init__(start, goal, env, heuristic_type, **params)
|
|
37
|
+
# RPP parameters
|
|
38
|
+
self.regulated_radius_min = 0.9
|
|
39
|
+
self.scaling_dist = 0.6
|
|
40
|
+
self.scaling_gain = 1.0
|
|
41
|
+
|
|
42
|
+
# global planner
|
|
43
|
+
g_start = (start[0], start[1])
|
|
44
|
+
g_goal = (goal[0], goal[1])
|
|
45
|
+
self.g_planner = {"planner_name": "a_star", "start": g_start, "goal": g_goal, "env": env}
|
|
46
|
+
self.path = self.g_path[::-1]
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return "Regulated Pure Pursuit (RPP)"
|
|
50
|
+
|
|
51
|
+
def plan(self):
|
|
52
|
+
"""
|
|
53
|
+
RPP motion plan function.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
flag (bool): planning successful if true else failed
|
|
57
|
+
pose_list (list): history poses of robot
|
|
58
|
+
lookahead_pts (list): history lookahead points
|
|
59
|
+
"""
|
|
60
|
+
lookahead_pts = []
|
|
61
|
+
dt = self.params["TIME_STEP"]
|
|
62
|
+
for _ in range(self.params["MAX_ITERATION"]):
|
|
63
|
+
# break until goal reached
|
|
64
|
+
if self.reachGoal(tuple(self.robot.state.squeeze(axis=1)[0:3]), self.goal):
|
|
65
|
+
return True, self.robot.history_pose, lookahead_pts
|
|
66
|
+
|
|
67
|
+
# get the particular point on the path at the lookahead distance
|
|
68
|
+
lookahead_pt, _, _ = self.getLookaheadPoint()
|
|
69
|
+
|
|
70
|
+
# get the tracking curvature with goalahead point
|
|
71
|
+
lookahead_k = 2 * math.sin(
|
|
72
|
+
self.angle(self.robot.position, lookahead_pt) - self.robot.theta
|
|
73
|
+
) / self.lookahead_dist
|
|
74
|
+
|
|
75
|
+
# calculate velocity command
|
|
76
|
+
e_theta = self.regularizeAngle(self.robot.theta - self.goal[2])
|
|
77
|
+
if not self.shouldMoveToGoal(self.robot.position, self.goal):
|
|
78
|
+
if not self.shouldRotateToPath(abs(e_theta)):
|
|
79
|
+
u = np.array([[0], [0]])
|
|
80
|
+
else:
|
|
81
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
82
|
+
else:
|
|
83
|
+
e_theta = self.regularizeAngle(self.angle(self.robot.position, lookahead_pt) - self.robot.theta)
|
|
84
|
+
if self.shouldRotateToPath(abs(e_theta)):
|
|
85
|
+
u = np.array([[0], [self.angularRegularization(e_theta / dt)]])
|
|
86
|
+
else:
|
|
87
|
+
# apply constraints
|
|
88
|
+
curv_vel = self.applyCurvatureConstraint(self.params["MAX_V"], lookahead_k)
|
|
89
|
+
cost_vel = self.applyObstacleConstraint(self.params["MAX_V"])
|
|
90
|
+
v_d = min(curv_vel, cost_vel)
|
|
91
|
+
u = np.array([[self.linearRegularization(v_d)], [self.angularRegularization(v_d * lookahead_k)]])
|
|
92
|
+
|
|
93
|
+
# update lookahead points
|
|
94
|
+
lookahead_pts.append(lookahead_pt)
|
|
95
|
+
|
|
96
|
+
# feed into robotic kinematic
|
|
97
|
+
self.robot.kinematic(u, dt)
|
|
98
|
+
|
|
99
|
+
return False, None, None
|
|
100
|
+
|
|
101
|
+
def run(self):
|
|
102
|
+
"""
|
|
103
|
+
Running both plannig and animation.
|
|
104
|
+
"""
|
|
105
|
+
_, history_pose, lookahead_pts = self.plan()
|
|
106
|
+
if not history_pose:
|
|
107
|
+
raise ValueError("Path not found and planning failed!")
|
|
108
|
+
|
|
109
|
+
path = np.array(history_pose)[:, 0:2]
|
|
110
|
+
cost = np.sum(np.sqrt(np.sum(np.diff(path, axis=0)**2, axis=1, keepdims=True)))
|
|
111
|
+
self.plot.plotPath(self.path, path_color="r", path_style="--")
|
|
112
|
+
self.plot.animation(path, str(self), cost, history_pose=history_pose, lookahead_pts=lookahead_pts)
|
|
113
|
+
|
|
114
|
+
def applyCurvatureConstraint(self, raw_linear_vel: float, curvature: float) -> float:
|
|
115
|
+
"""
|
|
116
|
+
Applying curvature constraints to regularize the speed of robot turning.
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
raw_linear_vel (float): the raw linear velocity of robot
|
|
120
|
+
curvature (float): the tracking curvature
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
reg_vel (float): the regulated velocity
|
|
124
|
+
"""
|
|
125
|
+
radius = abs(1.0 / curvature)
|
|
126
|
+
if radius < self.regulated_radius_min:
|
|
127
|
+
return raw_linear_vel * (radius / self.regulated_radius_min)
|
|
128
|
+
else:
|
|
129
|
+
return raw_linear_vel
|
|
130
|
+
|
|
131
|
+
def applyObstacleConstraint(self, raw_linear_vel: float) -> float:
|
|
132
|
+
"""
|
|
133
|
+
Applying obstacle constraints to regularize the speed of robot approaching obstacles.
|
|
134
|
+
|
|
135
|
+
Parameters:
|
|
136
|
+
raw_linear_vel (float): the raw linear velocity of robot
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
reg_vel (float): the regulated velocity
|
|
140
|
+
"""
|
|
141
|
+
obstacles = np.array(list(self.obstacles))
|
|
142
|
+
D = cdist(obstacles, np.array([[self.robot.px, self.robot.py]]))
|
|
143
|
+
obs_dist = np.min(D)
|
|
144
|
+
if obs_dist < self.scaling_dist:
|
|
145
|
+
return raw_linear_vel * self.scaling_gain * obs_dist / self.scaling_dist
|
|
146
|
+
else:
|
|
147
|
+
return raw_linear_vel
|