-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
modified frenet optimal trajectory generator to consider the left and… #1055
Open
MaheshKumar92
wants to merge
1
commit into
AtsushiSakai:master
Choose a base branch
from
MaheshKumar92:patch-2
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
375 changes: 375 additions & 0 deletions
375
PathPlanning/FrenetOptimalTrajectory/frenet_optimal_trajectory_with_boundary.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,375 @@ | ||
""" | ||
Frenet optimal trajectory generator with boundary | ||
|
||
author: Atsushi Sakai (@Atsushi_twi) | ||
|
||
Ref: | ||
|
||
- [Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame] | ||
(https://www.researchgate.net/profile/Moritz_Werling/publication/224156269_Optimal_Trajectory_Generation_for_Dynamic_Street_Scenarios_in_a_Frenet_Frame/links/54f749df0cf210398e9277af.pdf) | ||
|
||
- [Optimal trajectory generation for dynamic street scenarios in a Frenet Frame] | ||
(https://www.youtube.com/watch?v=Cj6tAQe7UCY) | ||
|
||
""" | ||
|
||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
import copy | ||
import math | ||
import sys | ||
import pathlib | ||
sys.path.append(str(pathlib.Path(__file__).parent.parent)) | ||
|
||
from QuinticPolynomialsPlanner.quintic_polynomials_planner import QuinticPolynomial | ||
from CubicSpline import cubic_spline_planner | ||
|
||
SIM_LOOP = 500 | ||
|
||
|
||
# Parameter | ||
MAX_SPEED = 50.0 / 3.6 # maximum speed [m/s] | ||
MAX_ACCEL = 2.0 # maximum acceleration [m/ss] | ||
MAX_CURVATURE = 1.0 # maximum curvature [1/m] | ||
MAX_ROAD_WIDTH = 7.0 # maximum road width [m] | ||
D_ROAD_W = 1.0 # road width sampling length [m] | ||
DT = 0.2 # time tick [s] | ||
MAX_T = 5.0 # max prediction time [m] | ||
MIN_T = 4.0 # min prediction time [m] | ||
TARGET_SPEED = 30.0 / 3.6 # target speed [m/s] | ||
D_T_S = 5.0 / 3.6 # target speed sampling length [m/s] | ||
N_S_SAMPLE = 1 # sampling number of target speed | ||
ROBOT_RADIUS = 2.0 # robot radius [m] | ||
|
||
# cost weights | ||
K_J = 0.1 | ||
K_T = 0.1 | ||
K_D = 1.0 | ||
K_LAT = 1.0 | ||
K_LON = 1.0 | ||
|
||
show_animation = True | ||
|
||
|
||
class QuarticPolynomial: | ||
def __init__(self, xs, vxs, axs, vxe, axe, time): | ||
# calc coefficient of quartic polynomial | ||
self.a0 = xs | ||
self.a1 = vxs | ||
self.a2 = axs / 2.0 | ||
|
||
A = np.array([[3 * time ** 2, 4 * time ** 3], | ||
[6 * time, 12 * time ** 2]]) | ||
b = np.array([vxe - self.a1 - 2 * self.a2 * time, | ||
axe - 2 * self.a2]) | ||
x = np.linalg.solve(A, b) | ||
|
||
self.a3 = x[0] | ||
self.a4 = x[1] | ||
|
||
def calc_point(self, t): | ||
xt = self.a0 + self.a1 * t + self.a2 * t ** 2 + \ | ||
self.a3 * t ** 3 + self.a4 * t ** 4 | ||
return xt | ||
|
||
def calc_first_derivative(self, t): | ||
xt = self.a1 + 2 * self.a2 * t + \ | ||
3 * self.a3 * t ** 2 + 4 * self.a4 * t ** 3 | ||
return xt | ||
|
||
def calc_second_derivative(self, t): | ||
xt = 2 * self.a2 + 6 * self.a3 * t + 12 * self.a4 * t ** 2 | ||
return xt | ||
|
||
def calc_third_derivative(self, t): | ||
xt = 6 * self.a3 + 24 * self.a4 * t | ||
return xt | ||
|
||
|
||
class FrenetPath: | ||
def __init__(self): | ||
self.t = [] | ||
self.d = [] | ||
self.d_d = [] | ||
self.d_dd = [] | ||
self.d_ddd = [] | ||
self.s = [] | ||
self.s_d = [] | ||
self.s_dd = [] | ||
self.s_ddd = [] | ||
self.cd = 0.0 | ||
self.cv = 0.0 | ||
self.cf = 0.0 | ||
|
||
self.x = [] | ||
self.y = [] | ||
self.yaw = [] | ||
self.ds = [] | ||
self.c = [] | ||
|
||
|
||
def calc_frenet_paths(c_speed, c_accel, c_d, c_d_d, c_d_dd, s0, max_left, max_right): | ||
frenet_paths = [] | ||
|
||
# generate path to each offset goal | ||
for di in np.arange(-max_left, max_right, D_ROAD_W): | ||
|
||
# Lateral motion planning | ||
for Ti in np.arange(MIN_T, MAX_T, DT): | ||
fp = FrenetPath() | ||
|
||
lat_qp = QuinticPolynomial(c_d, c_d_d, c_d_dd, di, 0.0, 0.0, Ti) | ||
|
||
fp.t = [t for t in np.arange(0.0, Ti, DT)] | ||
fp.d = [lat_qp.calc_point(t) for t in fp.t] | ||
fp.d_d = [lat_qp.calc_first_derivative(t) for t in fp.t] | ||
fp.d_dd = [lat_qp.calc_second_derivative(t) for t in fp.t] | ||
fp.d_ddd = [lat_qp.calc_third_derivative(t) for t in fp.t] | ||
|
||
# Longitudinal motion planning (Velocity keeping) | ||
for tv in np.arange(TARGET_SPEED - D_T_S * N_S_SAMPLE, | ||
TARGET_SPEED + D_T_S * N_S_SAMPLE, D_T_S): | ||
tfp = copy.deepcopy(fp) | ||
lon_qp = QuarticPolynomial(s0, c_speed, c_accel, tv, 0.0, Ti) | ||
|
||
tfp.s = [lon_qp.calc_point(t) for t in fp.t] | ||
tfp.s_d = [lon_qp.calc_first_derivative(t) for t in fp.t] | ||
tfp.s_dd = [lon_qp.calc_second_derivative(t) for t in fp.t] | ||
tfp.s_ddd = [lon_qp.calc_third_derivative(t) for t in fp.t] | ||
|
||
Jp = sum(np.power(tfp.d_ddd, 2)) # square of jerk | ||
Js = sum(np.power(tfp.s_ddd, 2)) # square of jerk | ||
|
||
# square of diff from target speed | ||
ds = (TARGET_SPEED - tfp.s_d[-1]) ** 2 | ||
|
||
tfp.cd = K_J * Jp + K_T * Ti + K_D * tfp.d[-1] ** 2 | ||
tfp.cv = K_J * Js + K_T * Ti + K_D * ds | ||
tfp.cf = K_LAT * tfp.cd + K_LON * tfp.cv | ||
|
||
# Check if the path is within the boundaries | ||
if all(-max_left <= d <= max_right for d in tfp.d): | ||
frenet_paths.append(tfp) | ||
|
||
return frenet_paths | ||
|
||
|
||
def calc_global_paths(fplist, csp): | ||
for fp in fplist: | ||
|
||
# calc global positions | ||
for i in range(len(fp.s)): | ||
ix, iy = csp.calc_position(fp.s[i]) | ||
if ix is None: | ||
break | ||
i_yaw = csp.calc_yaw(fp.s[i]) | ||
di = fp.d[i] | ||
fx = ix + di * math.cos(i_yaw + math.pi / 2.0) | ||
fy = iy + di * math.sin(i_yaw + math.pi / 2.0) | ||
fp.x.append(fx) | ||
fp.y.append(fy) | ||
|
||
# calc yaw and ds | ||
for i in range(len(fp.x) - 1): | ||
dx = fp.x[i + 1] - fp.x[i] | ||
dy = fp.y[i + 1] - fp.y[i] | ||
fp.yaw.append(math.atan2(dy, dx)) | ||
fp.ds.append(math.hypot(dx, dy)) | ||
|
||
fp.yaw.append(fp.yaw[-1]) | ||
fp.ds.append(fp.ds[-1]) | ||
|
||
# calc curvature | ||
for i in range(len(fp.yaw) - 1): | ||
fp.c.append((fp.yaw[i + 1] - fp.yaw[i]) / fp.ds[i]) | ||
|
||
return fplist | ||
|
||
|
||
def check_collision(fp, ob): | ||
for i in range(len(ob[:, 0])): | ||
d = [((ix - ob[i, 0]) ** 2 + (iy - ob[i, 1]) ** 2) | ||
for (ix, iy) in zip(fp.x, fp.y)] | ||
|
||
collision = any([di <= ROBOT_RADIUS ** 2 for di in d]) | ||
|
||
if collision: | ||
return False | ||
|
||
return True | ||
|
||
|
||
def check_paths(fplist, ob): | ||
ok_ind = [] | ||
for i, _ in enumerate(fplist): | ||
if any([v > MAX_SPEED for v in fplist[i].s_d]): # Max speed check | ||
continue | ||
elif any([abs(a) > MAX_ACCEL for a in fplist[i].s_dd]): # Max accel check | ||
continue | ||
elif any([abs(c) > MAX_CURVATURE for c in fplist[i].c]): # Max curvature check | ||
continue | ||
elif not check_collision(fplist[i], ob): | ||
continue | ||
|
||
ok_ind.append(i) | ||
|
||
return [fplist[i] for i in ok_ind] | ||
|
||
|
||
def frenet_optimal_planning(csp, s0, c_speed, c_accel, c_d, c_d_d, c_d_dd, ob, max_left, max_right): | ||
fplist = calc_frenet_paths(c_speed, c_accel, c_d, c_d_d, c_d_dd, s0, max_left, max_right) | ||
fplist = calc_global_paths(fplist, csp) | ||
fplist = check_paths(fplist, ob) | ||
|
||
# find minimum cost path | ||
min_cost = float("inf") | ||
best_path = None | ||
for fp in fplist: | ||
if min_cost >= fp.cf: | ||
min_cost = fp.cf | ||
best_path = fp | ||
|
||
return best_path | ||
|
||
|
||
def generate_target_course(x, y): | ||
csp = cubic_spline_planner.CubicSpline2D(x, y) | ||
s = np.arange(0, csp.s[-1], 0.1) | ||
|
||
rx, ry, ryaw, rk = [], [], [], [] | ||
for i_s in s: | ||
ix, iy = csp.calc_position(i_s) | ||
rx.append(ix) | ||
ry.append(iy) | ||
ryaw.append(csp.calc_yaw(i_s)) | ||
rk.append(csp.calc_curvature(i_s)) | ||
|
||
return rx, ry, ryaw, rk, csp | ||
|
||
def generate_parallel_paths(tx, ty, left_distance, right_distance): | ||
left_tx = [] | ||
left_ty = [] | ||
right_tx = [] | ||
right_ty = [] | ||
|
||
# Convert lists to numpy arrays for easier calculations | ||
tx = np.array(tx) | ||
ty = np.array(ty) | ||
|
||
for i in range(len(tx) - 1): | ||
x1, y1 = tx[i], ty[i] | ||
x2, y2 = tx[i + 1], ty[i + 1] | ||
|
||
# Direction vector of the segment | ||
dx = x2 - x1 | ||
dy = y2 - y1 | ||
|
||
# Perpendicular vector | ||
perp_x = -dy | ||
perp_y = dx | ||
|
||
# Normalize perpendicular vector | ||
length = np.sqrt(perp_x**2 + perp_y**2) | ||
perp_x /= length | ||
perp_y /= length | ||
|
||
# Calculate offset vectors | ||
left_offset_x = left_distance * perp_x | ||
left_offset_y = left_distance * perp_y | ||
right_offset_x = right_distance * perp_x | ||
right_offset_y = right_distance * perp_y | ||
|
||
# Apply offsets to get new paths | ||
left_tx.append(x1 + left_offset_x) | ||
left_ty.append(y1 + left_offset_y) | ||
right_tx.append(x1 + right_offset_x) | ||
right_ty.append(y1 + right_offset_y) | ||
|
||
left_tx.append(x2 + left_offset_x) | ||
left_ty.append(y2 + left_offset_y) | ||
right_tx.append(x2 + right_offset_x) | ||
right_ty.append(y2 + right_offset_y) | ||
|
||
return left_tx, left_ty, right_tx, right_ty | ||
|
||
def main(): | ||
print(__file__ + " start!!") | ||
|
||
# way points | ||
wx = [0.0, 10.0, 20.5, 35.0, 70.5, 100.0, 110.7, 150.5] | ||
wy = [0.0, -6.0, 5.0, 6.5, 0.0, -7.0, -10.0, -12.0] | ||
# obstacle lists | ||
ob = np.array([[20.0, 10.0], | ||
[30.0, 6.0], | ||
[30.0, 8.0], | ||
[35.0, 8.0], | ||
[50.0, 3.0] | ||
]) | ||
|
||
tx, ty, tyaw, tc, csp = generate_target_course(wx, wy) | ||
|
||
# initial state | ||
c_speed = 10.0 / 3.6 # current speed [m/s] | ||
c_accel = 0.0 # current acceleration [m/ss] | ||
c_d = 2.0 # current lateral position [m] | ||
c_d_d = 0.0 # current lateral speed [m/s] | ||
c_d_dd = 0.0 # current lateral acceleration [m/s] | ||
s0 = 0.0 # current course position | ||
|
||
area = 50.0 # animation area length [m] | ||
|
||
max_left = 5.0 # maximum left boundary [m] | ||
max_right = 5.0 # maximum right boundary [m] | ||
|
||
left_tx, left_ty, right_tx, right_ty = generate_parallel_paths(tx,ty, -max_left, max_right) | ||
frame_number = 0 | ||
for i in range(SIM_LOOP): | ||
path = frenet_optimal_planning(csp, s0, c_speed, c_accel, c_d, c_d_d, c_d_dd, ob, max_left, max_right) | ||
|
||
# Check if a valid path is returned | ||
if path is None: | ||
print("No valid path found") | ||
break | ||
|
||
s0 = path.s[1] | ||
c_d = path.d[1] | ||
c_d_d = path.d_d[1] | ||
c_d_dd = path.d_dd[1] | ||
c_speed = path.s_d[1] | ||
c_accel = path.s_dd[1] | ||
|
||
if np.hypot(path.x[1] - tx[-1], path.y[1] - ty[-1]) <= 1.0: | ||
print("Goal") | ||
break | ||
|
||
if show_animation: # pragma: no cover | ||
plt.cla() | ||
# for stopping simulation with the esc key. | ||
plt.gcf().canvas.mpl_connect( | ||
'key_release_event', | ||
lambda event: [exit(0) if event.key == 'escape' else None]) | ||
plt.plot(tx, ty) | ||
plt.plot(left_tx, left_ty) | ||
plt.plot(right_tx, right_ty) | ||
plt.plot(ob[:, 0], ob[:, 1], "xk") | ||
plt.plot(path.x[1:], path.y[1:], "-or") | ||
plt.plot(path.x[1], path.y[1], "vc") | ||
plt.xlim(path.x[1] - area, path.x[1] + area) | ||
plt.ylim(path.y[1] - area, path.y[1] + area) | ||
plt.title("v[km/h]:" + str(c_speed * 3.6)[0:4]) | ||
plt.grid(True) | ||
plt.pause(0.0001) | ||
# Save the current frame | ||
plt.savefig(f'frames/frame_{frame_number:04d}.png') | ||
frame_number += 1 | ||
|
||
print("Finish") | ||
if show_animation: # pragma: no cover | ||
plt.grid(True) | ||
plt.pause(0.0001) | ||
plt.show() | ||
|
||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this line.