Skip to content

Commit 27a83dd

Browse files
committed
perf: optimize Reeds-Shepp path planning with NumPy vectorization
1 parent e34b900 commit 27a83dd

File tree

1 file changed

+81
-49
lines changed

1 file changed

+81
-49
lines changed

PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44
55
author Atsushi Sakai(@Atsushi_twi)
66
co-author Videh Patel(@videh25) : Added the missing RS paths
7+
co-author fishyy119(@fishyy119) : Improved runtime efficiency
78
89
"""
9-
import sys
1010
import pathlib
11+
import sys
12+
1113
sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
1214

1315
import math
16+
from typing import List, Tuple
1417

1518
import matplotlib.pyplot as plt
1619
import numpy as np
20+
from numpy.typing import NDArray
21+
1722
from utils.angle import angle_mod
1823

1924
show_animation = True
@@ -342,63 +347,86 @@ def generate_path(q0, q1, max_curvature, step_size):
342347
return paths
343348

344349

345-
def calc_interpolate_dists_list(lengths, step_size):
346-
interpolate_dists_list = []
350+
def calc_interpolate_dists_list(lengths: List[float], step_size: float) -> List[NDArray[np.floating]]:
351+
interpolate_dists_list: List[NDArray[np.floating]] = []
347352
for length in lengths:
348353
d_dist = step_size if length >= 0.0 else -step_size
349-
interp_dists = np.arange(0.0, length, d_dist)
350-
interp_dists = np.append(interp_dists, length)
354+
355+
interp_core = np.arange(0.0, length, d_dist, dtype=np.float64)
356+
interp_dists = np.empty(len(interp_core) + 1, dtype=np.float64)
357+
interp_dists[:-1] = interp_core
358+
interp_dists[-1] = length
359+
351360
interpolate_dists_list.append(interp_dists)
352361

353362
return interpolate_dists_list
354363

355364

356-
def generate_local_course(lengths, modes, max_curvature, step_size):
365+
def generate_local_course(
366+
lengths: List[float],
367+
modes: List[str],
368+
max_curvature: float,
369+
step_size: float,
370+
) -> Tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.floating], NDArray[np.signedinteger]]:
357371
interpolate_dists_list = calc_interpolate_dists_list(lengths, step_size * max_curvature)
372+
total_len = sum(len(arr) for arr in interpolate_dists_list)
373+
xs = np.empty(total_len, dtype=np.float64)
374+
ys = np.empty_like(xs)
375+
yaws = np.empty_like(xs)
376+
directions = np.empty_like(xs, dtype=np.int32)
358377

359378
origin_x, origin_y, origin_yaw = 0.0, 0.0, 0.0
360-
361-
xs, ys, yaws, directions = [], [], [], []
362-
for (interp_dists, mode, length) in zip(interpolate_dists_list, modes,
363-
lengths):
364-
365-
for dist in interp_dists:
366-
x, y, yaw, direction = interpolate(dist, length, mode,
367-
max_curvature, origin_x,
368-
origin_y, origin_yaw)
369-
xs.append(x)
370-
ys.append(y)
371-
yaws.append(yaw)
372-
directions.append(direction)
373-
origin_x = xs[-1]
374-
origin_y = ys[-1]
375-
origin_yaw = yaws[-1]
379+
idx = 0
380+
381+
for interp_dists, mode, length in zip(interpolate_dists_list, modes, lengths):
382+
n = len(interp_dists)
383+
x_arr, y_arr, yaw_arr, dir_arr = interpolate_vectorized(
384+
interp_dists, length, mode, max_curvature, origin_x, origin_y, origin_yaw
385+
)
386+
xs[idx : idx + n] = x_arr
387+
ys[idx : idx + n] = y_arr
388+
yaws[idx : idx + n] = yaw_arr
389+
directions[idx : idx + n] = dir_arr
390+
391+
origin_x = x_arr[-1]
392+
origin_y = y_arr[-1]
393+
origin_yaw = yaw_arr[-1]
394+
idx += n
376395

377396
return xs, ys, yaws, directions
378397

379398

380-
def interpolate(dist, length, mode, max_curvature, origin_x, origin_y,
381-
origin_yaw):
399+
def interpolate_vectorized(
400+
dists: NDArray[np.floating],
401+
length: float,
402+
mode: str,
403+
max_curvature: float,
404+
origin_x: float,
405+
origin_y: float,
406+
origin_yaw: float,
407+
) -> Tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.floating], NDArray[np.signedinteger]]:
382408
if mode == "S":
383-
x = origin_x + dist / max_curvature * math.cos(origin_yaw)
384-
y = origin_y + dist / max_curvature * math.sin(origin_yaw)
385-
yaw = origin_yaw
409+
x = origin_x + dists / max_curvature * math.cos(origin_yaw)
410+
y = origin_y + dists / max_curvature * math.sin(origin_yaw)
411+
yaw = np.full_like(dists, origin_yaw)
386412
else: # curve
387-
ldx = math.sin(dist) / max_curvature
388-
ldy = 0.0
389-
yaw = None
413+
ldx = np.sin(dists) / max_curvature
390414
if mode == "L": # left turn
391-
ldy = (1.0 - math.cos(dist)) / max_curvature
392-
yaw = origin_yaw + dist
393-
elif mode == "R": # right turn
394-
ldy = (1.0 - math.cos(dist)) / -max_curvature
395-
yaw = origin_yaw - dist
396-
gdx = math.cos(-origin_yaw) * ldx + math.sin(-origin_yaw) * ldy
397-
gdy = -math.sin(-origin_yaw) * ldx + math.cos(-origin_yaw) * ldy
415+
ldy = (1.0 - np.cos(dists)) / max_curvature
416+
yaw = origin_yaw + dists
417+
else: # elif mode == "R": # right turn
418+
ldy = (1.0 - np.cos(dists)) / (-max_curvature)
419+
yaw = origin_yaw - dists
420+
421+
cos_oy = math.cos(-origin_yaw)
422+
sin_oy = math.sin(-origin_yaw)
423+
gdx = cos_oy * ldx + sin_oy * ldy
424+
gdy = -sin_oy * ldx + cos_oy * ldy
398425
x = origin_x + gdx
399426
y = origin_y + gdy
400427

401-
return x, y, yaw, 1 if length > 0.0 else -1
428+
direction = 1 if length > 0 else -1
429+
return x, y, yaw, np.full_like(dists, direction, dtype=np.int32)
402430

403431

404432
def calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size):
@@ -407,19 +435,23 @@ def calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size):
407435

408436
paths = generate_path(q0, q1, maxc, step_size)
409437
for path in paths:
410-
xs, ys, yaws, directions = generate_local_course(path.lengths,
411-
path.ctypes, maxc,
412-
step_size)
438+
xs, ys, yaws, directions = generate_local_course(path.lengths, path.ctypes, maxc, step_size)
413439

414440
# convert global coordinate
415-
path.x = [math.cos(-q0[2]) * ix + math.sin(-q0[2]) * iy + q0[0] for
416-
(ix, iy) in zip(xs, ys)]
417-
path.y = [-math.sin(-q0[2]) * ix + math.cos(-q0[2]) * iy + q0[1] for
418-
(ix, iy) in zip(xs, ys)]
419-
path.yaw = [pi_2_pi(yaw + q0[2]) for yaw in yaws]
420-
path.directions = directions
421-
path.lengths = [length / maxc for length in path.lengths]
422-
path.L = path.L / maxc
441+
local_pts = np.vstack([xs, ys, np.ones_like(xs)]) # shape: [3, N]
442+
cos_y = np.cos(syaw)
443+
sin_y = np.sin(syaw)
444+
se2 = np.array([[cos_y, -sin_y, sx],[sin_y, cos_y, sy],[0, 0, 1]])
445+
global_pts = se2 @ local_pts # shape: [3, N]
446+
447+
path.x = global_pts[0, :].tolist()
448+
path.y = global_pts[1, :].tolist()
449+
450+
path.yaw = ((yaws + syaw + np.pi) % (2 * np.pi) - np.pi).tolist()
451+
452+
path.directions = directions.tolist()
453+
path.lengths = [l / maxc for l in path.lengths]
454+
path.L /= maxc
423455

424456
return paths
425457

0 commit comments

Comments
 (0)