Skip to content

Commit 4d58978

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

File tree

1 file changed

+82
-49
lines changed

1 file changed

+82
-49
lines changed

PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py

Lines changed: 82 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
co-author Videh Patel(@videh25) : Added the missing RS paths
77
88
"""
9-
import sys
109
import pathlib
10+
import sys
11+
1112
sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
1213

1314
import math
15+
from typing import List, Tuple
1416

1517
import matplotlib.pyplot as plt
1618
import numpy as np
19+
from numpy.typing import NDArray
20+
1721
from utils.angle import angle_mod
1822

1923
show_animation = True
@@ -342,63 +346,88 @@ def generate_path(q0, q1, max_curvature, step_size):
342346
return paths
343347

344348

345-
def calc_interpolate_dists_list(lengths, step_size):
346-
interpolate_dists_list = []
349+
def calc_interpolate_dists_list(lengths: List[float], step_size: float) -> List[NDArray[np.floating]]:
350+
interpolate_dists_list: List[NDArray[np.floating]] = []
347351
for length in lengths:
348352
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)
353+
354+
interp_core = np.arange(0.0, length, d_dist, dtype=np.float64)
355+
interp_dists = np.empty(len(interp_core) + 1, dtype=np.float64)
356+
interp_dists[:-1] = interp_core
357+
interp_dists[-1] = length
358+
351359
interpolate_dists_list.append(interp_dists)
352360

353361
return interpolate_dists_list
354362

355363

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

359377
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]
378+
idx = 0
379+
380+
for interp_dists, mode, length in zip(interpolate_dists_list, modes, lengths):
381+
n = len(interp_dists)
382+
x_arr, y_arr, yaw_arr, dir_arr = interpolate_vectorized(
383+
interp_dists, length, mode, max_curvature, origin_x, origin_y, origin_yaw
384+
)
385+
xs[idx : idx + n] = x_arr
386+
ys[idx : idx + n] = y_arr
387+
yaws[idx : idx + n] = yaw_arr
388+
directions[idx : idx + n] = dir_arr
389+
390+
origin_x = x_arr[-1]
391+
origin_y = y_arr[-1]
392+
origin_yaw = yaw_arr[-1]
393+
idx += n
376394

377395
return xs, ys, yaws, directions
378396

379397

380-
def interpolate(dist, length, mode, max_curvature, origin_x, origin_y,
381-
origin_yaw):
398+
def interpolate_vectorized(
399+
dists: NDArray[np.floating],
400+
length: float,
401+
mode: str,
402+
max_curvature: float,
403+
origin_x: float,
404+
origin_y: float,
405+
origin_yaw: float,
406+
) -> Tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.floating], NDArray[np.signedinteger]]:
382407
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
408+
x = origin_x + dists / max_curvature * math.cos(origin_yaw)
409+
y = origin_y + dists / max_curvature * math.sin(origin_yaw)
410+
yaw = np.full_like(dists, origin_yaw)
386411
else: # curve
387-
ldx = math.sin(dist) / max_curvature
388-
ldy = 0.0
389-
yaw = None
412+
ldx = np.sin(dists) / max_curvature
413+
ldy = np.zeros_like(dists)
414+
yaw = np.zeros_like(dists)
390415
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
416+
ldy = (1.0 - np.cos(dists)) / max_curvature
417+
yaw = origin_yaw + dists
418+
else: # elif mode == "R": # right turn
419+
ldy = (1.0 - np.cos(dists)) / (-max_curvature)
420+
yaw = origin_yaw - dists
421+
422+
cos_oy = math.cos(-origin_yaw)
423+
sin_oy = math.sin(-origin_yaw)
424+
gdx = cos_oy * ldx + sin_oy * ldy
425+
gdy = -sin_oy * ldx + cos_oy * ldy
398426
x = origin_x + gdx
399427
y = origin_y + gdy
400428

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

403432

404433
def calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size):
@@ -407,19 +436,23 @@ def calc_paths(sx, sy, syaw, gx, gy, gyaw, maxc, step_size):
407436

408437
paths = generate_path(q0, q1, maxc, step_size)
409438
for path in paths:
410-
xs, ys, yaws, directions = generate_local_course(path.lengths,
411-
path.ctypes, maxc,
412-
step_size)
439+
xs, ys, yaws, directions = generate_local_course(path.lengths, path.ctypes, maxc, step_size)
413440

414441
# 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
442+
local_pts = np.vstack([xs, ys, np.ones_like(xs)]) # shape: [3, N]
443+
cos_y = np.cos(syaw)
444+
sin_y = np.sin(syaw)
445+
se2 = np.array([[cos_y, -sin_y, sx],[sin_y, cos_y, sy],[0, 0, 1]])
446+
global_pts = se2 @ local_pts # shape: [3, N]
447+
448+
path.x = global_pts[0, :].tolist()
449+
path.y = global_pts[1, :].tolist()
450+
451+
path.yaw = ((yaws + syaw + np.pi) % (2 * np.pi) - np.pi).tolist()
452+
453+
path.directions = directions.tolist()
454+
path.lengths = [l / maxc for l in path.lengths]
455+
path.L /= maxc
423456

424457
return paths
425458

0 commit comments

Comments
 (0)