Skip to content

Commit 828e1f6

Browse files
committed
Add up-to-date version of racing planning
1 parent bf5a151 commit 828e1f6

1 file changed

Lines changed: 76 additions & 256 deletions

File tree

GEMstack/onboard/planning/racing_planning.py

Lines changed: 76 additions & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -295,150 +295,6 @@ def dynamics_constraints(p):
295295

296296
return x_full, y_full, psi_full, c_full, v_full, eps_, final_error
297297

298-
######## more dynamics version
299-
def trajectory_generation_dynamics(init_state, final_state, N=30, Lr=1.5,
300-
a_min=-3.0, a_max=3.0,
301-
eps_min=-0.2, eps_max=0.2,
302-
v_min=2.0, v_max=11.0,
303-
T_min = 0.5, T_max = 1000.0,
304-
waypoints=None, waypoint_penalty_weight=10, waypoint_headings=None):
305-
"""
306-
Generate a dynamically feasible trajectory between init_state and final_state
307-
using curvature-based vehicle dynamics and nonlinear optimization.
308-
309-
Now supports multiple waypoints.
310-
311-
Parameters:
312-
- init_state (dict): Initial vehicle state with keys 'x', 'y', 'psi', 'c', 'v'.
313-
- final_state (dict): Target vehicle state with keys 'x', 'y', 'psi', 'c'.
314-
- N (int): Number of discrete time steps in the trajectory.
315-
- T (float): Duration of each time step (in seconds).
316-
- Lr (float): Distance from the vehicle's center to the rear axle.
317-
- w_c (float): Weight for penalizing curvature (smoothness of turns).
318-
- w_eps (float): Weight for penalizing curvature rate (reduces sharp steering changes).
319-
- w_vvar (float): Weight for penalizing speed variance (encourages speed smoothness).
320-
- w_terminal (float): Weight for penalizing final state deviation (soft constraint).
321-
- v_min (float): Minimum allowed speed (in m/s).
322-
- v_max (float): Maximum allowed speed (in m/s).
323-
- waypoints (list or None): Optional list of (x, y) coordinates that the trajectory should pass near.
324-
- waypoint_penalty_weight (float): Penalty weight for distance from waypoints (soft constraint).
325-
326-
Returns:
327-
- x, y, psi, c, v, eps (np.ndarray): Arrays of optimized state and control values.
328-
- final_error (dict): Final state errors in x, y, psi, and c.
329-
"""
330-
def cost(p):
331-
T_total = p[-1]
332-
return T_total
333-
334-
def dynamics_constraints(p):
335-
# x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
336-
# print("x_: " +str(x_))
337-
split_idx = 5 *(N - 1)
338-
x_, y_, psi_, c_, v_= np.split(p[:split_idx], 5)
339-
340-
a = p[split_idx:split_idx + (N - 1)]
341-
eps = p[split_idx + (N - 1):-1]
342-
T_total = p[-1]
343-
T_k = T_total / (N - 1)
344-
345-
constraints = []
346-
x_prev, y_prev, psi_prev, c_prev, v_prev = init_state['x'], init_state['y'], init_state['psi'], init_state['c'], init_state['v']
347-
348-
for k in range(N - 1):
349-
dx = v_prev * np.cos(psi_prev + c_prev * Lr) * T_k
350-
dy = v_prev * np.sin(psi_prev + c_prev * Lr) * T_k
351-
dpsi = v_prev * c_prev * T_k
352-
dv = a[k] * T_k
353-
dc = eps[k] * T_k
354-
constraints.extend([
355-
x_[k] - (x_prev + dx),
356-
y_[k] - (y_prev + dy),
357-
psi_[k] - (psi_prev + dpsi),
358-
v_[k] - (v_prev + dv),
359-
c_[k] - (c_prev + dc)
360-
])
361-
x_prev, y_prev, psi_prev, c_prev, v_prev = x_[k], y_[k], psi_[k], c_[k], v_[k]
362-
return constraints
363-
364-
def waypoint_penalty(p):
365-
if waypoints is None or len(waypoints) == 0:
366-
return 0.0
367-
368-
split_idx = 5 *(N - 1)
369-
x_, y_, psi_, c_, v_= np.split(p[:split_idx], 5)
370-
# x_ = p[:N -1]
371-
# y_ = p[N - 1:2 * (N-1)]
372-
# psi_ = [ 3 * (N - 1)]
373-
# Calculate equally spaced indices for each waypoint
374-
num_waypoints = len(waypoints)
375-
indices = [int((i + 1) * (N - 1) / (num_waypoints + 1)) for i in range(num_waypoints)]
376-
penalty = 0.0
377-
alignment_threshold = 0.3
378-
# Sum penalties for each waypoint at its corresponding index
379-
for wp_idx, waypoint in enumerate(waypoints):
380-
traj_idx = indices[wp_idx]
381-
penalty += waypoint_penalty_weight * (
382-
(x_[traj_idx] - waypoint[0])**2 + (y_[traj_idx] - waypoint[1])**2
383-
)
384-
return penalty
385-
386-
# Initial guesses
387-
x_vals = np.linspace(init_state['x'], final_state['x'], N)
388-
y_vals = np.linspace(init_state['y'], final_state['y'], N)
389-
psi_vals = np.linspace(init_state['psi'], final_state['psi'], N)
390-
c_vals = np.linspace(init_state['c'], final_state['c'], N)
391-
v_vals = np.ones(N) * init_state['v']
392-
393-
a_vals = np.zeros(N - 1)
394-
eps_vals = np.zeros(N - 1)
395-
T_guess = 60.0
396-
397-
p0 = np.concatenate([x_vals[1:], y_vals[1:], psi_vals[1:], c_vals[1:], v_vals[1:],
398-
a_vals, eps_vals, [T_guess]])
399-
num_vars = len(p0)
400-
bounds = ([(None, None)] * (4 * (N - 1)) +
401-
[(v_min, v_max)] * (N - 1) +
402-
[(a_min, a_max)] * (N - 1) +
403-
[(eps_min, eps_max)] * (N - 1) +
404-
[(T_min, T_max)]
405-
)
406-
def total_cost(p):
407-
return cost(p) + waypoint_penalty(p)
408-
409-
result = minimize(total_cost,
410-
p0,
411-
bounds=bounds,
412-
constraints={'type': 'eq', 'fun': dynamics_constraints},
413-
options={'maxiter': 1000})
414-
415-
# print("result.x:" + str(result.x))
416-
if not result.success:
417-
raise RuntimeError("Optimization failed")
418-
419-
split_idx = 5 *(N - 1)
420-
x_, y_, psi_, c_, v_= np.split(result.x[:split_idx], 5)
421-
422-
a = result.x[split_idx:split_idx + (N - 1)]
423-
eps = result.x[split_idx + (N - 1):-1]
424-
T_total = result.x[-1]
425-
426-
x_full = np.concatenate(([init_state['x']], x_))
427-
y_full = np.concatenate(([init_state['y']], y_))
428-
psi_full = np.concatenate(([init_state['psi']], psi_))
429-
c_full = np.concatenate(([init_state['c']], c_))
430-
v_full = np.concatenate(([init_state['v']], v_))
431-
432-
final_error = {
433-
'x_error': abs(x_full[-1] - final_state['x']),
434-
'y_error': abs(y_full[-1] - final_state['y']),
435-
'psi_error': abs(psi_full[-1] - final_state['psi']),
436-
'c_error': abs(c_full[-1] - final_state['c']),
437-
}
438-
439-
return x_full, y_full, psi_full, c_full, v_full, a, eps, T_total, final_error
440-
#########
441-
442298
def feasibility_check(trajectory, cone_map, car_width=2.0, safety_margin=0.3, v=10.0, Lr=1.5, T=0.1):
443299
"""
444300
Check if the car trajectory collides with any cones.
@@ -833,127 +689,91 @@ def test_4_cone_slalom():
833689
def plot_trajectory(x, y, v, c, eps, waypoint=None):
834690
plt.figure(figsize=(12, 10))
835691

836-
# Trajectory plot
837-
plt.subplot(4, 1, 1)
838-
plt.plot(x, y, label="Trajectory")
839-
plt.scatter([x[0], x[-1]], [y[0], y[-1]], color='red', label="Start/End")
840-
841-
if waypoint is not None:
842-
plt.scatter(*waypoint, color='purple', s=60, marker='X', label="Waypoint")
843-
plt.annotate("Waypoint", (waypoint[0], waypoint[1]), textcoords="offset points", xytext=(5,5), color='purple')
692+
# Trajectory plot
693+
plt.subplot(4, 1, 1)
694+
plt.plot(x, y, label="Trajectory")
695+
plt.scatter([x[0], x[-1]], [y[0], y[-1]], color='red', label="Start/End")
696+
697+
if waypoint is not None:
698+
plt.scatter(*waypoint, color='purple', s=60, marker='X', label="Waypoint")
699+
plt.annotate("Waypoint", (waypoint[0], waypoint[1]), textcoords="offset points", xytext=(5,5), color='purple')
844700

845-
plt.axis('equal')
846-
plt.ylabel("y (m)")
847-
plt.title("Trajectory")
848-
plt.grid(True)
849-
plt.legend()
701+
plt.axis('equal')
702+
plt.ylabel("y (m)")
703+
plt.title("Trajectory")
704+
plt.grid(True)
705+
plt.legend()
850706

851-
# Speed plot
852-
plt.subplot(4, 1, 2)
853-
plt.plot(v, label="Speed (v)", color="blue")
854-
plt.ylabel("Speed (m/s)")
855-
plt.grid(True)
856-
plt.legend()
707+
# Speed plot
708+
plt.subplot(4, 1, 2)
709+
plt.plot(v, label="Speed (v)", color="blue")
710+
plt.ylabel("Speed (m/s)")
711+
plt.grid(True)
712+
plt.legend()
857713

858-
# Curvature plot
859-
plt.subplot(4, 1, 3)
860-
plt.plot(c, label="Curvature (c)", color="orange")
861-
plt.ylabel("Curvature (1/m)")
862-
plt.grid(True)
863-
plt.legend()
714+
# Curvature plot
715+
plt.subplot(4, 1, 3)
716+
plt.plot(c, label="Curvature (c)", color="orange")
717+
plt.ylabel("Curvature (1/m)")
718+
plt.grid(True)
719+
plt.legend()
864720

865-
# Curvature rate plot
866-
plt.subplot(4, 1, 4)
867-
plt.plot(eps, label="Curvature Rate (ε)", color="green")
868-
plt.xlabel("Step")
869-
plt.ylabel("ε (1/m²)")
870-
plt.grid(True)
871-
plt.legend()
721+
# Curvature rate plot
722+
plt.subplot(4, 1, 4)
723+
plt.plot(eps, label="Curvature Rate (ε)", color="green")
724+
plt.xlabel("Step")
725+
plt.ylabel("ε (1/m²)")
726+
plt.grid(True)
727+
plt.legend()
872728

873729
plt.tight_layout()
874730
plt.show()
875731

876-
###### Test case 1: pass a cone in slalom
877-
def trajectory_generation_test1():
878-
# Init and final
879-
init_state = {'x': 0.0, 'y': 0.0, 'psi': 0.0, 'c': 0.0, 'v': 5.0}
880-
final_state = {'x': 15.0, 'y': 0.0, 'psi': np.pi / 20000000, 'c': np.pi / 20000000}
881-
waypoint = (8.0, 6.0)
882-
883-
# Solve
884-
x, y, psi, c, v, eps, final_error = trajectory_generation(
885-
init_state, final_state, waypoint=waypoint
886-
)
887-
plot_trajectory(x, y, v, c, eps, waypoint)
732+
# --- Test Case 1: pass a cone in slalom ---
733+
def trajectory_generation_test1():
734+
# Init and final
735+
init_state = {'x': 0.0, 'y': 0.0, 'psi': 0.0, 'c': 0.0, 'v': 5.0}
736+
final_state = {'x': 15.0, 'y': 0.0, 'psi': np.pi / 20000000, 'c': np.pi / 20000000}
737+
waypoint = (8.0, 6.0)
888738

889-
# Error
890-
print("\nFinal State Errors:")
891-
for k, e in final_error.items():
892-
print(f"{k}: {e:.6f}")
893-
894-
###### Test case 2: 90 degree turn
895-
def trajectory_generation_test2():
896-
# Init and final
897-
init_state = {'x': 0.0, 'y': 0.0, 'psi': 0.0, 'c': 0.0, 'v': 5.0}
898-
final_state = {'x': 15.0, 'y': 15.0, 'psi': np.pi / 2, 'c': np.pi / 2}
899-
waypoint = (13.0, 3.0)
900-
901-
# Solve
902-
x, y, psi, c, v, eps, final_error = trajectory_generation(
903-
init_state, final_state, waypoint=waypoint
904-
)
905-
plot_trajectory(x, y, v, c, eps, waypoint)
906-
907-
# Error
908-
print("\nFinal State Errors:")
909-
for k, e in final_error.items():
910-
print(f"{k}: {e:.6f}")
911-
912-
913-
914-
def feasibility_check(trajectory, cone_map, car_width=2.0, safety_margin=0.3, v=10.0, Lr=1.5, T=0.1):
915-
"""
916-
Check if the car trajectory collides with any cones.
917-
918-
Parameters:
919-
- trajectory: list of (y, psi, c) states
920-
- cone_map: list of (x, y) cone positions
921-
- car_width: width of the vehicle in meters
922-
- safety_margin: buffer around the vehicle
923-
- v: vehicle constant speed (used for x position estimation)
924-
- Lr: distance to rear axle
925-
- T: time step
926-
927-
Returns:
928-
- feasible: True if no collisions
929-
- collisions: list of indices of cones that were collided with (for plotting purpose)
930-
- x_vals, y_vals: trajectory positions for plotting (for plotting purpose)
931-
"""
932-
y_vals, psi_vals, c_vals = zip(*trajectory)
933-
x_vals = [0.0]
934-
for i in range(1, len(trajectory)):
935-
dx = v * np.cos(psi_vals[i-1] + c_vals[i-1] * Lr) * T
936-
x_vals.append(x_vals[-1] + dx)
937-
938-
collision_radius = (car_width / 2.0) + safety_margin
939-
collisions = []
940-
941-
for j, (cone_x, cone_y) in enumerate(cone_map):
942-
for x, y in zip(x_vals, y_vals):
943-
if np.hypot(x - cone_x, y - cone_y) < collision_radius:
944-
collisions.append(j)
945-
break
739+
# Solve
740+
x, y, psi, c, v, eps, final_error = trajectory_generation(
741+
init_state, final_state, waypoint=waypoint
742+
)
743+
plot_trajectory(x, y, v, c, eps, waypoint)
946744

947-
feasible = len(collisions) == 0
948-
return feasible, collisions, x_vals, y_vals
745+
# Error
746+
print("\nFinal State Errors:")
747+
for k, e in final_error.items():
748+
print(f"{k}: {e:.6f}")
749+
750+
# --- Test case 2: 90 degree turn ---
751+
def trajectory_generation_test2():
752+
# Init and final
753+
init_state = {'x': 0.0, 'y': 0.0, 'psi': 0.0, 'c': 0.0, 'v': 5.0}
754+
final_state = {'x': 15.0, 'y': 15.0, 'psi': np.pi / 2, 'c': np.pi / 2}
755+
waypoint = (13.0, 3.0)
756+
757+
# Solve
758+
x, y, psi, c, v, eps, final_error = trajectory_generation(
759+
init_state, final_state, waypoint=waypoint
760+
)
761+
plot_trajectory(x, y, v, c, eps, waypoint)
949762

950-
# --- Test Case ---
951-
def test_feasibility_check():
952-
N = 50
953-
y_traj = np.linspace(0, 10, N)
954-
psi_traj = np.linspace(0, 0.1, N)
955-
c_traj = np.linspace(0, 0.2, N)
956-
trajectory = list(zip(y_traj, psi_traj, c_traj))
763+
# Error
764+
print("\nFinal State Errors:")
765+
for k, e in final_error.items():
766+
print(f"{k}: {e:.6f}")
767+
768+
#############
769+
#########################
770+
# --- Test Case ---
771+
def test_feasibility_check():
772+
N = 50
773+
y_traj = np.linspace(0, 10, N)
774+
psi_traj = np.linspace(0, 0.1, N)
775+
c_traj = np.linspace(0, 0.2, N)
776+
trajectory = list(zip(y_traj, psi_traj, c_traj))
957777

958778
# Cone map near the path
959779
cone_map = [(5.0, 1.0), (10.0, 1.5), (15.0, 2.0), (25.0, 4.0), (25.0, 10.0), (16.0, 9.0), (40.0, 5.0)]
@@ -1206,8 +1026,8 @@ def test_planning(case='slalom', test_loop=2):
12061026
final_state = {'y': wpt_fixed[1], 'psi': 0.0, 'c': 0.0}
12071027

12081028
y_traj, psi_traj, c_traj, eps_traj = trajectory_generation(init_state, final_state)
1209-
plot_trajectory(y_traj, psi_traj, c_traj, label="Generated trajectory")
1210-
plot_dynamics(psi_traj, c_traj, eps_traj)
1029+
# plot_trajectory(y_traj, psi_traj, c_traj, label="Generated trajectory")
1030+
# plot_dynamics(psi_traj, c_traj, eps_traj)
12111031

12121032
# Iterate
12131033
vehicle_state = drive(vehicle_state)

0 commit comments

Comments
 (0)