|
| 1 | +import numpy as np |
| 2 | +import matplotlib.pyplot as plt |
| 3 | +from scipy.optimize import minimize |
| 4 | + |
| 5 | +def trajectory_generation(init_state, final_state, w_c=1.0, w_eps=1.0): |
| 6 | + N = 50 |
| 7 | + T = 0.1 |
| 8 | + v = 10.0 |
| 9 | + Lr = 1.5 |
| 10 | + small_constant = 1e-3 |
| 11 | + |
| 12 | + def optimize_with_weights(dynamic_weights_c=None, dynamic_weights_eps=None): |
| 13 | + y = np.linspace(init_state['y'], final_state['y'], N) |
| 14 | + psi = np.linspace(init_state['psi'], final_state['psi'], N) |
| 15 | + c = np.linspace(init_state['c'], final_state['c'], N) |
| 16 | + eps = np.zeros(N - 1) |
| 17 | + |
| 18 | + def cost(x): |
| 19 | + y_, psi_, c_, eps_ = np.split(x, [N-1, 2*(N-1), 3*(N-1)]) |
| 20 | + c_seq = np.concatenate(([init_state['c']], c_)) |
| 21 | + cost_c = np.sum((dynamic_weights_c if dynamic_weights_c is not None else w_c) * c_seq**2) |
| 22 | + cost_eps = np.sum((dynamic_weights_eps if dynamic_weights_eps is not None else w_eps) * eps_**2) |
| 23 | + return cost_c + cost_eps |
| 24 | + |
| 25 | + def dynamics_constraints(x): |
| 26 | + y_, psi_, c_, eps_ = np.split(x, [N-1, 2*(N-1), 3*(N-1)]) |
| 27 | + constraints = [] |
| 28 | + y_prev, psi_prev, c_prev = init_state['y'], init_state['psi'], init_state['c'] |
| 29 | + for k in range(N-1): |
| 30 | + dy = v * np.sin(psi_prev + c_prev * Lr) * T |
| 31 | + dpsi = v * c_prev * T |
| 32 | + dc = eps_[k] * T |
| 33 | + constraints.append(y_[k] - (y_prev + dy)) |
| 34 | + constraints.append(psi_[k] - (psi_prev + dpsi)) |
| 35 | + constraints.append(c_[k] - (c_prev + dc)) |
| 36 | + y_prev, psi_prev, c_prev = y_[k], psi_[k], c_[k] |
| 37 | + constraints.append(y_[-1] - final_state['y']) |
| 38 | + constraints.append(psi_[-1] - final_state['psi']) |
| 39 | + constraints.append(c_[-1] - final_state['c']) |
| 40 | + return constraints |
| 41 | + |
| 42 | + x0 = np.concatenate([y[1:], psi[1:], c[1:], eps]) |
| 43 | + result = minimize(cost, x0, constraints={'type': 'eq', 'fun': dynamics_constraints}, options={'maxiter': 1000}) |
| 44 | + |
| 45 | + if result.success: |
| 46 | + y_, psi_, c_, eps_ = np.split(result.x, [N-1, 2*(N-1), 3*(N-1)]) |
| 47 | + y_full = np.concatenate(([init_state['y']], y_)) |
| 48 | + psi_full = np.concatenate(([init_state['psi']], psi_)) |
| 49 | + c_full = np.concatenate(([init_state['c']], c_)) |
| 50 | + return y_full, psi_full, c_full, eps_ |
| 51 | + else: |
| 52 | + raise RuntimeError("Optimization failed") |
| 53 | + |
| 54 | + # First pass |
| 55 | + y1, psi1, c1, eps1 = optimize_with_weights() |
| 56 | + |
| 57 | + # Shape weights based on result |
| 58 | + dynamic_weights_c = np.abs(c1) + small_constant |
| 59 | + dynamic_weights_eps = np.abs(eps1) + small_constant |
| 60 | + |
| 61 | + # Second pass with weighted cost |
| 62 | + y2, psi2, c2, eps2 = optimize_with_weights(dynamic_weights_c, dynamic_weights_eps) |
| 63 | + return y2, psi2, c2, eps2 |
| 64 | + |
| 65 | +def plot_trajectory(y_traj, psi_traj, c_traj, label): |
| 66 | + N = len(y_traj) |
| 67 | + T = 0.1 |
| 68 | + v = 5.0 |
| 69 | + x_traj = np.zeros(N) |
| 70 | + for i in range(1, N): |
| 71 | + dx = v * np.cos(psi_traj[i-1] + c_traj[i-1] * 1.5) * T |
| 72 | + x_traj[i] = x_traj[i-1] + dx |
| 73 | + |
| 74 | + plt.figure(figsize=(8, 5)) |
| 75 | + plt.plot(x_traj, y_traj, label=label) |
| 76 | + plt.scatter([x_traj[0], x_traj[-1]], [y_traj[0], y_traj[-1]], color='red', zorder=5) |
| 77 | + plt.annotate("init", (x_traj[0], y_traj[0]), textcoords="offset points", xytext=(-10,10), ha='center') |
| 78 | + plt.annotate("final", (x_traj[-1], y_traj[-1]), textcoords="offset points", xytext=(10,10), ha='center') |
| 79 | + plt.xlabel('x (m)') |
| 80 | + plt.ylabel('y (m)') |
| 81 | + plt.title('Trajectory from Initial to Final State') |
| 82 | + plt.legend() |
| 83 | + plt.axis('equal') |
| 84 | + plt.grid(True) |
| 85 | + plt.show() |
| 86 | + |
| 87 | +def plot_dynamics(psi_traj, c_traj, eps_traj): |
| 88 | + time_steps = np.arange(len(psi_traj)) |
| 89 | + eps_time = np.arange(len(eps_traj)) |
| 90 | + |
| 91 | + plt.figure(figsize=(12, 8)) |
| 92 | + |
| 93 | + plt.subplot(3, 1, 1) |
| 94 | + plt.plot(time_steps, psi_traj, label="Heading (ψ)") |
| 95 | + plt.ylabel("ψ (rad)") |
| 96 | + plt.grid(True) |
| 97 | + plt.legend() |
| 98 | + |
| 99 | + plt.subplot(3, 1, 2) |
| 100 | + plt.plot(time_steps, c_traj, label="Curvature (c)", color='orange') |
| 101 | + plt.ylabel("Curvature (1/m)") |
| 102 | + plt.grid(True) |
| 103 | + plt.legend() |
| 104 | + |
| 105 | + plt.subplot(3, 1, 3) |
| 106 | + plt.plot(eps_time, eps_traj, label="Curvature Rate (ε)", color='green') |
| 107 | + plt.xlabel("Time Step") |
| 108 | + plt.ylabel("ε (1/m²)") |
| 109 | + plt.grid(True) |
| 110 | + plt.legend() |
| 111 | + |
| 112 | + plt.tight_layout() |
| 113 | + plt.show() |
| 114 | + |
| 115 | +# --- Test Case --- |
| 116 | +def test_trajectory_generation(): |
| 117 | + init_state = {'y': 0.0, 'psi': 0.0, 'c': 0.0} |
| 118 | + final_state = {'y': 10.0, 'psi': 0.0, 'c': 0.0} |
| 119 | + |
| 120 | + y_traj, psi_traj, c_traj, eps_traj = trajectory_generation(init_state, final_state) |
| 121 | + plot_trajectory(y_traj, psi_traj, c_traj, label="Generated trajectory") |
| 122 | + plot_dynamics(psi_traj, c_traj, eps_traj) |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | +def feasibility_check(trajectory, cone_map, car_width=2.0, safety_margin=0.3, v=10.0, Lr=1.5, T=0.1): |
| 128 | + """ |
| 129 | + Check if the car trajectory collides with any cones. |
| 130 | +
|
| 131 | + Parameters: |
| 132 | + - trajectory: list of (y, psi, c) states |
| 133 | + - cone_map: list of (x, y) cone positions |
| 134 | + - car_width: width of the vehicle in meters |
| 135 | + - safety_margin: buffer around the vehicle |
| 136 | + - v: vehicle constant speed (used for x position estimation) |
| 137 | + - Lr: distance to rear axle |
| 138 | + - T: time step |
| 139 | +
|
| 140 | + Returns: |
| 141 | + - feasible: True if no collisions |
| 142 | + - collisions: list of indices of cones that were collided with (for plotting purpose) |
| 143 | + - x_vals, y_vals: trajectory positions for plotting (for plotting purpose) |
| 144 | + """ |
| 145 | + y_vals, psi_vals, c_vals = zip(*trajectory) |
| 146 | + x_vals = [0.0] |
| 147 | + for i in range(1, len(trajectory)): |
| 148 | + dx = v * np.cos(psi_vals[i-1] + c_vals[i-1] * Lr) * T |
| 149 | + x_vals.append(x_vals[-1] + dx) |
| 150 | + |
| 151 | + collision_radius = (car_width / 2.0) + safety_margin |
| 152 | + collisions = [] |
| 153 | + |
| 154 | + for j, (cone_x, cone_y) in enumerate(cone_map): |
| 155 | + for x, y in zip(x_vals, y_vals): |
| 156 | + if np.hypot(x - cone_x, y - cone_y) < collision_radius: |
| 157 | + collisions.append(j) |
| 158 | + break |
| 159 | + |
| 160 | + feasible = len(collisions) == 0 |
| 161 | + return feasible, collisions, x_vals, y_vals |
| 162 | + |
| 163 | +# --- Test Case --- |
| 164 | +def test_feasibility_check(): |
| 165 | + N = 50 |
| 166 | + y_traj = np.linspace(0, 10, N) |
| 167 | + psi_traj = np.linspace(0, 0.1, N) |
| 168 | + c_traj = np.linspace(0, 0.2, N) |
| 169 | + trajectory = list(zip(y_traj, psi_traj, c_traj)) |
| 170 | + |
| 171 | + # Cone map near the path |
| 172 | + 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)] |
| 173 | + |
| 174 | + # Run check |
| 175 | + feasible, collisions, x_vals, y_vals = feasibility_check(trajectory, cone_map) |
| 176 | + |
| 177 | + # Plot |
| 178 | + plt.figure(figsize=(10, 6)) |
| 179 | + plt.plot(x_vals, y_vals, label="Trajectory", linewidth=2) |
| 180 | + for i, (cx, cy) in enumerate(cone_map): |
| 181 | + color = 'red' if i in collisions else 'green' |
| 182 | + plt.scatter(cx, cy, color=color, s=100, label=f'Cone {i}' if i == 0 else "") |
| 183 | + plt.xlabel("x (m)") |
| 184 | + plt.ylabel("y (m)") |
| 185 | + plt.title("Trajectory and Cone Map") |
| 186 | + plt.legend() |
| 187 | + plt.axis("equal") |
| 188 | + plt.grid(True) |
| 189 | + plt.show() |
| 190 | + |
| 191 | + print("Feasible:", feasible) |
| 192 | + print("Collisions with cones:", collisions) |
0 commit comments