Skip to content

Commit 49fdfaa

Browse files
committed
planner with turn rate
1 parent 0340e41 commit 49fdfaa

2 files changed

Lines changed: 328 additions & 20 deletions

File tree

GEMstack/onboard/planning/racing_planning.py

Lines changed: 326 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,146 @@ def waypoint_generate(vehicle_state, cones, cone_idx):
175175
target_heading -= 2 * np.pi
176176

177177
return scenario, flex_wps_list, fixed_wp, target_heading
178+
179+
def waypoint_generate_relaxed(vehicle_state, cones, cone_idx):
180+
"""
181+
Generate waypoints based on the scenario.
182+
Args:
183+
vehicle_state: The state of the vehicle.
184+
cone_state: The state of the cones.
185+
Returns:
186+
Tuple[str, Tuple[float, float], Tuple[float, float]]:
187+
scenario: detected scenario type
188+
flex_wp: flexible waypoint (used to maneuver)
189+
fixed_wp: fixed waypoint (goal position)
190+
"""
191+
scenario, cone_position = scenario_check(vehicle_state, [cones[cone_idx]])
192+
car_position = np.array(vehicle_state['position'])
193+
car_heading = vehicle_state['heading'] # in radians
194+
current_heading = car_heading
195+
target_heading = car_heading
196+
197+
# ===== Parameters =====
198+
u_turn_radius = 11.5 # Radius for U-turn
199+
offset = 2.0 # Offset for left/right pass
200+
lookahead_distance = 10.0 # Distance ahead for fixed point
201+
# ======================
202+
203+
# Direction vector based on heading
204+
heading_vector = np.array([np.cos(car_heading), np.sin(car_heading)])
205+
206+
# Vector perpendicular to heading (to determine left/right)
207+
perpendicular_vector = np.array([-np.sin(car_heading), np.cos(car_heading)])
208+
209+
if cone_idx > 0:
210+
prev_cone_position = np.array((cones[cone_idx - 1]['x'], cones[cone_idx - 1]['y']))
211+
cone_position = np.array((cones[cone_idx]['x'], cones[cone_idx]['y']))
212+
target_heading = np.arctan2(cone_position[1] - prev_cone_position[1],
213+
cone_position[0] - prev_cone_position[0])
214+
215+
if scenario == 'standing':
216+
# U-turn: Generate points in a semi-circular arc around the cone
217+
cone = np.array(cone_position)
218+
219+
# Number of waypoints to generate for the arc
220+
num_arc_points = 9
221+
222+
# Generate waypoints in a smooth semi-circular pattern on the RIGHT side
223+
flex_wps_list = []
224+
225+
# Create a semi-circular arc from 0 to π
226+
# But negate perpendicular_vector to go to the right side
227+
for i in range(num_arc_points):
228+
# Calculate angle in the semi-circle (0 to π)
229+
angle = i * np.pi * 1 / (num_arc_points - 1)
230+
231+
# MODIFIED: Negative perpendicular_vector to go to right side of cone
232+
# This creates a counter-clockwise turn around the cone
233+
wp = cone - u_turn_radius * (
234+
perpendicular_vector * np.cos(angle) -
235+
heading_vector * np.sin(angle)
236+
)
237+
238+
flex_wps_list.append(wp)
239+
240+
# Fixed waypoint: behind the cone after U-turn (also on right side)
241+
fixed_wp = cone + u_turn_radius * perpendicular_vector
242+
243+
target_heading = target_heading + np.pi
244+
245+
elif scenario == 'left':
246+
cone = np.array(cone_position)
247+
248+
# Flexible waypoint: go to the left of the cone
249+
flex_wp1 = cone + offset * perpendicular_vector
250+
flex_wps_list = [flex_wp1]
251+
252+
# Fixed waypoint: forward after passing the cone
253+
# fixed_wp = flex_wp + heading_vector * lookahead_distance - offset * perpendicular_vector * 2
254+
fixed_wp = flex_wp1 + heading_vector * lookahead_distance - offset * perpendicular_vector
255+
256+
elif scenario == 'right':
257+
cone = np.array(cone_position)
258+
259+
# Flexible waypoint: go to the right of the cone
260+
flex_wp1 = cone - offset * perpendicular_vector
261+
flex_wps_list = [flex_wp1]
262+
263+
# Fixed waypoint: forward after passing the cone
264+
# fixed_wp = flex_wp + heading_vector * lookahead_distance + offset * perpendicular_vector * 2
265+
fixed_wp = flex_wp1 + heading_vector * lookahead_distance + offset * perpendicular_vector
266+
267+
# TODO: move to a different setting instead of as a scenario
268+
elif scenario == '90turn':
269+
turn_vector = np.array([-np.sin(car_heading + np.pi / 6), np.cos(car_heading + np.pi / 6)])
270+
cone = np.array(cone_position)
271+
distance_to_cone = np.linalg.norm(car_position - cone)
272+
273+
flex_wps_list = []
274+
if distance_to_cone > 7:
275+
steps = int(distance_to_cone // 6)
276+
print("steps:", steps)
277+
278+
final_flex_wp = cone + 1.0 * perpendicular_vector + heading_vector * 1.0
279+
fixed_wp = cone - turn_vector * 2.5 + heading_vector * 0.0
280+
281+
cone_direction = (final_flex_wp - car_position) / np.linalg.norm(final_flex_wp - car_position)
282+
for i in range(steps - 2):
283+
intermediate_wp = car_position + cone_direction * ((i + 1) * 6)
284+
flex_wps_list.append(intermediate_wp)
285+
286+
flex_wps_list.append(final_flex_wp)
287+
else:
288+
flex_wp = cone + 1.0 * perpendicular_vector + heading_vector * 1.0
289+
fixed_wp = cone - turn_vector * 2.5 + heading_vector * 0.0
290+
flex_wps_list = [flex_wp]
291+
292+
else:
293+
flex_wps_list = None
294+
fixed_wp = None
295+
296+
target_heading = (target_heading + np.pi) % (2 * np.pi) - np.pi
297+
current_heading = (current_heading + np.pi) % (2 * np.pi) - np.pi
298+
299+
if abs(target_heading - current_heading) > np.pi:
300+
if target_heading < current_heading:
301+
target_heading += 2 * np.pi
302+
else:
303+
target_heading -= 2 * np.pi
304+
305+
# For visualization only
306+
for i, wp in enumerate(flex_wps_list):
307+
plt.plot(wp[0], wp[1], 'ro') # Plot all waypoints in red
308+
plt.text(wp[0], wp[1], f'wp{i}', fontsize=9)
309+
310+
plt.plot(fixed_wp[0], fixed_wp[1], 'bo') # Plot fixed waypoint in blue
311+
plt.plot(cone[0], cone[1], 'gx') # Plot cone in green
312+
plt.plot(car_position[0], car_position[1], 'ks') # Plot car position
313+
plt.axis('equal') # Equal aspect ratio for better visualization
314+
plt.grid(True)
315+
plt.show()
316+
317+
return scenario, flex_wps_list, fixed_wp, target_heading
178318

179319
def velocity_profiling(path, acceleration, deceleration, max_speed, current_speed, lateral_acc_limit):
180320
"""
@@ -431,6 +571,172 @@ def dynamics_constraints(p):
431571

432572
return x_full, y_full, psi_full, c_full, v_full, eps_, final_error
433573

574+
######## more dynamics version
575+
def trajectory_generation_dynamics(init_state, final_state, N=30, Lr=1.5,
576+
a_min=-3.0, a_max=3.0,
577+
eps_min=-0.2, eps_max=0.2,
578+
v_min=3.0, v_max=11.0,
579+
T_min = 0.5, T_max = 500.0,
580+
waypoints=None, waypoint_penalty_weight=1.0):
581+
"""
582+
Generate a dynamically feasible trajectory between init_state and final_state
583+
using curvature-based vehicle dynamics and nonlinear optimization.
584+
585+
Now supports multiple waypoints.
586+
587+
Parameters:
588+
- init_state (dict): Initial vehicle state with keys 'x', 'y', 'psi', 'c', 'v'.
589+
- final_state (dict): Target vehicle state with keys 'x', 'y', 'psi', 'c'.
590+
- N (int): Number of discrete time steps in the trajectory.
591+
- T (float): Duration of each time step (in seconds).
592+
- Lr (float): Distance from the vehicle's center to the rear axle.
593+
- w_c (float): Weight for penalizing curvature (smoothness of turns).
594+
- w_eps (float): Weight for penalizing curvature rate (reduces sharp steering changes).
595+
- w_vvar (float): Weight for penalizing speed variance (encourages speed smoothness).
596+
- w_terminal (float): Weight for penalizing final state deviation (soft constraint).
597+
- v_min (float): Minimum allowed speed (in m/s).
598+
- v_max (float): Maximum allowed speed (in m/s).
599+
- waypoints (list or None): Optional list of (x, y) coordinates that the trajectory should pass near.
600+
- waypoint_penalty_weight (float): Penalty weight for distance from waypoints (soft constraint).
601+
602+
Returns:
603+
- x, y, psi, c, v, eps (np.ndarray): Arrays of optimized state and control values.
604+
- final_error (dict): Final state errors in x, y, psi, and c.
605+
"""
606+
# def cost(p):
607+
# x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
608+
# c_seq = np.concatenate(([init_state['c']], c_))
609+
# v_seq = np.concatenate(([init_state['v']], v_))
610+
# x_final, y_final, psi_final, c_final = x_[-1], y_[-1], psi_[-1], c_[-1]
611+
612+
# cost_c = w_c * np.sum(c_seq ** 2)
613+
# cost_eps = w_eps * np.sum(eps_ ** 2)
614+
# v_mean = np.mean(v_seq)
615+
# cost_vvar = w_vvar * np.mean((v_seq - v_mean) ** 2)
616+
617+
# cost_terminal = w_terminal * (
618+
# (x_final - final_state['x']) ** 2 +
619+
# (y_final - final_state['y']) ** 2 +
620+
# (psi_final - final_state['psi']) ** 2 * 100 +
621+
# (c_final - final_state['c']) ** 2 * 100
622+
# )
623+
624+
# cost_waypoints = 0.0
625+
#
626+
627+
def cost(p):
628+
T_total = p[-1]
629+
return T_total
630+
631+
def dynamics_constraints(p):
632+
# x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
633+
# print("x_: " +str(x_))
634+
split_idx = 5 *(N - 1)
635+
x_, y_, psi_, c_, v_= np.split(p[:split_idx], 5)
636+
# print("x_: " +str(x_))
637+
638+
a = p[split_idx:split_idx + (N - 1)]
639+
eps = p[split_idx + (N - 1):-1]
640+
T_total = p[-1]
641+
T_k = T_total / (N - 1)
642+
643+
644+
constraints = []
645+
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']
646+
647+
for k in range(N - 1):
648+
dx = v_prev * np.cos(psi_prev + c_prev * Lr) * T_k
649+
dy = v_prev * np.sin(psi_prev + c_prev * Lr) * T_k
650+
dpsi = v_prev * c_prev * T_k
651+
dv = a[k] * T_k
652+
dc = eps[k] * T_k
653+
constraints.extend([
654+
x_[k] - (x_prev + dx),
655+
y_[k] - (y_prev + dy),
656+
psi_[k] - (psi_prev + dpsi),
657+
v_[k] - (v_prev + dv),
658+
c_[k] - (c_prev + dc)
659+
])
660+
x_prev, y_prev, psi_prev, c_prev, v_prev = x_[k], y_[k], psi_[k], c_[k], v_[k]
661+
return constraints
662+
663+
def waypoint_penalty(p):
664+
if waypoints is None or len(waypoints) == 0:
665+
return 0.0
666+
667+
x_ = p[:N -1]
668+
y_ = p[N - 1:2 * (N-1)]
669+
# Calculate equally spaced indices for each waypoint
670+
num_waypoints = len(waypoints)
671+
indices = [int((i + 1) * (N - 1) / (num_waypoints + 1)) for i in range(num_waypoints)]
672+
penalty = 0.0
673+
# Sum penalties for each waypoint at its corresponding index
674+
for wp_idx, waypoint in enumerate(waypoints):
675+
traj_idx = indices[wp_idx]
676+
penalty += waypoint_penalty_weight * (
677+
(x_[traj_idx] - waypoint[0])**2 + (y_[traj_idx] - waypoint[1])**2
678+
)
679+
680+
return penalty
681+
682+
# Initial guesses
683+
x_vals = np.linspace(init_state['x'], final_state['x'], N)
684+
y_vals = np.linspace(init_state['y'], final_state['y'], N)
685+
psi_vals = np.linspace(init_state['psi'], final_state['psi'], N)
686+
c_vals = np.linspace(init_state['c'], final_state['c'], N)
687+
v_vals = np.ones(N) * init_state['v']
688+
689+
a_vals = np.zeros(N - 1)
690+
eps_vals = np.zeros(N - 1)
691+
T_guess = 10.0
692+
693+
p0 = np.concatenate([x_vals[1:], y_vals[1:], psi_vals[1:], c_vals[1:], v_vals[1:],
694+
a_vals, eps_vals, [T_guess]])
695+
num_vars = len(p0)
696+
bounds = ([(None, None)] * (4 * (N - 1)) +
697+
[(v_min, v_max)] * (N - 1) +
698+
[(a_min, a_max)] * (N - 1) +
699+
[(eps_min, eps_max)] * (N - 1) +
700+
[(T_min, T_max)]
701+
)
702+
def total_cost(p):
703+
return cost(p) + waypoint_penalty(p)
704+
705+
result = minimize(total_cost,
706+
p0,
707+
bounds=bounds,
708+
constraints={'type': 'eq', 'fun': dynamics_constraints},
709+
options={'maxiter': 1000})
710+
711+
# print("result.x:" + str(result.x))
712+
if not result.success:
713+
raise RuntimeError("Optimization failed")
714+
715+
716+
split_idx = 5 *(N - 1)
717+
x_, y_, psi_, c_, v_= np.split(result.x[:split_idx], 5)
718+
719+
a = result.x[split_idx:split_idx + (N - 1)]
720+
eps = result.x[split_idx + (N - 1):-1]
721+
T_total = result.x[-1]
722+
723+
x_full = np.concatenate(([init_state['x']], x_))
724+
y_full = np.concatenate(([init_state['y']], y_))
725+
psi_full = np.concatenate(([init_state['psi']], psi_))
726+
c_full = np.concatenate(([init_state['c']], c_))
727+
v_full = np.concatenate(([init_state['v']], v_))
728+
729+
final_error = {
730+
'x_error': abs(x_full[-1] - final_state['x']),
731+
'y_error': abs(y_full[-1] - final_state['y']),
732+
'psi_error': abs(psi_full[-1] - final_state['psi']),
733+
'c_error': abs(c_full[-1] - final_state['c']),
734+
}
735+
736+
return x_full, y_full, psi_full, c_full, v_full, a, eps, T_total, final_error
737+
#########
738+
739+
434740
def feasibility_check(trajectory, cone_map, car_width=2.0, safety_margin=0.3, v=10.0, Lr=1.5, T=0.1):
435741
"""
436742
Check if the car trajectory collides with any cones.
@@ -796,7 +1102,8 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
7961102
current_heading = vehicle_state['heading']
7971103

7981104
for cone_idx, cone in enumerate(cones):
799-
scenario, flex_wps, fixed_wp, target_heading = waypoint_generate(vehicle_state, cones, cone_idx)
1105+
# scenario, flex_wps, fixed_wp, target_heading = waypoint_generate(vehicle_state, cones, cone_idx)
1106+
scenario, flex_wps, fixed_wp, target_heading = waypoint_generate_relaxed(vehicle_state, cones, cone_idx)
8001107
if not flex_wps or fixed_wp is None:
8011108
continue
8021109
# flex_wp = flex_wps[0]
@@ -810,7 +1117,8 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
8101117
'x': fixed_wp[0], 'y': fixed_wp[1], 'psi': target_heading, 'c': 0.0
8111118
}
8121119

813-
x, y, psi, c, v, eps, _ = trajectory_generation(init_state, final_state, waypoints=flex_wps)
1120+
# x, y, psi, c, v, eps, _ = trajectory_generation(init_state, final_state, waypoints=flex_wps)
1121+
x, y, psi, c, v, a, eps, T_total, final_error = trajectory_generation_dynamics(init_state, final_state, waypoints=flex_wps)
8141122
x_all.extend(x)
8151123
y_all.extend(y)
8161124
v_all.extend(v)
@@ -827,22 +1135,22 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
8271135

8281136
current_pos = np.array([x[-1], y[-1]])
8291137

830-
# # Plot overall trajectory
831-
# plt.figure()
832-
# plt.plot(x_all, y_all, label='Overall Trajectory')
833-
834-
# # Plot cones
835-
# for i, cone in enumerate(cones):
836-
# plt.scatter(cone['x'], cone['y'], color='orange', s=10, label='Cone' if i == 0 else "")
837-
# plt.text(cone['x'], cone['y'] + 0.5, f'C{i+1}', ha='center', fontsize=9, color='darkorange')
838-
839-
# plt.title('4-Cone Full Course Trajectory')
840-
# plt.xlabel('X')
841-
# plt.ylabel('Y')
842-
# plt.legend()
843-
# plt.axis('equal')
844-
# plt.grid(True)
845-
# plt.show()
1138+
# Plot overall trajectory
1139+
plt.figure()
1140+
plt.plot(x_all, y_all, label='Overall Trajectory')
1141+
1142+
# Plot cones
1143+
for i, cone in enumerate(cones):
1144+
plt.scatter(cone['x'], cone['y'], color='orange', s=10, label='Cone' if i == 0 else "")
1145+
plt.text(cone['x'], cone['y'] + 0.5, f'C{i+1}', ha='center', fontsize=9, color='darkorange')
1146+
1147+
plt.title('4-Cone Full Course Trajectory')
1148+
plt.xlabel('X')
1149+
plt.ylabel('Y')
1150+
plt.legend()
1151+
plt.axis('equal')
1152+
plt.grid(True)
1153+
plt.show()
8461154

8471155
combined_xy = [[x, y] for x, y in zip(x_all, y_all)]
8481156
path = Path(ObjectFrameEnum.START,combined_xy)

GEMstack/onboard/planning/velocity_profile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@ def compute_velocity_profile(points, plot=True):
278278
wheelbase = settings.get('vehicle.geometry.wheelbase')
279279

280280
points = np.array(points)
281-
# print(points)
281+
print(points)
282282
xs, ys = points[:,0], points [:,1]
283-
# print(xs)
283+
print(xs)
284284

285285
dx = np.diff(xs)
286286
dy = np.diff(ys)

0 commit comments

Comments
 (0)