Skip to content

Commit f343573

Browse files
authored
Update move_to_pose for cases where alpha > pi/2 or alpha < -pi/2 (#1168)
* Update move_to_pose for cases where alpha > pi/2 or alpha < -pi/2 * Update move_to_pose * Add move_to_pose test * Update move_to_pose
1 parent c7fb228 commit f343573

File tree

2 files changed

+134
-30
lines changed

2 files changed

+134
-30
lines changed

Control/move_to_pose/move_to_pose.py

+63-27
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@
55
Author: Daniel Ingram (daniel-s-ingram)
66
Atsushi Sakai (@Atsushi_twi)
77
Seied Muhammad Yazdian (@Muhammad-Yazdian)
8+
Wang Zheng (@Aglargil)
89
910
P. I. Corke, "Robotics, Vision & Control", Springer 2017, ISBN 978-3-319-54413-7
1011
1112
"""
1213

1314
import matplotlib.pyplot as plt
1415
import numpy as np
15-
from random import random
16+
import sys
17+
import pathlib
18+
19+
sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
1620
from utils.angle import angle_mod
21+
from random import random
22+
1723

1824
class PathFinderController:
1925
"""
@@ -70,21 +76,28 @@ def calc_control_command(self, x_diff, y_diff, theta, theta_goal):
7076
# [-pi, pi] to prevent unstable behavior e.g. difference going
7177
# from 0 rad to 2*pi rad with slight turn
7278

79+
# Ref: The velocity v always has a constant sign which depends on the initial value of α.
7380
rho = np.hypot(x_diff, y_diff)
74-
alpha = angle_mod(np.arctan2(y_diff, x_diff) - theta)
75-
beta = angle_mod(theta_goal - theta - alpha)
7681
v = self.Kp_rho * rho
77-
w = self.Kp_alpha * alpha - self.Kp_beta * beta
7882

83+
alpha = angle_mod(np.arctan2(y_diff, x_diff) - theta)
84+
beta = angle_mod(theta_goal - theta - alpha)
7985
if alpha > np.pi / 2 or alpha < -np.pi / 2:
86+
# recalculate alpha to make alpha in the range of [-pi/2, pi/2]
87+
alpha = angle_mod(np.arctan2(-y_diff, -x_diff) - theta)
88+
beta = angle_mod(theta_goal - theta - alpha)
89+
w = self.Kp_alpha * alpha - self.Kp_beta * beta
8090
v = -v
91+
else:
92+
w = self.Kp_alpha * alpha - self.Kp_beta * beta
8193

8294
return rho, v, w
8395

8496

8597
# simulation parameters
8698
controller = PathFinderController(9, 15, 3)
8799
dt = 0.01
100+
MAX_SIM_TIME = 5 # seconds, robot will stop moving when time exceeds this value
88101

89102
# Robot specifications
90103
MAX_LINEAR_SPEED = 15
@@ -101,37 +114,55 @@ def move_to_pose(x_start, y_start, theta_start, x_goal, y_goal, theta_goal):
101114
x_diff = x_goal - x
102115
y_diff = y_goal - y
103116

104-
x_traj, y_traj = [], []
117+
x_traj, y_traj, v_traj, w_traj = [x], [y], [0], [0]
105118

106119
rho = np.hypot(x_diff, y_diff)
107-
while rho > 0.001:
120+
t = 0
121+
while rho > 0.001 and t < MAX_SIM_TIME:
122+
t += dt
108123
x_traj.append(x)
109124
y_traj.append(y)
110125

111126
x_diff = x_goal - x
112127
y_diff = y_goal - y
113128

114-
rho, v, w = controller.calc_control_command(
115-
x_diff, y_diff, theta, theta_goal)
129+
rho, v, w = controller.calc_control_command(x_diff, y_diff, theta, theta_goal)
116130

117131
if abs(v) > MAX_LINEAR_SPEED:
118132
v = np.sign(v) * MAX_LINEAR_SPEED
119133

120134
if abs(w) > MAX_ANGULAR_SPEED:
121135
w = np.sign(w) * MAX_ANGULAR_SPEED
122136

137+
v_traj.append(v)
138+
w_traj.append(w)
139+
123140
theta = theta + w * dt
124141
x = x + v * np.cos(theta) * dt
125142
y = y + v * np.sin(theta) * dt
126143

127144
if show_animation: # pragma: no cover
128145
plt.cla()
129-
plt.arrow(x_start, y_start, np.cos(theta_start),
130-
np.sin(theta_start), color='r', width=0.1)
131-
plt.arrow(x_goal, y_goal, np.cos(theta_goal),
132-
np.sin(theta_goal), color='g', width=0.1)
146+
plt.arrow(
147+
x_start,
148+
y_start,
149+
np.cos(theta_start),
150+
np.sin(theta_start),
151+
color="r",
152+
width=0.1,
153+
)
154+
plt.arrow(
155+
x_goal,
156+
y_goal,
157+
np.cos(theta_goal),
158+
np.sin(theta_goal),
159+
color="g",
160+
width=0.1,
161+
)
133162
plot_vehicle(x, y, theta, x_traj, y_traj)
134163

164+
return x_traj, y_traj, v_traj, w_traj
165+
135166

136167
def plot_vehicle(x, y, theta, x_traj, y_traj): # pragma: no cover
137168
# Corners of triangular vehicle when pointing to the right (0 radians)
@@ -144,16 +175,16 @@ def plot_vehicle(x, y, theta, x_traj, y_traj): # pragma: no cover
144175
p2 = np.matmul(T, p2_i)
145176
p3 = np.matmul(T, p3_i)
146177

147-
plt.plot([p1[0], p2[0]], [p1[1], p2[1]], 'k-')
148-
plt.plot([p2[0], p3[0]], [p2[1], p3[1]], 'k-')
149-
plt.plot([p3[0], p1[0]], [p3[1], p1[1]], 'k-')
178+
plt.plot([p1[0], p2[0]], [p1[1], p2[1]], "k-")
179+
plt.plot([p2[0], p3[0]], [p2[1], p3[1]], "k-")
180+
plt.plot([p3[0], p1[0]], [p3[1], p1[1]], "k-")
150181

151-
plt.plot(x_traj, y_traj, 'b--')
182+
plt.plot(x_traj, y_traj, "b--")
152183

153184
# for stopping simulation with the esc key.
154185
plt.gcf().canvas.mpl_connect(
155-
'key_release_event',
156-
lambda event: [exit(0) if event.key == 'escape' else None])
186+
"key_release_event", lambda event: [exit(0) if event.key == "escape" else None]
187+
)
157188

158189
plt.xlim(0, 20)
159190
plt.ylim(0, 20)
@@ -162,26 +193,31 @@ def plot_vehicle(x, y, theta, x_traj, y_traj): # pragma: no cover
162193

163194

164195
def transformation_matrix(x, y, theta):
165-
return np.array([
166-
[np.cos(theta), -np.sin(theta), x],
167-
[np.sin(theta), np.cos(theta), y],
168-
[0, 0, 1]
169-
])
196+
return np.array(
197+
[
198+
[np.cos(theta), -np.sin(theta), x],
199+
[np.sin(theta), np.cos(theta), y],
200+
[0, 0, 1],
201+
]
202+
)
170203

171204

172205
def main():
173-
174206
for i in range(5):
175207
x_start = 20.0 * random()
176208
y_start = 20.0 * random()
177209
theta_start: float = 2 * np.pi * random() - np.pi
178210
x_goal = 20 * random()
179211
y_goal = 20 * random()
180212
theta_goal = 2 * np.pi * random() - np.pi
181-
print(f"Initial x: {round(x_start, 2)} m\nInitial y: {round(y_start, 2)} m\nInitial theta: {round(theta_start, 2)} rad\n")
182-
print(f"Goal x: {round(x_goal, 2)} m\nGoal y: {round(y_goal, 2)} m\nGoal theta: {round(theta_goal, 2)} rad\n")
213+
print(
214+
f"Initial x: {round(x_start, 2)} m\nInitial y: {round(y_start, 2)} m\nInitial theta: {round(theta_start, 2)} rad\n"
215+
)
216+
print(
217+
f"Goal x: {round(x_goal, 2)} m\nGoal y: {round(y_goal, 2)} m\nGoal theta: {round(theta_goal, 2)} rad\n"
218+
)
183219
move_to_pose(x_start, y_start, theta_start, x_goal, y_goal, theta_goal)
184220

185221

186-
if __name__ == '__main__':
222+
if __name__ == "__main__":
187223
main()

tests/test_move_to_pose.py

+71-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,81 @@
1+
import itertools
2+
import numpy as np
13
import conftest # Add root path to sys.path
24
from Control.move_to_pose import move_to_pose as m
35

46

5-
def test_1():
7+
def test_random():
68
m.show_animation = False
79
m.main()
810

911

10-
def test_2():
12+
def test_stability():
13+
"""
14+
This unit test tests the move_to_pose.py program for stability
15+
"""
16+
m.show_animation = False
17+
x_start = 5
18+
y_start = 5
19+
theta_start = 0
20+
x_goal = 1
21+
y_goal = 4
22+
theta_goal = 0
23+
_, _, v_traj, w_traj = m.move_to_pose(
24+
x_start, y_start, theta_start, x_goal, y_goal, theta_goal
25+
)
26+
27+
def v_is_change(current, previous):
28+
return abs(current - previous) > m.MAX_LINEAR_SPEED
29+
30+
def w_is_change(current, previous):
31+
return abs(current - previous) > m.MAX_ANGULAR_SPEED
32+
33+
# Check if the speed is changing too much
34+
window_size = 10
35+
count_threshold = 4
36+
v_change = [v_is_change(v_traj[i], v_traj[i - 1]) for i in range(1, len(v_traj))]
37+
w_change = [w_is_change(w_traj[i], w_traj[i - 1]) for i in range(1, len(w_traj))]
38+
for i in range(len(v_change) - window_size + 1):
39+
v_window = v_change[i : i + window_size]
40+
w_window = w_change[i : i + window_size]
41+
42+
v_unstable = sum(v_window) > count_threshold
43+
w_unstable = sum(w_window) > count_threshold
44+
45+
assert not v_unstable, (
46+
f"v_unstable in window [{i}, {i + window_size}], unstable count: {sum(v_window)}"
47+
)
48+
assert not w_unstable, (
49+
f"w_unstable in window [{i}, {i + window_size}], unstable count: {sum(w_window)}"
50+
)
51+
52+
53+
def test_reach_goal():
54+
"""
55+
This unit test tests the move_to_pose.py program for reaching the goal
56+
"""
57+
m.show_animation = False
58+
x_start = 5
59+
y_start = 5
60+
theta_start_list = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
61+
x_goal_list = [0, 5, 10]
62+
y_goal_list = [0, 5, 10]
63+
theta_goal = 0
64+
for theta_start, x_goal, y_goal in itertools.product(
65+
theta_start_list, x_goal_list, y_goal_list
66+
):
67+
x_traj, y_traj, _, _ = m.move_to_pose(
68+
x_start, y_start, theta_start, x_goal, y_goal, theta_goal
69+
)
70+
x_diff = x_goal - x_traj[-1]
71+
y_diff = y_goal - y_traj[-1]
72+
rho = np.hypot(x_diff, y_diff)
73+
assert rho < 0.001, (
74+
f"start:[{x_start}, {y_start}, {theta_start}], goal:[{x_goal}, {y_goal}, {theta_goal}], rho: {rho} is too large"
75+
)
76+
77+
78+
def test_max_speed():
1179
"""
1280
This unit test tests the move_to_pose.py program for a MAX_LINEAR_SPEED and
1381
MAX_ANGULAR_SPEED
@@ -18,5 +86,5 @@ def test_2():
1886
m.main()
1987

2088

21-
if __name__ == '__main__':
89+
if __name__ == "__main__":
2290
conftest.run_this_test(__file__)

0 commit comments

Comments
 (0)