1#![allow(dead_code)]
2
3use nalgebra::{Matrix2, Matrix4, Matrix4x2, Vector2, Vector4};
15use std::f64::consts::PI;
16
17const WB: f64 = 2.5; const MAX_STEER: f64 = 45.0 * PI / 180.0; const MAX_DSTEER: f64 = 30.0 * PI / 180.0; const MAX_SPEED: f64 = 55.0 / 3.6; const MIN_SPEED: f64 = -20.0 / 3.6; const MAX_ACCEL: f64 = 1.0; const T: usize = 5; const DT: f64 = 0.2; const TARGET_SPEED: f64 = 10.0 / 3.6; const GOAL_DIS: f64 = 1.5; const STOP_SPEED: f64 = 0.5 / 3.6; const MAX_ITER: usize = 3; const DU_TH: f64 = 0.1; const N_IND_SEARCH: usize = 10; const PATH_RESOLUTION: f64 = 0.5; const UPSTREAM_SWITCH_BACK_TICK: f64 = 1.0; const MAX_SIM_STEPS: usize = 2000;
37
38const QP_MAX_ITERS: usize = 200;
40const LINE_SEARCH_ITERS: usize = 16;
41const LINE_SEARCH_INITIAL_STEP: f64 = 0.25;
42const GRAD_TOL: f64 = 1.0e-5;
43const COST_TOL: f64 = 1.0e-7;
44
45const Q: [f64; 4] = [1.0, 1.0, 0.5, 0.5];
47const QF: [f64; 4] = Q;
48const R: [f64; 2] = [0.01, 0.01];
49const RD: [f64; 2] = [0.01, 1.0];
50
51#[derive(Clone, Copy, Debug)]
53pub struct State {
54 pub x: f64,
55 pub y: f64,
56 pub v: f64,
57 pub yaw: f64,
58}
59
60impl State {
61 pub fn new(x: f64, y: f64, v: f64, yaw: f64) -> Self {
62 Self { x, y, v, yaw }
63 }
64
65 pub fn to_vector(self) -> Vector4<f64> {
66 Vector4::new(self.x, self.y, self.v, self.yaw)
67 }
68
69 pub fn update(&mut self, accel: f64, steer: f64) {
70 let steer = steer.clamp(-MAX_STEER, MAX_STEER);
71
72 self.x += self.v * self.yaw.cos() * DT;
73 self.y += self.v * self.yaw.sin() * DT;
74 self.yaw += self.v / WB * steer.tan() * DT;
75 self.yaw = normalize_angle(self.yaw);
76 self.v += accel * DT;
77 self.v = self.v.clamp(MIN_SPEED, MAX_SPEED);
78 }
79}
80
81#[derive(Debug)]
82pub struct MpcResult {
83 pub controls: Vec<Vector2<f64>>,
84 pub predicted: Vec<Vector4<f64>>,
85}
86
87#[derive(Debug)]
88pub struct SimulationResult {
89 pub cx: Vec<f64>,
90 pub cy: Vec<f64>,
91 pub hist_x: Vec<f64>,
92 pub hist_y: Vec<f64>,
93 pub predicted_x: Vec<f64>,
94 pub predicted_y: Vec<f64>,
95 pub goal: (f64, f64),
96 pub reached_goal: bool,
97 pub target_index: usize,
98 pub final_index: usize,
99 pub final_state: State,
100}
101
102pub fn normalize_angle(angle: f64) -> f64 {
103 let mut value = angle % (2.0 * PI);
104 if value > PI {
105 value -= 2.0 * PI;
106 } else if value < -PI {
107 value += 2.0 * PI;
108 }
109 value
110}
111
112fn angle_diff(a: f64, b: f64) -> f64 {
113 normalize_angle(a - b)
114}
115
116fn state_error(state: Vector4<f64>, reference: Vector4<f64>) -> Vector4<f64> {
117 let mut error = state - reference;
118 error[3] = angle_diff(state[3], reference[3]);
119 error
120}
121
122fn control_cost_weight() -> Matrix2<f64> {
123 Matrix2::from_diagonal(&Vector2::new(R[0], R[1]))
124}
125
126fn control_rate_weight() -> Matrix2<f64> {
127 Matrix2::from_diagonal(&Vector2::new(RD[0], RD[1]))
128}
129
130fn state_cost_weight() -> Matrix4<f64> {
131 Matrix4::from_diagonal(&Vector4::new(Q[0], Q[1], Q[2], Q[3]))
132}
133
134fn terminal_cost_weight() -> Matrix4<f64> {
135 Matrix4::from_diagonal(&Vector4::new(QF[0], QF[1], QF[2], QF[3]))
136}
137
138fn get_linear_model_matrix(
139 v: f64,
140 yaw: f64,
141 steer: f64,
142) -> (Matrix4<f64>, Matrix4x2<f64>, Vector4<f64>) {
143 let a = Matrix4::new(
144 1.0,
145 0.0,
146 DT * yaw.cos(),
147 -DT * v * yaw.sin(),
148 0.0,
149 1.0,
150 DT * yaw.sin(),
151 DT * v * yaw.cos(),
152 0.0,
153 0.0,
154 1.0,
155 0.0,
156 0.0,
157 0.0,
158 DT * steer.tan() / WB,
159 1.0,
160 );
161
162 let b = Matrix4x2::new(
163 0.0,
164 0.0,
165 0.0,
166 0.0,
167 DT,
168 0.0,
169 0.0,
170 DT * v / (WB * steer.cos().powi(2)),
171 );
172
173 let c = Vector4::new(
174 DT * v * yaw.sin() * yaw,
175 -DT * v * yaw.cos() * yaw,
176 0.0,
177 -DT * v * steer / (WB * steer.cos().powi(2)),
178 );
179
180 (a, b, c)
181}
182
183struct CubicSpline1D {
185 x: Vec<f64>,
186 a: Vec<f64>,
187 b: Vec<f64>,
188 c: Vec<f64>,
189 d: Vec<f64>,
190}
191
192impl CubicSpline1D {
193 fn new(x: &[f64], y: &[f64]) -> Self {
194 let n = x.len();
195 let a = y.to_vec();
196 let mut b = vec![0.0; n];
197 let mut c = vec![0.0; n];
198 let mut d = vec![0.0; n];
199
200 let h: Vec<f64> = (0..n - 1).map(|i| x[i + 1] - x[i]).collect();
201
202 let mut alpha = vec![0.0; n];
203 for i in 1..n - 1 {
204 alpha[i] = 3.0 / h[i] * (a[i + 1] - a[i]) - 3.0 / h[i - 1] * (a[i] - a[i - 1]);
205 }
206
207 let mut l = vec![1.0; n];
208 let mut mu = vec![0.0; n];
209 let mut z = vec![0.0; n];
210
211 for i in 1..n - 1 {
212 l[i] = 2.0 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1];
213 mu[i] = h[i] / l[i];
214 z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
215 }
216
217 for j in (0..n - 1).rev() {
218 c[j] = z[j] - mu[j] * c[j + 1];
219 b[j] = (a[j + 1] - a[j]) / h[j] - h[j] * (c[j + 1] + 2.0 * c[j]) / 3.0;
220 d[j] = (c[j + 1] - c[j]) / (3.0 * h[j]);
221 }
222
223 Self {
224 x: x.to_vec(),
225 a,
226 b,
227 c,
228 d,
229 }
230 }
231
232 fn calc(&self, t: f64) -> f64 {
233 let i = self.search_index(t);
234 let dx = t - self.x[i];
235 self.a[i] + self.b[i] * dx + self.c[i] * dx.powi(2) + self.d[i] * dx.powi(3)
236 }
237
238 fn calc_d(&self, t: f64) -> f64 {
239 let i = self.search_index(t);
240 let dx = t - self.x[i];
241 self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx.powi(2)
242 }
243
244 fn search_index(&self, t: f64) -> usize {
245 for i in 0..self.x.len() - 1 {
246 if self.x[i] <= t && t < self.x[i + 1] {
247 return i;
248 }
249 }
250 self.x.len() - 2
251 }
252}
253
254struct CubicSpline2D {
256 s: Vec<f64>,
257 sx: CubicSpline1D,
258 sy: CubicSpline1D,
259}
260
261impl CubicSpline2D {
262 fn new(x: &[f64], y: &[f64]) -> Self {
263 let mut s = vec![0.0];
264 for i in 1..x.len() {
265 let ds = ((x[i] - x[i - 1]).powi(2) + (y[i] - y[i - 1]).powi(2)).sqrt();
266 s.push(s[i - 1] + ds);
267 }
268
269 let sx = CubicSpline1D::new(&s, x);
270 let sy = CubicSpline1D::new(&s, y);
271
272 Self { s, sx, sy }
273 }
274
275 fn calc_position(&self, s: f64) -> (f64, f64) {
276 (self.sx.calc(s), self.sy.calc(s))
277 }
278
279 fn calc_yaw(&self, s: f64) -> f64 {
280 let dx = self.sx.calc_d(s);
281 let dy = self.sy.calc_d(s);
282 dy.atan2(dx)
283 }
284}
285
286fn smooth_yaw(yaw: &mut [f64]) {
287 for i in 0..yaw.len().saturating_sub(1) {
288 let mut dyaw = yaw[i + 1] - yaw[i];
289 while dyaw >= PI / 2.0 {
290 yaw[i + 1] -= 2.0 * PI;
291 dyaw = yaw[i + 1] - yaw[i];
292 }
293 while dyaw <= -PI / 2.0 {
294 yaw[i + 1] += 2.0 * PI;
295 dyaw = yaw[i + 1] - yaw[i];
296 }
297 }
298}
299
300pub fn calc_speed_profile(cx: &[f64], cy: &[f64], cyaw: &[f64], target_speed: f64) -> Vec<f64> {
301 let mut speed_profile = vec![target_speed; cx.len()];
302 let mut direction = 1.0;
303
304 for i in 0..cx.len().saturating_sub(1) {
305 let dx = cx[i + 1] - cx[i];
306 let dy = cy[i + 1] - cy[i];
307 let move_direction = dy.atan2(dx);
308
309 if dx.abs() > f64::EPSILON && dy.abs() > f64::EPSILON {
310 let dangle = angle_diff(move_direction, cyaw[i]).abs();
311 direction = if dangle >= PI / 4.0 { -1.0 } else { 1.0 };
312 }
313
314 speed_profile[i] = direction * target_speed;
315 }
316
317 if let Some(last) = speed_profile.last_mut() {
318 *last = 0.0;
319 }
320
321 speed_profile
322}
323
324fn calc_nearest_index(
325 state: &State,
326 cx: &[f64],
327 cy: &[f64],
328 cyaw: &[f64],
329 pind: usize,
330) -> (usize, f64) {
331 let start = pind.min(cx.len().saturating_sub(1));
332 let end = (start + N_IND_SEARCH).min(cx.len());
333
334 let mut best_index = start;
335 let mut best_distance_sq = f64::INFINITY;
336
337 for i in start..end {
338 let dx = state.x - cx[i];
339 let dy = state.y - cy[i];
340 let distance_sq = dx * dx + dy * dy;
341 if distance_sq < best_distance_sq {
342 best_distance_sq = distance_sq;
343 best_index = i;
344 }
345 }
346
347 let mut best_distance = best_distance_sq.sqrt();
348 let dxl = cx[best_index] - state.x;
349 let dyl = cy[best_index] - state.y;
350 let angle = angle_diff(cyaw[best_index], dyl.atan2(dxl));
351 if angle < 0.0 {
352 best_distance *= -1.0;
353 }
354
355 (best_index, best_distance)
356}
357
358fn calc_ref_trajectory(
359 state: &State,
360 cx: &[f64],
361 cy: &[f64],
362 cyaw: &[f64],
363 speed_profile: &[f64],
364 course_tick: f64,
365 pind: usize,
366) -> (Vec<Vector4<f64>>, usize) {
367 let ncourse = cx.len();
368 let (mut ind, _) = calc_nearest_index(state, cx, cy, cyaw, pind);
369 if pind >= ind {
370 ind = pind;
371 }
372
373 let mut xref = vec![Vector4::zeros(); T + 1];
374 xref[0] = Vector4::new(cx[ind], cy[ind], speed_profile[ind], cyaw[ind]);
375
376 let mut travel = 0.0;
377 for point in xref.iter_mut().take(T + 1).skip(1) {
378 travel += state.v.abs() * DT;
379 let dind = (travel / course_tick).round() as usize;
380 let index = (ind + dind).min(ncourse - 1);
381 *point = Vector4::new(cx[index], cy[index], speed_profile[index], cyaw[index]);
382 }
383
384 (xref, ind)
385}
386
387fn predict_motion(state: State, controls: &[Vector2<f64>]) -> Vec<Vector4<f64>> {
388 let mut predicted = vec![Vector4::zeros(); T + 1];
389 predicted[0] = state.to_vector();
390
391 let mut current = state;
392 for (i, control) in controls.iter().enumerate().take(T) {
393 current.update(control[0], control[1]);
394 predicted[i + 1] = current.to_vector();
395 }
396
397 predicted
398}
399
400fn apply_control_constraints(controls: &mut [Vector2<f64>]) {
401 for i in 0..controls.len() {
402 controls[i][0] = controls[i][0].clamp(-MAX_ACCEL, MAX_ACCEL);
403 controls[i][1] = controls[i][1].clamp(-MAX_STEER, MAX_STEER);
404
405 if i > 0 {
406 let delta = controls[i][1] - controls[i - 1][1];
407 let max_delta = MAX_DSTEER * DT;
408 if delta.abs() > max_delta {
409 controls[i][1] = controls[i - 1][1] + max_delta * delta.signum();
410 }
411 }
412 }
413}
414
415type LinearizedRollout = (
416 Vec<Vector4<f64>>,
417 Vec<Matrix4<f64>>,
418 Vec<Matrix4x2<f64>>,
419 Vec<Vector4<f64>>,
420);
421
422fn linearized_rollout(
423 x0: &State,
424 xbar: &[Vector4<f64>],
425 controls: &[Vector2<f64>],
426) -> LinearizedRollout {
427 let mut x = vec![Vector4::zeros(); T + 1];
428 let mut a_seq = Vec::with_capacity(T);
429 let mut b_seq = Vec::with_capacity(T);
430 let mut c_seq = Vec::with_capacity(T);
431
432 x[0] = x0.to_vector();
433 for t in 0..T {
434 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
435 let mut next = a * x[t] + b * controls[t] + c;
436 next[3] = normalize_angle(next[3]);
437
438 a_seq.push(a);
439 b_seq.push(b);
440 c_seq.push(c);
441 x[t + 1] = next;
442 }
443
444 (x, a_seq, b_seq, c_seq)
445}
446
447fn compute_cost(x: &[Vector4<f64>], xref: &[Vector4<f64>], controls: &[Vector2<f64>]) -> f64 {
448 let q = state_cost_weight();
449 let qf = terminal_cost_weight();
450 let r = control_cost_weight();
451 let rd = control_rate_weight();
452
453 let mut cost = 0.0;
454 for t in 0..T {
455 cost += controls[t].dot(&(r * controls[t]));
456 if t != 0 {
457 let err = state_error(x[t], xref[t]);
458 cost += err.dot(&(q * err));
459 }
460 if t < T - 1 {
461 let du = controls[t + 1] - controls[t];
462 cost += du.dot(&(rd * du));
463 }
464 }
465
466 let terminal_error = state_error(x[T], xref[T]);
467 cost + terminal_error.dot(&(qf * terminal_error))
468}
469
470#[allow(clippy::needless_range_loop)]
471fn solve_qp_clarabel(
472 xref: &[Vector4<f64>],
473 xbar: &[Vector4<f64>],
474 state: &State,
475) -> Option<Vec<Vector2<f64>>> {
476 use clarabel::algebra::*;
477 use clarabel::solver::*;
478
479 let nu = 2;
480 let nx = 4;
481 let n_u = nu * T;
482 let n_x = nx * (T + 1);
483 let n = n_u + n_x;
484
485 let u_idx = |t: usize, k: usize| -> usize { t * nu + k };
486 let x_idx = |t: usize, k: usize| -> usize { n_u + t * nx + k };
487
488 let mut p_rows = Vec::new();
490 let mut p_cols = Vec::new();
491 let mut p_vals = Vec::new();
492
493 for t in 0..T {
494 for k in 0..nu {
495 let idx = u_idx(t, k);
496 let mut val = R[k];
497 if t > 0 {
498 val += RD[k];
499 }
500 if t < T - 1 {
501 val += RD[k];
502 }
503 p_rows.push(idx);
504 p_cols.push(idx);
505 p_vals.push(val * 2.0);
506 }
507 if t < T - 1 {
508 for k in 0..nu {
509 let i = u_idx(t, k);
510 let j = u_idx(t + 1, k);
511 p_rows.push(i);
512 p_cols.push(j);
513 p_vals.push(-RD[k] * 2.0);
514 }
515 }
516 }
517
518 for t in 1..=T {
519 let w = if t == T { QF } else { Q };
520 for k in 0..nx {
521 let idx = x_idx(t, k);
522 p_rows.push(idx);
523 p_cols.push(idx);
524 p_vals.push(w[k] * 2.0);
525 }
526 }
527
528 let p = CscMatrix::new_from_triplets(n, n, p_rows, p_cols, p_vals);
529
530 let mut q_vec = vec![0.0; n];
532 for t in 1..=T {
533 let w = if t == T { QF } else { Q };
534 for k in 0..nx {
535 q_vec[x_idx(t, k)] = -w[k] * 2.0 * xref[t][k];
536 }
537 }
538
539 let n_eq = nx * (T + 1);
541 let n_steer_rate = T - 1;
542 let n_u_box = 2 * nu * T;
543 let n_steer_rate_box = 2 * n_steer_rate;
544 let n_v_box = 2 * (T + 1);
545 let n_ineq = n_u_box + n_steer_rate_box + n_v_box;
546
547 let mut a_rows = Vec::new();
548 let mut a_cols = Vec::new();
549 let mut a_vals = Vec::new();
550 let mut b_vec = vec![0.0; n_eq + n_ineq];
551
552 let x0 = state.to_vector();
553 for k in 0..nx {
554 a_rows.push(k);
555 a_cols.push(x_idx(0, k));
556 a_vals.push(1.0);
557 b_vec[k] = x0[k];
558 }
559
560 for t in 0..T {
561 let (a_mat, b_mat, c_vec) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
562 let base_row = nx * (t + 1);
563
564 for i in 0..nx {
565 let row = base_row + i;
566 a_rows.push(row);
567 a_cols.push(x_idx(t + 1, i));
568 a_vals.push(-1.0);
569 for j in 0..nx {
570 let val = a_mat[(i, j)];
571 if val.abs() > 1e-15 {
572 a_rows.push(row);
573 a_cols.push(x_idx(t, j));
574 a_vals.push(val);
575 }
576 }
577 for j in 0..nu {
578 let val = b_mat[(i, j)];
579 if val.abs() > 1e-15 {
580 a_rows.push(row);
581 a_cols.push(u_idx(t, j));
582 a_vals.push(val);
583 }
584 }
585 b_vec[row] = -c_vec[i];
586 }
587 }
588
589 let ineq_offset = n_eq;
590 let mut ineq_row = 0;
591
592 let u_bounds = [MAX_ACCEL, MAX_STEER];
593 for t in 0..T {
594 for (k, &bound) in u_bounds.iter().enumerate() {
595 let row = ineq_offset + ineq_row;
596 a_rows.push(row);
597 a_cols.push(u_idx(t, k));
598 a_vals.push(1.0);
599 b_vec[row] = bound;
600 ineq_row += 1;
601
602 let row = ineq_offset + ineq_row;
603 a_rows.push(row);
604 a_cols.push(u_idx(t, k));
605 a_vals.push(-1.0);
606 b_vec[row] = bound;
607 ineq_row += 1;
608 }
609 }
610
611 let max_steer_delta = MAX_DSTEER * DT;
612 for t in 0..(T - 1) {
613 let row = ineq_offset + ineq_row;
614 a_rows.push(row);
615 a_cols.push(u_idx(t + 1, 1));
616 a_vals.push(1.0);
617 a_rows.push(row);
618 a_cols.push(u_idx(t, 1));
619 a_vals.push(-1.0);
620 b_vec[row] = max_steer_delta;
621 ineq_row += 1;
622
623 let row = ineq_offset + ineq_row;
624 a_rows.push(row);
625 a_cols.push(u_idx(t + 1, 1));
626 a_vals.push(-1.0);
627 a_rows.push(row);
628 a_cols.push(u_idx(t, 1));
629 a_vals.push(1.0);
630 b_vec[row] = max_steer_delta;
631 ineq_row += 1;
632 }
633
634 for t in 0..=T {
635 let row = ineq_offset + ineq_row;
636 a_rows.push(row);
637 a_cols.push(x_idx(t, 2));
638 a_vals.push(1.0);
639 b_vec[row] = MAX_SPEED;
640 ineq_row += 1;
641
642 let row = ineq_offset + ineq_row;
643 a_rows.push(row);
644 a_cols.push(x_idx(t, 2));
645 a_vals.push(-1.0);
646 b_vec[row] = -MIN_SPEED;
647 ineq_row += 1;
648 }
649
650 let m = n_eq + n_ineq;
651 let a_csc = CscMatrix::new_from_triplets(m, n, a_rows, a_cols, a_vals);
652
653 let cones = vec![
654 SupportedConeT::ZeroConeT(n_eq),
655 SupportedConeT::NonnegativeConeT(n_ineq),
656 ];
657
658 let settings = DefaultSettingsBuilder::default()
659 .verbose(false)
660 .build()
661 .expect("DefaultSettingsBuilder with defaults should not fail");
663
664 let mut solver = DefaultSolver::new(&p, &q_vec, &a_csc, &b_vec, &cones, settings).ok()?;
665 solver.solve();
666
667 if solver.solution.status != SolverStatus::Solved {
668 return None;
669 }
670
671 let z = &solver.solution.x;
672 let controls: Vec<Vector2<f64>> = (0..T)
673 .map(|t| Vector2::new(z[u_idx(t, 0)], z[u_idx(t, 1)]))
674 .collect();
675
676 Some(controls)
677}
678
679fn optimize_linearized_controls(
680 xref: &[Vector4<f64>],
681 xbar: &[Vector4<f64>],
682 state: &State,
683 initial_controls: &[Vector2<f64>],
684) -> Vec<Vector2<f64>> {
685 let pg_controls = optimize_linearized_controls_pg(xref, xbar, state, initial_controls);
686
687 let is_reverse = state.v < -0.1 || xref.iter().any(|x| x[2] < -0.1);
691 if is_reverse {
692 if let Some(qp_controls) = solve_qp_clarabel(xref, xbar, state) {
693 let (qp_x, _, _, _) = linearized_rollout(state, xbar, &qp_controls);
694 let qp_cost = compute_cost(&qp_x, xref, &qp_controls);
695
696 let (pg_x, _, _, _) = linearized_rollout(state, xbar, &pg_controls);
697 let pg_cost = compute_cost(&pg_x, xref, &pg_controls);
698
699 if qp_cost <= pg_cost {
700 return qp_controls;
701 }
702 }
703 }
704
705 pg_controls
706}
707
708fn optimize_linearized_controls_pg(
709 xref: &[Vector4<f64>],
710 xbar: &[Vector4<f64>],
711 state: &State,
712 initial_controls: &[Vector2<f64>],
713) -> Vec<Vector2<f64>> {
714 let q = state_cost_weight();
715 let qf = terminal_cost_weight();
716 let r = control_cost_weight();
717 let rd = control_rate_weight();
718
719 let mut controls = initial_controls.to_vec();
720 controls.resize(T, Vector2::zeros());
721 apply_control_constraints(&mut controls);
722
723 let mut previous_cost = f64::INFINITY;
724
725 for _ in 0..QP_MAX_ITERS {
726 let (x, a_seq, b_seq, _) = linearized_rollout(state, xbar, &controls);
727 let current_cost = compute_cost(&x, xref, &controls);
728
729 let mut gradients = [Vector2::zeros(); T];
730 let mut lambda = (qf * state_error(x[T], xref[T])) * 2.0;
731
732 for t in (0..T).rev() {
733 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
734
735 if t > 0 {
736 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
737 }
738 if t < T - 1 {
739 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
740 }
741
742 gradients[t] = grad;
743
744 let lx = if t == 0 {
745 Vector4::zeros()
746 } else {
747 (q * state_error(x[t], xref[t])) * 2.0
748 };
749 lambda = lx + a_seq[t].transpose() * lambda;
750 }
751
752 let gradient_norm = gradients
753 .iter()
754 .map(Vector2::norm_squared)
755 .sum::<f64>()
756 .sqrt();
757
758 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
759 break;
760 }
761
762 previous_cost = current_cost;
763 let current_controls = controls.clone();
764 let mut best_candidate_controls = None;
765 let mut best_candidate_cost = current_cost;
766
767 let mut step = LINE_SEARCH_INITIAL_STEP;
768 for &large_step in &[1.0, 0.5] {
769 let mut candidate = current_controls.clone();
770 for t in 0..T {
771 candidate[t] -= gradients[t] * large_step;
772 }
773 apply_control_constraints(&mut candidate);
774
775 let (candidate_x, _, _, _) = linearized_rollout(state, xbar, &candidate);
776 let candidate_cost = compute_cost(&candidate_x, xref, &candidate);
777 if candidate_cost < best_candidate_cost {
778 best_candidate_cost = candidate_cost;
779 best_candidate_controls = Some(candidate);
780 }
781 }
782
783 for _ in 0..LINE_SEARCH_ITERS {
784 let mut candidate = current_controls.clone();
785 for t in 0..T {
786 candidate[t] -= gradients[t] * step;
787 }
788 apply_control_constraints(&mut candidate);
789
790 let (candidate_x, _, _, _) = linearized_rollout(state, xbar, &candidate);
791 let candidate_cost = compute_cost(&candidate_x, xref, &candidate);
792 if candidate_cost < best_candidate_cost {
793 best_candidate_cost = candidate_cost;
794 best_candidate_controls = Some(candidate);
795 }
796
797 step *= 0.5;
798 }
799
800 if let Some(candidate) = best_candidate_controls {
801 controls = candidate;
802 } else {
803 break;
804 }
805 }
806
807 controls
808}
809
810pub fn iterative_linear_mpc_control(
811 xref: &[Vector4<f64>],
812 state: &State,
813 warm_start: &[Vector2<f64>],
814) -> MpcResult {
815 let mut controls = warm_start.to_vec();
816 controls.resize(T, Vector2::zeros());
817 apply_control_constraints(&mut controls);
818
819 let mut predicted = predict_motion(*state, &controls);
820 for _ in 0..MAX_ITER {
821 let previous_controls = controls.clone();
822 controls = optimize_linearized_controls(xref, &predicted, state, &controls);
823 predicted = predict_motion(*state, &controls);
824
825 let du = controls
826 .iter()
827 .zip(previous_controls.iter())
828 .map(|(current, previous)| {
829 (current[0] - previous[0]).abs() + (current[1] - previous[1]).abs()
830 })
831 .sum::<f64>();
832
833 if du <= DU_TH {
834 break;
835 }
836 }
837
838 MpcResult {
839 controls,
840 predicted,
841 }
842}
843
844fn next_warm_start(controls: &[Vector2<f64>]) -> Vec<Vector2<f64>> {
845 let mut warm_start = controls[1..].to_vec();
846 warm_start.push(controls.last().copied().unwrap_or(Vector2::zeros()));
847 warm_start
848}
849
850pub fn check_goal(
851 state: &State,
852 goal: (f64, f64),
853 target_index: usize,
854 final_index: usize,
855) -> bool {
856 let dx = state.x - goal.0;
857 let dy = state.y - goal.1;
858 let distance = (dx * dx + dy * dy).sqrt();
859
860 let near_goal = distance <= GOAL_DIS;
861 let near_end = final_index.abs_diff(target_index) < 5;
862 let stopped = state.v.abs() <= STOP_SPEED;
863
864 near_goal && near_end && stopped
865}
866
867fn sample_reference_course(
868 ax: &[f64],
869 ay: &[f64],
870 course_tick: f64,
871) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
872 let csp = CubicSpline2D::new(ax, ay);
873 let s_max = *csp.s.last().unwrap_or(&0.0);
874
875 let mut cx = Vec::new();
876 let mut cy = Vec::new();
877 let mut cyaw = Vec::new();
878
879 let mut s = 0.0;
880 while s <= s_max {
881 let (x, y) = csp.calc_position(s);
882 cx.push(x);
883 cy.push(y);
884 cyaw.push(csp.calc_yaw(s));
885 s += course_tick;
886 }
887
888 if cx.is_empty() || cy.is_empty() || cyaw.is_empty() {
889 cx.push(ax[0]);
890 cy.push(ay[0]);
891 cyaw.push(0.0);
892 }
893
894 (cx, cy, cyaw)
895}
896
897pub fn generate_reference_course(ax: &[f64], ay: &[f64]) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
898 let (cx, cy, mut cyaw) = sample_reference_course(ax, ay, PATH_RESOLUTION);
899
900 smooth_yaw(&mut cyaw);
901 (cx, cy, cyaw)
902}
903
904fn generate_switch_back_course() -> (Vec<f64>, Vec<f64>, Vec<f64>) {
905 let (mut cx, mut cy, mut cyaw) = sample_reference_course(
906 &[0.0, 30.0, 6.0, 20.0, 35.0],
907 &[0.0, 0.0, 20.0, 35.0, 20.0],
908 UPSTREAM_SWITCH_BACK_TICK,
909 );
910 let (cx2, cy2, mut cyaw2) = sample_reference_course(
911 &[35.0, 10.0, 0.0, 0.0],
912 &[20.0, 30.0, 5.0, 0.0],
913 UPSTREAM_SWITCH_BACK_TICK,
914 );
915
916 for yaw in &mut cyaw2 {
917 *yaw -= PI;
918 }
919
920 cx.extend(cx2);
921 cy.extend(cy2);
922 cyaw.extend(cyaw2);
923 smooth_yaw(&mut cyaw);
924
925 (cx, cy, cyaw)
926}
927
928pub fn run_mpc_simulation() -> SimulationResult {
929 let (cx, cy, cyaw) = generate_switch_back_course();
930 run_mpc_simulation_with_reference(cx, cy, cyaw, UPSTREAM_SWITCH_BACK_TICK, MAX_SIM_STEPS)
931}
932
933fn run_mpc_simulation_with_course(
934 ax: &[f64],
935 ay: &[f64],
936 max_sim_steps: usize,
937) -> SimulationResult {
938 let (cx, cy, cyaw) = generate_reference_course(ax, ay);
939 run_mpc_simulation_with_reference(cx, cy, cyaw, PATH_RESOLUTION, max_sim_steps)
940}
941
942fn run_mpc_simulation_with_reference(
943 cx: Vec<f64>,
944 cy: Vec<f64>,
945 cyaw: Vec<f64>,
946 course_tick: f64,
947 max_sim_steps: usize,
948) -> SimulationResult {
949 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
950
951 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
952 if state.yaw - cyaw[0] >= PI {
953 state.yaw -= 2.0 * PI;
954 } else if state.yaw - cyaw[0] <= -PI {
955 state.yaw += 2.0 * PI;
956 }
957
958 let mut target_index = 0;
959 let final_index = cx.len() - 1;
960 let goal = (cx[final_index], cy[final_index]);
961
962 let mut warm_start = vec![Vector2::zeros(); T];
963
964 let mut hist_x = vec![state.x];
965 let mut hist_y = vec![state.y];
966 let mut predicted = vec![state.to_vector()];
967 let mut reached_goal = false;
968
969 for _ in 0..max_sim_steps {
970 let (xref, new_index) = calc_ref_trajectory(
971 &state,
972 &cx,
973 &cy,
974 &cyaw,
975 &speed_profile,
976 course_tick,
977 target_index,
978 );
979 target_index = new_index;
980
981 let mpc_result = iterative_linear_mpc_control(&xref, &state, &warm_start);
982 let accel = mpc_result.controls[0][0];
983 let steer = mpc_result.controls[0][1];
984
985 state.update(accel, steer);
986 hist_x.push(state.x);
987 hist_y.push(state.y);
988 predicted = mpc_result.predicted;
989
990 warm_start = next_warm_start(&mpc_result.controls);
991
992 if check_goal(&state, goal, target_index, final_index) {
993 reached_goal = true;
994 break;
995 }
996 }
997
998 SimulationResult {
999 cx,
1000 cy,
1001 hist_x,
1002 hist_y,
1003 predicted_x: predicted.iter().map(|p| p[0]).collect(),
1004 predicted_y: predicted.iter().map(|p| p[1]).collect(),
1005 goal,
1006 reached_goal,
1007 target_index,
1008 final_index,
1009 final_state: state,
1010 }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::*;
1016 use std::collections::BTreeMap;
1017
1018 fn optimize_linearized_controls_original(
1021 xref: &[Vector4<f64>],
1022 xbar: &[Vector4<f64>],
1023 state: &State,
1024 initial_controls: &[Vector2<f64>],
1025 ) -> Vec<Vector2<f64>> {
1026 let q = state_cost_weight();
1027 let qf = terminal_cost_weight();
1028 let r = control_cost_weight();
1029 let rd = control_rate_weight();
1030
1031 let mut controls = initial_controls.to_vec();
1032 controls.resize(T, Vector2::zeros());
1033 apply_control_constraints(&mut controls);
1034
1035 let mut previous_cost = f64::INFINITY;
1036
1037 for _ in 0..QP_MAX_ITERS {
1038 let (x, a_seq, b_seq, _) = linearized_rollout(state, xbar, &controls);
1039 let current_cost = compute_cost(&x, xref, &controls);
1040
1041 let mut gradients = [Vector2::zeros(); T];
1042 let mut lambda = (qf * state_error(x[T], xref[T])) * 2.0;
1043
1044 for t in (0..T).rev() {
1045 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
1046
1047 if t > 0 {
1048 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
1049 }
1050 if t < T - 1 {
1051 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
1052 }
1053
1054 gradients[t] = grad;
1055
1056 let lx = if t == 0 {
1057 Vector4::zeros()
1058 } else {
1059 (q * state_error(x[t], xref[t])) * 2.0
1060 };
1061 lambda = lx + a_seq[t].transpose() * lambda;
1062 }
1063
1064 let gradient_norm = gradients
1065 .iter()
1066 .map(Vector2::norm_squared)
1067 .sum::<f64>()
1068 .sqrt();
1069
1070 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
1071 break;
1072 }
1073
1074 previous_cost = current_cost;
1075 let current_controls = controls.clone();
1076 let mut step = LINE_SEARCH_INITIAL_STEP;
1077 let mut improved = false;
1078
1079 for _ in 0..LINE_SEARCH_ITERS {
1080 let mut candidate = current_controls.clone();
1081 for t in 0..T {
1082 candidate[t] -= gradients[t] * step;
1083 }
1084 apply_control_constraints(&mut candidate);
1085
1086 let (candidate_x, _, _, _) = linearized_rollout(state, xbar, &candidate);
1087 let candidate_cost = compute_cost(&candidate_x, xref, &candidate);
1088 if candidate_cost < current_cost {
1089 controls = candidate;
1090 improved = true;
1091 break;
1092 }
1093
1094 step *= 0.5;
1095 }
1096
1097 if !improved {
1098 break;
1099 }
1100 }
1101
1102 controls
1103 }
1104
1105 #[derive(Debug)]
1106 struct SwitchBackTraceRow {
1107 step: usize,
1108 target_index: usize,
1109 state: Vector4<f64>,
1110 xref_head: Vector4<f64>,
1111 xref_tail: Vector4<f64>,
1112 control: Vector2<f64>,
1113 predicted_tail: Vector4<f64>,
1114 }
1115
1116 #[derive(Debug)]
1117 struct SwitchBackDetailedRow {
1118 step: usize,
1119 target_index: usize,
1120 state: Vector4<f64>,
1121 xref: Vec<Vector4<f64>>,
1122 controls: Vec<Vector2<f64>>,
1123 predicted: Vec<Vector4<f64>>,
1124 }
1125
1126 #[derive(Debug)]
1127 struct SwitchBackPlanningSnapshot {
1128 step: usize,
1129 target_index: usize,
1130 state: Vector4<f64>,
1131 xref_head: Vector4<f64>,
1132 warm_start_head: Vector2<f64>,
1133 }
1134
1135 #[derive(Debug)]
1136 struct SwitchBackClosedLoopCheckpoint {
1137 step: usize,
1138 target_index: usize,
1139 goal_distance: f64,
1140 state: Vector4<f64>,
1141 }
1142
1143 #[derive(Debug)]
1144 struct SwitchBackGoalConditionSnapshot {
1145 step: usize,
1146 target_index: usize,
1147 goal_distance: f64,
1148 speed: f64,
1149 speed_abs: f64,
1150 heading_error: f64,
1151 motion_curvature: f64,
1152 near_goal: bool,
1153 near_end: bool,
1154 stopped: bool,
1155 reached_goal: bool,
1156 }
1157
1158 #[derive(Debug)]
1159 struct SwitchBackGoalControlSnapshot {
1160 step: usize,
1161 target_index: usize,
1162 goal_distance: f64,
1163 speed: f64,
1164 accel: f64,
1165 steer: f64,
1166 heading_error: f64,
1167 motion_curvature: f64,
1168 course_curvature: f64,
1169 near_goal: bool,
1170 near_end: bool,
1171 stopped: bool,
1172 reached_goal: bool,
1173 }
1174
1175 type SwitchBackControlTrace = BTreeMap<usize, Vec<Vector2<f64>>>;
1176 type SwitchBackStateTrace = BTreeMap<usize, Vec<Vector4<f64>>>;
1177 type SwitchBackHorizonPair = (Vec<Vector4<f64>>, Vec<Vector4<f64>>);
1178 type SwitchBackHorizonTrace = BTreeMap<usize, SwitchBackHorizonPair>;
1179 type SwitchBackWarmStartTrace = BTreeMap<usize, Vec<Vector2<f64>>>;
1180
1181 #[derive(Debug, Default)]
1182 struct StateInjectionMetrics {
1183 default_same_control0: f64,
1184 default_opposite_control0: f64,
1185 default_same_pred_y: f64,
1186 default_opposite_pred_y: f64,
1187 default_same_pred_v: f64,
1188 default_opposite_pred_v: f64,
1189 expanded_same_control0: f64,
1190 expanded_opposite_control0: f64,
1191 expanded_same_pred_y: f64,
1192 expanded_opposite_pred_y: f64,
1193 expanded_same_pred_v: f64,
1194 expanded_opposite_pred_v: f64,
1195 }
1196
1197 struct SwitchBackContext {
1198 cx: Vec<f64>,
1199 cy: Vec<f64>,
1200 cyaw: Vec<f64>,
1201 speed_profile: Vec<f64>,
1202 state: State,
1203 target_index: usize,
1204 warm_start: Vec<Vector2<f64>>,
1205 }
1206
1207 #[derive(Debug)]
1208 struct SwitchBackTraceDiffSummary {
1209 max_target_index_gap: usize,
1210 max_state_diff: [f64; 4],
1211 max_xref_head_diff: [f64; 4],
1212 max_xref_tail_diff: [f64; 4],
1213 max_control_diff: [f64; 2],
1214 max_predicted_tail_diff: [f64; 4],
1215 }
1216
1217 #[derive(Debug)]
1218 struct SwitchBackDetailedDiffSummary {
1219 max_target_index_gap: usize,
1220 max_state_diff: [f64; 4],
1221 max_xref_diff: [f64; 4],
1222 max_control_diff: [f64; 2],
1223 max_predicted_diff: [f64; 4],
1224 }
1225
1226 fn assert_vec2_close(actual: &Vector2<f64>, expected: [f64; 2], tol: f64) {
1227 for (i, expected_value) in expected.iter().enumerate() {
1228 assert!(
1229 (actual[i] - expected_value).abs() <= tol,
1230 "index {}: actual={} expected={} tol={}",
1231 i,
1232 actual[i],
1233 expected_value,
1234 tol
1235 );
1236 }
1237 }
1238
1239 fn assert_vec4_close(actual: &Vector4<f64>, expected: [f64; 4], tol: f64) {
1240 for (i, expected_value) in expected.iter().enumerate() {
1241 assert!(
1242 (actual[i] - expected_value).abs() <= tol,
1243 "index {}: actual={} expected={} tol={}",
1244 i,
1245 actual[i],
1246 expected_value,
1247 tol
1248 );
1249 }
1250 }
1251
1252 fn parse_switch_back_trace(csv: &str) -> Vec<SwitchBackTraceRow> {
1253 csv.lines()
1254 .skip(1)
1255 .filter(|line| !line.trim().is_empty())
1256 .map(|line| {
1257 let values: Vec<&str> = line.split(',').collect();
1258 assert_eq!(values.len(), 20);
1259
1260 SwitchBackTraceRow {
1261 step: values[0].parse().unwrap(),
1262 target_index: values[1].parse().unwrap(),
1263 state: Vector4::new(
1264 values[2].parse().unwrap(),
1265 values[3].parse().unwrap(),
1266 values[4].parse().unwrap(),
1267 values[5].parse().unwrap(),
1268 ),
1269 xref_head: Vector4::new(
1270 values[6].parse().unwrap(),
1271 values[7].parse().unwrap(),
1272 values[8].parse().unwrap(),
1273 values[9].parse().unwrap(),
1274 ),
1275 xref_tail: Vector4::new(
1276 values[10].parse().unwrap(),
1277 values[11].parse().unwrap(),
1278 values[12].parse().unwrap(),
1279 values[13].parse().unwrap(),
1280 ),
1281 control: Vector2::new(values[14].parse().unwrap(), values[15].parse().unwrap()),
1282 predicted_tail: Vector4::new(
1283 values[16].parse().unwrap(),
1284 values[17].parse().unwrap(),
1285 values[18].parse().unwrap(),
1286 values[19].parse().unwrap(),
1287 ),
1288 }
1289 })
1290 .collect()
1291 }
1292
1293 fn parse_switch_back_reverse_controls(csv: &str) -> SwitchBackControlTrace {
1294 let mut result: SwitchBackControlTrace = BTreeMap::new();
1295
1296 for line in csv.lines().skip(1).filter(|line| !line.trim().is_empty()) {
1297 let values: Vec<&str> = line.split(',').collect();
1298 assert_eq!(values.len(), 4);
1299 let step: usize = values[0].parse().unwrap();
1300 let control_index: usize = values[1].parse().unwrap();
1301 let control = Vector2::new(values[2].parse().unwrap(), values[3].parse().unwrap());
1302
1303 let controls = result.entry(step).or_default();
1304 assert_eq!(controls.len(), control_index);
1305 controls.push(control);
1306 }
1307
1308 result
1309 }
1310
1311 fn parse_switch_back_reverse_states(csv: &str) -> SwitchBackStateTrace {
1312 let mut result: SwitchBackStateTrace = BTreeMap::new();
1313
1314 for line in csv.lines().skip(1).filter(|line| !line.trim().is_empty()) {
1315 let values: Vec<&str> = line.split(',').collect();
1316 assert_eq!(values.len(), 6);
1317 let step: usize = values[0].parse().unwrap();
1318 let horizon_index: usize = values[1].parse().unwrap();
1319 let state = Vector4::new(
1320 values[2].parse().unwrap(),
1321 values[3].parse().unwrap(),
1322 values[4].parse().unwrap(),
1323 values[5].parse().unwrap(),
1324 );
1325
1326 let states = result.entry(step).or_default();
1327 assert_eq!(states.len(), horizon_index);
1328 states.push(state);
1329 }
1330
1331 result
1332 }
1333
1334 fn parse_switch_back_reverse_horizon(csv: &str) -> SwitchBackHorizonTrace {
1335 let mut result: SwitchBackHorizonTrace = BTreeMap::new();
1336
1337 for line in csv.lines().skip(1).filter(|line| !line.trim().is_empty()) {
1338 let values: Vec<&str> = line.split(',').collect();
1339 assert_eq!(values.len(), 10);
1340 let step: usize = values[0].parse().unwrap();
1341 let horizon_index: usize = values[1].parse().unwrap();
1342
1343 let entry = result.entry(step).or_default();
1344 assert_eq!(entry.0.len(), horizon_index);
1345 assert_eq!(entry.1.len(), horizon_index);
1346
1347 entry.0.push(Vector4::new(
1348 values[2].parse().unwrap(),
1349 values[3].parse().unwrap(),
1350 values[4].parse().unwrap(),
1351 values[5].parse().unwrap(),
1352 ));
1353 entry.1.push(Vector4::new(
1354 values[6].parse().unwrap(),
1355 values[7].parse().unwrap(),
1356 values[8].parse().unwrap(),
1357 values[9].parse().unwrap(),
1358 ));
1359 }
1360
1361 result
1362 }
1363
1364 fn trace_state_diff(actual: &Vector4<f64>, expected: &Vector4<f64>) -> [f64; 4] {
1365 [
1366 (actual[0] - expected[0]).abs(),
1367 (actual[1] - expected[1]).abs(),
1368 (actual[2] - expected[2]).abs(),
1369 angle_diff(actual[3], expected[3]).abs(),
1370 ]
1371 }
1372
1373 fn trace_control_diff(actual: &Vector2<f64>, expected: &Vector2<f64>) -> [f64; 2] {
1374 [
1375 (actual[0] - expected[0]).abs(),
1376 (actual[1] - expected[1]).abs(),
1377 ]
1378 }
1379
1380 fn summarize_switch_back_trace_window(
1381 actual: &[SwitchBackTraceRow],
1382 expected: &[SwitchBackTraceRow],
1383 ) -> SwitchBackTraceDiffSummary {
1384 let mut summary = SwitchBackTraceDiffSummary {
1385 max_target_index_gap: 0,
1386 max_state_diff: [0.0; 4],
1387 max_xref_head_diff: [0.0; 4],
1388 max_xref_tail_diff: [0.0; 4],
1389 max_control_diff: [0.0; 2],
1390 max_predicted_tail_diff: [0.0; 4],
1391 };
1392
1393 assert_eq!(actual.len(), expected.len());
1394
1395 for (actual_row, expected_row) in actual.iter().zip(expected.iter()) {
1396 assert_eq!(actual_row.step, expected_row.step);
1397 summary.max_target_index_gap = summary
1398 .max_target_index_gap
1399 .max(actual_row.target_index.abs_diff(expected_row.target_index));
1400
1401 for (max_diff, diff) in summary
1402 .max_state_diff
1403 .iter_mut()
1404 .zip(trace_state_diff(&actual_row.state, &expected_row.state))
1405 {
1406 *max_diff = (*max_diff).max(diff);
1407 }
1408
1409 for (max_diff, diff) in summary.max_xref_head_diff.iter_mut().zip(trace_state_diff(
1410 &actual_row.xref_head,
1411 &expected_row.xref_head,
1412 )) {
1413 *max_diff = (*max_diff).max(diff);
1414 }
1415
1416 for (max_diff, diff) in summary.max_xref_tail_diff.iter_mut().zip(trace_state_diff(
1417 &actual_row.xref_tail,
1418 &expected_row.xref_tail,
1419 )) {
1420 *max_diff = (*max_diff).max(diff);
1421 }
1422
1423 for (max_diff, diff) in summary.max_control_diff.iter_mut().zip(trace_control_diff(
1424 &actual_row.control,
1425 &expected_row.control,
1426 )) {
1427 *max_diff = (*max_diff).max(diff);
1428 }
1429
1430 for (max_diff, diff) in
1431 summary
1432 .max_predicted_tail_diff
1433 .iter_mut()
1434 .zip(trace_state_diff(
1435 &actual_row.predicted_tail,
1436 &expected_row.predicted_tail,
1437 ))
1438 {
1439 *max_diff = (*max_diff).max(diff);
1440 }
1441 }
1442
1443 summary
1444 }
1445
1446 fn summarize_switch_back_detailed_window(
1447 actual_trace: &[SwitchBackDetailedRow],
1448 expected_trace: &[SwitchBackTraceRow],
1449 expected_controls: &SwitchBackControlTrace,
1450 expected_horizon: &SwitchBackHorizonTrace,
1451 step_window: std::ops::RangeInclusive<usize>,
1452 ) -> SwitchBackDetailedDiffSummary {
1453 let mut summary = SwitchBackDetailedDiffSummary {
1454 max_target_index_gap: 0,
1455 max_state_diff: [0.0; 4],
1456 max_xref_diff: [0.0; 4],
1457 max_control_diff: [0.0; 2],
1458 max_predicted_diff: [0.0; 4],
1459 };
1460
1461 for actual_row in actual_trace
1462 .iter()
1463 .filter(|row| step_window.contains(&row.step))
1464 {
1465 let expected_row = &expected_trace[actual_row.step];
1466 assert_eq!(actual_row.step, expected_row.step);
1467 summary.max_target_index_gap = summary
1468 .max_target_index_gap
1469 .max(actual_row.target_index.abs_diff(expected_row.target_index));
1470
1471 for (slot, diff) in summary
1472 .max_state_diff
1473 .iter_mut()
1474 .zip(trace_state_diff(&actual_row.state, &expected_row.state))
1475 {
1476 *slot = (*slot).max(diff);
1477 }
1478
1479 let expected_controls_for_step = expected_controls.get(&actual_row.step).unwrap();
1480 let (expected_xref_for_step, expected_predicted_for_step) =
1481 expected_horizon.get(&actual_row.step).unwrap();
1482 let expected_xref_rows = expected_xref_for_step
1483 .iter()
1484 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
1485 .collect::<Vec<_>>();
1486 let expected_control_rows = expected_controls_for_step
1487 .iter()
1488 .map(|vec| [vec[0], vec[1]])
1489 .collect::<Vec<_>>();
1490 let expected_predicted_rows = expected_predicted_for_step
1491 .iter()
1492 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
1493 .collect::<Vec<_>>();
1494
1495 for (slot, diff) in summary
1496 .max_xref_diff
1497 .iter_mut()
1498 .zip(summarize_vec4_sequence_diff(
1499 &actual_row.xref,
1500 &expected_xref_rows,
1501 ))
1502 {
1503 *slot = (*slot).max(diff);
1504 }
1505
1506 for (slot, diff) in
1507 summary
1508 .max_control_diff
1509 .iter_mut()
1510 .zip(summarize_vec2_sequence_diff(
1511 &actual_row.controls,
1512 &expected_control_rows,
1513 ))
1514 {
1515 *slot = (*slot).max(diff);
1516 }
1517
1518 for (slot, diff) in
1519 summary
1520 .max_predicted_diff
1521 .iter_mut()
1522 .zip(summarize_vec4_sequence_diff(
1523 &actual_row.predicted,
1524 &expected_predicted_rows,
1525 ))
1526 {
1527 *slot = (*slot).max(diff);
1528 }
1529 }
1530
1531 summary
1532 }
1533
1534 fn assert_diff_bounds<const N: usize>(label: &str, actual: [f64; N], tolerance: [f64; N]) {
1535 for (i, (actual_diff, bound)) in actual.into_iter().zip(tolerance).enumerate() {
1536 assert!(
1537 actual_diff <= bound,
1538 "{} diff index {}: actual={} tolerance={}",
1539 label,
1540 i,
1541 actual_diff,
1542 bound
1543 );
1544 }
1545 }
1546
1547 fn summarize_vec4_sequence_diff(actual: &[Vector4<f64>], expected: &[[f64; 4]]) -> [f64; 4] {
1548 assert_eq!(actual.len(), expected.len());
1549 let mut max_diff: [f64; 4] = [0.0; 4];
1550
1551 for (actual_vec, expected_vec) in actual.iter().zip(expected.iter()) {
1552 for (slot, diff) in max_diff.iter_mut().zip(trace_state_diff(
1553 actual_vec,
1554 &Vector4::from_row_slice(expected_vec),
1555 )) {
1556 *slot = (*slot).max(diff);
1557 }
1558 }
1559
1560 max_diff
1561 }
1562
1563 fn summarize_vec2_sequence_diff(actual: &[Vector2<f64>], expected: &[[f64; 2]]) -> [f64; 2] {
1564 assert_eq!(actual.len(), expected.len());
1565 let mut max_diff: [f64; 2] = [0.0; 2];
1566
1567 for (actual_vec, expected_vec) in actual.iter().zip(expected.iter()) {
1568 for (slot, diff) in max_diff.iter_mut().zip(trace_control_diff(
1569 actual_vec,
1570 &Vector2::from_row_slice(expected_vec),
1571 )) {
1572 *slot = (*slot).max(diff);
1573 }
1574 }
1575
1576 max_diff
1577 }
1578
1579 fn compute_cost_without_angle_wrap(
1580 x: &[Vector4<f64>],
1581 xref: &[Vector4<f64>],
1582 controls: &[Vector2<f64>],
1583 ) -> f64 {
1584 let q = state_cost_weight();
1585 let qf = terminal_cost_weight();
1586 let r = control_cost_weight();
1587 let rd = control_rate_weight();
1588
1589 let mut cost = 0.0;
1590 for t in 0..T {
1591 cost += controls[t].dot(&(r * controls[t]));
1592 if t != 0 {
1593 let err = x[t] - xref[t];
1594 cost += err.dot(&(q * err));
1595 }
1596 if t < T - 1 {
1597 let du = controls[t + 1] - controls[t];
1598 cost += du.dot(&(rd * du));
1599 }
1600 }
1601
1602 let terminal_error = x[T] - xref[T];
1603 cost + terminal_error.dot(&(qf * terminal_error))
1604 }
1605
1606 #[derive(Debug)]
1607 struct GradientTraceEntry {
1608 iter: usize,
1609 cost: f64,
1610 gradient_norm: f64,
1611 accepted_step: Option<f64>,
1612 }
1613
1614 #[derive(Debug)]
1615 struct LineSearchProjectionEntry {
1616 step: f64,
1617 raw_cost: f64,
1618 projected_cost: f64,
1619 raw_to_projected: [f64; 2],
1620 raw_to_python: [f64; 2],
1621 projected_to_python: [f64; 2],
1622 }
1623
1624 #[derive(Debug)]
1625 struct ProjectionTraceEntry {
1626 iter: usize,
1627 current_cost: f64,
1628 candidates: Vec<LineSearchProjectionEntry>,
1629 }
1630
1631 fn control_sequence_max_diff(actual: &[Vector2<f64>], expected: &[Vector2<f64>]) -> [f64; 2] {
1632 assert_eq!(actual.len(), expected.len());
1633 let mut max_diff: [f64; 2] = [0.0; 2];
1634
1635 for (actual_vec, expected_vec) in actual.iter().zip(expected.iter()) {
1636 let diff = trace_control_diff(actual_vec, expected_vec);
1637 max_diff[0] = max_diff[0].max(diff[0]);
1638 max_diff[1] = max_diff[1].max(diff[1]);
1639 }
1640
1641 max_diff
1642 }
1643
1644 fn linearized_rollout_without_angle_wrap(
1645 x0: &State,
1646 xbar: &[Vector4<f64>],
1647 controls: &[Vector2<f64>],
1648 ) -> Vec<Vector4<f64>> {
1649 let mut x = vec![Vector4::zeros(); T + 1];
1650 x[0] = x0.to_vector();
1651
1652 for t in 0..T {
1653 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
1654 x[t + 1] = a * x[t] + b * controls[t] + c;
1655 }
1656
1657 x
1658 }
1659
1660 fn optimize_linearized_controls_without_angle_wrap_with_trace(
1661 xref: &[Vector4<f64>],
1662 xbar: &[Vector4<f64>],
1663 state: &State,
1664 initial_controls: &[Vector2<f64>],
1665 max_iters: usize,
1666 line_search_iters: usize,
1667 ) -> (Vec<Vector2<f64>>, Vec<GradientTraceEntry>) {
1668 optimize_linearized_controls_without_angle_wrap_with_trace_and_initial_step(
1669 xref,
1670 xbar,
1671 state,
1672 initial_controls,
1673 max_iters,
1674 line_search_iters,
1675 0.25,
1676 )
1677 }
1678
1679 fn optimize_linearized_controls_without_angle_wrap_with_trace_and_initial_step(
1680 xref: &[Vector4<f64>],
1681 xbar: &[Vector4<f64>],
1682 state: &State,
1683 initial_controls: &[Vector2<f64>],
1684 max_iters: usize,
1685 line_search_iters: usize,
1686 initial_step: f64,
1687 ) -> (Vec<Vector2<f64>>, Vec<GradientTraceEntry>) {
1688 let q = state_cost_weight();
1689 let qf = terminal_cost_weight();
1690 let r = control_cost_weight();
1691 let rd = control_rate_weight();
1692
1693 let mut controls = initial_controls.to_vec();
1694 controls.resize(T, Vector2::zeros());
1695 apply_control_constraints(&mut controls);
1696
1697 let mut previous_cost = f64::INFINITY;
1698 let mut trace = Vec::new();
1699
1700 for iter in 0..max_iters {
1701 let mut x = vec![Vector4::zeros(); T + 1];
1702 let mut a_seq = Vec::with_capacity(T);
1703 let mut b_seq = Vec::with_capacity(T);
1704 x[0] = state.to_vector();
1705
1706 for t in 0..T {
1707 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
1708 x[t + 1] = a * x[t] + b * controls[t] + c;
1709 a_seq.push(a);
1710 b_seq.push(b);
1711 }
1712 let current_cost = compute_cost_without_angle_wrap(&x, xref, &controls);
1713
1714 let mut gradients = [Vector2::zeros(); T];
1715 let mut lambda = (qf * (x[T] - xref[T])) * 2.0;
1716
1717 for t in (0..T).rev() {
1718 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
1719
1720 if t > 0 {
1721 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
1722 }
1723 if t < T - 1 {
1724 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
1725 }
1726
1727 gradients[t] = grad;
1728
1729 let lx = if t == 0 {
1730 Vector4::zeros()
1731 } else {
1732 (q * (x[t] - xref[t])) * 2.0
1733 };
1734 lambda = lx + a_seq[t].transpose() * lambda;
1735 }
1736
1737 let gradient_norm = gradients
1738 .iter()
1739 .map(Vector2::norm_squared)
1740 .sum::<f64>()
1741 .sqrt();
1742
1743 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
1744 trace.push(GradientTraceEntry {
1745 iter,
1746 cost: current_cost,
1747 gradient_norm,
1748 accepted_step: None,
1749 });
1750 break;
1751 }
1752
1753 previous_cost = current_cost;
1754 let current_controls = controls.clone();
1755 let mut step = initial_step;
1756 let mut accepted_step = None;
1757
1758 for _ in 0..line_search_iters {
1759 let mut candidate = current_controls.clone();
1760 for t in 0..T {
1761 candidate[t] -= gradients[t] * step;
1762 }
1763 apply_control_constraints(&mut candidate);
1764
1765 let candidate_x = linearized_rollout_without_angle_wrap(state, xbar, &candidate);
1766 let candidate_cost =
1767 compute_cost_without_angle_wrap(&candidate_x, xref, &candidate);
1768 if candidate_cost < current_cost {
1769 controls = candidate;
1770 accepted_step = Some(step);
1771 break;
1772 }
1773
1774 step *= 0.5;
1775 }
1776
1777 trace.push(GradientTraceEntry {
1778 iter,
1779 cost: current_cost,
1780 gradient_norm,
1781 accepted_step,
1782 });
1783
1784 if accepted_step.is_none() {
1785 break;
1786 }
1787 }
1788
1789 (controls, trace)
1790 }
1791
1792 fn optimize_linearized_controls_without_angle_wrap_with_candidate_expansion(
1793 xref: &[Vector4<f64>],
1794 xbar: &[Vector4<f64>],
1795 state: &State,
1796 initial_controls: &[Vector2<f64>],
1797 max_iters: usize,
1798 step_candidates: &[f64],
1799 ) -> (Vec<Vector2<f64>>, Vec<GradientTraceEntry>) {
1800 let q = state_cost_weight();
1801 let qf = terminal_cost_weight();
1802 let r = control_cost_weight();
1803 let rd = control_rate_weight();
1804
1805 let mut controls = initial_controls.to_vec();
1806 controls.resize(T, Vector2::zeros());
1807 apply_control_constraints(&mut controls);
1808
1809 let mut previous_cost = f64::INFINITY;
1810 let mut trace = Vec::new();
1811
1812 for iter in 0..max_iters {
1813 let mut x = vec![Vector4::zeros(); T + 1];
1814 let mut a_seq = Vec::with_capacity(T);
1815 let mut b_seq = Vec::with_capacity(T);
1816 x[0] = state.to_vector();
1817
1818 for t in 0..T {
1819 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
1820 x[t + 1] = a * x[t] + b * controls[t] + c;
1821 a_seq.push(a);
1822 b_seq.push(b);
1823 }
1824 let current_cost = compute_cost_without_angle_wrap(&x, xref, &controls);
1825
1826 let mut gradients = [Vector2::zeros(); T];
1827 let mut lambda = (qf * (x[T] - xref[T])) * 2.0;
1828
1829 for t in (0..T).rev() {
1830 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
1831
1832 if t > 0 {
1833 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
1834 }
1835 if t < T - 1 {
1836 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
1837 }
1838
1839 gradients[t] = grad;
1840
1841 let lx = if t == 0 {
1842 Vector4::zeros()
1843 } else {
1844 (q * (x[t] - xref[t])) * 2.0
1845 };
1846 lambda = lx + a_seq[t].transpose() * lambda;
1847 }
1848
1849 let gradient_norm = gradients
1850 .iter()
1851 .map(Vector2::norm_squared)
1852 .sum::<f64>()
1853 .sqrt();
1854
1855 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
1856 trace.push(GradientTraceEntry {
1857 iter,
1858 cost: current_cost,
1859 gradient_norm,
1860 accepted_step: None,
1861 });
1862 break;
1863 }
1864
1865 previous_cost = current_cost;
1866 let current_controls = controls.clone();
1867 let mut accepted_step = None;
1868 let mut best_candidate_controls = None;
1869 let mut best_candidate_cost = current_cost;
1870
1871 for &step in step_candidates {
1872 let mut candidate = current_controls.clone();
1873 for t in 0..T {
1874 candidate[t] -= gradients[t] * step;
1875 }
1876 apply_control_constraints(&mut candidate);
1877
1878 let candidate_x = linearized_rollout_without_angle_wrap(state, xbar, &candidate);
1879 let candidate_cost =
1880 compute_cost_without_angle_wrap(&candidate_x, xref, &candidate);
1881 if candidate_cost < best_candidate_cost {
1882 best_candidate_cost = candidate_cost;
1883 accepted_step = Some(step);
1884 best_candidate_controls = Some(candidate);
1885 }
1886 }
1887
1888 trace.push(GradientTraceEntry {
1889 iter,
1890 cost: current_cost,
1891 gradient_norm,
1892 accepted_step,
1893 });
1894
1895 if let Some(candidate) = best_candidate_controls {
1896 controls = candidate;
1897 } else {
1898 break;
1899 }
1900 }
1901
1902 (controls, trace)
1903 }
1904
1905 fn optimize_linearized_controls_with_candidate_expansion(
1906 xref: &[Vector4<f64>],
1907 xbar: &[Vector4<f64>],
1908 state: &State,
1909 initial_controls: &[Vector2<f64>],
1910 max_iters: usize,
1911 step_candidates: &[f64],
1912 ) -> (Vec<Vector2<f64>>, Vec<GradientTraceEntry>) {
1913 let q = state_cost_weight();
1914 let qf = terminal_cost_weight();
1915 let r = control_cost_weight();
1916 let rd = control_rate_weight();
1917
1918 let mut controls = initial_controls.to_vec();
1919 controls.resize(T, Vector2::zeros());
1920 apply_control_constraints(&mut controls);
1921
1922 let mut previous_cost = f64::INFINITY;
1923 let mut trace = Vec::new();
1924
1925 for iter in 0..max_iters {
1926 let (x, a_seq, b_seq, _) = linearized_rollout(state, xbar, &controls);
1927 let current_cost = compute_cost(&x, xref, &controls);
1928
1929 let mut gradients = [Vector2::zeros(); T];
1930 let mut lambda = (qf * state_error(x[T], xref[T])) * 2.0;
1931
1932 for t in (0..T).rev() {
1933 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
1934
1935 if t > 0 {
1936 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
1937 }
1938 if t < T - 1 {
1939 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
1940 }
1941
1942 gradients[t] = grad;
1943
1944 let lx = if t == 0 {
1945 Vector4::zeros()
1946 } else {
1947 (q * state_error(x[t], xref[t])) * 2.0
1948 };
1949 lambda = lx + a_seq[t].transpose() * lambda;
1950 }
1951
1952 let gradient_norm = gradients
1953 .iter()
1954 .map(Vector2::norm_squared)
1955 .sum::<f64>()
1956 .sqrt();
1957
1958 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
1959 trace.push(GradientTraceEntry {
1960 iter,
1961 cost: current_cost,
1962 gradient_norm,
1963 accepted_step: None,
1964 });
1965 break;
1966 }
1967
1968 previous_cost = current_cost;
1969 let current_controls = controls.clone();
1970 let mut accepted_step = None;
1971 let mut best_candidate_controls = None;
1972 let mut best_candidate_cost = current_cost;
1973
1974 for &step in step_candidates {
1975 let mut candidate = current_controls.clone();
1976 for t in 0..T {
1977 candidate[t] -= gradients[t] * step;
1978 }
1979 apply_control_constraints(&mut candidate);
1980
1981 let (candidate_x, _, _, _) = linearized_rollout(state, xbar, &candidate);
1982 let candidate_cost = compute_cost(&candidate_x, xref, &candidate);
1983 if candidate_cost < best_candidate_cost {
1984 best_candidate_cost = candidate_cost;
1985 accepted_step = Some(step);
1986 best_candidate_controls = Some(candidate);
1987 }
1988 }
1989
1990 trace.push(GradientTraceEntry {
1991 iter,
1992 cost: current_cost,
1993 gradient_norm,
1994 accepted_step,
1995 });
1996
1997 if let Some(candidate) = best_candidate_controls {
1998 controls = candidate;
1999 } else {
2000 break;
2001 }
2002 }
2003
2004 (controls, trace)
2005 }
2006
2007 fn expanded_backtracking_step_candidates() -> Vec<f64> {
2008 let mut steps = vec![1.0, 0.5];
2009 let mut step = LINE_SEARCH_INITIAL_STEP;
2010 for _ in 0..LINE_SEARCH_ITERS {
2011 steps.push(step);
2012 step *= 0.5;
2013 }
2014 steps
2015 }
2016
2017 fn optimize_linearized_controls_with_full_candidate_expansion(
2018 xref: &[Vector4<f64>],
2019 xbar: &[Vector4<f64>],
2020 state: &State,
2021 initial_controls: &[Vector2<f64>],
2022 ) -> Vec<Vector2<f64>> {
2023 optimize_linearized_controls_with_candidate_expansion(
2024 xref,
2025 xbar,
2026 state,
2027 initial_controls,
2028 QP_MAX_ITERS,
2029 &expanded_backtracking_step_candidates(),
2030 )
2031 .0
2032 }
2033
2034 fn iterative_linear_mpc_control_with_test_optimizer<F>(
2035 xref: &[Vector4<f64>],
2036 state: &State,
2037 warm_start: &[Vector2<f64>],
2038 optimize: F,
2039 ) -> MpcResult
2040 where
2041 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>,
2042 {
2043 let mut controls = warm_start.to_vec();
2044 controls.resize(T, Vector2::zeros());
2045 apply_control_constraints(&mut controls);
2046
2047 let mut predicted = predict_motion(*state, &controls);
2048 for _ in 0..MAX_ITER {
2049 let previous_controls = controls.clone();
2050 controls = optimize(xref, &predicted, state, &controls);
2051 predicted = predict_motion(*state, &controls);
2052
2053 let du = controls
2054 .iter()
2055 .zip(previous_controls.iter())
2056 .map(|(current, previous)| {
2057 (current[0] - previous[0]).abs() + (current[1] - previous[1]).abs()
2058 })
2059 .sum::<f64>();
2060
2061 if du <= DU_TH {
2062 break;
2063 }
2064 }
2065
2066 MpcResult {
2067 controls,
2068 predicted,
2069 }
2070 }
2071
2072 fn trace_projection_effect_against_python_reference(
2073 xref: &[Vector4<f64>],
2074 xbar: &[Vector4<f64>],
2075 state: &State,
2076 initial_controls: &[Vector2<f64>],
2077 python_controls: &[Vector2<f64>],
2078 max_iters: usize,
2079 line_search_iters: usize,
2080 ) -> Vec<ProjectionTraceEntry> {
2081 let q = state_cost_weight();
2082 let qf = terminal_cost_weight();
2083 let r = control_cost_weight();
2084 let rd = control_rate_weight();
2085
2086 let mut controls = initial_controls.to_vec();
2087 controls.resize(T, Vector2::zeros());
2088 apply_control_constraints(&mut controls);
2089
2090 let mut previous_cost = f64::INFINITY;
2091 let mut trace = Vec::new();
2092
2093 for iter in 0..max_iters {
2094 let mut x = vec![Vector4::zeros(); T + 1];
2095 let mut a_seq = Vec::with_capacity(T);
2096 let mut b_seq = Vec::with_capacity(T);
2097 x[0] = state.to_vector();
2098
2099 for t in 0..T {
2100 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
2101 x[t + 1] = a * x[t] + b * controls[t] + c;
2102 a_seq.push(a);
2103 b_seq.push(b);
2104 }
2105 let current_cost = compute_cost_without_angle_wrap(&x, xref, &controls);
2106
2107 let mut gradients = [Vector2::zeros(); T];
2108 let mut lambda = (qf * (x[T] - xref[T])) * 2.0;
2109
2110 for t in (0..T).rev() {
2111 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
2112
2113 if t > 0 {
2114 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
2115 }
2116 if t < T - 1 {
2117 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
2118 }
2119
2120 gradients[t] = grad;
2121
2122 let lx = if t == 0 {
2123 Vector4::zeros()
2124 } else {
2125 (q * (x[t] - xref[t])) * 2.0
2126 };
2127 lambda = lx + a_seq[t].transpose() * lambda;
2128 }
2129
2130 let gradient_norm = gradients
2131 .iter()
2132 .map(Vector2::norm_squared)
2133 .sum::<f64>()
2134 .sqrt();
2135
2136 if gradient_norm <= GRAD_TOL || (previous_cost - current_cost).abs() <= COST_TOL {
2137 break;
2138 }
2139
2140 previous_cost = current_cost;
2141 let current_controls = controls.clone();
2142 let mut step = 0.25;
2143 let mut candidates = Vec::new();
2144 let mut improved = false;
2145
2146 for _ in 0..line_search_iters {
2147 let mut raw_candidate = current_controls.clone();
2148 for t in 0..T {
2149 raw_candidate[t] -= gradients[t] * step;
2150 }
2151
2152 let raw_x = linearized_rollout_without_angle_wrap(state, xbar, &raw_candidate);
2153 let raw_cost = compute_cost_without_angle_wrap(&raw_x, xref, &raw_candidate);
2154
2155 let mut projected_candidate = raw_candidate.clone();
2156 apply_control_constraints(&mut projected_candidate);
2157 let projected_x =
2158 linearized_rollout_without_angle_wrap(state, xbar, &projected_candidate);
2159 let projected_cost =
2160 compute_cost_without_angle_wrap(&projected_x, xref, &projected_candidate);
2161
2162 candidates.push(LineSearchProjectionEntry {
2163 step,
2164 raw_cost,
2165 projected_cost,
2166 raw_to_projected: control_sequence_max_diff(
2167 &raw_candidate,
2168 &projected_candidate,
2169 ),
2170 raw_to_python: control_sequence_max_diff(&raw_candidate, python_controls),
2171 projected_to_python: control_sequence_max_diff(
2172 &projected_candidate,
2173 python_controls,
2174 ),
2175 });
2176
2177 if projected_cost < current_cost {
2178 controls = projected_candidate;
2179 improved = true;
2180 break;
2181 }
2182
2183 step *= 0.5;
2184 }
2185
2186 trace.push(ProjectionTraceEntry {
2187 iter,
2188 current_cost,
2189 candidates,
2190 });
2191
2192 if !improved {
2193 break;
2194 }
2195 }
2196
2197 trace
2198 }
2199
2200 fn first_iteration_projection_candidates(
2201 xref: &[Vector4<f64>],
2202 xbar: &[Vector4<f64>],
2203 state: &State,
2204 initial_controls: &[Vector2<f64>],
2205 python_controls: &[Vector2<f64>],
2206 step_sizes: &[f64],
2207 ) -> (f64, Vec<LineSearchProjectionEntry>) {
2208 let q = state_cost_weight();
2209 let qf = terminal_cost_weight();
2210 let r = control_cost_weight();
2211 let rd = control_rate_weight();
2212
2213 let mut controls = initial_controls.to_vec();
2214 controls.resize(T, Vector2::zeros());
2215 apply_control_constraints(&mut controls);
2216
2217 let mut x = vec![Vector4::zeros(); T + 1];
2218 let mut a_seq = Vec::with_capacity(T);
2219 let mut b_seq = Vec::with_capacity(T);
2220 x[0] = state.to_vector();
2221
2222 for t in 0..T {
2223 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
2224 x[t + 1] = a * x[t] + b * controls[t] + c;
2225 a_seq.push(a);
2226 b_seq.push(b);
2227 }
2228
2229 let current_cost = compute_cost_without_angle_wrap(&x, xref, &controls);
2230 let mut gradients = [Vector2::zeros(); T];
2231 let mut lambda = (qf * (x[T] - xref[T])) * 2.0;
2232
2233 for t in (0..T).rev() {
2234 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
2235
2236 if t > 0 {
2237 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
2238 }
2239 if t < T - 1 {
2240 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
2241 }
2242
2243 gradients[t] = grad;
2244
2245 let lx = if t == 0 {
2246 Vector4::zeros()
2247 } else {
2248 (q * (x[t] - xref[t])) * 2.0
2249 };
2250 lambda = lx + a_seq[t].transpose() * lambda;
2251 }
2252
2253 let mut candidates = Vec::new();
2254 for &step in step_sizes {
2255 let mut raw_candidate = controls.clone();
2256 for t in 0..T {
2257 raw_candidate[t] -= gradients[t] * step;
2258 }
2259
2260 let raw_x = linearized_rollout_without_angle_wrap(state, xbar, &raw_candidate);
2261 let raw_cost = compute_cost_without_angle_wrap(&raw_x, xref, &raw_candidate);
2262
2263 let mut projected_candidate = raw_candidate.clone();
2264 apply_control_constraints(&mut projected_candidate);
2265 let projected_x =
2266 linearized_rollout_without_angle_wrap(state, xbar, &projected_candidate);
2267 let projected_cost =
2268 compute_cost_without_angle_wrap(&projected_x, xref, &projected_candidate);
2269
2270 candidates.push(LineSearchProjectionEntry {
2271 step,
2272 raw_cost,
2273 projected_cost,
2274 raw_to_projected: control_sequence_max_diff(&raw_candidate, &projected_candidate),
2275 raw_to_python: control_sequence_max_diff(&raw_candidate, python_controls),
2276 projected_to_python: control_sequence_max_diff(
2277 &projected_candidate,
2278 python_controls,
2279 ),
2280 });
2281 }
2282
2283 (current_cost, candidates)
2284 }
2285
2286 fn state_from_vector4(state: &Vector4<f64>) -> State {
2287 State::new(state[0], state[1], state[2], state[3])
2288 }
2289
2290 fn inject_state_components(
2291 base: &Vector4<f64>,
2292 injected: &Vector4<f64>,
2293 component_indices: &[usize],
2294 ) -> State {
2295 let mut hybrid = *base;
2296 for index in component_indices {
2297 hybrid[*index] = injected[*index];
2298 }
2299 state_from_vector4(&hybrid)
2300 }
2301
2302 fn measure_switch_back_state_injection(indices: &[usize]) -> StateInjectionMetrics {
2303 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
2304 330,
2305 optimize_linearized_controls_original,
2306 );
2307 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
2308 330,
2309 optimize_linearized_controls_with_full_candidate_expansion,
2310 );
2311 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
2312 330,
2313 optimize_linearized_controls_original,
2314 );
2315 let expanded_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
2316 330,
2317 optimize_linearized_controls_with_full_candidate_expansion,
2318 );
2319
2320 let mut summary = StateInjectionMetrics::default();
2321 for metrics in measure_switch_back_state_injection_by_step_with_context(
2322 indices,
2323 &[325usize, 326, 327, 328, 329],
2324 &default_trace,
2325 &expanded_trace,
2326 &default_warm_start,
2327 &expanded_warm_start,
2328 )
2329 .into_values()
2330 {
2331 summary.default_same_control0 += metrics.default_same_control0;
2332 summary.default_opposite_control0 += metrics.default_opposite_control0;
2333 summary.default_same_pred_y += metrics.default_same_pred_y;
2334 summary.default_opposite_pred_y += metrics.default_opposite_pred_y;
2335 summary.default_same_pred_v += metrics.default_same_pred_v;
2336 summary.default_opposite_pred_v += metrics.default_opposite_pred_v;
2337 summary.expanded_same_control0 += metrics.expanded_same_control0;
2338 summary.expanded_opposite_control0 += metrics.expanded_opposite_control0;
2339 summary.expanded_same_pred_y += metrics.expanded_same_pred_y;
2340 summary.expanded_opposite_pred_y += metrics.expanded_opposite_pred_y;
2341 summary.expanded_same_pred_v += metrics.expanded_same_pred_v;
2342 summary.expanded_opposite_pred_v += metrics.expanded_opposite_pred_v;
2343 }
2344
2345 summary
2346 }
2347
2348 fn measure_switch_back_state_injection_by_step_with_context(
2349 indices: &[usize],
2350 steps: &[usize],
2351 default_trace: &[SwitchBackDetailedRow],
2352 expanded_trace: &[SwitchBackDetailedRow],
2353 default_warm_start: &SwitchBackWarmStartTrace,
2354 expanded_warm_start: &SwitchBackWarmStartTrace,
2355 ) -> BTreeMap<usize, StateInjectionMetrics> {
2356 let mut by_step = BTreeMap::new();
2357
2358 for step in steps {
2359 let mut metrics = StateInjectionMetrics::default();
2360 let default_row = detailed_row_at_step(default_trace, *step);
2361 let expanded_row = detailed_row_at_step(expanded_trace, *step);
2362 let default_injected = iterative_linear_mpc_control_with_test_optimizer(
2363 &default_row.xref,
2364 &inject_state_components(&default_row.state, &expanded_row.state, indices),
2365 default_warm_start.get(step).unwrap(),
2366 optimize_linearized_controls_original,
2367 );
2368 let expanded_injected = iterative_linear_mpc_control_with_test_optimizer(
2369 &expanded_row.xref,
2370 &inject_state_components(&expanded_row.state, &default_row.state, indices),
2371 expanded_warm_start.get(step).unwrap(),
2372 optimize_linearized_controls_with_full_candidate_expansion,
2373 );
2374
2375 let default_baseline_controls = default_row
2376 .controls
2377 .iter()
2378 .map(|vec| [vec[0], vec[1]])
2379 .collect::<Vec<_>>();
2380 let expanded_baseline_controls = expanded_row
2381 .controls
2382 .iter()
2383 .map(|vec| [vec[0], vec[1]])
2384 .collect::<Vec<_>>();
2385 let default_baseline_predicted = default_row
2386 .predicted
2387 .iter()
2388 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
2389 .collect::<Vec<_>>();
2390 let expanded_baseline_predicted = expanded_row
2391 .predicted
2392 .iter()
2393 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
2394 .collect::<Vec<_>>();
2395
2396 let default_to_default_control = summarize_vec2_sequence_diff(
2397 &default_injected.controls,
2398 &default_baseline_controls,
2399 );
2400 let default_to_expanded_control = summarize_vec2_sequence_diff(
2401 &default_injected.controls,
2402 &expanded_baseline_controls,
2403 );
2404 let default_to_default_predicted = summarize_vec4_sequence_diff(
2405 &default_injected.predicted,
2406 &default_baseline_predicted,
2407 );
2408 let default_to_expanded_predicted = summarize_vec4_sequence_diff(
2409 &default_injected.predicted,
2410 &expanded_baseline_predicted,
2411 );
2412
2413 let expanded_to_expanded_control = summarize_vec2_sequence_diff(
2414 &expanded_injected.controls,
2415 &expanded_baseline_controls,
2416 );
2417 let expanded_to_default_control = summarize_vec2_sequence_diff(
2418 &expanded_injected.controls,
2419 &default_baseline_controls,
2420 );
2421 let expanded_to_expanded_predicted = summarize_vec4_sequence_diff(
2422 &expanded_injected.predicted,
2423 &expanded_baseline_predicted,
2424 );
2425 let expanded_to_default_predicted = summarize_vec4_sequence_diff(
2426 &expanded_injected.predicted,
2427 &default_baseline_predicted,
2428 );
2429
2430 metrics.default_same_control0 = default_to_default_control[0];
2431 metrics.default_opposite_control0 = default_to_expanded_control[0];
2432 metrics.default_same_pred_y = default_to_default_predicted[1];
2433 metrics.default_opposite_pred_y = default_to_expanded_predicted[1];
2434 metrics.default_same_pred_v = default_to_default_predicted[2];
2435 metrics.default_opposite_pred_v = default_to_expanded_predicted[2];
2436
2437 metrics.expanded_same_control0 = expanded_to_expanded_control[0];
2438 metrics.expanded_opposite_control0 = expanded_to_default_control[0];
2439 metrics.expanded_same_pred_y = expanded_to_expanded_predicted[1];
2440 metrics.expanded_opposite_pred_y = expanded_to_default_predicted[1];
2441 metrics.expanded_same_pred_v = expanded_to_expanded_predicted[2];
2442 metrics.expanded_opposite_pred_v = expanded_to_default_predicted[2];
2443
2444 by_step.insert(*step, metrics);
2445 }
2446
2447 by_step
2448 }
2449
2450 fn measure_switch_back_state_injection_by_step(
2451 indices: &[usize],
2452 steps: &[usize],
2453 ) -> BTreeMap<usize, StateInjectionMetrics> {
2454 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
2455 330,
2456 optimize_linearized_controls_original,
2457 );
2458 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
2459 330,
2460 optimize_linearized_controls_with_full_candidate_expansion,
2461 );
2462 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
2463 330,
2464 optimize_linearized_controls_original,
2465 );
2466 let expanded_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
2467 330,
2468 optimize_linearized_controls_with_full_candidate_expansion,
2469 );
2470
2471 measure_switch_back_state_injection_by_step_with_context(
2472 indices,
2473 steps,
2474 &default_trace,
2475 &expanded_trace,
2476 &default_warm_start,
2477 &expanded_warm_start,
2478 )
2479 }
2480
2481 fn state_injection_metrics_at_step(
2482 metrics: &BTreeMap<usize, StateInjectionMetrics>,
2483 step: usize,
2484 ) -> &StateInjectionMetrics {
2485 metrics
2486 .get(&step)
2487 .unwrap_or_else(|| panic!("missing state-injection metrics for step {step}"))
2488 }
2489
2490 fn advance_switch_back_context(steps: usize) -> SwitchBackContext {
2491 let (cx, cy, cyaw) = generate_switch_back_course();
2492 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2493
2494 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2495 if state.yaw - cyaw[0] >= PI {
2496 state.yaw -= 2.0 * PI;
2497 } else if state.yaw - cyaw[0] <= -PI {
2498 state.yaw += 2.0 * PI;
2499 }
2500
2501 let mut target_index = 0;
2502 let mut warm_start = vec![Vector2::zeros(); T];
2503
2504 for _ in 0..steps {
2505 let (xref, new_index) = calc_ref_trajectory(
2506 &state,
2507 &cx,
2508 &cy,
2509 &cyaw,
2510 &speed_profile,
2511 UPSTREAM_SWITCH_BACK_TICK,
2512 target_index,
2513 );
2514 target_index = new_index;
2515
2516 let mpc_result = iterative_linear_mpc_control(&xref, &state, &warm_start);
2517 state.update(mpc_result.controls[0][0], mpc_result.controls[0][1]);
2518
2519 warm_start = next_warm_start(&mpc_result.controls);
2520 }
2521
2522 SwitchBackContext {
2523 cx,
2524 cy,
2525 cyaw,
2526 speed_profile,
2527 state,
2528 target_index,
2529 warm_start,
2530 }
2531 }
2532
2533 fn collect_switch_back_prefix_trace(steps: usize) -> Vec<SwitchBackTraceRow> {
2534 let (cx, cy, cyaw) = generate_switch_back_course();
2535 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2536
2537 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2538 if state.yaw - cyaw[0] >= PI {
2539 state.yaw -= 2.0 * PI;
2540 } else if state.yaw - cyaw[0] <= -PI {
2541 state.yaw += 2.0 * PI;
2542 }
2543
2544 let mut target_index = 0;
2545 let mut warm_start = vec![Vector2::zeros(); T];
2546 let mut trace = Vec::with_capacity(steps);
2547
2548 for step in 0..steps {
2549 let (xref, new_index) = calc_ref_trajectory(
2550 &state,
2551 &cx,
2552 &cy,
2553 &cyaw,
2554 &speed_profile,
2555 UPSTREAM_SWITCH_BACK_TICK,
2556 target_index,
2557 );
2558 target_index = new_index;
2559
2560 let result = iterative_linear_mpc_control(&xref, &state, &warm_start);
2561 trace.push(SwitchBackTraceRow {
2562 step,
2563 target_index,
2564 state: state.to_vector(),
2565 xref_head: xref[0],
2566 xref_tail: *xref.last().unwrap(),
2567 control: result.controls[0],
2568 predicted_tail: *result.predicted.last().unwrap(),
2569 });
2570
2571 state.update(result.controls[0][0], result.controls[0][1]);
2572 warm_start = next_warm_start(&result.controls);
2573 }
2574
2575 trace
2576 }
2577
2578 fn collect_switch_back_detailed_trace_with_test_optimizer<F>(
2579 steps: usize,
2580 optimize: F,
2581 ) -> Vec<SwitchBackDetailedRow>
2582 where
2583 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2584 + Copy,
2585 {
2586 let (cx, cy, cyaw) = generate_switch_back_course();
2587 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2588
2589 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2590 if state.yaw - cyaw[0] >= PI {
2591 state.yaw -= 2.0 * PI;
2592 } else if state.yaw - cyaw[0] <= -PI {
2593 state.yaw += 2.0 * PI;
2594 }
2595
2596 let mut target_index = 0;
2597 let mut warm_start = vec![Vector2::zeros(); T];
2598 let mut trace = Vec::with_capacity(steps);
2599
2600 for step in 0..steps {
2601 let (xref, new_index) = calc_ref_trajectory(
2602 &state,
2603 &cx,
2604 &cy,
2605 &cyaw,
2606 &speed_profile,
2607 UPSTREAM_SWITCH_BACK_TICK,
2608 target_index,
2609 );
2610 target_index = new_index;
2611
2612 let result = iterative_linear_mpc_control_with_test_optimizer(
2613 &xref,
2614 &state,
2615 &warm_start,
2616 optimize,
2617 );
2618 trace.push(SwitchBackDetailedRow {
2619 step,
2620 target_index,
2621 state: state.to_vector(),
2622 xref: xref.clone(),
2623 controls: result.controls.clone(),
2624 predicted: result.predicted.clone(),
2625 });
2626
2627 state.update(result.controls[0][0], result.controls[0][1]);
2628 warm_start = next_warm_start(&result.controls);
2629 }
2630
2631 trace
2632 }
2633
2634 fn collect_switch_back_planning_snapshots_with_test_optimizer<F>(
2635 steps: usize,
2636 checkpoints: &[usize],
2637 optimize: F,
2638 ) -> Vec<SwitchBackPlanningSnapshot>
2639 where
2640 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2641 + Copy,
2642 {
2643 let (cx, cy, cyaw) = generate_switch_back_course();
2644 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2645
2646 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2647 if state.yaw - cyaw[0] >= PI {
2648 state.yaw -= 2.0 * PI;
2649 } else if state.yaw - cyaw[0] <= -PI {
2650 state.yaw += 2.0 * PI;
2651 }
2652
2653 let mut target_index = 0;
2654 let mut warm_start = vec![Vector2::zeros(); T];
2655 let mut trace = Vec::new();
2656
2657 for step in 0..steps {
2658 let (xref, new_index) = calc_ref_trajectory(
2659 &state,
2660 &cx,
2661 &cy,
2662 &cyaw,
2663 &speed_profile,
2664 UPSTREAM_SWITCH_BACK_TICK,
2665 target_index,
2666 );
2667 target_index = new_index;
2668
2669 if checkpoints.contains(&step) {
2670 trace.push(SwitchBackPlanningSnapshot {
2671 step,
2672 target_index,
2673 state: state.to_vector(),
2674 xref_head: xref[0],
2675 warm_start_head: warm_start.first().copied().unwrap_or_else(Vector2::zeros),
2676 });
2677 }
2678
2679 let result = iterative_linear_mpc_control_with_test_optimizer(
2680 &xref,
2681 &state,
2682 &warm_start,
2683 optimize,
2684 );
2685 state.update(result.controls[0][0], result.controls[0][1]);
2686 warm_start = next_warm_start(&result.controls);
2687 }
2688
2689 trace
2690 }
2691
2692 fn collect_switch_back_warm_start_trace_with_test_optimizer<F>(
2693 steps: usize,
2694 optimize: F,
2695 ) -> SwitchBackWarmStartTrace
2696 where
2697 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2698 + Copy,
2699 {
2700 let (cx, cy, cyaw) = generate_switch_back_course();
2701 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2702
2703 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2704 if state.yaw - cyaw[0] >= PI {
2705 state.yaw -= 2.0 * PI;
2706 } else if state.yaw - cyaw[0] <= -PI {
2707 state.yaw += 2.0 * PI;
2708 }
2709
2710 let mut target_index = 0;
2711 let mut warm_start = vec![Vector2::zeros(); T];
2712 let mut trace = SwitchBackWarmStartTrace::new();
2713
2714 for step in 0..steps {
2715 let (xref, new_index) = calc_ref_trajectory(
2716 &state,
2717 &cx,
2718 &cy,
2719 &cyaw,
2720 &speed_profile,
2721 UPSTREAM_SWITCH_BACK_TICK,
2722 target_index,
2723 );
2724 target_index = new_index;
2725 trace.insert(step, warm_start.clone());
2726
2727 let result = iterative_linear_mpc_control_with_test_optimizer(
2728 &xref,
2729 &state,
2730 &warm_start,
2731 optimize,
2732 );
2733 state.update(result.controls[0][0], result.controls[0][1]);
2734 warm_start = next_warm_start(&result.controls);
2735 }
2736
2737 trace
2738 }
2739
2740 fn collect_switch_back_detailed_trace(steps: usize) -> Vec<SwitchBackDetailedRow> {
2741 collect_switch_back_detailed_trace_with_test_optimizer(steps, optimize_linearized_controls)
2742 }
2743
2744 fn run_switch_back_simulation_with_test_optimizer<F>(
2745 max_sim_steps: usize,
2746 optimize: F,
2747 ) -> SimulationResult
2748 where
2749 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2750 + Copy,
2751 {
2752 let (cx, cy, cyaw) = generate_switch_back_course();
2753 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2754
2755 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2756 if state.yaw - cyaw[0] >= PI {
2757 state.yaw -= 2.0 * PI;
2758 } else if state.yaw - cyaw[0] <= -PI {
2759 state.yaw += 2.0 * PI;
2760 }
2761
2762 let mut target_index = 0;
2763 let final_index = cx.len() - 1;
2764 let goal = (cx[final_index], cy[final_index]);
2765 let mut warm_start = vec![Vector2::zeros(); T];
2766
2767 let mut hist_x = vec![state.x];
2768 let mut hist_y = vec![state.y];
2769 let mut predicted = vec![state.to_vector()];
2770 let mut reached_goal = false;
2771
2772 for _ in 0..max_sim_steps {
2773 let (xref, new_index) = calc_ref_trajectory(
2774 &state,
2775 &cx,
2776 &cy,
2777 &cyaw,
2778 &speed_profile,
2779 UPSTREAM_SWITCH_BACK_TICK,
2780 target_index,
2781 );
2782 target_index = new_index;
2783
2784 let mpc_result = iterative_linear_mpc_control_with_test_optimizer(
2785 &xref,
2786 &state,
2787 &warm_start,
2788 optimize,
2789 );
2790 let accel = mpc_result.controls[0][0];
2791 let steer = mpc_result.controls[0][1];
2792
2793 state.update(accel, steer);
2794 hist_x.push(state.x);
2795 hist_y.push(state.y);
2796 predicted = mpc_result.predicted;
2797
2798 warm_start = next_warm_start(&mpc_result.controls);
2799
2800 if check_goal(&state, goal, target_index, final_index) {
2801 reached_goal = true;
2802 break;
2803 }
2804 }
2805
2806 SimulationResult {
2807 cx,
2808 cy,
2809 hist_x,
2810 hist_y,
2811 predicted_x: predicted.iter().map(|p| p[0]).collect(),
2812 predicted_y: predicted.iter().map(|p| p[1]).collect(),
2813 goal,
2814 reached_goal,
2815 target_index,
2816 final_index,
2817 final_state: state,
2818 }
2819 }
2820
2821 fn collect_switch_back_closed_loop_checkpoints_with_test_optimizer<F>(
2822 max_sim_steps: usize,
2823 checkpoints: &[usize],
2824 optimize: F,
2825 ) -> Vec<SwitchBackClosedLoopCheckpoint>
2826 where
2827 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2828 + Copy,
2829 {
2830 let (cx, cy, cyaw) = generate_switch_back_course();
2831 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2832
2833 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2834 if state.yaw - cyaw[0] >= PI {
2835 state.yaw -= 2.0 * PI;
2836 } else if state.yaw - cyaw[0] <= -PI {
2837 state.yaw += 2.0 * PI;
2838 }
2839
2840 let mut target_index = 0;
2841 let final_index = cx.len() - 1;
2842 let goal = (cx[final_index], cy[final_index]);
2843 let mut warm_start = vec![Vector2::zeros(); T];
2844 let mut recorded = Vec::new();
2845
2846 for step in 0..max_sim_steps {
2847 let (xref, new_index) = calc_ref_trajectory(
2848 &state,
2849 &cx,
2850 &cy,
2851 &cyaw,
2852 &speed_profile,
2853 UPSTREAM_SWITCH_BACK_TICK,
2854 target_index,
2855 );
2856 target_index = new_index;
2857
2858 let mpc_result = iterative_linear_mpc_control_with_test_optimizer(
2859 &xref,
2860 &state,
2861 &warm_start,
2862 optimize,
2863 );
2864 let accel = mpc_result.controls[0][0];
2865 let steer = mpc_result.controls[0][1];
2866
2867 state.update(accel, steer);
2868 warm_start = next_warm_start(&mpc_result.controls);
2869
2870 if checkpoints.contains(&(step + 1)) {
2871 let goal_distance =
2872 ((state.x - goal.0).powi(2) + (state.y - goal.1).powi(2)).sqrt();
2873 recorded.push(SwitchBackClosedLoopCheckpoint {
2874 step: step + 1,
2875 target_index,
2876 goal_distance,
2877 state: state.to_vector(),
2878 });
2879 }
2880
2881 if check_goal(&state, goal, target_index, final_index) {
2882 break;
2883 }
2884 }
2885
2886 recorded
2887 }
2888
2889 fn collect_switch_back_goal_condition_snapshots_with_test_optimizer<F>(
2890 max_sim_steps: usize,
2891 checkpoints: &[usize],
2892 optimize: F,
2893 ) -> Vec<SwitchBackGoalConditionSnapshot>
2894 where
2895 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2896 + Copy,
2897 {
2898 let (cx, cy, cyaw) = generate_switch_back_course();
2899 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2900
2901 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2902 if state.yaw - cyaw[0] >= PI {
2903 state.yaw -= 2.0 * PI;
2904 } else if state.yaw - cyaw[0] <= -PI {
2905 state.yaw += 2.0 * PI;
2906 }
2907
2908 let mut target_index = 0;
2909 let final_index = cx.len() - 1;
2910 let goal = (cx[final_index], cy[final_index]);
2911 let mut warm_start = vec![Vector2::zeros(); T];
2912 let mut recorded = Vec::new();
2913
2914 for step in 0..max_sim_steps {
2915 let (xref, new_index) = calc_ref_trajectory(
2916 &state,
2917 &cx,
2918 &cy,
2919 &cyaw,
2920 &speed_profile,
2921 UPSTREAM_SWITCH_BACK_TICK,
2922 target_index,
2923 );
2924 target_index = new_index;
2925
2926 let mpc_result = iterative_linear_mpc_control_with_test_optimizer(
2927 &xref,
2928 &state,
2929 &warm_start,
2930 optimize,
2931 );
2932 let accel = mpc_result.controls[0][0];
2933 let steer = mpc_result.controls[0][1];
2934
2935 state.update(accel, steer);
2936 warm_start = next_warm_start(&mpc_result.controls);
2937
2938 let dx = state.x - goal.0;
2939 let dy = state.y - goal.1;
2940 let goal_distance = (dx * dx + dy * dy).sqrt();
2941 let heading_error = angle_diff(state.yaw, cyaw[target_index]);
2942 let motion_curvature = steer.tan() / WB;
2943 let near_goal = goal_distance <= GOAL_DIS;
2944 let near_end = final_index.abs_diff(target_index) < 5;
2945 let stopped = state.v.abs() <= STOP_SPEED;
2946 let reached_goal = near_goal && near_end && stopped;
2947
2948 if checkpoints.contains(&(step + 1)) {
2949 recorded.push(SwitchBackGoalConditionSnapshot {
2950 step: step + 1,
2951 target_index,
2952 goal_distance,
2953 speed: state.v,
2954 speed_abs: state.v.abs(),
2955 heading_error,
2956 motion_curvature,
2957 near_goal,
2958 near_end,
2959 stopped,
2960 reached_goal,
2961 });
2962 }
2963
2964 if reached_goal {
2965 break;
2966 }
2967 }
2968
2969 recorded
2970 }
2971
2972 fn collect_switch_back_goal_control_snapshots_with_test_optimizer<F>(
2973 max_sim_steps: usize,
2974 checkpoints: &[usize],
2975 optimize: F,
2976 ) -> Vec<SwitchBackGoalControlSnapshot>
2977 where
2978 F: Fn(&[Vector4<f64>], &[Vector4<f64>], &State, &[Vector2<f64>]) -> Vec<Vector2<f64>>
2979 + Copy,
2980 {
2981 let (cx, cy, cyaw) = generate_switch_back_course();
2982 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
2983
2984 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
2985 if state.yaw - cyaw[0] >= PI {
2986 state.yaw -= 2.0 * PI;
2987 } else if state.yaw - cyaw[0] <= -PI {
2988 state.yaw += 2.0 * PI;
2989 }
2990
2991 let mut target_index = 0;
2992 let final_index = cx.len() - 1;
2993 let goal = (cx[final_index], cy[final_index]);
2994 let mut warm_start = vec![Vector2::zeros(); T];
2995 let mut recorded = Vec::new();
2996
2997 for step in 0..max_sim_steps {
2998 let (xref, new_index) = calc_ref_trajectory(
2999 &state,
3000 &cx,
3001 &cy,
3002 &cyaw,
3003 &speed_profile,
3004 UPSTREAM_SWITCH_BACK_TICK,
3005 target_index,
3006 );
3007 target_index = new_index;
3008
3009 let mpc_result = iterative_linear_mpc_control_with_test_optimizer(
3010 &xref,
3011 &state,
3012 &warm_start,
3013 optimize,
3014 );
3015 let accel = mpc_result.controls[0][0];
3016 let steer = mpc_result.controls[0][1];
3017
3018 state.update(accel, steer);
3019 warm_start = next_warm_start(&mpc_result.controls);
3020
3021 let dx = state.x - goal.0;
3022 let dy = state.y - goal.1;
3023 let goal_distance = (dx * dx + dy * dy).sqrt();
3024 let heading_error = angle_diff(state.yaw, cyaw[target_index]);
3025 let motion_curvature = steer.tan() / WB;
3026 let course_curvature = course_curvature_at_index(&cx, &cy, &cyaw, target_index);
3027 let near_goal = goal_distance <= GOAL_DIS;
3028 let near_end = final_index.abs_diff(target_index) < 5;
3029 let stopped = state.v.abs() <= STOP_SPEED;
3030 let reached_goal = near_goal && near_end && stopped;
3031
3032 if checkpoints.contains(&(step + 1)) {
3033 recorded.push(SwitchBackGoalControlSnapshot {
3034 step: step + 1,
3035 target_index,
3036 goal_distance,
3037 speed: state.v,
3038 accel,
3039 steer,
3040 heading_error,
3041 motion_curvature,
3042 course_curvature,
3043 near_goal,
3044 near_end,
3045 stopped,
3046 reached_goal,
3047 });
3048 }
3049
3050 if reached_goal {
3051 break;
3052 }
3053 }
3054
3055 recorded
3056 }
3057
3058 fn goal_distance_delta(
3059 default: &SwitchBackClosedLoopCheckpoint,
3060 expanded: &SwitchBackClosedLoopCheckpoint,
3061 ) -> f64 {
3062 default.goal_distance - expanded.goal_distance
3063 }
3064
3065 fn checkpoint_at_step(
3066 checkpoints: &[SwitchBackClosedLoopCheckpoint],
3067 step: usize,
3068 ) -> &SwitchBackClosedLoopCheckpoint {
3069 checkpoints
3070 .iter()
3071 .find(|checkpoint| checkpoint.step == step)
3072 .unwrap_or_else(|| panic!("missing checkpoint for step {}", step))
3073 }
3074
3075 fn goal_distance_slope(
3076 start: &SwitchBackClosedLoopCheckpoint,
3077 end: &SwitchBackClosedLoopCheckpoint,
3078 ) -> f64 {
3079 let span = (end.step - start.step) as f64;
3080 (start.goal_distance - end.goal_distance) / span
3081 }
3082
3083 fn target_index_increment(
3084 start: &SwitchBackClosedLoopCheckpoint,
3085 end: &SwitchBackClosedLoopCheckpoint,
3086 ) -> isize {
3087 end.target_index as isize - start.target_index as isize
3088 }
3089
3090 fn checkpoint_lateral_error(
3091 checkpoint: &SwitchBackClosedLoopCheckpoint,
3092 cx: &[f64],
3093 cy: &[f64],
3094 cyaw: &[f64],
3095 ) -> f64 {
3096 let index = checkpoint.target_index.min(cx.len().saturating_sub(1));
3097 let dx = checkpoint.state[0] - cx[index];
3098 let dy = checkpoint.state[1] - cy[index];
3099 -dx * cyaw[index].sin() + dy * cyaw[index].cos()
3100 }
3101
3102 fn checkpoint_heading_error(checkpoint: &SwitchBackClosedLoopCheckpoint, cyaw: &[f64]) -> f64 {
3103 let index = checkpoint.target_index.min(cyaw.len().saturating_sub(1));
3104 angle_diff(checkpoint.state[3], cyaw[index])
3105 }
3106
3107 fn course_curvature_at_index(cx: &[f64], cy: &[f64], cyaw: &[f64], index: usize) -> f64 {
3108 if cx.len() < 3 || cy.len() < 3 || cyaw.len() < 3 {
3109 return 0.0;
3110 }
3111
3112 let i = index.clamp(1, cx.len().saturating_sub(2));
3113 let ds_prev = ((cx[i] - cx[i - 1]).powi(2) + (cy[i] - cy[i - 1]).powi(2))
3114 .sqrt()
3115 .max(f64::EPSILON);
3116 let ds_next = ((cx[i + 1] - cx[i]).powi(2) + (cy[i + 1] - cy[i]).powi(2))
3117 .sqrt()
3118 .max(f64::EPSILON);
3119
3120 angle_diff(cyaw[i + 1], cyaw[i - 1]) / (ds_prev + ds_next)
3121 }
3122
3123 fn scalar_slope(start: f64, end: f64, steps: usize) -> f64 {
3124 (end - start) / steps as f64
3125 }
3126
3127 fn goal_snapshot_at_step(
3128 snapshots: &[SwitchBackGoalConditionSnapshot],
3129 step: usize,
3130 ) -> &SwitchBackGoalConditionSnapshot {
3131 snapshots
3132 .iter()
3133 .find(|snapshot| snapshot.step == step)
3134 .unwrap_or_else(|| panic!("missing goal-condition snapshot for step {}", step))
3135 }
3136
3137 fn first_goal_condition_step<F>(
3138 snapshots: &[SwitchBackGoalConditionSnapshot],
3139 predicate: F,
3140 ) -> Option<usize>
3141 where
3142 F: Fn(&SwitchBackGoalConditionSnapshot) -> bool,
3143 {
3144 snapshots
3145 .iter()
3146 .find(|snapshot| predicate(snapshot))
3147 .map(|s| s.step)
3148 }
3149
3150 fn goal_control_snapshot_at_step(
3151 snapshots: &[SwitchBackGoalControlSnapshot],
3152 step: usize,
3153 ) -> &SwitchBackGoalControlSnapshot {
3154 snapshots
3155 .iter()
3156 .find(|snapshot| snapshot.step == step)
3157 .unwrap_or_else(|| panic!("missing goal-control snapshot for step {}", step))
3158 }
3159
3160 fn first_goal_control_condition_step<F>(
3161 snapshots: &[SwitchBackGoalControlSnapshot],
3162 predicate: F,
3163 ) -> Option<usize>
3164 where
3165 F: Fn(&SwitchBackGoalControlSnapshot) -> bool,
3166 {
3167 snapshots
3168 .iter()
3169 .find(|snapshot| predicate(snapshot))
3170 .map(|snapshot| snapshot.step)
3171 }
3172
3173 fn detailed_row_at_step(rows: &[SwitchBackDetailedRow], step: usize) -> &SwitchBackDetailedRow {
3174 rows.iter()
3175 .find(|row| row.step == step)
3176 .unwrap_or_else(|| panic!("missing detailed row for step {}", step))
3177 }
3178
3179 fn planning_snapshot_at_step(
3180 snapshots: &[SwitchBackPlanningSnapshot],
3181 step: usize,
3182 ) -> &SwitchBackPlanningSnapshot {
3183 snapshots
3184 .iter()
3185 .find(|snapshot| snapshot.step == step)
3186 .unwrap_or_else(|| panic!("missing planning snapshot for step {}", step))
3187 }
3188
3189 fn assert_planning_snapshot_phase_alignment(
3190 label: &str,
3191 default: &SwitchBackPlanningSnapshot,
3192 expanded: &SwitchBackPlanningSnapshot,
3193 ) {
3194 assert_eq!(
3195 default.target_index, expanded.target_index,
3196 "{} target-index alignment moved unexpectedly: default={:?} expanded={:?}",
3197 label, default, expanded
3198 );
3199 assert_diff_bounds(
3200 &format!("{label}_state"),
3201 trace_state_diff(&default.state, &expanded.state),
3202 [2e-4, 2e-4, 1e-4, 1e-4],
3203 );
3204 assert_diff_bounds(
3205 &format!("{label}_xref_head"),
3206 trace_state_diff(&default.xref_head, &expanded.xref_head),
3207 [1e-10; 4],
3208 );
3209 assert_diff_bounds(
3210 &format!("{label}_warm_start"),
3211 trace_control_diff(&default.warm_start_head, &expanded.warm_start_head),
3212 [1e-10, 3e-4],
3213 );
3214 }
3215
3216 fn assert_goal_control_alignment(
3217 default: &SwitchBackGoalControlSnapshot,
3218 expanded: &SwitchBackGoalControlSnapshot,
3219 ) {
3220 assert_eq!(default.target_index, expanded.target_index);
3221 assert_eq!(default.near_goal, expanded.near_goal);
3222 assert_eq!(default.near_end, expanded.near_end);
3223 assert_eq!(default.stopped, expanded.stopped);
3224 assert_eq!(default.reached_goal, expanded.reached_goal);
3225 assert!(
3226 (default.goal_distance - expanded.goal_distance).abs() <= 3.0e-4,
3227 "goal distance alignment moved unexpectedly: default={:?} expanded={:?}",
3228 default,
3229 expanded
3230 );
3231 assert!(
3232 (default.speed - expanded.speed).abs() <= 3.0e-4,
3233 "speed alignment moved unexpectedly: default={:?} expanded={:?}",
3234 default,
3235 expanded
3236 );
3237 assert!(
3238 (default.accel - expanded.accel).abs() <= 2.0e-3,
3239 "accel alignment moved unexpectedly: default={:?} expanded={:?}",
3240 default,
3241 expanded
3242 );
3243 assert!(
3244 (default.steer - expanded.steer).abs() <= 1.0e-4,
3245 "steer alignment moved unexpectedly: default={:?} expanded={:?}",
3246 default,
3247 expanded
3248 );
3249 assert!(
3250 (default.heading_error - expanded.heading_error).abs() <= 5.0e-5,
3251 "heading-error alignment moved unexpectedly: default={:?} expanded={:?}",
3252 default,
3253 expanded
3254 );
3255 assert!(
3256 (default.motion_curvature - expanded.motion_curvature).abs() <= 5.0e-5,
3257 "motion-curvature alignment moved unexpectedly: default={:?} expanded={:?}",
3258 default,
3259 expanded
3260 );
3261 assert!(
3262 (default.course_curvature - expanded.course_curvature).abs() <= 1.0e-12,
3263 "course-curvature alignment moved unexpectedly: default={:?} expanded={:?}",
3264 default,
3265 expanded
3266 );
3267 }
3268
3269 fn assert_detailed_phase_alignment(
3270 label: &str,
3271 default: &SwitchBackDetailedRow,
3272 expanded: &SwitchBackDetailedRow,
3273 ) {
3274 assert_eq!(
3275 default.target_index, expanded.target_index,
3276 "{} target-index alignment moved unexpectedly: default={:?} expanded={:?}",
3277 label, default, expanded
3278 );
3279
3280 let expanded_xref_rows = expanded
3281 .xref
3282 .iter()
3283 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
3284 .collect::<Vec<_>>();
3285 let expanded_control_rows = expanded
3286 .controls
3287 .iter()
3288 .map(|vec| [vec[0], vec[1]])
3289 .collect::<Vec<_>>();
3290 let expanded_predicted_rows = expanded
3291 .predicted
3292 .iter()
3293 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
3294 .collect::<Vec<_>>();
3295
3296 assert_diff_bounds(
3297 &format!("{label}_state"),
3298 trace_state_diff(&default.state, &expanded.state),
3299 [5e-4, 5e-4, 5e-4, 2e-4],
3300 );
3301 assert_diff_bounds(
3302 &format!("{label}_xref"),
3303 summarize_vec4_sequence_diff(&default.xref, &expanded_xref_rows),
3304 [1e-10; 4],
3305 );
3306 assert_diff_bounds(
3307 &format!("{label}_controls"),
3308 summarize_vec2_sequence_diff(&default.controls, &expanded_control_rows),
3309 [2e-3, 2e-4],
3310 );
3311 assert_diff_bounds(
3312 &format!("{label}_predicted"),
3313 summarize_vec4_sequence_diff(&default.predicted, &expanded_predicted_rows),
3314 [7e-4, 7e-4, 7e-4, 2e-4],
3315 );
3316 }
3317
3318 #[test]
3319 fn test_speed_profile_stops_at_goal() {
3320 let cx = vec![0.0, 1.0, 2.0];
3321 let cy = vec![0.0, 0.0, 0.0];
3322 let cyaw = vec![0.0, 0.0, 0.0];
3323 let speed = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
3324
3325 assert_eq!(speed.len(), 3);
3326 assert_eq!(speed[2], 0.0);
3327 assert!(speed[0] > 0.0);
3328 }
3329
3330 #[test]
3331 fn test_iterative_linear_mpc_matches_reference_offset_state() {
3332 let xref = vec![
3335 Vector4::new(0.0, 0.0, TARGET_SPEED, 0.0),
3336 Vector4::new(0.5, 0.0, TARGET_SPEED, 0.0),
3337 Vector4::new(1.0, 0.0, TARGET_SPEED, 0.0),
3338 Vector4::new(1.5, 0.0, TARGET_SPEED, 0.0),
3339 Vector4::new(2.0, 0.0, TARGET_SPEED, 0.0),
3340 Vector4::new(2.5, 0.0, TARGET_SPEED, 0.0),
3341 ];
3342 let state = State::new(0.0, 0.3, 1.0, 0.1);
3343
3344 let result = iterative_linear_mpc_control(&xref, &state, &[]);
3345
3346 let expected_controls = [
3347 [1.0, -0.669_667_215_071],
3348 [1.0, -0.630_794_908_167],
3349 [1.0, -0.577_878_962_843],
3350 [1.0, -0.534_142_026_172],
3351 [1.0, -0.513_091_902_297],
3352 ];
3353 for (actual, expected) in result.controls.iter().zip(expected_controls.iter()) {
3354 assert_vec2_close(actual, *expected, 5e-3);
3355 }
3356
3357 let final_predicted = result.predicted.last().unwrap();
3358 assert_vec4_close(
3359 final_predicted,
3360 [1.394_401_496_574, 0.249_081_964_95, 2.0, -0.221_107_545_509],
3361 5e-2,
3362 );
3363 }
3364
3365 #[test]
3366 fn test_mpc_simulation_reaches_goal() {
3367 let ax = vec![0.0, 6.0, 12.0];
3368 let ay = vec![0.0, 0.0, 0.0];
3369 let result = run_mpc_simulation_with_course(&ax, &ay, 120);
3370 let goal_distance = ((result.final_state.x - result.goal.0).powi(2)
3371 + (result.final_state.y - result.goal.1).powi(2))
3372 .sqrt();
3373
3374 assert!(result.reached_goal);
3375 assert!(goal_distance <= GOAL_DIS + 0.25);
3376 assert!(result.hist_x.len() < 120);
3377 }
3378
3379 #[test]
3380 fn test_mpc_straight_course_closed_loop_matches_reference() {
3381 let ax = vec![0.0, 6.0, 12.0];
3384 let ay = vec![0.0, 0.0, 0.0];
3385 let result = run_mpc_simulation_with_course(&ax, &ay, 120);
3386
3387 assert!(result.reached_goal);
3388 assert_eq!(result.target_index, result.final_index);
3389 assert_eq!(result.target_index, 24);
3390 assert_eq!(result.hist_x.len(), 56);
3391
3392 assert_vec4_close(
3393 &result.final_state.to_vector(),
3394 [
3395 12.126_756_016_762_242,
3396 0.000_001_043_289_690_068_484_6,
3397 -0.123_832_283_912_188_83,
3398 0.000_000_381_531_819_982_818_5,
3399 ],
3400 1e-2,
3401 );
3402 }
3403
3404 #[test]
3405 fn test_switch_back_course_contains_reverse_segment() {
3406 let (cx, cy, cyaw) = generate_switch_back_course();
3407 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
3408
3409 assert_eq!(cx.first().copied(), Some(0.0));
3410 assert_eq!(cy.first().copied(), Some(0.0));
3411 assert_eq!(cx.len(), 162);
3412 assert!(cy.iter().copied().fold(f64::NEG_INFINITY, f64::max) > 30.0);
3413 assert!((cx.last().unwrap() + 0.016_802_167_501_358_26).abs() <= 1e-9);
3414 assert!((cy.last().unwrap() - 0.833_302_319_727_219_5).abs() <= 1e-9);
3415 assert!(speed_profile[..speed_profile.len().saturating_sub(1)]
3416 .iter()
3417 .any(|speed| *speed < 0.0));
3418 assert_eq!(speed_profile.last().copied(), Some(0.0));
3419 }
3420
3421 #[test]
3422 fn test_mpc_switch_back_midcourse_window_matches_reference() {
3423 let ctx = advance_switch_back_context(20);
3426 let (xref, target_index) = calc_ref_trajectory(
3427 &ctx.state,
3428 &ctx.cx,
3429 &ctx.cy,
3430 &ctx.cyaw,
3431 &ctx.speed_profile,
3432 UPSTREAM_SWITCH_BACK_TICK,
3433 ctx.target_index,
3434 );
3435 let result = iterative_linear_mpc_control(&xref, &ctx.state, &ctx.warm_start);
3436
3437 assert_eq!(target_index, 5);
3438 assert_vec4_close(
3439 &ctx.state.to_vector(),
3440 [
3441 7.528_990_211_684_919,
3442 -0.616_644_989_009_243_4,
3443 3.855_895_792_930_033_5,
3444 -0.078_281_934_138_131_24,
3445 ],
3446 1e-2,
3447 );
3448
3449 let expected_xref = [
3450 [
3451 7.884_351_723_374_65,
3452 -0.646_692_295_268_678_9,
3453 TARGET_SPEED,
3454 -0.078_822_806_954_514_1,
3455 ],
3456 [
3457 9.417_709_562_051_241,
3458 -0.766_274_939_125_219_6,
3459 TARGET_SPEED,
3460 -0.076_759_688_257_237_88,
3461 ],
3462 [
3463 10.927_333_306_546_922,
3464 -0.880_536_229_237_835_5,
3465 TARGET_SPEED,
3466 -0.074_243_230_035_395_7,
3467 ],
3468 [
3469 10.927_333_306_546_922,
3470 -0.880_536_229_237_835_5,
3471 TARGET_SPEED,
3472 -0.074_243_230_035_395_7,
3473 ],
3474 [
3475 12.409_267_274_498_205,
3476 -0.988_589_273_315_872_4,
3477 TARGET_SPEED,
3478 -0.071_228_758_450_065_36,
3479 ],
3480 [
3481 13.859_555_783_541_607,
3482 -1.089_547_179_068_676,
3483 TARGET_SPEED,
3484 -0.067_659_809_911_111_4,
3485 ],
3486 ];
3487 for (actual, expected) in xref.iter().zip(expected_xref.iter()) {
3488 assert_vec4_close(actual, *expected, 1e-3);
3489 }
3490
3491 let expected_controls = [
3492 [0.999_999_995_100_728_5, 0.004_502_534_498_214_272],
3493 [0.619_401_970_887_073, 0.003_626_365_725_009_755],
3494 [-0.999_999_586_012_278, 0.003_497_926_790_795_169],
3495 [-0.999_999_988_263_528_5, 0.004_178_759_548_203_836],
3496 [-0.999_999_985_187_766_1, 0.004_753_969_705_983_846],
3497 ];
3498 for (actual, expected) in result.controls.iter().zip(expected_controls.iter()) {
3499 assert_vec2_close(actual, *expected, 2e-2);
3500 }
3501
3502 let final_predicted = result.predicted.last().unwrap();
3503 assert_vec4_close(
3504 final_predicted,
3505 [
3506 11.487_827_888_938_794,
3507 -0.917_080_324_028_499_6,
3508 3.579_776_274_235_015_4,
3509 -0.071_778_774_991_017_19,
3510 ],
3511 5e-2,
3512 );
3513 }
3514
3515 #[test]
3516 fn test_mpc_switch_back_prefix_trace_matches_pythonrobotics_reference() {
3517 let expected =
3518 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3519 let strict_prefix_steps = 71;
3523 let expected = &expected[..strict_prefix_steps];
3524 let actual = collect_switch_back_prefix_trace(expected.len());
3525 let summary = summarize_switch_back_trace_window(&actual, expected);
3526
3527 assert_eq!(summary.max_target_index_gap, 0);
3528 assert_diff_bounds("state", summary.max_state_diff, [4e-3, 3e-3, 4e-3, 2e-3]);
3529 assert_diff_bounds("xref_head", summary.max_xref_head_diff, [1e-10; 4]);
3530 assert_diff_bounds("xref_tail", summary.max_xref_tail_diff, [1e-10; 4]);
3531 assert_diff_bounds("control", summary.max_control_diff, [1e-2, 6e-3]);
3532 assert_diff_bounds(
3533 "predicted_tail",
3534 summary.max_predicted_tail_diff,
3535 [0.11, 0.13, 5e-3, 0.17],
3536 );
3537 }
3538
3539 #[test]
3540 fn test_mpc_switch_back_reverse_transition_matches_pythonrobotics_reference() {
3541 let expected =
3542 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3543 let reverse_window = 71..81;
3547 let actual = collect_switch_back_prefix_trace(reverse_window.end);
3548 let summary = summarize_switch_back_trace_window(
3549 &actual[reverse_window.clone()],
3550 &expected[reverse_window],
3551 );
3552
3553 assert!(summary.max_target_index_gap <= 1);
3554 assert_diff_bounds(
3555 "reverse_state",
3556 summary.max_state_diff,
3557 [0.04, 0.35, 0.17, 0.2],
3558 );
3559 assert_diff_bounds(
3560 "reverse_xref_head",
3561 summary.max_xref_head_diff,
3562 [0.75, 0.45, 1e-12, 0.02],
3563 );
3564 assert_diff_bounds(
3565 "reverse_xref_tail",
3566 summary.max_xref_tail_diff,
3567 [0.95, 0.5, 1e-12, 0.01],
3568 );
3569 assert_diff_bounds("reverse_control", summary.max_control_diff, [0.86, 0.41]);
3570 assert_diff_bounds(
3571 "reverse_predicted_tail",
3572 summary.max_predicted_tail_diff,
3573 [0.26, 0.62, 0.36, 0.55],
3574 );
3575 }
3576
3577 #[test]
3578 fn test_mpc_switch_back_reverse_detailed_trace_matches_pythonrobotics_reference() {
3579 let expected_trace =
3580 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3581 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3582 "testdata/mpc_switch_back_reverse_controls.csv"
3583 ));
3584 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
3585 "testdata/mpc_switch_back_reverse_horizon.csv"
3586 ));
3587 let actual_trace = collect_switch_back_detailed_trace(81);
3588 let summary = summarize_switch_back_detailed_window(
3589 &actual_trace,
3590 &expected_trace,
3591 &expected_controls,
3592 &expected_horizon,
3593 71..=80,
3594 );
3595
3596 assert!(summary.max_target_index_gap <= 1);
3597 assert_diff_bounds(
3598 "reverse_detailed_state",
3599 summary.max_state_diff,
3600 [0.04, 0.35, 0.17, 0.21],
3601 );
3602 assert_diff_bounds(
3603 "reverse_detailed_xref",
3604 summary.max_xref_diff,
3605 [0.92, 0.49, 1e-12, 0.02],
3606 );
3607 assert_diff_bounds(
3608 "reverse_detailed_control",
3609 summary.max_control_diff,
3610 [1.46, 0.5],
3611 );
3612 assert_diff_bounds(
3613 "reverse_detailed_predicted",
3614 summary.max_predicted_diff,
3615 [0.26, 0.62, 0.47, 0.55],
3616 );
3617 }
3618
3619 #[test]
3620 fn test_mpc_switch_back_reverse_full_candidate_expansion_characterizes_detailed_trace_tradeoff()
3621 {
3622 let expected_trace =
3623 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3624 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3625 "testdata/mpc_switch_back_reverse_controls.csv"
3626 ));
3627 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
3628 "testdata/mpc_switch_back_reverse_horizon.csv"
3629 ));
3630 let actual_trace = collect_switch_back_detailed_trace_with_test_optimizer(
3631 81,
3632 optimize_linearized_controls_with_full_candidate_expansion,
3633 );
3634 let default_summary = summarize_switch_back_detailed_window(
3635 &collect_switch_back_detailed_trace(81),
3636 &expected_trace,
3637 &expected_controls,
3638 &expected_horizon,
3639 71..=80,
3640 );
3641 let expanded_summary = summarize_switch_back_detailed_window(
3642 &actual_trace,
3643 &expected_trace,
3644 &expected_controls,
3645 &expected_horizon,
3646 71..=80,
3647 );
3648
3649 assert!(expanded_summary.max_target_index_gap <= 1);
3650 assert_eq!(
3651 expanded_summary.max_xref_diff,
3652 default_summary.max_xref_diff
3653 );
3654 assert_diff_bounds(
3655 "reverse_detailed_full_candidates_state_characterization",
3656 expanded_summary.max_state_diff,
3657 [0.043, 0.35, 0.17, 0.21],
3658 );
3659 assert_diff_bounds(
3660 "reverse_detailed_full_candidates_control_characterization",
3661 expanded_summary.max_control_diff,
3662 [1.32, 0.5],
3663 );
3664 assert_diff_bounds(
3665 "reverse_detailed_full_candidates_predicted_characterization",
3666 expanded_summary.max_predicted_diff,
3667 [0.22, 0.62, 0.41, 0.43],
3668 );
3669
3670 for (i, (d, e)) in default_summary
3674 .max_state_diff
3675 .iter()
3676 .zip(expanded_summary.max_state_diff.iter())
3677 .enumerate()
3678 {
3679 assert!(
3680 (d - e).abs() < 0.2,
3681 "state diff index {} diverged too much: default={} expanded={}",
3682 i,
3683 d,
3684 e
3685 );
3686 }
3687 for (i, (d, e)) in default_summary
3688 .max_control_diff
3689 .iter()
3690 .zip(expanded_summary.max_control_diff.iter())
3691 .enumerate()
3692 {
3693 assert!(
3694 (d - e).abs() < 0.2,
3695 "control diff index {} diverged too much: default={} expanded={}",
3696 i,
3697 d,
3698 e
3699 );
3700 }
3701 }
3702
3703 #[test]
3704 fn test_mpc_switch_back_reverse_solver_gap_with_python_warm_start() {
3705 let expected_trace =
3706 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3707 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3708 "testdata/mpc_switch_back_reverse_controls.csv"
3709 ));
3710 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
3711 "testdata/mpc_switch_back_reverse_horizon.csv"
3712 ));
3713
3714 for step in [75usize, 78usize] {
3715 let expected_state = &expected_trace[step].state;
3716 let python_warm_start = expected_controls.get(&(step - 1)).unwrap();
3717 let expected_controls_for_step = expected_controls.get(&step).unwrap();
3718 let (expected_xref_for_step, expected_predicted_for_step) =
3719 expected_horizon.get(&step).unwrap();
3720
3721 let expected_control_rows = expected_controls_for_step
3722 .iter()
3723 .map(|vec| [vec[0], vec[1]])
3724 .collect::<Vec<_>>();
3725 let expected_predicted_rows = expected_predicted_for_step
3726 .iter()
3727 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
3728 .collect::<Vec<_>>();
3729
3730 let result = iterative_linear_mpc_control(
3731 expected_xref_for_step,
3732 &state_from_vector4(expected_state),
3733 python_warm_start,
3734 );
3735
3736 let control_diff =
3737 summarize_vec2_sequence_diff(&result.controls, &expected_control_rows);
3738 let predicted_diff =
3739 summarize_vec4_sequence_diff(&result.predicted, &expected_predicted_rows);
3740 let (control_tol, predicted_tol) = match step {
3741 75 => ([0.38, 0.11], [0.09, 0.23, 0.11, 0.18]),
3742 78 => ([0.52, 0.35], [0.03, 0.46, 0.11, 0.25]),
3743 _ => unreachable!(),
3744 };
3745
3746 assert_diff_bounds(
3747 &format!("python_warm_start_step_{step}_control"),
3748 control_diff,
3749 control_tol,
3750 );
3751 assert_diff_bounds(
3752 &format!("python_warm_start_step_{step}_predicted"),
3753 predicted_diff,
3754 predicted_tol,
3755 );
3756 }
3757 }
3758
3759 #[test]
3760 fn test_mpc_switch_back_predict_motion_matches_python_warm_start_reference() {
3761 let expected_trace =
3762 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3763 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3764 "testdata/mpc_switch_back_reverse_controls.csv"
3765 ));
3766 let expected_xbar = parse_switch_back_reverse_states(include_str!(
3767 "testdata/mpc_switch_back_reverse_xbar.csv"
3768 ));
3769
3770 for step in [75usize, 78usize] {
3771 let expected_state = &expected_trace[step].state;
3772 let python_warm_start = expected_controls.get(&(step - 1)).unwrap();
3773 let expected_xbar_for_step = expected_xbar.get(&step).unwrap();
3774 let actual_xbar = predict_motion(state_from_vector4(expected_state), python_warm_start);
3775 let xbar_diff = summarize_vec4_sequence_diff(
3776 &actual_xbar,
3777 &expected_xbar_for_step
3778 .iter()
3779 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
3780 .collect::<Vec<_>>(),
3781 );
3782
3783 assert_diff_bounds(
3784 &format!("python_warm_start_step_{step}_xbar"),
3785 xbar_diff,
3786 [1e-9, 1e-9, 1e-9, 1e-9],
3787 );
3788 }
3789 }
3790
3791 #[test]
3792 fn test_mpc_switch_back_reverse_linearized_cost_gap_against_python_reference() {
3793 let expected_trace =
3794 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3795 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3796 "testdata/mpc_switch_back_reverse_controls.csv"
3797 ));
3798 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
3799 "testdata/mpc_switch_back_reverse_horizon.csv"
3800 ));
3801 let expected_xbar = parse_switch_back_reverse_states(include_str!(
3802 "testdata/mpc_switch_back_reverse_xbar.csv"
3803 ));
3804
3805 for step in [75usize, 78usize] {
3806 let expected_state = &expected_trace[step].state;
3807 let python_warm_start = expected_controls.get(&(step - 1)).unwrap();
3808 let python_controls = expected_controls.get(&step).unwrap();
3809 let (expected_xref_for_step, expected_predicted_for_step) =
3810 expected_horizon.get(&step).unwrap();
3811 let expected_xbar_for_step = expected_xbar.get(&step).unwrap();
3812 let state = state_from_vector4(expected_state);
3813
3814 let rust_controls = optimize_linearized_controls(
3815 expected_xref_for_step,
3816 expected_xbar_for_step,
3817 &state,
3818 python_warm_start,
3819 );
3820 let python_x_python_style = linearized_rollout_without_angle_wrap(
3821 &state,
3822 expected_xbar_for_step,
3823 python_controls,
3824 );
3825 let python_cost_python_style = compute_cost_without_angle_wrap(
3826 &python_x_python_style,
3827 expected_xref_for_step,
3828 python_controls,
3829 );
3830
3831 let rust_x_python_style = linearized_rollout_without_angle_wrap(
3832 &state,
3833 expected_xbar_for_step,
3834 &rust_controls,
3835 );
3836 let rust_cost_python_style = compute_cost_without_angle_wrap(
3837 &rust_x_python_style,
3838 expected_xref_for_step,
3839 &rust_controls,
3840 );
3841 let predicted_diff_python_style = summarize_vec4_sequence_diff(
3842 &python_x_python_style,
3843 &expected_predicted_for_step
3844 .iter()
3845 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
3846 .collect::<Vec<_>>(),
3847 );
3848 let predicted_tol = match step {
3849 75 => [0.11, 0.07, 1e-12, 0.002],
3850 78 => [0.06, 0.02, 1e-12, 0.006],
3851 _ => unreachable!(),
3852 };
3853
3854 assert_diff_bounds(
3855 &format!("python_style_linearized_rollout_step_{step}"),
3856 predicted_diff_python_style,
3857 predicted_tol,
3858 );
3859
3860 let solver_gap = rust_cost_python_style - python_cost_python_style;
3861 let min_gap = match step {
3862 75 => 1.0,
3863 78 => -0.1,
3865 _ => unreachable!(),
3866 };
3867 assert!(
3868 solver_gap > min_gap,
3869 "step {} solver gap too small: rust={} python={} gap={}",
3870 step,
3871 rust_cost_python_style,
3872 python_cost_python_style,
3873 solver_gap
3874 );
3875 }
3876 }
3877
3878 #[test]
3879 fn test_mpc_switch_back_reverse_python_style_solver_trace() {
3880 let expected_trace =
3881 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
3882 let expected_controls = parse_switch_back_reverse_controls(include_str!(
3883 "testdata/mpc_switch_back_reverse_controls.csv"
3884 ));
3885 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
3886 "testdata/mpc_switch_back_reverse_horizon.csv"
3887 ));
3888 let expected_xbar = parse_switch_back_reverse_states(include_str!(
3889 "testdata/mpc_switch_back_reverse_xbar.csv"
3890 ));
3891
3892 for step in [75usize, 78usize] {
3893 let expected_state = &expected_trace[step].state;
3894 let python_warm_start = expected_controls.get(&(step - 1)).unwrap();
3895 let python_controls = expected_controls.get(&step).unwrap();
3896 let (expected_xref_for_step, _) = expected_horizon.get(&step).unwrap();
3897 let expected_xbar_for_step = expected_xbar.get(&step).unwrap();
3898 let state = state_from_vector4(expected_state);
3899
3900 let (default_controls, default_trace) =
3901 optimize_linearized_controls_without_angle_wrap_with_trace(
3902 expected_xref_for_step,
3903 expected_xbar_for_step,
3904 &state,
3905 python_warm_start,
3906 QP_MAX_ITERS,
3907 LINE_SEARCH_ITERS,
3908 );
3909 let (extended_controls, extended_trace) =
3910 optimize_linearized_controls_without_angle_wrap_with_trace(
3911 expected_xref_for_step,
3912 expected_xbar_for_step,
3913 &state,
3914 python_warm_start,
3915 QP_MAX_ITERS * 10,
3916 LINE_SEARCH_ITERS * 2,
3917 );
3918
3919 let python_x = linearized_rollout_without_angle_wrap(
3920 &state,
3921 expected_xbar_for_step,
3922 python_controls,
3923 );
3924 let python_cost =
3925 compute_cost_without_angle_wrap(&python_x, expected_xref_for_step, python_controls);
3926
3927 let default_x = linearized_rollout_without_angle_wrap(
3928 &state,
3929 expected_xbar_for_step,
3930 &default_controls,
3931 );
3932 let default_cost = compute_cost_without_angle_wrap(
3933 &default_x,
3934 expected_xref_for_step,
3935 &default_controls,
3936 );
3937
3938 let extended_x = linearized_rollout_without_angle_wrap(
3939 &state,
3940 expected_xbar_for_step,
3941 &extended_controls,
3942 );
3943 let extended_cost = compute_cost_without_angle_wrap(
3944 &extended_x,
3945 expected_xref_for_step,
3946 &extended_controls,
3947 );
3948
3949 let default_gap = default_cost - python_cost;
3950 let extended_gap = extended_cost - python_cost;
3951 let last_default_grad = default_trace
3952 .last()
3953 .map(|entry| entry.gradient_norm)
3954 .unwrap_or(0.0);
3955 let last_extended_grad = extended_trace
3956 .last()
3957 .map(|entry| entry.gradient_norm)
3958 .unwrap_or(0.0);
3959
3960 assert!(
3961 extended_cost <= default_cost + 1e-9,
3962 "step {} extended cost regressed: default={} extended={}",
3963 step,
3964 default_cost,
3965 extended_cost
3966 );
3967 assert!(
3968 default_trace
3969 .iter()
3970 .any(|entry| entry.accepted_step.is_some()),
3971 "step {} default trace never accepted a line-search step: {:?}",
3972 step,
3973 default_trace
3974 );
3975 assert!(
3976 extended_trace
3977 .iter()
3978 .any(|entry| entry.accepted_step.is_some()),
3979 "step {} extended trace never accepted a line-search step: {:?}",
3980 step,
3981 extended_trace
3982 );
3983 assert!(
3984 default_trace.len() >= 150,
3985 "step {} default trace terminated too early: {:?}",
3986 step,
3987 default_trace
3988 );
3989 assert!(
3990 extended_trace.len() >= default_trace.len(),
3991 "step {} extended trace shorter than default: default={} extended={}",
3992 step,
3993 default_trace.len(),
3994 extended_trace.len()
3995 );
3996 assert!(
3997 last_default_grad > 0.1 && last_extended_grad > 0.1,
3998 "step {} gradients converged unexpectedly: default={} extended={}",
3999 step,
4000 last_default_grad,
4001 last_extended_grad
4002 );
4003
4004 let min_default_cost = default_trace
4005 .iter()
4006 .map(|entry| entry.cost)
4007 .fold(f64::INFINITY, f64::min);
4008 let min_extended_cost = extended_trace
4009 .iter()
4010 .map(|entry| entry.cost)
4011 .fold(f64::INFINITY, f64::min);
4012 assert!(
4013 (default_cost - extended_cost) <= 1e-3,
4014 "step {} extended budget changed cost too much: default={} extended={} gap_default={} gap_extended={}",
4015 step,
4016 default_cost,
4017 extended_cost,
4018 default_gap,
4019 extended_gap
4020 );
4021 assert!(
4022 (min_default_cost - min_extended_cost) <= 1e-3,
4023 "step {} extended budget changed min trace cost too much: default={} extended={}",
4024 step,
4025 min_default_cost,
4026 min_extended_cost
4027 );
4028 }
4029 }
4030
4031 #[test]
4032 fn test_mpc_switch_back_reverse_line_search_scan_characterization() {
4033 let expected_trace =
4034 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
4035 let expected_controls = parse_switch_back_reverse_controls(include_str!(
4036 "testdata/mpc_switch_back_reverse_controls.csv"
4037 ));
4038 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
4039 "testdata/mpc_switch_back_reverse_horizon.csv"
4040 ));
4041 let expected_xbar = parse_switch_back_reverse_states(include_str!(
4042 "testdata/mpc_switch_back_reverse_xbar.csv"
4043 ));
4044
4045 let step_75_state = state_from_vector4(&expected_trace[75].state);
4046 let step_75_scan = first_iteration_projection_candidates(
4047 expected_horizon.get(&75).unwrap().0.as_slice(),
4048 expected_xbar.get(&75).unwrap(),
4049 &step_75_state,
4050 expected_controls.get(&74).unwrap(),
4051 expected_controls.get(&75).unwrap(),
4052 &[0.25, 0.5, 1.0],
4053 )
4054 .1;
4055 let step_75_small = &step_75_scan[0];
4056 let step_75_medium = &step_75_scan[1];
4057 let step_75_large = &step_75_scan[2];
4058
4059 assert!(
4060 step_75_large.projected_cost + 0.05 < step_75_medium.projected_cost,
4061 "step 75 larger initial step did not improve projected cost enough: {:?}",
4062 step_75_scan
4063 );
4064 assert!(
4065 step_75_large.projected_cost + 0.25 < step_75_small.projected_cost,
4066 "step 75 larger initial step did not beat default projected cost enough: {:?}",
4067 step_75_scan
4068 );
4069 assert!(
4070 step_75_large.projected_to_python[1] + 0.05 < step_75_small.projected_to_python[1],
4071 "step 75 larger initial step did not move steering closer to python enough: {:?}",
4072 step_75_scan
4073 );
4074
4075 let step_78_state = state_from_vector4(&expected_trace[78].state);
4076 let step_78_scan = first_iteration_projection_candidates(
4077 expected_horizon.get(&78).unwrap().0.as_slice(),
4078 expected_xbar.get(&78).unwrap(),
4079 &step_78_state,
4080 expected_controls.get(&77).unwrap(),
4081 expected_controls.get(&78).unwrap(),
4082 &[0.25, 0.5, 1.0],
4083 )
4084 .1;
4085 let step_78_small = &step_78_scan[0];
4086 let step_78_medium = &step_78_scan[1];
4087 let step_78_large = &step_78_scan[2];
4088
4089 assert!(
4090 step_78_small.projected_cost + 0.15 < step_78_medium.projected_cost,
4091 "step 78 default initial step should remain better than 0.5: {:?}",
4092 step_78_scan
4093 );
4094 assert!(
4095 step_78_small.projected_cost + 0.2 < step_78_large.projected_cost,
4096 "step 78 default initial step should remain better than 1.0: {:?}",
4097 step_78_scan
4098 );
4099 }
4100
4101 #[test]
4102 fn test_mpc_switch_back_reverse_candidate_expansion_improves_python_style_cost() {
4103 let expected_trace =
4104 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
4105 let expected_controls = parse_switch_back_reverse_controls(include_str!(
4106 "testdata/mpc_switch_back_reverse_controls.csv"
4107 ));
4108 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
4109 "testdata/mpc_switch_back_reverse_horizon.csv"
4110 ));
4111 let expected_xbar = parse_switch_back_reverse_states(include_str!(
4112 "testdata/mpc_switch_back_reverse_xbar.csv"
4113 ));
4114 let step_candidates = [1.0, 0.5, 0.25, 0.125, 0.0625];
4115
4116 for step in [75usize, 78usize] {
4117 let expected_state = &expected_trace[step].state;
4118 let python_warm_start = expected_controls.get(&(step - 1)).unwrap();
4119 let python_controls = expected_controls.get(&step).unwrap();
4120 let (expected_xref_for_step, _) = expected_horizon.get(&step).unwrap();
4121 let expected_xbar_for_step = expected_xbar.get(&step).unwrap();
4122 let state = state_from_vector4(expected_state);
4123
4124 let (default_controls, _) = optimize_linearized_controls_without_angle_wrap_with_trace(
4125 expected_xref_for_step,
4126 expected_xbar_for_step,
4127 &state,
4128 python_warm_start,
4129 QP_MAX_ITERS,
4130 LINE_SEARCH_ITERS,
4131 );
4132 let (expanded_controls, expanded_trace) =
4133 optimize_linearized_controls_without_angle_wrap_with_candidate_expansion(
4134 expected_xref_for_step,
4135 expected_xbar_for_step,
4136 &state,
4137 python_warm_start,
4138 QP_MAX_ITERS,
4139 &step_candidates,
4140 );
4141
4142 let python_x = linearized_rollout_without_angle_wrap(
4143 &state,
4144 expected_xbar_for_step,
4145 python_controls,
4146 );
4147 let python_cost =
4148 compute_cost_without_angle_wrap(&python_x, expected_xref_for_step, python_controls);
4149 let default_x = linearized_rollout_without_angle_wrap(
4150 &state,
4151 expected_xbar_for_step,
4152 &default_controls,
4153 );
4154 let default_cost = compute_cost_without_angle_wrap(
4155 &default_x,
4156 expected_xref_for_step,
4157 &default_controls,
4158 );
4159 let expanded_x = linearized_rollout_without_angle_wrap(
4160 &state,
4161 expected_xbar_for_step,
4162 &expanded_controls,
4163 );
4164 let expanded_cost = compute_cost_without_angle_wrap(
4165 &expanded_x,
4166 expected_xref_for_step,
4167 &expanded_controls,
4168 );
4169 let default_gap = default_cost - python_cost;
4170 let expanded_gap = expanded_cost - python_cost;
4171
4172 assert!(
4173 expanded_trace.iter().any(|entry| {
4174 entry
4175 .accepted_step
4176 .is_some_and(|accepted| (accepted - 0.25).abs() > 1e-12)
4177 }),
4178 "step {} candidate expansion never used a non-default step: {:?}",
4179 step,
4180 expanded_trace
4181 );
4182
4183 match step {
4184 75 => {
4185 assert!(
4186 default_gap > 0.1,
4187 "step 75 default gap regressed unexpectedly: {}",
4188 default_gap
4189 );
4190 assert!(
4191 expanded_gap < 0.0,
4192 "step 75 candidate expansion did not beat python-style reference: {}",
4193 expanded_gap
4194 );
4195 assert!(
4196 expanded_cost + 0.1 < default_cost,
4197 "step 75 candidate expansion did not improve enough: default={} expanded={}",
4198 default_cost,
4199 expanded_cost
4200 );
4201 }
4202 78 => {
4203 assert!(
4204 expanded_cost <= default_cost + 1e-6,
4205 "step 78 candidate expansion regressed: default={} expanded={}",
4206 default_cost,
4207 expanded_cost
4208 );
4209 assert!(
4210 expanded_gap <= default_gap + 1e-6,
4211 "step 78 candidate expansion widened gap: default_gap={} expanded_gap={}",
4212 default_gap,
4213 expanded_gap
4214 );
4215 }
4216 _ => unreachable!(),
4217 }
4218 }
4219 }
4220
4221 #[test]
4222 fn test_mpc_switch_back_reverse_wrapped_candidate_expansion_needs_small_step_fallback() {
4223 let expected_trace =
4224 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
4225 let expected_controls = parse_switch_back_reverse_controls(include_str!(
4226 "testdata/mpc_switch_back_reverse_controls.csv"
4227 ));
4228 let expected_horizon = parse_switch_back_reverse_horizon(include_str!(
4229 "testdata/mpc_switch_back_reverse_horizon.csv"
4230 ));
4231 let expected_xbar = parse_switch_back_reverse_states(include_str!(
4232 "testdata/mpc_switch_back_reverse_xbar.csv"
4233 ));
4234 let step_78_state = state_from_vector4(&expected_trace[78].state);
4235 let python_warm_start = expected_controls.get(&77).unwrap();
4236 let (expected_xref_for_step, _) = expected_horizon.get(&78).unwrap();
4237 let expected_xbar_for_step = expected_xbar.get(&78).unwrap();
4238
4239 let default_controls = optimize_linearized_controls(
4240 expected_xref_for_step,
4241 expected_xbar_for_step,
4242 &step_78_state,
4243 python_warm_start,
4244 );
4245 let (coarse_controls, _coarse_trace) =
4246 optimize_linearized_controls_with_candidate_expansion(
4247 expected_xref_for_step,
4248 expected_xbar_for_step,
4249 &step_78_state,
4250 python_warm_start,
4251 QP_MAX_ITERS,
4252 &[1.0, 0.5, 0.25, 0.125, 0.0625],
4253 );
4254 let (full_controls, _full_trace) = optimize_linearized_controls_with_candidate_expansion(
4255 expected_xref_for_step,
4256 expected_xbar_for_step,
4257 &step_78_state,
4258 python_warm_start,
4259 QP_MAX_ITERS,
4260 &expanded_backtracking_step_candidates(),
4261 );
4262
4263 let (default_x, _, _, _) =
4264 linearized_rollout(&step_78_state, expected_xbar_for_step, &default_controls);
4265 let (coarse_x, _, _, _) =
4266 linearized_rollout(&step_78_state, expected_xbar_for_step, &coarse_controls);
4267 let (full_x, _, _, _) =
4268 linearized_rollout(&step_78_state, expected_xbar_for_step, &full_controls);
4269 let default_cost = compute_cost(&default_x, expected_xref_for_step, &default_controls);
4270 let coarse_cost = compute_cost(&coarse_x, expected_xref_for_step, &coarse_controls);
4271 let full_cost = compute_cost(&full_x, expected_xref_for_step, &full_controls);
4272
4273 assert!(
4277 coarse_cost > default_cost - 1.0,
4278 "step 78 coarse candidate expansion cost unexpectedly much lower: default={} coarse={}",
4279 default_cost,
4280 coarse_cost
4281 );
4282 assert!(
4283 full_cost < default_cost * 6.0,
4284 "step 78 full candidate expansion cost unreasonably high: default={} full={}",
4285 default_cost,
4286 full_cost
4287 );
4288 }
4289
4290 #[test]
4291 fn test_mpc_switch_back_reverse_full_candidate_expansion_preserves_outer_loop_cost() {
4292 for step in [75usize, 78usize] {
4293 let ctx = advance_switch_back_context(step);
4294 let (xref, _) = calc_ref_trajectory(
4295 &ctx.state,
4296 &ctx.cx,
4297 &ctx.cy,
4298 &ctx.cyaw,
4299 &ctx.speed_profile,
4300 UPSTREAM_SWITCH_BACK_TICK,
4301 ctx.target_index,
4302 );
4303
4304 let default_result = iterative_linear_mpc_control(&xref, &ctx.state, &ctx.warm_start);
4305 let expanded_result = iterative_linear_mpc_control_with_test_optimizer(
4306 &xref,
4307 &ctx.state,
4308 &ctx.warm_start,
4309 optimize_linearized_controls_with_full_candidate_expansion,
4310 );
4311
4312 let default_cost =
4313 compute_cost(&default_result.predicted, &xref, &default_result.controls);
4314 let expanded_cost =
4315 compute_cost(&expanded_result.predicted, &xref, &expanded_result.controls);
4316
4317 assert!(
4318 expanded_cost <= default_cost + 1e-9,
4319 "step {} full candidate expansion outer-loop cost regressed: default={} expanded={}",
4320 step,
4321 default_cost,
4322 expanded_cost
4323 );
4324 }
4325 }
4326
4327 #[test]
4328 fn test_mpc_switch_back_reverse_step_75_matches_pythonrobotics_reference() {
4329 let ctx = advance_switch_back_context(75);
4330 let (xref, target_index) = calc_ref_trajectory(
4331 &ctx.state,
4332 &ctx.cx,
4333 &ctx.cy,
4334 &ctx.cyaw,
4335 &ctx.speed_profile,
4336 UPSTREAM_SWITCH_BACK_TICK,
4337 ctx.target_index,
4338 );
4339 let result = iterative_linear_mpc_control(&xref, &ctx.state, &ctx.warm_start);
4340
4341 assert_eq!(target_index, 35);
4342 let state_diff = trace_state_diff(
4343 &ctx.state.to_vector(),
4344 &Vector4::new(
4345 29.037_582_480_114_636,
4346 3.815_773_924_700_736,
4347 2.555_321_662_179_444_3,
4348 3.092_675_307_077_598_7,
4349 ),
4350 );
4351 assert_diff_bounds(
4352 "reverse_step_75_state",
4353 state_diff,
4354 [1e-2, 0.039, 5e-3, 0.17],
4355 );
4356
4357 let expected_xref = [
4358 [
4359 27.746_027_874_214_72,
4360 1.657_608_713_384_614,
4361 TARGET_SPEED,
4362 2.615_811_171_700_94,
4363 ],
4364 [
4365 27.025_250_293_779_735,
4366 2.066_144_795_139_825_5,
4367 TARGET_SPEED,
4368 2.634_462_746_003_994_7,
4369 ],
4370 [
4371 27.025_250_293_779_735,
4372 2.066_144_795_139_825_5,
4373 TARGET_SPEED,
4374 2.634_462_746_003_994_7,
4375 ],
4376 [
4377 26.232_726_746_496_056,
4378 2.499_757_118_116_159_7,
4379 TARGET_SPEED,
4380 2.646_329_056_016_381,
4381 ],
4382 [
4383 26.232_726_746_496_056,
4384 2.499_757_118_116_159_7,
4385 TARGET_SPEED,
4386 2.646_329_056_016_381,
4387 ],
4388 [
4389 25.376_277_983_039_653,
4390 2.958_190_594_397_198_7,
4391 TARGET_SPEED,
4392 2.653_104_280_632_957_7,
4393 ],
4394 ];
4395 let xref_diff = summarize_vec4_sequence_diff(&xref, &expected_xref);
4396 assert_diff_bounds(
4397 "reverse_step_75_xref",
4398 xref_diff,
4399 [1e-10, 1e-10, 1e-12, 1e-10],
4400 );
4401
4402 let expected_controls = [
4403 [0.999_999_999_725_671_9, 0.562_383_993_404_798_6],
4404 [0.999_999_999_289_914_1, 0.457_664_238_591_364_85],
4405 [0.998_139_695_362_811_2, 0.352_944_483_746_078_1],
4406 [-0.296_638_673_829_085_65, 0.248_224_729_252_603_17],
4407 [-0.868_769_805_574_562_3, 0.143_736_759_161_897_54],
4408 ];
4409 let control_diff = summarize_vec2_sequence_diff(&result.controls, &expected_controls);
4410 assert_diff_bounds("reverse_step_75_control", control_diff, [0.18, 0.36]);
4411
4412 let expected_predicted = [
4413 [
4414 29.037_582_480_114_633,
4415 3.815_773_924_700_736,
4416 2.555_321_662_179_444_3,
4417 3.092_675_307_077_598_7,
4418 ],
4419 [
4420 28.527_129_490_412_527,
4421 3.840_763_866_523_892,
4422 2.755_321_662_124_578_6,
4423 3.207_641_067_142_419,
4424 ],
4425 [
4426 27.976_795_579_388_14,
4427 3.804_349_136_114_007_4,
4428 2.955_321_661_982_561_7,
4429 3.308_522_042_381_720_3,
4430 ],
4431 [
4432 27.392_314_838_387_758,
4433 3.705_780_688_303_695,
4434 3.154_949_601_055_123_7,
4435 3.391_967_200_625_691_3,
4436 ],
4437 [
4438 26.777_902_427_743_353,
4439 3.547_962_330_090_222,
4440 3.095_621_866_289_306_5,
4441 3.454_206_605_778_239_7,
4442 ],
4443 [
4444 26.184_119_418_583_78,
4445 3.355_144_266_828_263_6,
4446 2.921_867_905_174_394_3,
4447 3.489_624_562_748_850_7,
4448 ],
4449 ];
4450 let predicted_diff = summarize_vec4_sequence_diff(&result.predicted, &expected_predicted);
4451 assert_diff_bounds(
4452 "reverse_step_75_predicted",
4453 predicted_diff,
4454 [0.12, 0.42, 0.04, 0.33],
4455 );
4456 }
4457
4458 #[test]
4459 fn test_mpc_switch_back_reverse_step_78_matches_pythonrobotics_reference() {
4460 let ctx = advance_switch_back_context(78);
4461 let (xref, target_index) = calc_ref_trajectory(
4462 &ctx.state,
4463 &ctx.cx,
4464 &ctx.cy,
4465 &ctx.cyaw,
4466 &ctx.speed_profile,
4467 UPSTREAM_SWITCH_BACK_TICK,
4468 ctx.target_index,
4469 );
4470 let result = iterative_linear_mpc_control(&xref, &ctx.state, &ctx.warm_start);
4471
4472 assert!(target_index.abs_diff(37) <= 1);
4473 let state_diff = trace_state_diff(
4474 &ctx.state.to_vector(),
4475 &Vector4::new(
4476 27.396_351_837_762_67,
4477 3.690_715_343_715_656,
4478 3.155_321_660_492_834_5,
4479 3.307_411_595_480_136,
4480 ),
4481 );
4482 assert_diff_bounds(
4483 "reverse_step_78_state",
4484 state_diff,
4485 [1e-2, 0.33, 5e-3, 0.042],
4486 );
4487
4488 let expected_xref = [
4489 [
4490 26.232_726_746_496_056,
4491 2.499_757_118_116_159_7,
4492 TARGET_SPEED,
4493 2.646_329_056_016_381,
4494 ],
4495 [
4496 25.376_277_983_039_653,
4497 2.958_190_594_397_198_7,
4498 TARGET_SPEED,
4499 2.653_104_280_632_957_7,
4500 ],
4501 [
4502 25.376_277_983_039_653,
4503 2.958_190_594_397_198_7,
4504 TARGET_SPEED,
4505 2.653_104_280_632_957_7,
4506 ],
4507 [
4508 24.463_724_754_086_51,
4509 3.441_190_136_066_525_4,
4510 TARGET_SPEED,
4511 2.655_851_531_497_457_3,
4512 ],
4513 [
4514 23.502_887_810_312_604,
4515 3.948_500_655_207_722_4,
4516 TARGET_SPEED,
4517 2.655_248_764_939_293_5,
4518 ],
4519 [
4520 23.502_887_810_312_604,
4521 3.948_500_655_207_722_4,
4522 TARGET_SPEED,
4523 2.655_248_764_939_293_5,
4524 ],
4525 ];
4526 let xref_diff = summarize_vec4_sequence_diff(&xref, &expected_xref);
4527 assert_diff_bounds(
4528 "reverse_step_78_xref",
4529 xref_diff,
4530 [1e-10, 1e-10, 1e-12, 1e-10],
4531 );
4532
4533 let expected_controls = [
4534 [0.999_999_999_807_820_6, -0.487_721_747_027_616_45],
4535 [0.782_500_923_395_977_7, -0.477_987_056_920_691],
4536 [-0.698_729_449_948_941_9, -0.456_028_220_361_946_7],
4537 [-0.999_999_995_989_379_2, -0.443_086_192_245_847_8],
4538 [-0.999_999_978_879_091_9, -0.443_921_255_425_959_9],
4539 ];
4540 let control_diff = summarize_vec2_sequence_diff(&result.controls, &expected_controls);
4541 assert_diff_bounds("reverse_step_78_control", control_diff, [1.46, 0.51]);
4542
4543 let expected_predicted = [
4544 [
4545 27.396_351_837_762_67,
4546 3.690_715_343_715_655,
4547 3.155_321_660_492_829_6,
4548 3.307_411_595_480_137_7,
4549 ],
4550 [
4551 26.773_943_492_338_02,
4552 3.586_551_805_024_059,
4553 3.355_321_660_454_389_4,
4554 3.184_298_076_064_967_7,
4555 ],
4556 [
4557 26.103_420_596_888_107,
4558 3.557_898_739_267_037_6,
4559 3.511_821_845_133_581,
4560 3.055_994_050_024_563_7,
4561 ],
4562 [
4563 25.403_376_777_674_16,
4564 3.617_906_440_004_764_7,
4565 3.372_075_955_143_793_5,
4566 2.928_267_659_325_554,
4567 ],
4568 [
4569 24.743_907_999_953_26,
4570 3.760_672_834_366_53,
4571 3.172_075_955_945_920_6,
4572 2.809_186_243_738_390_3,
4573 ],
4574 [
4575 24.143_945_332_102_11,
4576 3.967_706_477_151_346_6,
4577 2.972_075_960_170_106,
4578 2.696_983_141_208_553_6,
4579 ],
4580 ];
4581 let predicted_diff = summarize_vec4_sequence_diff(&result.predicted, &expected_predicted);
4582 assert_diff_bounds(
4583 "reverse_step_78_predicted",
4584 predicted_diff,
4585 [0.26, 0.42, 0.47, 0.48],
4586 );
4587 }
4588
4589 #[test]
4590 #[ignore = "long-running regression scenario"]
4591 fn test_mpc_simulation_reaches_goal_full_course() {
4592 let result = run_mpc_simulation();
4595 let goal_distance = ((result.final_state.x - result.goal.0).powi(2)
4596 + (result.final_state.y - result.goal.1).powi(2))
4597 .sqrt();
4598
4599 assert_eq!(result.target_index, result.final_index);
4600 assert_eq!(result.final_index, 161);
4601 assert!((result.hist_x.len() as isize - 347).abs() <= 8);
4602 assert!(result.reached_goal);
4603 assert!(goal_distance <= GOAL_DIS + 0.25);
4604 assert!(result.hist_x.len() < MAX_SIM_STEPS);
4605 assert_vec4_close(
4606 &result.final_state.to_vector(),
4607 [
4608 -0.014_888_855_512_389,
4609 0.698_023_990_780_443,
4610 0.132_179_560_675_735,
4611 1.589_745_981_585_03,
4612 ],
4613 2e-2,
4614 );
4615 }
4616
4617 #[test]
4618 #[ignore = "long-running regression scenario"]
4619 fn test_mpc_switch_back_full_candidate_expansion_characterizes_closed_loop_tradeoff() {
4620 let original_result = run_switch_back_simulation_with_test_optimizer(
4621 MAX_SIM_STEPS,
4622 optimize_linearized_controls_original,
4623 );
4624 let expanded_result = run_switch_back_simulation_with_test_optimizer(
4625 MAX_SIM_STEPS,
4626 optimize_linearized_controls_with_full_candidate_expansion,
4627 );
4628 let original_goal_distance = ((original_result.final_state.x - original_result.goal.0)
4629 .powi(2)
4630 + (original_result.final_state.y - original_result.goal.1).powi(2))
4631 .sqrt();
4632 let expanded_goal_distance = ((expanded_result.final_state.x - expanded_result.goal.0)
4633 .powi(2)
4634 + (expanded_result.final_state.y - expanded_result.goal.1).powi(2))
4635 .sqrt();
4636
4637 assert!(original_result.reached_goal);
4638 assert!(expanded_result.reached_goal);
4639 assert_eq!(original_result.target_index, original_result.final_index);
4640 assert_eq!(expanded_result.target_index, expanded_result.final_index);
4641 assert_eq!(expanded_result.final_index, 161);
4642 assert_eq!(expanded_result.hist_x.len(), 347);
4643 assert!(expanded_result.hist_x.len() < original_result.hist_x.len());
4644 assert!(expanded_goal_distance <= original_goal_distance + 1e-9);
4645 assert!(expanded_goal_distance <= GOAL_DIS + 0.25);
4646 assert!(expanded_result.hist_x.len() < MAX_SIM_STEPS);
4647 assert_vec4_close(
4648 &expanded_result.final_state.to_vector(),
4649 [
4650 -0.014_888_855_512_389,
4651 0.698_023_990_780_443,
4652 0.132_179_560_675_735,
4653 1.589_745_981_585_03,
4654 ],
4655 5e-3,
4656 );
4657 }
4658
4659 #[test]
4660 #[ignore = "long-running regression scenario"]
4661 fn test_mpc_switch_back_full_candidate_expansion_characterizes_recovery_window() {
4662 let checkpoints = [80usize, 120, 160, 200, 260, 320, 346];
4663 let default_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4664 MAX_SIM_STEPS,
4665 &checkpoints,
4666 optimize_linearized_controls_original,
4667 );
4668 let expanded_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4669 MAX_SIM_STEPS,
4670 &checkpoints,
4671 optimize_linearized_controls_with_full_candidate_expansion,
4672 );
4673
4674 assert_eq!(default_checkpoints.len(), checkpoints.len());
4675 assert_eq!(expanded_checkpoints.len(), checkpoints.len());
4676
4677 for ((default, expanded), expected_step) in default_checkpoints
4678 .iter()
4679 .zip(expanded_checkpoints.iter())
4680 .zip(checkpoints)
4681 {
4682 assert_eq!(default.step, expected_step);
4683 assert_eq!(expanded.step, expected_step);
4684 }
4685
4686 let step_80_delta = goal_distance_delta(&default_checkpoints[0], &expanded_checkpoints[0]);
4687 let step_120_delta = goal_distance_delta(&default_checkpoints[1], &expanded_checkpoints[1]);
4688 let step_160_delta = goal_distance_delta(&default_checkpoints[2], &expanded_checkpoints[2]);
4689 let step_200_delta = goal_distance_delta(&default_checkpoints[3], &expanded_checkpoints[3]);
4690 let step_260_delta = goal_distance_delta(&default_checkpoints[4], &expanded_checkpoints[4]);
4691 let step_320_delta = goal_distance_delta(&default_checkpoints[5], &expanded_checkpoints[5]);
4692 let step_346_delta = goal_distance_delta(&default_checkpoints[6], &expanded_checkpoints[6]);
4693
4694 assert!(
4695 (-0.03..=-0.005).contains(&step_80_delta),
4696 "step 80 delta moved unexpectedly: default={:?} expanded={:?}",
4697 default_checkpoints[0],
4698 expanded_checkpoints[0]
4699 );
4700 assert!(
4701 (0.15..=0.25).contains(&step_120_delta),
4702 "step 120 delta moved unexpectedly: default={:?} expanded={:?}",
4703 default_checkpoints[1],
4704 expanded_checkpoints[1]
4705 );
4706 assert!(
4707 (-0.35..=-0.2).contains(&step_160_delta),
4708 "step 160 delta moved unexpectedly: default={:?} expanded={:?}",
4709 default_checkpoints[2],
4710 expanded_checkpoints[2]
4711 );
4712 assert!(
4713 (-0.35..=-0.15).contains(&step_200_delta),
4714 "step 200 delta moved unexpectedly: default={:?} expanded={:?}",
4715 default_checkpoints[3],
4716 expanded_checkpoints[3]
4717 );
4718 assert!(
4719 (0.8..=1.2).contains(&step_260_delta),
4720 "step 260 delta moved unexpectedly: default={:?} expanded={:?}",
4721 default_checkpoints[4],
4722 expanded_checkpoints[4]
4723 );
4724 assert!(
4725 (1.4..=1.8).contains(&step_320_delta),
4726 "step 320 delta moved unexpectedly: default={:?} expanded={:?}",
4727 default_checkpoints[5],
4728 expanded_checkpoints[5]
4729 );
4730 assert!(
4731 (0.15..=0.25).contains(&step_346_delta),
4732 "step 346 delta moved unexpectedly: default={:?} expanded={:?}",
4733 default_checkpoints[6],
4734 expanded_checkpoints[6]
4735 );
4736
4737 assert_eq!(default_checkpoints[5].target_index, 159);
4738 assert_eq!(expanded_checkpoints[5].target_index, 161);
4739 assert_eq!(default_checkpoints[6].target_index, 161);
4740 assert_eq!(expanded_checkpoints[6].target_index, 161);
4741 }
4742
4743 #[test]
4744 #[ignore = "long-running regression scenario"]
4745 fn test_mpc_switch_back_full_candidate_expansion_characterizes_dense_recovery_window() {
4746 let checkpoints = [
4747 120usize, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260,
4748 ];
4749 let default_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4750 MAX_SIM_STEPS,
4751 &checkpoints,
4752 optimize_linearized_controls_original,
4753 );
4754 let expanded_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4755 MAX_SIM_STEPS,
4756 &checkpoints,
4757 optimize_linearized_controls_with_full_candidate_expansion,
4758 );
4759
4760 assert_eq!(default_checkpoints.len(), checkpoints.len());
4761 assert_eq!(expanded_checkpoints.len(), checkpoints.len());
4762
4763 let step_130_default = checkpoint_at_step(&default_checkpoints, 130);
4764 let step_130_expanded = checkpoint_at_step(&expanded_checkpoints, 130);
4765 let step_140_default = checkpoint_at_step(&default_checkpoints, 140);
4766 let step_140_expanded = checkpoint_at_step(&expanded_checkpoints, 140);
4767 let step_180_default = checkpoint_at_step(&default_checkpoints, 180);
4768 let step_180_expanded = checkpoint_at_step(&expanded_checkpoints, 180);
4769 let step_200_default = checkpoint_at_step(&default_checkpoints, 200);
4770 let step_200_expanded = checkpoint_at_step(&expanded_checkpoints, 200);
4771 let step_210_default = checkpoint_at_step(&default_checkpoints, 210);
4772 let step_210_expanded = checkpoint_at_step(&expanded_checkpoints, 210);
4773 let step_220_default = checkpoint_at_step(&default_checkpoints, 220);
4774 let step_220_expanded = checkpoint_at_step(&expanded_checkpoints, 220);
4775 let step_240_default = checkpoint_at_step(&default_checkpoints, 240);
4776 let step_240_expanded = checkpoint_at_step(&expanded_checkpoints, 240);
4777 let step_260_default = checkpoint_at_step(&default_checkpoints, 260);
4778 let step_260_expanded = checkpoint_at_step(&expanded_checkpoints, 260);
4779
4780 let step_130_delta = goal_distance_delta(step_130_default, step_130_expanded);
4781 let step_140_delta = goal_distance_delta(step_140_default, step_140_expanded);
4782 let step_180_delta = goal_distance_delta(step_180_default, step_180_expanded);
4783 let step_200_delta = goal_distance_delta(step_200_default, step_200_expanded);
4784 let step_210_delta = goal_distance_delta(step_210_default, step_210_expanded);
4785 let step_220_delta = goal_distance_delta(step_220_default, step_220_expanded);
4786 let step_240_delta = goal_distance_delta(step_240_default, step_240_expanded);
4787 let step_260_delta = goal_distance_delta(step_260_default, step_260_expanded);
4788
4789 assert!(
4790 (0.1..=0.18).contains(&step_130_delta),
4791 "step 130 delta moved unexpectedly: default={:?} expanded={:?}",
4792 step_130_default,
4793 step_130_expanded
4794 );
4795 assert!(
4796 (-0.2..=-0.05).contains(&step_140_delta),
4797 "step 140 delta moved unexpectedly: default={:?} expanded={:?}",
4798 step_140_default,
4799 step_140_expanded
4800 );
4801 assert!(
4802 (0.05..=0.15).contains(&step_180_delta),
4803 "step 180 delta moved unexpectedly: default={:?} expanded={:?}",
4804 step_180_default,
4805 step_180_expanded
4806 );
4807 assert!(
4808 (-0.35..=-0.15).contains(&step_200_delta),
4809 "step 200 delta moved unexpectedly: default={:?} expanded={:?}",
4810 step_200_default,
4811 step_200_expanded
4812 );
4813 assert!(
4814 (-0.2..=-0.08).contains(&step_210_delta),
4815 "step 210 delta moved unexpectedly: default={:?} expanded={:?}",
4816 step_210_default,
4817 step_210_expanded
4818 );
4819 assert!(
4820 (0.35..=0.55).contains(&step_220_delta),
4821 "step 220 delta moved unexpectedly: default={:?} expanded={:?}",
4822 step_220_default,
4823 step_220_expanded
4824 );
4825 assert!(
4826 (0.3..=0.55).contains(&step_240_delta),
4827 "step 240 delta moved unexpectedly: default={:?} expanded={:?}",
4828 step_240_default,
4829 step_240_expanded
4830 );
4831 assert!(
4832 (0.85..=1.05).contains(&step_260_delta),
4833 "step 260 delta moved unexpectedly: default={:?} expanded={:?}",
4834 step_260_default,
4835 step_260_expanded
4836 );
4837
4838 assert_eq!(step_140_default.target_index, 71);
4839 assert_eq!(step_140_expanded.target_index, 72);
4840 assert_eq!(step_180_default.target_index, 94);
4841 assert_eq!(step_180_expanded.target_index, 95);
4842 assert_eq!(step_220_default.target_index, 105);
4843 assert_eq!(step_220_expanded.target_index, 106);
4844 assert_eq!(step_240_default.target_index, 115);
4845 assert_eq!(step_240_expanded.target_index, 117);
4846 assert_eq!(step_260_default.target_index, 127);
4847 assert_eq!(step_260_expanded.target_index, 129);
4848 }
4849
4850 #[test]
4851 #[ignore = "long-running regression scenario"]
4852 fn test_mpc_switch_back_full_candidate_expansion_characterizes_flip_window_210_220() {
4853 let checkpoints = [210usize, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220];
4854 let default_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4855 MAX_SIM_STEPS,
4856 &checkpoints,
4857 optimize_linearized_controls_original,
4858 );
4859 let expanded_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4860 MAX_SIM_STEPS,
4861 &checkpoints,
4862 optimize_linearized_controls_with_full_candidate_expansion,
4863 );
4864
4865 assert_eq!(default_checkpoints.len(), checkpoints.len());
4866 assert_eq!(expanded_checkpoints.len(), checkpoints.len());
4867
4868 for ((default, expanded), expected_step) in default_checkpoints
4869 .iter()
4870 .zip(expanded_checkpoints.iter())
4871 .zip(checkpoints)
4872 {
4873 assert_eq!(default.step, expected_step);
4874 assert_eq!(expanded.step, expected_step);
4875 }
4876
4877 let step_210_default = checkpoint_at_step(&default_checkpoints, 210);
4878 let step_210_expanded = checkpoint_at_step(&expanded_checkpoints, 210);
4879 let step_212_default = checkpoint_at_step(&default_checkpoints, 212);
4880 let step_212_expanded = checkpoint_at_step(&expanded_checkpoints, 212);
4881 let step_213_default = checkpoint_at_step(&default_checkpoints, 213);
4882 let step_213_expanded = checkpoint_at_step(&expanded_checkpoints, 213);
4883 let step_214_default = checkpoint_at_step(&default_checkpoints, 214);
4884 let step_214_expanded = checkpoint_at_step(&expanded_checkpoints, 214);
4885 let step_216_default = checkpoint_at_step(&default_checkpoints, 216);
4886 let step_216_expanded = checkpoint_at_step(&expanded_checkpoints, 216);
4887 let step_220_default = checkpoint_at_step(&default_checkpoints, 220);
4888 let step_220_expanded = checkpoint_at_step(&expanded_checkpoints, 220);
4889
4890 let step_210_delta = goal_distance_delta(step_210_default, step_210_expanded);
4891 let step_212_delta = goal_distance_delta(step_212_default, step_212_expanded);
4892 let step_213_delta = goal_distance_delta(step_213_default, step_213_expanded);
4893 let step_214_delta = goal_distance_delta(step_214_default, step_214_expanded);
4894 let step_216_delta = goal_distance_delta(step_216_default, step_216_expanded);
4895 let step_220_delta = goal_distance_delta(step_220_default, step_220_expanded);
4896
4897 assert!(
4898 (-0.18..=-0.1).contains(&step_210_delta),
4899 "step 210 delta moved unexpectedly: default={:?} expanded={:?}",
4900 step_210_default,
4901 step_210_expanded
4902 );
4903 assert!(
4904 (-0.08..=-0.03).contains(&step_212_delta),
4905 "step 212 delta moved unexpectedly: default={:?} expanded={:?}",
4906 step_212_default,
4907 step_212_expanded
4908 );
4909 assert!(
4910 (0.0..=0.02).contains(&step_213_delta),
4911 "step 213 delta moved unexpectedly: default={:?} expanded={:?}",
4912 step_213_default,
4913 step_213_expanded
4914 );
4915 assert!(
4916 (0.05..=0.09).contains(&step_214_delta),
4917 "step 214 delta moved unexpectedly: default={:?} expanded={:?}",
4918 step_214_default,
4919 step_214_expanded
4920 );
4921 assert!(
4922 (0.18..=0.25).contains(&step_216_delta),
4923 "step 216 delta moved unexpectedly: default={:?} expanded={:?}",
4924 step_216_default,
4925 step_216_expanded
4926 );
4927 assert!(
4928 (0.42..=0.5).contains(&step_220_delta),
4929 "step 220 delta moved unexpectedly: default={:?} expanded={:?}",
4930 step_220_default,
4931 step_220_expanded
4932 );
4933
4934 assert_eq!(step_210_default.target_index, 103);
4935 assert_eq!(step_210_expanded.target_index, 103);
4936 assert_eq!(step_212_default.target_index, 103);
4937 assert_eq!(step_212_expanded.target_index, 103);
4938 assert_eq!(step_213_default.target_index, 103);
4939 assert_eq!(step_213_expanded.target_index, 103);
4940 assert_eq!(step_214_default.target_index, 103);
4941 assert_eq!(step_214_expanded.target_index, 104);
4942 assert_eq!(step_216_default.target_index, 103);
4943 assert_eq!(step_216_expanded.target_index, 104);
4944 assert_eq!(step_220_default.target_index, 105);
4945 assert_eq!(step_220_expanded.target_index, 106);
4946 }
4947
4948 #[test]
4949 #[ignore = "long-running regression scenario"]
4950 fn test_mpc_switch_back_full_candidate_expansion_characterizes_progression_to_recovery() {
4951 let checkpoints = [213usize, 214, 220, 230, 240, 260];
4952 let default_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4953 MAX_SIM_STEPS,
4954 &checkpoints,
4955 optimize_linearized_controls_original,
4956 );
4957 let expanded_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
4958 MAX_SIM_STEPS,
4959 &checkpoints,
4960 optimize_linearized_controls_with_full_candidate_expansion,
4961 );
4962
4963 assert_eq!(default_checkpoints.len(), checkpoints.len());
4964 assert_eq!(expanded_checkpoints.len(), checkpoints.len());
4965
4966 let step_213_default = checkpoint_at_step(&default_checkpoints, 213);
4967 let step_213_expanded = checkpoint_at_step(&expanded_checkpoints, 213);
4968 let step_214_default = checkpoint_at_step(&default_checkpoints, 214);
4969 let step_214_expanded = checkpoint_at_step(&expanded_checkpoints, 214);
4970 let step_220_default = checkpoint_at_step(&default_checkpoints, 220);
4971 let step_220_expanded = checkpoint_at_step(&expanded_checkpoints, 220);
4972 let step_230_default = checkpoint_at_step(&default_checkpoints, 230);
4973 let step_230_expanded = checkpoint_at_step(&expanded_checkpoints, 230);
4974 let step_240_default = checkpoint_at_step(&default_checkpoints, 240);
4975 let step_240_expanded = checkpoint_at_step(&expanded_checkpoints, 240);
4976 let step_260_default = checkpoint_at_step(&default_checkpoints, 260);
4977 let step_260_expanded = checkpoint_at_step(&expanded_checkpoints, 260);
4978
4979 let step_214_y_delta = step_214_default.state[1] - step_214_expanded.state[1];
4980 let step_214_speed_delta = step_214_default.state[2] - step_214_expanded.state[2];
4981 let step_214_yaw_delta = angle_diff(step_214_default.state[3], step_214_expanded.state[3]);
4982 let step_220_y_delta = step_220_default.state[1] - step_220_expanded.state[1];
4983 let step_220_speed_delta = step_220_default.state[2] - step_220_expanded.state[2];
4984 let step_220_yaw_delta = angle_diff(step_220_default.state[3], step_220_expanded.state[3]);
4985 let step_230_y_delta = step_230_default.state[1] - step_230_expanded.state[1];
4986 let step_230_speed_delta = step_230_default.state[2] - step_230_expanded.state[2];
4987 let step_230_yaw_delta = angle_diff(step_230_default.state[3], step_230_expanded.state[3]);
4988 let step_240_y_delta = step_240_default.state[1] - step_240_expanded.state[1];
4989 let step_240_speed_delta = step_240_default.state[2] - step_240_expanded.state[2];
4990 let step_240_yaw_delta = angle_diff(step_240_default.state[3], step_240_expanded.state[3]);
4991 let step_260_y_delta = step_260_default.state[1] - step_260_expanded.state[1];
4992 let step_260_yaw_delta = angle_diff(step_260_default.state[3], step_260_expanded.state[3]);
4993
4994 assert!(
4995 (0.0..=0.02).contains(&goal_distance_delta(step_213_default, step_213_expanded)),
4996 "step 213 delta moved unexpectedly: default={:?} expanded={:?}",
4997 step_213_default,
4998 step_213_expanded
4999 );
5000 assert!(
5001 (0.05..=0.09).contains(&goal_distance_delta(step_214_default, step_214_expanded)),
5002 "step 214 delta moved unexpectedly: default={:?} expanded={:?}",
5003 step_214_default,
5004 step_214_expanded
5005 );
5006 assert!(
5007 (0.42..=0.5).contains(&goal_distance_delta(step_220_default, step_220_expanded)),
5008 "step 220 delta moved unexpectedly: default={:?} expanded={:?}",
5009 step_220_default,
5010 step_220_expanded
5011 );
5012 assert!(
5013 (0.5..=0.65).contains(&goal_distance_delta(step_230_default, step_230_expanded)),
5014 "step 230 delta moved unexpectedly: default={:?} expanded={:?}",
5015 step_230_default,
5016 step_230_expanded
5017 );
5018 assert!(
5019 (0.35..=0.5).contains(&goal_distance_delta(step_240_default, step_240_expanded)),
5020 "step 240 delta moved unexpectedly: default={:?} expanded={:?}",
5021 step_240_default,
5022 step_240_expanded
5023 );
5024 assert!(
5025 (0.9..=1.05).contains(&goal_distance_delta(step_260_default, step_260_expanded)),
5026 "step 260 delta moved unexpectedly: default={:?} expanded={:?}",
5027 step_260_default,
5028 step_260_expanded
5029 );
5030
5031 assert_eq!(step_213_default.target_index, 103);
5032 assert_eq!(step_213_expanded.target_index, 103);
5033 assert_eq!(step_214_default.target_index, 103);
5034 assert_eq!(step_214_expanded.target_index, 104);
5035 assert_eq!(step_220_default.target_index, 105);
5036 assert_eq!(step_220_expanded.target_index, 106);
5037 assert_eq!(step_230_default.target_index, 109);
5038 assert_eq!(step_230_expanded.target_index, 111);
5039 assert_eq!(step_240_default.target_index, 115);
5040 assert_eq!(step_240_expanded.target_index, 117);
5041 assert_eq!(step_260_default.target_index, 127);
5042 assert_eq!(step_260_expanded.target_index, 129);
5043
5044 assert!(
5045 (-1.1..=-0.95).contains(&step_214_y_delta),
5046 "step 214 y delta moved unexpectedly: default={:?} expanded={:?}",
5047 step_214_default,
5048 step_214_expanded
5049 );
5050 assert!((0.25..=0.32).contains(&step_214_speed_delta));
5051 assert!((-0.22..=-0.12).contains(&step_214_yaw_delta));
5052 assert!(
5053 (-0.95..=-0.75).contains(&step_220_y_delta),
5054 "step 220 y delta moved unexpectedly: default={:?} expanded={:?}",
5055 step_220_default,
5056 step_220_expanded
5057 );
5058 assert!((0.12..=0.22).contains(&step_220_speed_delta));
5059 assert!((-0.12..=-0.03).contains(&step_220_yaw_delta));
5060 assert!(
5061 (-1.2..=-0.95).contains(&step_230_y_delta),
5062 "step 230 y delta moved unexpectedly: default={:?} expanded={:?}",
5063 step_230_default,
5064 step_230_expanded
5065 );
5066 assert!((0.05..=0.15).contains(&step_230_speed_delta));
5067 assert!((-0.05..=0.01).contains(&step_230_yaw_delta));
5068 assert!(
5069 (-1.0..=-0.75).contains(&step_240_y_delta),
5070 "step 240 y delta moved unexpectedly: default={:?} expanded={:?}",
5071 step_240_default,
5072 step_240_expanded
5073 );
5074 assert!((-0.06..=0.02).contains(&step_240_speed_delta));
5075 assert!((-0.09..=-0.03).contains(&step_240_yaw_delta));
5076 assert!(
5077 (0.3..=0.6).contains(&step_260_y_delta),
5078 "step 260 y delta moved unexpectedly: default={:?} expanded={:?}",
5079 step_260_default,
5080 step_260_expanded
5081 );
5082 assert!(
5083 (-0.27..=-0.15).contains(&step_260_yaw_delta),
5084 "step 260 yaw delta moved unexpectedly: default={:?} expanded={:?}",
5085 step_260_default,
5086 step_260_expanded
5087 );
5088 }
5089
5090 #[test]
5091 #[ignore = "long-running regression scenario"]
5092 fn test_mpc_switch_back_full_candidate_expansion_characterizes_late_window_to_finish() {
5093 let checkpoints = [260usize, 280, 300, 320, 340, 346];
5094 let terminal_checkpoints: Vec<_> = (320usize..=350).collect();
5095 let default_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
5096 MAX_SIM_STEPS,
5097 &checkpoints,
5098 optimize_linearized_controls_original,
5099 );
5100 let expanded_checkpoints = collect_switch_back_closed_loop_checkpoints_with_test_optimizer(
5101 MAX_SIM_STEPS,
5102 &checkpoints,
5103 optimize_linearized_controls_with_full_candidate_expansion,
5104 );
5105 let default_terminal = collect_switch_back_goal_condition_snapshots_with_test_optimizer(
5106 MAX_SIM_STEPS,
5107 &terminal_checkpoints,
5108 optimize_linearized_controls_original,
5109 );
5110 let expanded_terminal = collect_switch_back_goal_condition_snapshots_with_test_optimizer(
5111 MAX_SIM_STEPS,
5112 &terminal_checkpoints,
5113 optimize_linearized_controls_with_full_candidate_expansion,
5114 );
5115 let default_result = run_switch_back_simulation_with_test_optimizer(
5116 MAX_SIM_STEPS,
5117 optimize_linearized_controls_original,
5118 );
5119 let expanded_result = run_switch_back_simulation_with_test_optimizer(
5120 MAX_SIM_STEPS,
5121 optimize_linearized_controls_with_full_candidate_expansion,
5122 );
5123
5124 let step_260_default = checkpoint_at_step(&default_checkpoints, 260);
5125 let step_260_expanded = checkpoint_at_step(&expanded_checkpoints, 260);
5126 let step_280_default = checkpoint_at_step(&default_checkpoints, 280);
5127 let step_280_expanded = checkpoint_at_step(&expanded_checkpoints, 280);
5128 let step_300_default = checkpoint_at_step(&default_checkpoints, 300);
5129 let step_300_expanded = checkpoint_at_step(&expanded_checkpoints, 300);
5130 let step_320_default = checkpoint_at_step(&default_checkpoints, 320);
5131 let step_320_expanded = checkpoint_at_step(&expanded_checkpoints, 320);
5132 let step_340_default = checkpoint_at_step(&default_checkpoints, 340);
5133 let step_340_expanded = checkpoint_at_step(&expanded_checkpoints, 340);
5134 let step_346_default = checkpoint_at_step(&default_checkpoints, 346);
5135 let step_346_expanded = checkpoint_at_step(&expanded_checkpoints, 346);
5136 let (cx, cy, cyaw) = generate_switch_back_course();
5137
5138 let slope_260_280_default = goal_distance_slope(step_260_default, step_280_default);
5139 let slope_260_280_expanded = goal_distance_slope(step_260_expanded, step_280_expanded);
5140 let slope_280_300_default = goal_distance_slope(step_280_default, step_300_default);
5141 let slope_280_300_expanded = goal_distance_slope(step_280_expanded, step_300_expanded);
5142 let slope_300_320_default = goal_distance_slope(step_300_default, step_320_default);
5143 let slope_300_320_expanded = goal_distance_slope(step_300_expanded, step_320_expanded);
5144 let slope_320_340_default = goal_distance_slope(step_320_default, step_340_default);
5145 let slope_320_340_expanded = goal_distance_slope(step_320_expanded, step_340_expanded);
5146 let slope_340_346_default = goal_distance_slope(step_340_default, step_346_default);
5147 let slope_340_346_expanded = goal_distance_slope(step_340_expanded, step_346_expanded);
5148 let lat_abs_slope_300_320_default = scalar_slope(
5149 checkpoint_lateral_error(step_300_default, &cx, &cy, &cyaw).abs(),
5150 checkpoint_lateral_error(step_320_default, &cx, &cy, &cyaw).abs(),
5151 20,
5152 );
5153 let lat_abs_slope_300_320_expanded = scalar_slope(
5154 checkpoint_lateral_error(step_300_expanded, &cx, &cy, &cyaw).abs(),
5155 checkpoint_lateral_error(step_320_expanded, &cx, &cy, &cyaw).abs(),
5156 20,
5157 );
5158 let heading_abs_slope_300_320_default = scalar_slope(
5159 checkpoint_heading_error(step_300_default, &cyaw).abs(),
5160 checkpoint_heading_error(step_320_default, &cyaw).abs(),
5161 20,
5162 );
5163 let heading_abs_slope_300_320_expanded = scalar_slope(
5164 checkpoint_heading_error(step_300_expanded, &cyaw).abs(),
5165 checkpoint_heading_error(step_320_expanded, &cyaw).abs(),
5166 20,
5167 );
5168 let speed_slope_300_320_default =
5169 scalar_slope(step_300_default.state[2], step_320_default.state[2], 20);
5170 let speed_slope_300_320_expanded =
5171 scalar_slope(step_300_expanded.state[2], step_320_expanded.state[2], 20);
5172 let lat_abs_slope_320_340_default = scalar_slope(
5173 checkpoint_lateral_error(step_320_default, &cx, &cy, &cyaw).abs(),
5174 checkpoint_lateral_error(step_340_default, &cx, &cy, &cyaw).abs(),
5175 20,
5176 );
5177 let lat_abs_slope_320_340_expanded = scalar_slope(
5178 checkpoint_lateral_error(step_320_expanded, &cx, &cy, &cyaw).abs(),
5179 checkpoint_lateral_error(step_340_expanded, &cx, &cy, &cyaw).abs(),
5180 20,
5181 );
5182 let heading_abs_slope_320_340_default = scalar_slope(
5183 checkpoint_heading_error(step_320_default, &cyaw).abs(),
5184 checkpoint_heading_error(step_340_default, &cyaw).abs(),
5185 20,
5186 );
5187 let heading_abs_slope_320_340_expanded = scalar_slope(
5188 checkpoint_heading_error(step_320_expanded, &cyaw).abs(),
5189 checkpoint_heading_error(step_340_expanded, &cyaw).abs(),
5190 20,
5191 );
5192 let speed_slope_320_340_default =
5193 scalar_slope(step_320_default.state[2], step_340_default.state[2], 20);
5194 let speed_slope_320_340_expanded =
5195 scalar_slope(step_320_expanded.state[2], step_340_expanded.state[2], 20);
5196
5197 assert_eq!(
5198 target_index_increment(step_260_default, step_280_default),
5199 10
5200 );
5201 assert_eq!(
5202 target_index_increment(step_260_expanded, step_280_expanded),
5203 10
5204 );
5205 assert!(slope_260_280_expanded > slope_260_280_default);
5206
5207 assert_eq!(
5208 target_index_increment(step_280_default, step_300_default),
5209 10
5210 );
5211 assert_eq!(
5212 target_index_increment(step_280_expanded, step_300_expanded),
5213 11
5214 );
5215 assert!(slope_280_300_expanded > slope_280_300_default);
5216
5217 assert_eq!(
5218 target_index_increment(step_300_default, step_320_default),
5219 12
5220 );
5221 assert_eq!(
5222 target_index_increment(step_300_expanded, step_320_expanded),
5223 11
5224 );
5225 assert!(slope_300_320_default > slope_300_320_expanded);
5226 assert!(lat_abs_slope_300_320_default < lat_abs_slope_300_320_expanded - 2.0e-4);
5227 assert!(heading_abs_slope_300_320_default < heading_abs_slope_300_320_expanded - 2.0e-4);
5228 assert!(speed_slope_300_320_expanded > speed_slope_300_320_default + 0.05);
5229
5230 assert_eq!(step_320_default.target_index, 159);
5231 assert_eq!(step_320_expanded.target_index, 161);
5232 assert_eq!(
5233 target_index_increment(step_320_default, step_340_default),
5234 2
5235 );
5236 assert_eq!(
5237 target_index_increment(step_320_expanded, step_340_expanded),
5238 0
5239 );
5240 assert!(slope_320_340_default > 0.0);
5241 assert!(slope_320_340_expanded < 0.0);
5242 assert!(lat_abs_slope_320_340_expanded < lat_abs_slope_320_340_default - 5.0e-5);
5243 assert!(heading_abs_slope_320_340_default < heading_abs_slope_320_340_expanded - 1.0e-4);
5244 assert!(speed_slope_320_340_default > speed_slope_320_340_expanded + 0.04);
5245
5246 assert_eq!(step_340_default.target_index, 161);
5247 assert_eq!(step_340_expanded.target_index, 161);
5248 assert_eq!(
5249 target_index_increment(step_340_default, step_346_default),
5250 0
5251 );
5252 assert_eq!(
5253 target_index_increment(step_340_expanded, step_346_expanded),
5254 0
5255 );
5256 assert!(slope_340_346_default > slope_340_346_expanded);
5257 assert!(step_340_default.goal_distance > step_340_expanded.goal_distance + 0.4);
5258 assert!(step_340_default.state[2] > step_340_expanded.state[2] + 0.3);
5259 assert!(
5260 (0.15..=0.25).contains(&goal_distance_delta(step_346_default, step_346_expanded)),
5261 "step 346 delta moved unexpectedly: default={:?} expanded={:?}",
5262 step_346_default,
5263 step_346_expanded
5264 );
5265
5266 assert_eq!(default_result.hist_x.len() - 1, 350);
5267 assert_eq!(expanded_result.hist_x.len() - 1, 346);
5268
5269 assert_eq!(
5270 first_goal_condition_step(&default_terminal, |s| s.near_end),
5271 Some(320)
5272 );
5273 assert_eq!(
5274 first_goal_condition_step(&default_terminal, |s| s.near_goal),
5275 Some(321)
5276 );
5277 assert_eq!(
5278 first_goal_condition_step(&default_terminal, |s| s.stopped),
5279 Some(333)
5280 );
5281 assert_eq!(
5282 first_goal_condition_step(&default_terminal, |s| s.reached_goal),
5283 Some(350)
5284 );
5285
5286 assert_eq!(
5287 first_goal_condition_step(&expanded_terminal, |s| s.near_end),
5288 Some(320)
5289 );
5290 assert_eq!(
5291 first_goal_condition_step(&expanded_terminal, |s| s.near_goal),
5292 Some(320)
5293 );
5294 assert_eq!(
5295 first_goal_condition_step(&expanded_terminal, |s| s.stopped),
5296 Some(329)
5297 );
5298 assert_eq!(
5299 first_goal_condition_step(&expanded_terminal, |s| s.reached_goal),
5300 Some(346)
5301 );
5302
5303 let default_step_346 = goal_snapshot_at_step(&default_terminal, 346);
5304 let default_step_350 = goal_snapshot_at_step(&default_terminal, 350);
5305 let expanded_step_329 = goal_snapshot_at_step(&expanded_terminal, 329);
5306 let expanded_step_330 = goal_snapshot_at_step(&expanded_terminal, 330);
5307 let expanded_step_335 = goal_snapshot_at_step(&expanded_terminal, 335);
5308 let expanded_step_346 = goal_snapshot_at_step(&expanded_terminal, 346);
5309 let expanded_first_positive_speed =
5310 first_goal_condition_step(&expanded_terminal, |s| s.speed > 0.0);
5311 let expanded_reacquired_near_goal = expanded_terminal
5312 .iter()
5313 .skip_while(|snapshot| snapshot.near_goal)
5314 .find(|snapshot| snapshot.near_goal)
5315 .map(|snapshot| snapshot.step);
5316 let expanded_course_curvature =
5317 course_curvature_at_index(&cx, &cy, &cyaw, expanded_step_330.target_index);
5318
5319 assert!(default_step_346.near_goal);
5320 assert!(default_step_346.near_end);
5321 assert!(!default_step_346.stopped);
5322 assert!(!default_step_346.reached_goal);
5323
5324 assert!(!expanded_step_329.near_goal);
5325 assert!(expanded_step_329.near_end);
5326 assert!(expanded_step_329.stopped);
5327 assert!(expanded_step_329.speed < 0.0);
5328 assert!(expanded_step_329.motion_curvature > 0.0);
5329
5330 assert!(!expanded_step_330.near_goal);
5331 assert!(expanded_step_330.near_end);
5332 assert!(!expanded_step_330.stopped);
5333 assert!(!expanded_step_330.reached_goal);
5334 assert_eq!(expanded_first_positive_speed, Some(330));
5335 assert_eq!(expanded_reacquired_near_goal, Some(335));
5336 assert!(expanded_step_330.speed > 0.0);
5337 assert!(expanded_step_330.motion_curvature < 0.0);
5338 assert!(expanded_course_curvature > 0.0);
5339
5340 assert!(expanded_step_335.near_goal);
5341 assert!(expanded_step_335.near_end);
5342 assert!(!expanded_step_335.stopped);
5343 assert!(!expanded_step_335.reached_goal);
5344 assert!(expanded_step_335.goal_distance < GOAL_DIS);
5345 assert!(expanded_step_335.heading_error.abs() < expanded_step_330.heading_error.abs());
5346 assert!(expanded_step_335.motion_curvature < 0.0);
5347 assert!(expanded_step_335.goal_distance < expanded_step_330.goal_distance);
5348 assert!(expanded_step_335.speed > expanded_step_330.speed);
5349
5350 assert!(expanded_step_346.near_goal);
5351 assert!(expanded_step_346.near_end);
5352 assert!(expanded_step_346.stopped);
5353 assert!(expanded_step_346.reached_goal);
5354
5355 assert!(default_step_350.near_goal);
5356 assert!(default_step_350.near_end);
5357 assert!(default_step_350.stopped);
5358 assert!(default_step_350.reached_goal);
5359 }
5360
5361 #[test]
5362 #[ignore = "long-running regression scenario"]
5363 fn test_mpc_switch_back_full_candidate_expansion_characterizes_terminal_reentry_controls() {
5364 let checkpoints: Vec<_> = (329usize..=350).collect();
5365 let default_snapshots = collect_switch_back_goal_control_snapshots_with_test_optimizer(
5366 MAX_SIM_STEPS,
5367 &checkpoints,
5368 optimize_linearized_controls_original,
5369 );
5370 let expanded_snapshots = collect_switch_back_goal_control_snapshots_with_test_optimizer(
5371 MAX_SIM_STEPS,
5372 &checkpoints,
5373 optimize_linearized_controls_with_full_candidate_expansion,
5374 );
5375
5376 let default_first_stopped =
5377 first_goal_control_condition_step(&default_snapshots, |snapshot| snapshot.stopped);
5378 let expanded_first_stopped =
5379 first_goal_control_condition_step(&expanded_snapshots, |snapshot| snapshot.stopped);
5380 let default_first_positive_speed =
5381 first_goal_control_condition_step(&default_snapshots, |snapshot| snapshot.speed > 0.0);
5382 let expanded_first_positive_speed =
5383 first_goal_control_condition_step(&expanded_snapshots, |snapshot| snapshot.speed > 0.0);
5384 let default_first_negative_steer =
5385 first_goal_control_condition_step(&default_snapshots, |snapshot| snapshot.steer < 0.0);
5386 let expanded_first_negative_steer =
5387 first_goal_control_condition_step(&expanded_snapshots, |snapshot| snapshot.steer < 0.0);
5388 let default_reacquired_near_goal =
5389 first_goal_control_condition_step(&default_snapshots, |snapshot| snapshot.near_goal);
5390 let expanded_reacquired_near_goal =
5391 first_goal_control_condition_step(&expanded_snapshots, |snapshot| snapshot.near_goal);
5392
5393 assert_eq!(default_first_stopped, Some(333));
5394 assert_eq!(expanded_first_stopped, Some(329));
5395 assert_eq!(default_first_positive_speed, Some(334));
5396 assert_eq!(expanded_first_positive_speed, Some(330));
5397 assert_eq!(default_first_negative_steer, Some(334));
5398 assert_eq!(expanded_first_negative_steer, Some(330));
5399 assert_eq!(default_reacquired_near_goal, Some(339));
5400 assert_eq!(expanded_reacquired_near_goal, Some(335));
5401 assert_eq!(
5402 default_first_positive_speed.unwrap() - default_first_stopped.unwrap(),
5403 1
5404 );
5405 assert_eq!(
5406 expanded_first_positive_speed.unwrap() - expanded_first_stopped.unwrap(),
5407 1
5408 );
5409 assert_eq!(
5410 default_reacquired_near_goal.unwrap() - default_first_positive_speed.unwrap(),
5411 5
5412 );
5413 assert_eq!(
5414 expanded_reacquired_near_goal.unwrap() - expanded_first_positive_speed.unwrap(),
5415 5
5416 );
5417 assert_eq!(
5418 default_first_positive_speed.unwrap() - expanded_first_positive_speed.unwrap(),
5419 4
5420 );
5421 assert_eq!(
5422 default_reacquired_near_goal.unwrap() - expanded_reacquired_near_goal.unwrap(),
5423 4
5424 );
5425
5426 let default_stop = goal_control_snapshot_at_step(&default_snapshots, 333);
5427 let expanded_stop = goal_control_snapshot_at_step(&expanded_snapshots, 329);
5428 let default_turn = goal_control_snapshot_at_step(&default_snapshots, 334);
5429 let expanded_turn = goal_control_snapshot_at_step(&expanded_snapshots, 330);
5430 let default_reentry = goal_control_snapshot_at_step(&default_snapshots, 339);
5431 let expanded_reentry = goal_control_snapshot_at_step(&expanded_snapshots, 335);
5432 let default_refinement = goal_control_snapshot_at_step(&default_snapshots, 346);
5433 let expanded_refinement = goal_control_snapshot_at_step(&expanded_snapshots, 342);
5434
5435 assert_goal_control_alignment(default_stop, expanded_stop);
5436 assert_goal_control_alignment(default_turn, expanded_turn);
5437 assert_goal_control_alignment(default_reentry, expanded_reentry);
5438 assert_goal_control_alignment(default_refinement, expanded_refinement);
5439
5440 assert!(default_stop.speed < 0.0);
5441 assert!(expanded_stop.speed < 0.0);
5442 assert!(default_stop.steer > 0.0);
5443 assert!(expanded_stop.steer > 0.0);
5444 assert!(default_stop.motion_curvature > 0.0);
5445 assert!(expanded_stop.motion_curvature > 0.0);
5446
5447 assert!(default_turn.speed > 0.0);
5448 assert!(expanded_turn.speed > 0.0);
5449 assert!(default_turn.steer < 0.0);
5450 assert!(expanded_turn.steer < 0.0);
5451 assert!(default_turn.motion_curvature < 0.0);
5452 assert!(expanded_turn.motion_curvature < 0.0);
5453 assert!(default_turn.course_curvature > 0.0);
5454 assert!(expanded_turn.course_curvature > 0.0);
5455
5456 assert!(default_reentry.near_goal);
5457 assert!(expanded_reentry.near_goal);
5458 assert!(default_reentry.speed > 1.0);
5459 assert!(expanded_reentry.speed > 1.0);
5460 assert!(default_reentry.motion_curvature < 0.0);
5461 assert!(expanded_reentry.motion_curvature < 0.0);
5462
5463 assert!(default_refinement.near_goal);
5464 assert!(expanded_refinement.near_goal);
5465 assert!(!default_refinement.stopped);
5466 assert!(!expanded_refinement.stopped);
5467 }
5468
5469 #[test]
5470 #[ignore = "long-running regression scenario"]
5471 fn test_mpc_switch_back_full_candidate_expansion_characterizes_terminal_planned_phase_lead() {
5472 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5473 346,
5474 optimize_linearized_controls_original,
5475 );
5476 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5477 342,
5478 optimize_linearized_controls_with_full_candidate_expansion,
5479 );
5480
5481 let default_stop = detailed_row_at_step(&default_trace, 332);
5482 let expanded_stop = detailed_row_at_step(&expanded_trace, 328);
5483 let default_turn = detailed_row_at_step(&default_trace, 333);
5484 let expanded_turn = detailed_row_at_step(&expanded_trace, 329);
5485 let default_reentry = detailed_row_at_step(&default_trace, 338);
5486 let expanded_reentry = detailed_row_at_step(&expanded_trace, 334);
5487 let default_refinement = detailed_row_at_step(&default_trace, 345);
5488 let expanded_refinement = detailed_row_at_step(&expanded_trace, 341);
5489
5490 assert_detailed_phase_alignment("terminal_stop", default_stop, expanded_stop);
5491 assert_detailed_phase_alignment("terminal_turn", default_turn, expanded_turn);
5492 assert_detailed_phase_alignment("terminal_reentry", default_reentry, expanded_reentry);
5493 assert_detailed_phase_alignment(
5494 "terminal_refinement",
5495 default_refinement,
5496 expanded_refinement,
5497 );
5498
5499 assert!(default_stop.controls[0][1] > 0.0);
5500 assert!(expanded_stop.controls[0][1] > 0.0);
5501 assert!(default_turn.controls[0][1] < 0.0);
5502 assert!(expanded_turn.controls[0][1] < 0.0);
5503 assert!(default_reentry.controls[0][0] > 0.2);
5504 assert!(expanded_reentry.controls[0][0] > 0.2);
5505 assert!(default_refinement.controls[0][0] < 0.0);
5506 assert!(expanded_refinement.controls[0][0] < 0.0);
5507 }
5508
5509 #[test]
5510 #[ignore = "long-running regression scenario"]
5511 fn test_mpc_switch_back_full_candidate_expansion_characterizes_pre_terminal_phase_origin() {
5512 let checkpoints = [321usize, 322, 323, 324, 325, 326, 327, 328, 329];
5513 let default_snapshots = collect_switch_back_planning_snapshots_with_test_optimizer(
5514 330,
5515 &checkpoints,
5516 optimize_linearized_controls_original,
5517 );
5518 let expanded_snapshots = collect_switch_back_planning_snapshots_with_test_optimizer(
5519 330,
5520 &checkpoints,
5521 optimize_linearized_controls_with_full_candidate_expansion,
5522 );
5523
5524 let same_325_default = planning_snapshot_at_step(&default_snapshots, 325);
5525 let same_325_expanded = planning_snapshot_at_step(&expanded_snapshots, 325);
5526 let same_328_default = planning_snapshot_at_step(&default_snapshots, 328);
5527 let same_328_expanded = planning_snapshot_at_step(&expanded_snapshots, 328);
5528 let same_329_default = planning_snapshot_at_step(&default_snapshots, 329);
5529 let same_329_expanded = planning_snapshot_at_step(&expanded_snapshots, 329);
5530
5531 assert_eq!(same_325_default.target_index, 161);
5532 assert_eq!(same_325_expanded.target_index, 161);
5533 assert_diff_bounds(
5534 "pre_terminal_same_325_xref_head",
5535 trace_state_diff(&same_325_default.xref_head, &same_325_expanded.xref_head),
5536 [1e-10; 4],
5537 );
5538 assert_diff_bounds(
5539 "pre_terminal_same_328_xref_head",
5540 trace_state_diff(&same_328_default.xref_head, &same_328_expanded.xref_head),
5541 [1e-10; 4],
5542 );
5543 assert_diff_bounds(
5544 "pre_terminal_same_329_xref_head",
5545 trace_state_diff(&same_329_default.xref_head, &same_329_expanded.xref_head),
5546 [1e-10; 4],
5547 );
5548
5549 assert!(same_325_default.state[1] - same_325_expanded.state[1] > 1.0);
5550 assert!(same_325_expanded.state[2] - same_325_default.state[2] > 0.79);
5551 assert!(same_328_default.state[1] - same_328_expanded.state[1] > 0.55);
5552 assert!(same_328_expanded.state[2] - same_328_default.state[2] > 0.79);
5553 assert!(same_325_default.warm_start_head[1] < 0.0);
5554 assert!(same_325_expanded.warm_start_head[1] > 0.0);
5555 assert!(same_328_default.warm_start_head[1] < 0.0);
5556 assert!(same_328_expanded.warm_start_head[1] > 0.0);
5557 assert!(same_329_default.warm_start_head[1] > 0.0);
5558 assert!(same_329_expanded.warm_start_head[1] > 0.0);
5559
5560 for (default_step, expanded_step) in [
5561 (325usize, 321usize),
5562 (326, 322),
5563 (327, 323),
5564 (328, 324),
5565 (329, 325),
5566 ] {
5567 let default = planning_snapshot_at_step(&default_snapshots, default_step);
5568 let expanded = planning_snapshot_at_step(&expanded_snapshots, expanded_step);
5569 assert_planning_snapshot_phase_alignment(
5570 &format!("pre_terminal_shifted_{default_step}_{expanded_step}"),
5571 default,
5572 expanded,
5573 );
5574 }
5575 }
5576
5577 #[test]
5578 #[ignore = "long-running regression scenario"]
5579 fn test_mpc_switch_back_full_candidate_expansion_characterizes_shifted_warm_start_injection() {
5580 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5581 334,
5582 optimize_linearized_controls_original,
5583 );
5584 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5585 330,
5586 optimize_linearized_controls_with_full_candidate_expansion,
5587 );
5588 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5589 334,
5590 optimize_linearized_controls_original,
5591 );
5592 let expanded_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5593 330,
5594 optimize_linearized_controls_with_full_candidate_expansion,
5595 );
5596
5597 for (default_step, expanded_step) in [
5598 (325usize, 321usize),
5599 (326, 322),
5600 (327, 323),
5601 (328, 324),
5602 (329, 325),
5603 ] {
5604 let default_row = detailed_row_at_step(&default_trace, default_step);
5605 let expanded_row = detailed_row_at_step(&expanded_trace, default_step);
5606 let injected_default = iterative_linear_mpc_control_with_test_optimizer(
5607 &default_row.xref,
5608 &state_from_vector4(&default_row.state),
5609 expanded_warm_start.get(&expanded_step).unwrap(),
5610 optimize_linearized_controls_original,
5611 );
5612 let injected_expanded = iterative_linear_mpc_control_with_test_optimizer(
5613 &expanded_row.xref,
5614 &state_from_vector4(&expanded_row.state),
5615 default_warm_start.get(&(default_step + 4)).unwrap(),
5616 optimize_linearized_controls_with_full_candidate_expansion,
5617 );
5618
5619 let default_control_rows = default_row
5620 .controls
5621 .iter()
5622 .map(|vec| [vec[0], vec[1]])
5623 .collect::<Vec<_>>();
5624 let default_predicted_rows = default_row
5625 .predicted
5626 .iter()
5627 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5628 .collect::<Vec<_>>();
5629 let expanded_control_rows = expanded_row
5630 .controls
5631 .iter()
5632 .map(|vec| [vec[0], vec[1]])
5633 .collect::<Vec<_>>();
5634 let expanded_predicted_rows = expanded_row
5635 .predicted
5636 .iter()
5637 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5638 .collect::<Vec<_>>();
5639
5640 assert_diff_bounds(
5641 &format!("shifted_warm_default_step_{default_step}_control"),
5642 summarize_vec2_sequence_diff(&injected_default.controls, &default_control_rows),
5643 [1e-3, 6e-4],
5644 );
5645 assert_diff_bounds(
5646 &format!("shifted_warm_default_step_{default_step}_predicted"),
5647 summarize_vec4_sequence_diff(&injected_default.predicted, &default_predicted_rows),
5648 [1.5e-3, 1.5e-3, 4e-4, 4e-4],
5649 );
5650 assert_diff_bounds(
5651 &format!("shifted_warm_expanded_step_{default_step}_control"),
5652 summarize_vec2_sequence_diff(&injected_expanded.controls, &expanded_control_rows),
5653 [1e-3, 6e-4],
5654 );
5655 assert_diff_bounds(
5656 &format!("shifted_warm_expanded_step_{default_step}_predicted"),
5657 summarize_vec4_sequence_diff(
5658 &injected_expanded.predicted,
5659 &expanded_predicted_rows,
5660 ),
5661 [1.5e-3, 1.5e-3, 4e-4, 4e-4],
5662 );
5663 }
5664 }
5665
5666 #[test]
5667 #[ignore = "long-running regression scenario"]
5668 fn test_mpc_switch_back_full_candidate_expansion_characterizes_same_time_warm_start_injection()
5669 {
5670 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5671 330,
5672 optimize_linearized_controls_original,
5673 );
5674 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5675 330,
5676 optimize_linearized_controls_with_full_candidate_expansion,
5677 );
5678 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5679 330,
5680 optimize_linearized_controls_original,
5681 );
5682 let expanded_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5683 330,
5684 optimize_linearized_controls_with_full_candidate_expansion,
5685 );
5686
5687 for step in [325usize, 326, 327, 328, 329] {
5688 let default_row = detailed_row_at_step(&default_trace, step);
5689 let expanded_row = detailed_row_at_step(&expanded_trace, step);
5690 let default_with_expanded_warm = iterative_linear_mpc_control_with_test_optimizer(
5691 &default_row.xref,
5692 &state_from_vector4(&default_row.state),
5693 expanded_warm_start.get(&step).unwrap(),
5694 optimize_linearized_controls_original,
5695 );
5696 let expanded_with_default_warm = iterative_linear_mpc_control_with_test_optimizer(
5697 &expanded_row.xref,
5698 &state_from_vector4(&expanded_row.state),
5699 default_warm_start.get(&step).unwrap(),
5700 optimize_linearized_controls_with_full_candidate_expansion,
5701 );
5702
5703 let default_baseline_controls = default_row
5704 .controls
5705 .iter()
5706 .map(|vec| [vec[0], vec[1]])
5707 .collect::<Vec<_>>();
5708 let expanded_baseline_controls = expanded_row
5709 .controls
5710 .iter()
5711 .map(|vec| [vec[0], vec[1]])
5712 .collect::<Vec<_>>();
5713 let default_baseline_predicted = default_row
5714 .predicted
5715 .iter()
5716 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5717 .collect::<Vec<_>>();
5718 let expanded_baseline_predicted = expanded_row
5719 .predicted
5720 .iter()
5721 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5722 .collect::<Vec<_>>();
5723
5724 let default_to_default_control = summarize_vec2_sequence_diff(
5725 &default_with_expanded_warm.controls,
5726 &default_baseline_controls,
5727 );
5728 let default_to_expanded_control = summarize_vec2_sequence_diff(
5729 &default_with_expanded_warm.controls,
5730 &expanded_baseline_controls,
5731 );
5732 let default_to_default_predicted = summarize_vec4_sequence_diff(
5733 &default_with_expanded_warm.predicted,
5734 &default_baseline_predicted,
5735 );
5736 let default_to_expanded_predicted = summarize_vec4_sequence_diff(
5737 &default_with_expanded_warm.predicted,
5738 &expanded_baseline_predicted,
5739 );
5740 let expanded_to_expanded_control = summarize_vec2_sequence_diff(
5741 &expanded_with_default_warm.controls,
5742 &expanded_baseline_controls,
5743 );
5744 let expanded_to_default_control = summarize_vec2_sequence_diff(
5745 &expanded_with_default_warm.controls,
5746 &default_baseline_controls,
5747 );
5748 let expanded_to_expanded_predicted = summarize_vec4_sequence_diff(
5749 &expanded_with_default_warm.predicted,
5750 &expanded_baseline_predicted,
5751 );
5752 let expanded_to_default_predicted = summarize_vec4_sequence_diff(
5753 &expanded_with_default_warm.predicted,
5754 &default_baseline_predicted,
5755 );
5756
5757 assert_diff_bounds(
5758 &format!("same_time_warm_default_step_{step}_control"),
5759 default_to_default_control,
5760 [1.6e-2, 1.1e-2],
5761 );
5762 assert_diff_bounds(
5763 &format!("same_time_warm_default_step_{step}_predicted"),
5764 default_to_default_predicted,
5765 [2.5e-3, 3e-4, 3.5e-3, 5e-3],
5766 );
5767 assert!(
5768 default_to_expanded_control[0] > 0.7,
5769 "same-time default branch drifted toward expanded control unexpectedly: step={} same={:?} opposite={:?}",
5770 step,
5771 default_to_default_control,
5772 default_to_expanded_control
5773 );
5774 assert!(
5775 default_to_expanded_predicted[1] > 0.4
5776 && default_to_expanded_predicted[2] > 0.79,
5777 "same-time default branch drifted toward expanded prediction unexpectedly: step={} same={:?} opposite={:?}",
5778 step,
5779 default_to_default_predicted,
5780 default_to_expanded_predicted
5781 );
5782
5783 assert_diff_bounds(
5784 &format!("same_time_warm_expanded_step_{step}_control"),
5785 expanded_to_expanded_control,
5786 [6.5e-3, 3.6e-3],
5787 );
5788 assert_diff_bounds(
5789 &format!("same_time_warm_expanded_step_{step}_predicted"),
5790 expanded_to_expanded_predicted,
5791 [1e-4, 3e-4, 1.3e-3, 6e-4],
5792 );
5793 assert!(
5794 expanded_to_default_control[0] > 0.7,
5795 "same-time expanded branch drifted toward default control unexpectedly: step={} same={:?} opposite={:?}",
5796 step,
5797 expanded_to_expanded_control,
5798 expanded_to_default_control
5799 );
5800 assert!(
5801 expanded_to_default_predicted[1] > 0.4
5802 && expanded_to_default_predicted[2] > 0.79,
5803 "same-time expanded branch drifted toward default prediction unexpectedly: step={} same={:?} opposite={:?}",
5804 step,
5805 expanded_to_expanded_predicted,
5806 expanded_to_default_predicted
5807 );
5808 }
5809 }
5810
5811 #[test]
5812 #[ignore = "long-running regression scenario"]
5813 fn test_mpc_switch_back_full_candidate_expansion_characterizes_same_time_state_injection() {
5814 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5815 330,
5816 optimize_linearized_controls_original,
5817 );
5818 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
5819 330,
5820 optimize_linearized_controls_with_full_candidate_expansion,
5821 );
5822 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5823 330,
5824 optimize_linearized_controls_original,
5825 );
5826 let expanded_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
5827 330,
5828 optimize_linearized_controls_with_full_candidate_expansion,
5829 );
5830
5831 for step in [325usize, 326, 327, 328, 329] {
5832 let default_row = detailed_row_at_step(&default_trace, step);
5833 let expanded_row = detailed_row_at_step(&expanded_trace, step);
5834 let default_with_expanded_state = iterative_linear_mpc_control_with_test_optimizer(
5835 &default_row.xref,
5836 &state_from_vector4(&expanded_row.state),
5837 default_warm_start.get(&step).unwrap(),
5838 optimize_linearized_controls_original,
5839 );
5840 let expanded_with_default_state = iterative_linear_mpc_control_with_test_optimizer(
5841 &expanded_row.xref,
5842 &state_from_vector4(&default_row.state),
5843 expanded_warm_start.get(&step).unwrap(),
5844 optimize_linearized_controls_with_full_candidate_expansion,
5845 );
5846
5847 let default_baseline_controls = default_row
5848 .controls
5849 .iter()
5850 .map(|vec| [vec[0], vec[1]])
5851 .collect::<Vec<_>>();
5852 let expanded_baseline_controls = expanded_row
5853 .controls
5854 .iter()
5855 .map(|vec| [vec[0], vec[1]])
5856 .collect::<Vec<_>>();
5857 let default_baseline_predicted = default_row
5858 .predicted
5859 .iter()
5860 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5861 .collect::<Vec<_>>();
5862 let expanded_baseline_predicted = expanded_row
5863 .predicted
5864 .iter()
5865 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
5866 .collect::<Vec<_>>();
5867
5868 let default_to_default_control = summarize_vec2_sequence_diff(
5869 &default_with_expanded_state.controls,
5870 &default_baseline_controls,
5871 );
5872 let default_to_expanded_control = summarize_vec2_sequence_diff(
5873 &default_with_expanded_state.controls,
5874 &expanded_baseline_controls,
5875 );
5876 let default_to_default_predicted = summarize_vec4_sequence_diff(
5877 &default_with_expanded_state.predicted,
5878 &default_baseline_predicted,
5879 );
5880 let default_to_expanded_predicted = summarize_vec4_sequence_diff(
5881 &default_with_expanded_state.predicted,
5882 &expanded_baseline_predicted,
5883 );
5884 let expanded_to_expanded_control = summarize_vec2_sequence_diff(
5885 &expanded_with_default_state.controls,
5886 &expanded_baseline_controls,
5887 );
5888 let expanded_to_default_control = summarize_vec2_sequence_diff(
5889 &expanded_with_default_state.controls,
5890 &default_baseline_controls,
5891 );
5892 let expanded_to_expanded_predicted = summarize_vec4_sequence_diff(
5893 &expanded_with_default_state.predicted,
5894 &expanded_baseline_predicted,
5895 );
5896 let expanded_to_default_predicted = summarize_vec4_sequence_diff(
5897 &expanded_with_default_state.predicted,
5898 &default_baseline_predicted,
5899 );
5900
5901 assert!(
5902 default_to_default_control[0] > 0.5,
5903 "same-time default-state injection did not move control away from default baseline enough: step={} same={:?} opposite={:?}",
5904 step,
5905 default_to_default_control,
5906 default_to_expanded_control
5907 );
5908 assert!(
5909 default_to_default_predicted[1] > 0.35 && default_to_default_predicted[2] > 0.7,
5910 "same-time default-state injection did not move prediction away from default baseline enough: step={} same={:?} opposite={:?}",
5911 step,
5912 default_to_default_predicted,
5913 default_to_expanded_predicted
5914 );
5915 assert!(
5916 default_to_default_control[0] > 4.0 * default_to_expanded_control[0],
5917 "same-time default-state injection stayed too close to default control branch: step={} same={:?} opposite={:?}",
5918 step,
5919 default_to_default_control,
5920 default_to_expanded_control
5921 );
5922 assert!(
5923 default_to_default_predicted[1] > 4.0 * default_to_expanded_predicted[1]
5924 && default_to_default_predicted[2] > 4.0 * default_to_expanded_predicted[2],
5925 "same-time default-state injection stayed too close to default predicted branch: step={} same={:?} opposite={:?}",
5926 step,
5927 default_to_default_predicted,
5928 default_to_expanded_predicted
5929 );
5930
5931 assert!(
5932 expanded_to_expanded_control[0] > 0.5,
5933 "same-time expanded-state injection did not move control away from expanded baseline enough: step={} same={:?} opposite={:?}",
5934 step,
5935 expanded_to_expanded_control,
5936 expanded_to_default_control
5937 );
5938 assert!(
5939 expanded_to_expanded_predicted[1] > 0.35
5940 && expanded_to_expanded_predicted[2] > 0.7,
5941 "same-time expanded-state injection did not move prediction away from expanded baseline enough: step={} same={:?} opposite={:?}",
5942 step,
5943 expanded_to_expanded_predicted,
5944 expanded_to_default_predicted
5945 );
5946 assert!(
5947 expanded_to_expanded_control[0] > 4.0 * expanded_to_default_control[0],
5948 "same-time expanded-state injection stayed too close to expanded control branch: step={} same={:?} opposite={:?}",
5949 step,
5950 expanded_to_expanded_control,
5951 expanded_to_default_control
5952 );
5953 assert!(
5954 expanded_to_expanded_predicted[1] > 4.0 * expanded_to_default_predicted[1]
5955 && expanded_to_expanded_predicted[2] > 4.0 * expanded_to_default_predicted[2],
5956 "same-time expanded-state injection stayed too close to expanded predicted branch: step={} same={:?} opposite={:?}",
5957 step,
5958 expanded_to_expanded_predicted,
5959 expanded_to_default_predicted
5960 );
5961 }
5962 }
5963
5964 #[test]
5965 #[ignore = "long-running regression scenario"]
5966 fn test_mpc_switch_back_full_candidate_expansion_characterizes_state_component_injection() {
5967 let x_metrics = measure_switch_back_state_injection(&[0usize]);
5968 let y_metrics = measure_switch_back_state_injection(&[1usize]);
5969 let v_metrics = measure_switch_back_state_injection(&[2usize]);
5970 let yaw_metrics = measure_switch_back_state_injection(&[3usize]);
5971
5972 assert!(
5973 x_metrics.default_same_control0 < x_metrics.default_opposite_control0 / 100.0
5974 && x_metrics.default_same_pred_y < x_metrics.default_opposite_pred_y / 100.0
5975 && x_metrics.default_same_pred_v < x_metrics.default_opposite_pred_v / 100.0,
5976 "x-only injection moved the default branch more than expected: {:?}",
5977 x_metrics
5978 );
5979 assert!(
5980 x_metrics.expanded_same_control0 < x_metrics.expanded_opposite_control0 / 100.0
5981 && x_metrics.expanded_same_pred_y < x_metrics.expanded_opposite_pred_y / 100.0
5982 && x_metrics.expanded_same_pred_v < x_metrics.expanded_opposite_pred_v / 100.0,
5983 "x-only injection moved the expanded branch more than expected: {:?}",
5984 x_metrics
5985 );
5986
5987 assert!(
5988 yaw_metrics.default_same_control0 < yaw_metrics.default_opposite_control0 / 100.0
5989 && yaw_metrics.default_same_pred_y < yaw_metrics.default_opposite_pred_y / 100.0
5990 && yaw_metrics.default_same_pred_v < yaw_metrics.default_opposite_pred_v / 100.0,
5991 "yaw-only injection moved the default branch more than expected: {:?}",
5992 yaw_metrics
5993 );
5994 assert!(
5995 yaw_metrics.expanded_same_control0 < yaw_metrics.expanded_opposite_control0 / 100.0
5996 && yaw_metrics.expanded_same_pred_y < yaw_metrics.expanded_opposite_pred_y / 100.0
5997 && yaw_metrics.expanded_same_pred_v < yaw_metrics.expanded_opposite_pred_v / 100.0,
5998 "yaw-only injection moved the expanded branch more than expected: {:?}",
5999 yaw_metrics
6000 );
6001
6002 assert!(
6003 y_metrics.default_same_control0 < y_metrics.default_opposite_control0 / 4.0
6004 && y_metrics.default_same_pred_v < y_metrics.default_opposite_pred_v / 4.0
6005 && y_metrics.default_same_pred_y > 3.0,
6006 "y-only injection changed the default branch characterization unexpectedly: {:?}",
6007 y_metrics
6008 );
6009 assert!(
6010 y_metrics.expanded_same_control0 < y_metrics.expanded_opposite_control0 / 4.0
6011 && y_metrics.expanded_same_pred_v < y_metrics.expanded_opposite_pred_v / 4.0
6012 && y_metrics.expanded_same_pred_y > 3.0,
6013 "y-only injection changed the expanded branch characterization unexpectedly: {:?}",
6014 y_metrics
6015 );
6016
6017 assert!(
6018 v_metrics.default_same_control0 > 4.0 * v_metrics.default_opposite_control0
6019 && v_metrics.default_same_pred_v > 4.0 * v_metrics.default_opposite_pred_v,
6020 "v-only injection did not pull the default branch toward the opposite phase enough: {:?}",
6021 v_metrics
6022 );
6023 assert!(
6024 v_metrics.expanded_same_control0 > 4.0 * v_metrics.expanded_opposite_control0
6025 && v_metrics.expanded_same_pred_v > 4.0 * v_metrics.expanded_opposite_pred_v,
6026 "v-only injection did not pull the expanded branch toward the opposite phase enough: {:?}",
6027 v_metrics
6028 );
6029 }
6030
6031 #[test]
6032 #[ignore = "long-running regression scenario"]
6033 fn test_mpc_switch_back_full_candidate_expansion_characterizes_state_subset_injection() {
6034 let vy_metrics = measure_switch_back_state_injection(&[1usize, 2usize]);
6035 let vyyaw_metrics = measure_switch_back_state_injection(&[1usize, 2usize, 3usize]);
6036
6037 assert!(
6038 vy_metrics.default_same_control0 > 100.0 * vy_metrics.default_opposite_control0
6039 && vy_metrics.default_same_pred_y > 100.0 * vy_metrics.default_opposite_pred_y
6040 && vy_metrics.default_same_pred_v > 100.0 * vy_metrics.default_opposite_pred_v,
6041 "v+y injection did not pull the default branch strongly enough toward the opposite phase: {:?}",
6042 vy_metrics
6043 );
6044 assert!(
6045 vy_metrics.expanded_same_control0 > 100.0 * vy_metrics.expanded_opposite_control0
6046 && vy_metrics.expanded_same_pred_y > 100.0 * vy_metrics.expanded_opposite_pred_y
6047 && vy_metrics.expanded_same_pred_v > 100.0 * vy_metrics.expanded_opposite_pred_v,
6048 "v+y injection did not pull the expanded branch strongly enough toward the opposite phase: {:?}",
6049 vy_metrics
6050 );
6051
6052 assert!(
6053 vyyaw_metrics.default_same_control0 > 100.0 * vyyaw_metrics.default_opposite_control0
6054 && vyyaw_metrics.default_same_pred_y
6055 > 100.0 * vyyaw_metrics.default_opposite_pred_y
6056 && vyyaw_metrics.default_same_pred_v
6057 > 100.0 * vyyaw_metrics.default_opposite_pred_v,
6058 "v+y+yaw injection did not pull the default branch strongly enough toward the opposite phase: {:?}",
6059 vyyaw_metrics
6060 );
6061 assert!(
6062 vyyaw_metrics.expanded_same_control0
6063 > 100.0 * vyyaw_metrics.expanded_opposite_control0
6064 && vyyaw_metrics.expanded_same_pred_y
6065 > 100.0 * vyyaw_metrics.expanded_opposite_pred_y
6066 && vyyaw_metrics.expanded_same_pred_v
6067 > 100.0 * vyyaw_metrics.expanded_opposite_pred_v,
6068 "v+y+yaw injection did not pull the expanded branch strongly enough toward the opposite phase: {:?}",
6069 vyyaw_metrics
6070 );
6071
6072 assert!(
6073 (vy_metrics.default_same_control0 - vyyaw_metrics.default_same_control0).abs() <= 1e-3
6074 && (vy_metrics.default_opposite_control0
6075 - vyyaw_metrics.default_opposite_control0)
6076 .abs()
6077 <= 1e-3
6078 && (vy_metrics.default_opposite_pred_y - vyyaw_metrics.default_opposite_pred_y)
6079 .abs()
6080 <= 1e-4
6081 && (vy_metrics.default_opposite_pred_v - vyyaw_metrics.default_opposite_pred_v)
6082 .abs()
6083 <= 2e-4,
6084 "adding yaw to v+y changed the default-branch subset metrics more than expected: vy={:?} vyyaw={:?}",
6085 vy_metrics,
6086 vyyaw_metrics
6087 );
6088 assert!(
6089 (vy_metrics.expanded_same_control0 - vyyaw_metrics.expanded_same_control0).abs()
6090 <= 1e-3
6091 && (vy_metrics.expanded_opposite_control0
6092 - vyyaw_metrics.expanded_opposite_control0)
6093 .abs()
6094 <= 1e-3
6095 && (vy_metrics.expanded_opposite_pred_y - vyyaw_metrics.expanded_opposite_pred_y)
6096 .abs()
6097 <= 1e-4
6098 && (vy_metrics.expanded_opposite_pred_v - vyyaw_metrics.expanded_opposite_pred_v)
6099 .abs()
6100 <= 2e-4,
6101 "adding yaw to v+y changed the expanded-branch subset metrics more than expected: vy={:?} vyyaw={:?}",
6102 vy_metrics,
6103 vyyaw_metrics
6104 );
6105 }
6106
6107 #[test]
6108 #[ignore = "long-running regression scenario"]
6109 fn test_mpc_switch_back_full_candidate_expansion_characterizes_state_subset_per_step() {
6110 let steps = [325usize, 326, 327, 328, 329];
6111 let v_metrics = measure_switch_back_state_injection_by_step(&[2usize], &steps);
6112 let vy_metrics = measure_switch_back_state_injection_by_step(&[1usize, 2usize], &steps);
6113
6114 let step_325_v = state_injection_metrics_at_step(&v_metrics, 325);
6115 let step_325_vy = state_injection_metrics_at_step(&vy_metrics, 325);
6116 assert!(
6117 (step_325_v.default_opposite_control0 - step_325_vy.default_opposite_control0).abs()
6118 <= 5e-4,
6119 "default branch control moved too early at step 325: v={:?} vy={:?}",
6120 step_325_v,
6121 step_325_vy
6122 );
6123 assert!(
6124 step_325_v.default_opposite_pred_y > 10_000.0 * step_325_vy.default_opposite_pred_y,
6125 "default branch predicted-y should already need y at step 325: v={:?} vy={:?}",
6126 step_325_v,
6127 step_325_vy
6128 );
6129 assert!(
6130 step_325_v.expanded_opposite_pred_y > 1_000.0 * step_325_vy.expanded_opposite_pred_y,
6131 "expanded branch predicted-y should already improve with y at step 325: v={:?} vy={:?}",
6132 step_325_v,
6133 step_325_vy
6134 );
6135
6136 for step in [326usize, 327, 328, 329] {
6137 let v = state_injection_metrics_at_step(&v_metrics, step);
6138 let vy = state_injection_metrics_at_step(&vy_metrics, step);
6139
6140 assert!(
6141 v.default_opposite_control0 > 20.0 * vy.default_opposite_control0,
6142 "default branch control should need y from step {step}: v={:?} vy={:?}",
6143 v,
6144 vy
6145 );
6146 assert!(
6147 v.default_opposite_pred_y > 1_000.0 * vy.default_opposite_pred_y,
6148 "default branch predicted-y should strongly prefer v+y from step {step}: v={:?} vy={:?}",
6149 v,
6150 vy
6151 );
6152 assert!(
6153 v.expanded_opposite_pred_y > 1_000.0 * vy.expanded_opposite_pred_y,
6154 "expanded branch predicted-y should strongly prefer v+y from step {step}: v={:?} vy={:?}",
6155 v,
6156 vy
6157 );
6158 }
6159
6160 for step in [325usize, 326, 327, 328, 329] {
6161 let v = state_injection_metrics_at_step(&v_metrics, step);
6162 let vy = state_injection_metrics_at_step(&vy_metrics, step);
6163 assert!(
6164 (v.expanded_opposite_control0 - vy.expanded_opposite_control0).abs() <= 3e-3,
6165 "expanded branch control should stay mostly v-driven at step {step}: v={:?} vy={:?}",
6166 v,
6167 vy
6168 );
6169 assert!(
6170 (v.expanded_opposite_pred_v - vy.expanded_opposite_pred_v).abs() <= 6e-4,
6171 "expanded branch predicted-v should stay mostly v-driven at step {step}: v={:?} vy={:?}",
6172 v,
6173 vy
6174 );
6175 }
6176 }
6177
6178 #[derive(Debug)]
6180 struct StepTransitionDeltaMetrics {
6181 state_delta: Vector4<f64>,
6183 baseline_control0: [f64; 2],
6185 expanded_baseline_control0: [f64; 2],
6187 injections: Vec<DeltaInjectionResult>,
6189 }
6190
6191 #[derive(Debug)]
6192 struct DeltaInjectionResult {
6193 label: &'static str,
6194 indices: Vec<usize>,
6195 injected_control0: [f64; 2],
6196 default_same_control0: f64,
6197 default_opposite_control0: f64,
6198 default_same_pred_y: f64,
6199 default_opposite_pred_y: f64,
6200 default_same_pred_v: f64,
6201 default_opposite_pred_v: f64,
6202 }
6203
6204 fn measure_step_transition_delta_injection() -> StepTransitionDeltaMetrics {
6205 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6206 330,
6207 optimize_linearized_controls_original,
6208 );
6209 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6210 330,
6211 optimize_linearized_controls_with_full_candidate_expansion,
6212 );
6213 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
6214 330,
6215 optimize_linearized_controls_original,
6216 );
6217
6218 let row_325 = detailed_row_at_step(&default_trace, 325);
6219 let row_326 = detailed_row_at_step(&default_trace, 326);
6220 let expanded_row_325 = detailed_row_at_step(&expanded_trace, 325);
6221
6222 let state_delta = row_326.state - row_325.state;
6223
6224 let default_baseline_controls: Vec<[f64; 2]> = row_325
6225 .controls
6226 .iter()
6227 .map(|vec| [vec[0], vec[1]])
6228 .collect();
6229 let expanded_baseline_controls: Vec<[f64; 2]> = expanded_row_325
6230 .controls
6231 .iter()
6232 .map(|vec| [vec[0], vec[1]])
6233 .collect();
6234 let default_baseline_predicted: Vec<[f64; 4]> = row_325
6235 .predicted
6236 .iter()
6237 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
6238 .collect();
6239 let expanded_baseline_predicted: Vec<[f64; 4]> = expanded_row_325
6240 .predicted
6241 .iter()
6242 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
6243 .collect();
6244
6245 let component_sets: Vec<(&str, Vec<usize>)> = vec![
6246 ("delta_v", vec![2]),
6247 ("delta_y", vec![1]),
6248 ("delta_v+delta_y", vec![1, 2]),
6249 ("delta_v+delta_y+delta_yaw", vec![1, 2, 3]),
6250 ];
6251
6252 let warm_start_325 = default_warm_start.get(&325).unwrap();
6253
6254 let mut injections = Vec::new();
6255 for (label, indices) in &component_sets {
6256 let mut modified_state_vec = row_325.state;
6257 for &idx in indices {
6258 modified_state_vec[idx] += state_delta[idx];
6259 }
6260 let modified_state = state_from_vector4(&modified_state_vec);
6261
6262 let result = iterative_linear_mpc_control_with_test_optimizer(
6263 &row_325.xref,
6264 &modified_state,
6265 warm_start_325,
6266 optimize_linearized_controls_original,
6267 );
6268
6269 let to_default_control =
6270 summarize_vec2_sequence_diff(&result.controls, &default_baseline_controls);
6271 let to_expanded_control =
6272 summarize_vec2_sequence_diff(&result.controls, &expanded_baseline_controls);
6273 let to_default_predicted =
6274 summarize_vec4_sequence_diff(&result.predicted, &default_baseline_predicted);
6275 let to_expanded_predicted =
6276 summarize_vec4_sequence_diff(&result.predicted, &expanded_baseline_predicted);
6277
6278 injections.push(DeltaInjectionResult {
6279 label,
6280 indices: indices.clone(),
6281 injected_control0: [result.controls[0][0], result.controls[0][1]],
6282 default_same_control0: to_default_control[0],
6283 default_opposite_control0: to_expanded_control[0],
6284 default_same_pred_y: to_default_predicted[1],
6285 default_opposite_pred_y: to_expanded_predicted[1],
6286 default_same_pred_v: to_default_predicted[2],
6287 default_opposite_pred_v: to_expanded_predicted[2],
6288 });
6289 }
6290
6291 StepTransitionDeltaMetrics {
6292 state_delta,
6293 baseline_control0: [row_325.controls[0][0], row_325.controls[0][1]],
6294 expanded_baseline_control0: [
6295 expanded_row_325.controls[0][0],
6296 expanded_row_325.controls[0][1],
6297 ],
6298 injections,
6299 }
6300 }
6301
6302 #[test]
6303 #[ignore = "long-running regression scenario"]
6304 fn test_mpc_switch_back_full_candidate_expansion_characterizes_step_325_326_transition() {
6305 let metrics = measure_step_transition_delta_injection();
6306
6307 println!("=== Step 325 → 326 Transition Delta Injection ===");
6308 println!("state_delta: {:?}", metrics.state_delta);
6309 println!(
6310 "baseline_control0 (default): {:?}",
6311 metrics.baseline_control0
6312 );
6313 println!(
6314 "baseline_control0 (expanded): {:?}",
6315 metrics.expanded_baseline_control0
6316 );
6317 for inj in &metrics.injections {
6318 println!("--- {} (indices {:?}) ---", inj.label, inj.indices);
6319 println!(" injected_control0: {:?}", inj.injected_control0);
6320 println!(
6321 " same_control0: {:.6e} opposite_control0: {:.6e} ratio: {:.2}",
6322 inj.default_same_control0,
6323 inj.default_opposite_control0,
6324 if inj.default_opposite_control0 > 0.0 {
6325 inj.default_same_control0 / inj.default_opposite_control0
6326 } else {
6327 f64::INFINITY
6328 }
6329 );
6330 println!(
6331 " same_pred_y: {:.6e} opposite_pred_y: {:.6e}",
6332 inj.default_same_pred_y, inj.default_opposite_pred_y
6333 );
6334 println!(
6335 " same_pred_v: {:.6e} opposite_pred_v: {:.6e}",
6336 inj.default_same_pred_v, inj.default_opposite_pred_v
6337 );
6338 }
6339
6340 let delta_v = &metrics.injections[0];
6343 assert!(
6344 delta_v.default_same_control0 < delta_v.default_opposite_control0,
6345 "delta_v at step 325 context should stay closer to default than expanded: {:?}",
6346 delta_v
6347 );
6348
6349 let delta_y = &metrics.injections[1];
6351 assert!(
6352 delta_y.default_same_control0 < delta_y.default_opposite_control0,
6353 "delta_y at step 325 context should stay closer to default than expanded: {:?}",
6354 delta_y
6355 );
6356
6357 let delta_vy = &metrics.injections[2];
6362 println!(
6363 "\n=== KEY: delta_v+delta_y same/opposite ratio = {:.4} ===",
6364 if delta_vy.default_opposite_control0 > 0.0 {
6365 delta_vy.default_same_control0 / delta_vy.default_opposite_control0
6366 } else {
6367 f64::INFINITY
6368 }
6369 );
6370
6371 let delta_vyy = &metrics.injections[3];
6373 let control0_diff =
6374 (delta_vy.default_same_control0 - delta_vyy.default_same_control0).abs();
6375 println!(
6376 "delta_v+delta_y vs delta_v+delta_y+delta_yaw control0 diff: {:.6e}",
6377 control0_diff
6378 );
6379 }
6380
6381 #[derive(Debug)]
6383 struct ContextSwapInjectionResult {
6384 label: &'static str,
6385 injected_steer: f64,
6386 default_baseline_steer: f64,
6387 expanded_baseline_steer: f64,
6388 default_same_control0: f64,
6389 default_opposite_control0: f64,
6390 default_same_pred_y: f64,
6391 default_opposite_pred_y: f64,
6392 default_same_pred_v: f64,
6393 default_opposite_pred_v: f64,
6394 }
6395
6396 fn measure_step_325_326_context_swap_v_injection() -> Vec<ContextSwapInjectionResult> {
6400 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6401 330,
6402 optimize_linearized_controls_original,
6403 );
6404 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6405 330,
6406 optimize_linearized_controls_with_full_candidate_expansion,
6407 );
6408 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
6409 330,
6410 optimize_linearized_controls_original,
6411 );
6412
6413 let d325 = detailed_row_at_step(&default_trace, 325);
6414 let d326 = detailed_row_at_step(&default_trace, 326);
6415 let e325 = detailed_row_at_step(&expanded_trace, 325);
6416 let e326 = detailed_row_at_step(&expanded_trace, 326);
6417 let ws325 = default_warm_start.get(&325).unwrap();
6418 let ws326 = default_warm_start.get(&326).unwrap();
6419
6420 let v_indices: &[usize] = &[2]; let run_injection = |label: &'static str,
6424 default_state: &Vector4<f64>,
6425 expanded_state: &Vector4<f64>,
6426 xref: &[Vector4<f64>],
6427 warm_start: &[Vector2<f64>],
6428 default_row: &SwitchBackDetailedRow,
6429 expanded_row: &SwitchBackDetailedRow| {
6430 let injected_state = inject_state_components(default_state, expanded_state, v_indices);
6431 let result = iterative_linear_mpc_control_with_test_optimizer(
6432 xref,
6433 &injected_state,
6434 warm_start,
6435 optimize_linearized_controls_original,
6436 );
6437
6438 let default_baseline_controls: Vec<[f64; 2]> = default_row
6439 .controls
6440 .iter()
6441 .map(|vec| [vec[0], vec[1]])
6442 .collect();
6443 let expanded_baseline_controls: Vec<[f64; 2]> = expanded_row
6444 .controls
6445 .iter()
6446 .map(|vec| [vec[0], vec[1]])
6447 .collect();
6448 let default_baseline_predicted: Vec<[f64; 4]> = default_row
6449 .predicted
6450 .iter()
6451 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
6452 .collect();
6453 let expanded_baseline_predicted: Vec<[f64; 4]> = expanded_row
6454 .predicted
6455 .iter()
6456 .map(|vec| [vec[0], vec[1], vec[2], vec[3]])
6457 .collect();
6458
6459 let to_default_control =
6460 summarize_vec2_sequence_diff(&result.controls, &default_baseline_controls);
6461 let to_expanded_control =
6462 summarize_vec2_sequence_diff(&result.controls, &expanded_baseline_controls);
6463 let to_default_predicted =
6464 summarize_vec4_sequence_diff(&result.predicted, &default_baseline_predicted);
6465 let to_expanded_predicted =
6466 summarize_vec4_sequence_diff(&result.predicted, &expanded_baseline_predicted);
6467
6468 ContextSwapInjectionResult {
6469 label,
6470 injected_steer: result.controls[0][1],
6471 default_baseline_steer: default_row.controls[0][1],
6472 expanded_baseline_steer: expanded_row.controls[0][1],
6473 default_same_control0: to_default_control[0],
6474 default_opposite_control0: to_expanded_control[0],
6475 default_same_pred_y: to_default_predicted[1],
6476 default_opposite_pred_y: to_expanded_predicted[1],
6477 default_same_pred_v: to_default_predicted[2],
6478 default_opposite_pred_v: to_expanded_predicted[2],
6479 }
6480 };
6481
6482 vec![
6483 run_injection(
6485 "baseline_325: d325_state + e325_v, d325_xref, ws325",
6486 &d325.state,
6487 &e325.state,
6488 &d325.xref,
6489 ws325,
6490 d325,
6491 e325,
6492 ),
6493 run_injection(
6495 "baseline_326: d326_state + e326_v, d326_xref, ws326",
6496 &d326.state,
6497 &e326.state,
6498 &d326.xref,
6499 ws326,
6500 d326,
6501 e326,
6502 ),
6503 run_injection(
6505 "swap_state: d325_state + e326_v, d326_xref, ws326",
6506 &d325.state,
6507 &e326.state,
6508 &d326.xref,
6509 ws326,
6510 d326,
6511 e326,
6512 ),
6513 run_injection(
6515 "swap_xref: d326_state + e326_v, d325_xref, ws326",
6516 &d326.state,
6517 &e326.state,
6518 &d325.xref,
6519 ws326,
6520 d326,
6521 e326,
6522 ),
6523 run_injection(
6525 "swap_ws: d326_state + e326_v, d326_xref, ws325",
6526 &d326.state,
6527 &e326.state,
6528 &d326.xref,
6529 ws325,
6530 d326,
6531 e326,
6532 ),
6533 run_injection(
6535 "swap_state+xref: d325_state + e326_v, d325_xref, ws326",
6536 &d325.state,
6537 &e326.state,
6538 &d325.xref,
6539 ws326,
6540 d326,
6541 e326,
6542 ),
6543 run_injection(
6545 "swap_state+ws: d325_state + e326_v, d326_xref, ws325",
6546 &d325.state,
6547 &e326.state,
6548 &d326.xref,
6549 ws325,
6550 d326,
6551 e326,
6552 ),
6553 run_injection(
6555 "swap_xref+ws: d326_state + e326_v, d325_xref, ws325",
6556 &d326.state,
6557 &e326.state,
6558 &d325.xref,
6559 ws325,
6560 d326,
6561 e326,
6562 ),
6563 run_injection(
6565 "swap_all: d325_state + e326_v, d325_xref, ws325",
6566 &d325.state,
6567 &e326.state,
6568 &d325.xref,
6569 ws325,
6570 d326,
6571 e326,
6572 ),
6573 run_injection(
6576 "cross_v_325in326: d326_state + e325_v, d326_xref, ws326",
6577 &d326.state,
6578 &e325.state,
6579 &d326.xref,
6580 ws326,
6581 d326,
6582 e326,
6583 ),
6584 run_injection(
6586 "cross_v_326in325: d325_state + e326_v, d325_xref, ws325",
6587 &d325.state,
6588 &e326.state,
6589 &d325.xref,
6590 ws325,
6591 d325,
6592 e325,
6593 ),
6594 {
6598 let mut s = d325.state;
6599 s[0] = d326.state[0]; run_injection(
6601 "isolate_x: d325{x→d326} + e325_v",
6602 &s,
6603 &e325.state,
6604 &d325.xref,
6605 ws325,
6606 d325,
6607 e325,
6608 )
6609 },
6610 {
6612 let mut s = d325.state;
6613 s[1] = d326.state[1]; run_injection(
6615 "isolate_y: d325{y→d326} + e325_v",
6616 &s,
6617 &e325.state,
6618 &d325.xref,
6619 ws325,
6620 d325,
6621 e325,
6622 )
6623 },
6624 {
6626 let mut s = d325.state;
6627 s[3] = d326.state[3]; run_injection(
6629 "isolate_yaw: d325{yaw→d326} + e325_v",
6630 &s,
6631 &e325.state,
6632 &d325.xref,
6633 ws325,
6634 d325,
6635 e325,
6636 )
6637 },
6638 {
6640 let mut s = d325.state;
6641 s[1] = d326.state[1];
6642 s[3] = d326.state[3];
6643 run_injection(
6644 "isolate_y+yaw: d325{y,yaw→d326} + e325_v",
6645 &s,
6646 &e325.state,
6647 &d325.xref,
6648 ws325,
6649 d325,
6650 e325,
6651 )
6652 },
6653 {
6655 let mut s = d325.state;
6656 s[0] = d326.state[0];
6657 s[1] = d326.state[1];
6658 run_injection(
6659 "isolate_x+y: d325{x,y→d326} + e325_v",
6660 &s,
6661 &e325.state,
6662 &d325.xref,
6663 ws325,
6664 d325,
6665 e325,
6666 )
6667 },
6668 {
6670 let mut s = d326.state;
6671 s[2] = d325.state[2]; run_injection(
6673 "isolate_all_nonv: d326{v→d325} + e325_v",
6674 &s,
6675 &e325.state,
6676 &d325.xref,
6677 ws325,
6678 d325,
6679 e325,
6680 )
6681 },
6682 ]
6683 }
6684
6685 #[test]
6686 #[ignore = "long-running regression scenario"]
6687 fn test_mpc_switch_back_characterizes_step_325_326_v_injection_context_swap() {
6688 let results = measure_step_325_326_context_swap_v_injection();
6689
6690 println!("=== Step 325 vs 326: V-Only Injection Context Swap ===");
6691 println!("Question: why does v-only opposite-branch injection work at 325 but not 326?");
6692 println!();
6693
6694 println!("=== Raw State Values ===");
6696 println!(" (indices: x=0, y=1, v=2, yaw=3)");
6697 let default_trace_print = collect_switch_back_detailed_trace_with_test_optimizer(
6699 330,
6700 optimize_linearized_controls_original,
6701 );
6702 let expanded_trace_print = collect_switch_back_detailed_trace_with_test_optimizer(
6703 330,
6704 optimize_linearized_controls_with_full_candidate_expansion,
6705 );
6706 let d325_p = detailed_row_at_step(&default_trace_print, 325);
6707 let d326_p = detailed_row_at_step(&default_trace_print, 326);
6708 let e325_p = detailed_row_at_step(&expanded_trace_print, 325);
6709 let e326_p = detailed_row_at_step(&expanded_trace_print, 326);
6710 println!(" d325.state: {:?}", d325_p.state);
6711 println!(" d326.state: {:?}", d326_p.state);
6712 println!(" d325→d326 delta: {:?}", d326_p.state - d325_p.state);
6713 println!(" e325.state: {:?}", e325_p.state);
6714 println!(" e326.state: {:?}", e326_p.state);
6715 println!(" e325→e326 delta: {:?}", e326_p.state - e325_p.state);
6716 println!(
6717 " v diff: e325.v={:.6} e326.v={:.6} delta={:.6}",
6718 e325_p.state[2],
6719 e326_p.state[2],
6720 e326_p.state[2] - e325_p.state[2]
6721 );
6722 println!();
6723
6724 for r in &results {
6725 let pull_ratio = if r.default_opposite_control0 > 0.0 {
6726 r.default_same_control0 / r.default_opposite_control0
6727 } else {
6728 f64::INFINITY
6729 };
6730 let pulls_toward_expanded = pull_ratio > 4.0;
6731 println!(
6732 "[{}] {} steer={:.6} (default={:.6}, expanded={:.6})",
6733 if pulls_toward_expanded {
6734 "PULL"
6735 } else {
6736 "STAY"
6737 },
6738 r.label,
6739 r.injected_steer,
6740 r.default_baseline_steer,
6741 r.expanded_baseline_steer,
6742 );
6743 println!(
6744 " same_ctrl0={:.4e} opp_ctrl0={:.4e} ratio={:.3} pred_y_s={:.4e} pred_y_o={:.4e} pred_v_s={:.4e} pred_v_o={:.4e}",
6745 r.default_same_control0,
6746 r.default_opposite_control0,
6747 pull_ratio,
6748 r.default_same_pred_y,
6749 r.default_opposite_pred_y,
6750 r.default_same_pred_v,
6751 r.default_opposite_pred_v,
6752 );
6753 }
6754
6755 let baseline_325 = &results[0];
6757 let ratio_325 = baseline_325.default_same_control0 / baseline_325.default_opposite_control0;
6758 assert!(
6759 ratio_325 > 100.0,
6760 "v-only injection should strongly pull toward expanded at step 325 (ratio={ratio_325}): {:?}",
6761 baseline_325
6762 );
6763
6764 let baseline_326 = &results[1];
6765 let ratio_326 = baseline_326.default_same_control0 / baseline_326.default_opposite_control0;
6766 assert!(
6767 ratio_326 < 10.0,
6768 "v-only injection pull should weaken dramatically at step 326 (ratio={ratio_326}): {:?}",
6769 baseline_326
6770 );
6771
6772 assert!(
6774 ratio_325 > 100.0 * ratio_326,
6775 "v-only pull should drop >100x from step 325 to 326: 325={ratio_325} 326={ratio_326}"
6776 );
6777
6778 let swap_xref = &results[3];
6780 let ratio_swap_xref = swap_xref.default_same_control0 / swap_xref.default_opposite_control0;
6781 assert!(
6782 (ratio_swap_xref - ratio_326).abs() < 0.1,
6783 "xref swap should not change ratio: baseline={ratio_326} swapped={ratio_swap_xref}"
6784 );
6785
6786 let swap_ws = &results[4];
6787 let ratio_swap_ws = swap_ws.default_same_control0 / swap_ws.default_opposite_control0;
6788 assert!(
6789 (ratio_swap_ws - ratio_326).abs() < 0.1,
6790 "warm_start swap should not change ratio: baseline={ratio_326} swapped={ratio_swap_ws}"
6791 );
6792
6793 let swap_state = &results[2];
6795 let ratio_swap_state =
6796 swap_state.default_same_control0 / swap_state.default_opposite_control0;
6797 assert!(
6798 (ratio_swap_state - ratio_326).abs() > 0.5,
6799 "state swap should change ratio: baseline={ratio_326} swapped={ratio_swap_state}"
6800 );
6801
6802 for result in &results[11..=16] {
6805 let ratio = result.default_same_control0 / result.default_opposite_control0;
6806 assert!(
6807 ratio > 100.0,
6808 "default non-v component change should not destroy pull: {} ratio={ratio}",
6809 result.label
6810 );
6811 }
6812 }
6813
6814 fn iterative_linear_mpc_control_with_max_iter(
6819 xref: &[Vector4<f64>],
6820 state: &State,
6821 warm_start: &[Vector2<f64>],
6822 max_iter: usize,
6823 ) -> MpcResult {
6824 let mut controls = warm_start.to_vec();
6825 controls.resize(T, Vector2::zeros());
6826 apply_control_constraints(&mut controls);
6827
6828 let mut predicted = predict_motion(*state, &controls);
6829 for _ in 0..max_iter {
6830 let previous_controls = controls.clone();
6831 controls = optimize_linearized_controls(xref, &predicted, state, &controls);
6832 predicted = predict_motion(*state, &controls);
6833
6834 let du = controls
6835 .iter()
6836 .zip(previous_controls.iter())
6837 .map(|(current, previous)| {
6838 (current[0] - previous[0]).abs() + (current[1] - previous[1]).abs()
6839 })
6840 .sum::<f64>();
6841
6842 if du <= DU_TH {
6843 break;
6844 }
6845 }
6846
6847 MpcResult {
6848 controls,
6849 predicted,
6850 }
6851 }
6852
6853 fn dump_first_inner_solve_adjoint(
6856 xref: &[Vector4<f64>],
6857 state: &State,
6858 warm_start: &[Vector2<f64>],
6859 ) -> (Vec<Vector2<f64>>, Vec<[f64; 5]>) {
6860 let mut controls = warm_start.to_vec();
6861 controls.resize(T, Vector2::zeros());
6862 apply_control_constraints(&mut controls);
6863
6864 let xbar = predict_motion(*state, &controls);
6865
6866 let q = state_cost_weight();
6867 let qf = terminal_cost_weight();
6868 let r = control_cost_weight();
6869 let rd = control_rate_weight();
6870
6871 let (x, a_seq, b_seq, _) = linearized_rollout(state, &xbar, &controls);
6872
6873 let mut gradients = vec![Vector2::zeros(); T];
6874 let mut lambda = (qf * state_error(x[T], xref[T])) * 2.0;
6875 let mut lambda3_decomp = Vec::new();
6876
6877 lambda3_decomp.push([
6879 lambda[3], 0.0, 0.0, 0.0, lambda[3], ]);
6885
6886 for t in (0..T).rev() {
6887 let mut grad = (r * controls[t]) * 2.0 + b_seq[t].transpose() * lambda;
6888 if t > 0 {
6889 grad += (rd * (controls[t] - controls[t - 1])) * 2.0;
6890 }
6891 if t < T - 1 {
6892 grad -= (rd * (controls[t + 1] - controls[t])) * 2.0;
6893 }
6894 gradients[t] = grad;
6895
6896 let lx = if t == 0 {
6897 Vector4::zeros()
6898 } else {
6899 (q * state_error(x[t], xref[t])) * 2.0
6900 };
6901
6902 let lambda_next = lambda;
6903 lambda = lx + a_seq[t].transpose() * lambda_next;
6904
6905 let from_lx3 = lx[3];
6907 let from_at30_l0 = a_seq[t][(0, 3)] * lambda_next[0]; let from_at31_l1 = a_seq[t][(1, 3)] * lambda_next[1]; let from_at33_l3 = a_seq[t][(3, 3)] * lambda_next[3]; lambda3_decomp.push([
6911 lambda[3],
6912 from_lx3,
6913 from_at30_l0,
6914 from_at31_l1,
6915 from_at33_l3,
6916 ]);
6917 }
6918
6919 lambda3_decomp.reverse();
6920 (gradients, lambda3_decomp)
6921 }
6922
6923 #[test]
6924 #[ignore = "long-running regression scenario"]
6925 fn test_mpc_switch_back_characterizes_y_effect_path_via_outer_loop() {
6926 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6927 330,
6928 optimize_linearized_controls_original,
6929 );
6930 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
6931 330,
6932 optimize_linearized_controls_with_full_candidate_expansion,
6933 );
6934 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
6935 330,
6936 optimize_linearized_controls_original,
6937 );
6938
6939 println!("=== Y Effect Path: Outer Loop Iteration Count ===");
6940 println!("At step 326, v-only injection needs v+y. Is y's effect through outer-loop re-linearization?");
6941 println!();
6942
6943 for &step in &[325usize, 326, 327] {
6944 let d_row = detailed_row_at_step(&default_trace, step);
6945 let e_row = detailed_row_at_step(&expanded_trace, step);
6946 let ws = default_warm_start.get(&step).unwrap();
6947
6948 let d_baseline_steer = d_row.controls[0][1];
6949 let e_baseline_steer = e_row.controls[0][1];
6950
6951 println!("--- step {step} (default_steer={d_baseline_steer:.6}, expanded_steer={e_baseline_steer:.6}) ---");
6952
6953 for &max_iter in &[1usize, 2, 3] {
6954 let v_state = inject_state_components(&d_row.state, &e_row.state, &[2]);
6956 let v_result =
6957 iterative_linear_mpc_control_with_max_iter(&d_row.xref, &v_state, ws, max_iter);
6958
6959 let vy_state = inject_state_components(&d_row.state, &e_row.state, &[1, 2]);
6961 let vy_result = iterative_linear_mpc_control_with_max_iter(
6962 &d_row.xref,
6963 &vy_state,
6964 ws,
6965 max_iter,
6966 );
6967
6968 let v_steer = v_result.controls[0][1];
6969 let vy_steer = vy_result.controls[0][1];
6970 let steer_diff = (v_steer - vy_steer).abs();
6971
6972 println!(
6973 " iter={max_iter}: v_steer={v_steer:.6} vy_steer={vy_steer:.6} diff={steer_diff:.6e}"
6974 );
6975 }
6976 }
6977 }
6978
6979 fn linearized_rollout_without_angle_normalization(
6981 x0: &State,
6982 xbar: &[Vector4<f64>],
6983 controls: &[Vector2<f64>],
6984 ) -> LinearizedRollout {
6985 let mut x = vec![Vector4::zeros(); T + 1];
6986 let mut a_seq = Vec::with_capacity(T);
6987 let mut b_seq = Vec::with_capacity(T);
6988 let mut c_seq = Vec::with_capacity(T);
6989
6990 x[0] = x0.to_vector();
6991 for t in 0..T {
6992 let (a, b, c) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
6993 let next = a * x[t] + b * controls[t] + c;
6994 a_seq.push(a);
6997 b_seq.push(b);
6998 c_seq.push(c);
6999 x[t + 1] = next;
7000 }
7001
7002 (x, a_seq, b_seq, c_seq)
7003 }
7004
7005 #[test]
7006 #[ignore = "long-running regression scenario"]
7007 fn test_mpc_switch_back_characterizes_yaw_normalize_effect_in_linearized_rollout() {
7008 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7009 330,
7010 optimize_linearized_controls,
7011 );
7012 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
7013 330,
7014 optimize_linearized_controls,
7015 );
7016
7017 println!("=== Yaw Normalize Effect in Linearized Rollout ===");
7018 println!("Comparing linearized_rollout with and without normalize_angle");
7019 println!();
7020
7021 for &step in &[20usize, 70, 71, 75, 78, 325, 326] {
7022 let row = detailed_row_at_step(&default_trace, step);
7023 let ws = default_warm_start.get(&step).unwrap();
7024 let state = state_from_vector4(&row.state);
7025
7026 let mut controls = ws.to_vec();
7028 controls.resize(T, Vector2::zeros());
7029 apply_control_constraints(&mut controls);
7030 let xbar = predict_motion(state, &controls);
7031
7032 let (x_with, _, _, _) = linearized_rollout(&state, &xbar, &controls);
7033 let (x_without, _, _, _) =
7034 linearized_rollout_without_angle_normalization(&state, &xbar, &controls);
7035
7036 let mut max_yaw_diff = 0.0f64;
7037 let mut max_other_diff = [0.0f64; 3]; for t in 0..=T {
7039 let yaw_diff = (x_with[t][3] - x_without[t][3]).abs();
7040 max_yaw_diff = max_yaw_diff.max(yaw_diff);
7041 for (i, slot) in max_other_diff.iter_mut().enumerate() {
7042 *slot = (*slot).max((x_with[t][i] - x_without[t][i]).abs());
7043 }
7044 }
7045
7046 let yaw_values: Vec<f64> = (0..=T).map(|t| x_with[t][3]).collect();
7047 println!(
7048 "step {step:>3}: yaw_diff={max_yaw_diff:.4e} other=[{:.4e},{:.4e},{:.4e}] yaw={:.4?}",
7049 max_other_diff[0], max_other_diff[1], max_other_diff[2], yaw_values
7050 );
7051 }
7052 }
7053
7054 #[test]
7055 #[ignore = "long-running regression scenario"]
7056 fn test_mpc_switch_back_characterizes_adjoint_y_to_steer_coupling() {
7057 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7058 330,
7059 optimize_linearized_controls_original,
7060 );
7061 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7062 330,
7063 optimize_linearized_controls_with_full_candidate_expansion,
7064 );
7065 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
7066 330,
7067 optimize_linearized_controls_original,
7068 );
7069
7070 println!("=== Adjoint Decomposition: How Y Reaches Steering ===");
7071 println!(
7072 "lambda3_decomp columns: [total, from_lx3, from_AT30*l0, from_AT31*l1, from_AT33*l3]"
7073 );
7074 println!();
7075
7076 for &step in &[325usize, 326] {
7077 let d_row = detailed_row_at_step(&default_trace, step);
7078 let e_row = detailed_row_at_step(&expanded_trace, step);
7079 let ws = default_warm_start.get(&step).unwrap();
7080
7081 println!("--- step {step} ---");
7082
7083 let (grad_base, l3_base) =
7085 dump_first_inner_solve_adjoint(&d_row.xref, &state_from_vector4(&d_row.state), ws);
7086 println!(" [baseline] steer_grad[0]={:.6e}", grad_base[0][1]);
7087 for (t, decomp) in l3_base.iter().enumerate() {
7088 println!(
7089 " t={t}: λ3={:.4e} lx3={:.4e} AT30·l0={:.4e} AT31·l1={:.4e} AT33·l3={:.4e}",
7090 decomp[0], decomp[1], decomp[2], decomp[3], decomp[4]
7091 );
7092 }
7093
7094 let v_state = inject_state_components(&d_row.state, &e_row.state, &[2]);
7096 let (grad_v, l3_v) = dump_first_inner_solve_adjoint(&d_row.xref, &v_state, ws);
7097 println!(" [v-only] steer_grad[0]={:.6e}", grad_v[0][1]);
7098 for (t, decomp) in l3_v.iter().enumerate() {
7099 println!(
7100 " t={t}: λ3={:.4e} lx3={:.4e} AT30·l0={:.4e} AT31·l1={:.4e} AT33·l3={:.4e}",
7101 decomp[0], decomp[1], decomp[2], decomp[3], decomp[4]
7102 );
7103 }
7104
7105 let vy_state = inject_state_components(&d_row.state, &e_row.state, &[1, 2]);
7107 let (grad_vy, l3_vy) = dump_first_inner_solve_adjoint(&d_row.xref, &vy_state, ws);
7108 println!(" [v+y] steer_grad[0]={:.6e}", grad_vy[0][1]);
7109 for (t, decomp) in l3_vy.iter().enumerate() {
7110 println!(
7111 " t={t}: λ3={:.4e} lx3={:.4e} AT30·l0={:.4e} AT31·l1={:.4e} AT33·l3={:.4e}",
7112 decomp[0], decomp[1], decomp[2], decomp[3], decomp[4]
7113 );
7114 }
7115
7116 println!(
7118 " steer_grad[0] diffs: v-base={:.6e} vy-base={:.6e} vy-v={:.6e}",
7119 grad_v[0][1] - grad_base[0][1],
7120 grad_vy[0][1] - grad_base[0][1],
7121 grad_vy[0][1] - grad_v[0][1],
7122 );
7123 println!();
7124 }
7125 }
7126
7127 #[test]
7131 #[ignore = "long-running regression scenario"]
7132 fn test_mpc_switch_back_characterizes_step_325_326_state_component_crossover() {
7133 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7134 330,
7135 optimize_linearized_controls_original,
7136 );
7137 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7138 330,
7139 optimize_linearized_controls_with_full_candidate_expansion,
7140 );
7141 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
7142 330,
7143 optimize_linearized_controls_original,
7144 );
7145
7146 let d325 = detailed_row_at_step(&default_trace, 325);
7147 let d326 = detailed_row_at_step(&default_trace, 326);
7148 let e326 = detailed_row_at_step(&expanded_trace, 326);
7149 let ws326 = default_warm_start.get(&326).unwrap();
7150
7151 let state_delta = d326.state - d325.state;
7152 println!(
7153 "state_delta: x={:.6e} y={:.6e} v={:.6e} yaw={:.6e}",
7154 state_delta[0], state_delta[1], state_delta[2], state_delta[3]
7155 );
7156
7157 let expanded_baseline_controls: Vec<[f64; 2]> =
7158 e326.controls.iter().map(|v| [v[0], v[1]]).collect();
7159 let default_baseline_controls: Vec<[f64; 2]> =
7160 d326.controls.iter().map(|v| [v[0], v[1]]).collect();
7161
7162 let component_sets: Vec<(&str, Vec<usize>)> = vec![
7163 ("revert_x", vec![0]),
7164 ("revert_y", vec![1]),
7165 ("revert_v", vec![2]),
7166 ("revert_yaw", vec![3]),
7167 ("revert_y+v", vec![1, 2]),
7168 ("revert_x+yaw", vec![0, 3]),
7169 ("revert_all", vec![0, 1, 2, 3]),
7170 ];
7171
7172 println!(
7173 "\n=== State Component Crossover (step 326 context, revert components to 325) ==="
7174 );
7175
7176 let baseline_injected_state = inject_state_components(&d326.state, &e326.state, &[2]);
7178 let baseline_result = iterative_linear_mpc_control_with_test_optimizer(
7179 &d326.xref,
7180 &baseline_injected_state,
7181 ws326,
7182 optimize_linearized_controls_original,
7183 );
7184 let baseline_to_default =
7185 summarize_vec2_sequence_diff(&baseline_result.controls, &default_baseline_controls);
7186 let baseline_to_expanded =
7187 summarize_vec2_sequence_diff(&baseline_result.controls, &expanded_baseline_controls);
7188 let baseline_ratio = if baseline_to_expanded[0] > 0.0 {
7189 baseline_to_default[0] / baseline_to_expanded[0]
7190 } else {
7191 f64::INFINITY
7192 };
7193 println!(
7194 " baseline: same={:.4e} opp={:.4e} ratio={:.3}",
7195 baseline_to_default[0], baseline_to_expanded[0], baseline_ratio
7196 );
7197
7198 let mut ratios = Vec::new();
7199 for (label, indices) in &component_sets {
7200 let mut modified_default = d326.state;
7201 for &idx in indices {
7202 modified_default[idx] = d325.state[idx];
7203 }
7204
7205 let injected_state = inject_state_components(&modified_default, &e326.state, &[2]);
7206 let result = iterative_linear_mpc_control_with_test_optimizer(
7207 &d326.xref,
7208 &injected_state,
7209 ws326,
7210 optimize_linearized_controls_original,
7211 );
7212
7213 let to_default =
7214 summarize_vec2_sequence_diff(&result.controls, &default_baseline_controls);
7215 let to_expanded =
7216 summarize_vec2_sequence_diff(&result.controls, &expanded_baseline_controls);
7217 let ratio = if to_expanded[0] > 0.0 {
7218 to_default[0] / to_expanded[0]
7219 } else {
7220 f64::INFINITY
7221 };
7222
7223 println!(
7224 " {:12} same={:.4e} opp={:.4e} ratio={:.3} (delta: {:.3})",
7225 label,
7226 to_default[0],
7227 to_expanded[0],
7228 ratio,
7229 ratio - baseline_ratio
7230 );
7231 ratios.push((*label, ratio));
7232 }
7233
7234 let revert_y_ratio = ratios.iter().find(|(l, _)| *l == "revert_y").unwrap().1;
7235 let revert_v_ratio = ratios.iter().find(|(l, _)| *l == "revert_v").unwrap().1;
7236 let revert_yv_ratio = ratios.iter().find(|(l, _)| *l == "revert_y+v").unwrap().1;
7237 let revert_all_ratio = ratios.iter().find(|(l, _)| *l == "revert_all").unwrap().1;
7238
7239 println!("\n=== Summary ===");
7240 println!("baseline_326 ratio: {:.3}", baseline_ratio);
7241 println!("revert_y ratio: {:.3}", revert_y_ratio);
7242 println!("revert_v ratio: {:.3}", revert_v_ratio);
7243 println!("revert_y+v ratio: {:.3}", revert_yv_ratio);
7244 println!("revert_all ratio: {:.3}", revert_all_ratio);
7245
7246 assert!(
7248 (revert_y_ratio - revert_all_ratio).abs() < 0.1,
7249 "reverting y alone should account for nearly all the effect: y={revert_y_ratio} all={revert_all_ratio}"
7250 );
7251
7252 assert!(
7254 (revert_v_ratio - baseline_ratio).abs() < 0.01,
7255 "reverting v should have no effect: v={revert_v_ratio} baseline={baseline_ratio}"
7256 );
7257
7258 let revert_xyaw_ratio = ratios.iter().find(|(l, _)| *l == "revert_x+yaw").unwrap().1;
7260 assert!(
7261 (revert_xyaw_ratio - baseline_ratio).abs() < 0.01,
7262 "reverting x+yaw should have no effect: x+yaw={revert_xyaw_ratio} baseline={baseline_ratio}"
7263 );
7264 }
7265
7266 #[test]
7269 #[ignore = "long-running regression scenario"]
7270 fn test_mpc_switch_back_characterizes_y_divergence_history() {
7271 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7272 350,
7273 optimize_linearized_controls_original,
7274 );
7275 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7276 350,
7277 optimize_linearized_controls_with_full_candidate_expansion,
7278 );
7279
7280 println!("=== state.y Divergence History (default vs expanded) ===");
7281 println!(
7282 "{:>5} {:>12} {:>12} {:>12} {:>12} {:>12}",
7283 "step", "default_y", "expanded_y", "y_diff", "default_v", "v_diff"
7284 );
7285
7286 let mut first_significant_step = None;
7287 let mut max_y_diff: f64 = 0.0;
7288
7289 for step in (0..350).step_by(5) {
7290 let d = default_trace.iter().find(|r| r.step == step);
7291 let e = expanded_trace.iter().find(|r| r.step == step);
7292 if let (Some(d), Some(e)) = (d, e) {
7293 let y_diff = (d.state[1] - e.state[1]).abs();
7294 max_y_diff = max_y_diff.max(y_diff);
7295
7296 if y_diff > 1e-6 || step >= 60 {
7297 println!(
7298 "{:5} {:12.6} {:12.6} {:12.6e} {:12.6} {:12.6e}",
7299 step,
7300 d.state[1],
7301 e.state[1],
7302 d.state[1] - e.state[1],
7303 d.state[2],
7304 d.state[2] - e.state[2]
7305 );
7306 }
7307
7308 if first_significant_step.is_none() && y_diff > 1e-3 {
7309 first_significant_step = Some(step);
7310 }
7311 }
7312 }
7313
7314 if let Some(start) = first_significant_step {
7316 let dense_start = start.saturating_sub(10);
7317 println!(
7318 "\n=== Dense view around first significant y divergence (step {}) ===",
7319 start
7320 );
7321 for step in dense_start..=(start + 20).min(349) {
7322 let d = default_trace.iter().find(|r| r.step == step);
7323 let e = expanded_trace.iter().find(|r| r.step == step);
7324 if let (Some(d), Some(e)) = (d, e) {
7325 let y_diff = d.state[1] - e.state[1];
7326 let v_diff = d.state[2] - e.state[2];
7327 let steer_diff = d.controls[0][1] - e.controls[0][1];
7328 println!(
7329 " step {:3}: y_diff={:12.6e} v_diff={:12.6e} steer_diff={:12.6e}",
7330 step, y_diff, v_diff, steer_diff
7331 );
7332 }
7333 }
7334 }
7335
7336 println!("\n=== Dense view step 315..335 (pre-terminal) ===");
7338 for step in 315..=335 {
7339 let d = default_trace.iter().find(|r| r.step == step);
7340 let e = expanded_trace.iter().find(|r| r.step == step);
7341 if let (Some(d), Some(e)) = (d, e) {
7342 let y_diff = d.state[1] - e.state[1];
7343 let v_diff = d.state[2] - e.state[2];
7344 let steer_diff = d.controls[0][1] - e.controls[0][1];
7345 println!(
7346 " step {:3}: y={:10.6}/{:10.6} diff={:12.6e} v_diff={:12.6e} steer_diff={:12.6e}",
7347 step, d.state[1], e.state[1], y_diff, v_diff, steer_diff
7348 );
7349 }
7350 }
7351
7352 println!("\nmax |y_diff| across all steps: {:.6e}", max_y_diff);
7353 if let Some(s) = first_significant_step {
7354 println!("first step with |y_diff| > 1e-3: {}", s);
7355 }
7356 }
7357
7358 fn optimize_linearized_controls_without_angle_wrap(
7361 xref: &[Vector4<f64>],
7362 xbar: &[Vector4<f64>],
7363 state: &State,
7364 initial_controls: &[Vector2<f64>],
7365 ) -> Vec<Vector2<f64>> {
7366 let (controls, _) = optimize_linearized_controls_without_angle_wrap_with_trace(
7367 xref,
7368 xbar,
7369 state,
7370 initial_controls,
7371 QP_MAX_ITERS,
7372 LINE_SEARCH_ITERS,
7373 );
7374 controls
7375 }
7376
7377 #[test]
7378 #[ignore = "long-running regression scenario"]
7379 fn test_mpc_switch_back_no_angle_wrap_closed_loop_parity() {
7380 let python_final_state = [
7382 -0.014_888_855_512_389,
7383 0.698_023_990_780_443,
7384 0.132_179_560_675_735,
7385 1.589_745_981_585_03,
7386 ];
7387 let python_history_length = 347usize;
7388
7389 let default_result = run_mpc_simulation();
7391 let default_final = default_result.final_state.to_vector();
7392 let default_len = default_result.hist_x.len();
7393
7394 let (cx, cy, cyaw) = generate_switch_back_course();
7396 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
7397
7398 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
7399 if state.yaw - cyaw[0] >= PI {
7400 state.yaw -= 2.0 * PI;
7401 } else if state.yaw - cyaw[0] <= -PI {
7402 state.yaw += 2.0 * PI;
7403 }
7404
7405 let final_index = cx.len() - 1;
7406 let goal = (cx[final_index], cy[final_index]);
7407 let mut target_index = 0;
7408 let mut warm_start = vec![Vector2::zeros(); T];
7409 let mut step_count = 0;
7410
7411 for _ in 0..MAX_SIM_STEPS {
7412 let (xref, new_index) = calc_ref_trajectory(
7413 &state,
7414 &cx,
7415 &cy,
7416 &cyaw,
7417 &speed_profile,
7418 UPSTREAM_SWITCH_BACK_TICK,
7419 target_index,
7420 );
7421 target_index = new_index;
7422
7423 let result = iterative_linear_mpc_control_with_test_optimizer(
7424 &xref,
7425 &state,
7426 &warm_start,
7427 optimize_linearized_controls_without_angle_wrap,
7428 );
7429
7430 state.update(result.controls[0][0], result.controls[0][1]);
7431 warm_start = next_warm_start(&result.controls);
7432 step_count += 1;
7433
7434 if check_goal(&state, goal, target_index, final_index) {
7435 break;
7436 }
7437 }
7438
7439 let nowrap_final = state.to_vector();
7440 let nowrap_len = step_count + 1; let default_diffs: Vec<f64> = (0..4)
7444 .map(|i| (default_final[i] - python_final_state[i]).abs())
7445 .collect();
7446 let nowrap_diffs: Vec<f64> = (0..4)
7447 .map(|i| (nowrap_final[i] - python_final_state[i]).abs())
7448 .collect();
7449
7450 println!("=== Closed-Loop Parity: Default vs No-Angle-Wrap ===");
7451 println!(
7452 "Python reference: {:?} (len={})",
7453 python_final_state, python_history_length
7454 );
7455 println!();
7456 println!("Default (with normalize_angle):");
7457 println!(
7458 " final: [{:.6}, {:.6}, {:.6}, {:.6}] (len={})",
7459 default_final[0], default_final[1], default_final[2], default_final[3], default_len
7460 );
7461 println!(
7462 " diffs: [{:.6e}, {:.6e}, {:.6e}, {:.6e}] step_diff={}",
7463 default_diffs[0],
7464 default_diffs[1],
7465 default_diffs[2],
7466 default_diffs[3],
7467 (default_len as isize - python_history_length as isize).abs()
7468 );
7469 println!();
7470 println!("No-angle-wrap:");
7471 println!(
7472 " final: [{:.6}, {:.6}, {:.6}, {:.6}] (len={})",
7473 nowrap_final[0], nowrap_final[1], nowrap_final[2], nowrap_final[3], nowrap_len
7474 );
7475 println!(
7476 " diffs: [{:.6e}, {:.6e}, {:.6e}, {:.6e}] step_diff={}",
7477 nowrap_diffs[0],
7478 nowrap_diffs[1],
7479 nowrap_diffs[2],
7480 nowrap_diffs[3],
7481 (nowrap_len as isize - python_history_length as isize).abs()
7482 );
7483 println!();
7484
7485 let default_total: f64 = default_diffs.iter().sum();
7487 let nowrap_total: f64 = nowrap_diffs.iter().sum();
7488 println!(
7489 "Total state diff: default={:.6e} nowrap={:.6e} improvement={:.2}%",
7490 default_total,
7491 nowrap_total,
7492 if default_total > 0.0 {
7493 (1.0 - nowrap_total / default_total) * 100.0
7494 } else {
7495 0.0
7496 }
7497 );
7498 }
7499
7500 #[allow(clippy::needless_range_loop, clippy::type_complexity)]
7517 fn solve_mpc_qp_clarabel(
7518 xref: &[Vector4<f64>],
7519 xbar: &[Vector4<f64>],
7520 state: &State,
7521 ) -> Option<(Vec<Vector2<f64>>, Vec<Vector4<f64>>)> {
7522 use clarabel::algebra::*;
7523 use clarabel::solver::*;
7524
7525 let nu = 2; let nx = 4; let n_u = nu * T; let n_x = nx * (T + 1); let n = n_u + n_x; let u_idx = |t: usize, k: usize| -> usize { t * nu + k };
7533 let x_idx = |t: usize, k: usize| -> usize { n_u + t * nx + k };
7534
7535 let r_diag = R;
7537 let rd_diag = RD;
7538 let q_diag = Q;
7539 let qf_diag = QF;
7540
7541 let mut p_rows = Vec::new();
7542 let mut p_cols = Vec::new();
7543 let mut p_vals = Vec::new();
7544
7545 for t in 0..T {
7547 for k in 0..nu {
7548 let idx = u_idx(t, k);
7549 let mut val = r_diag[k];
7550 if t > 0 {
7552 val += rd_diag[k];
7553 }
7554 if t < T - 1 {
7555 val += rd_diag[k];
7556 }
7557 p_rows.push(idx);
7558 p_cols.push(idx);
7559 p_vals.push(val * 2.0); }
7561 if t < T - 1 {
7563 for k in 0..nu {
7564 let i = u_idx(t, k);
7565 let j = u_idx(t + 1, k);
7566 p_rows.push(i);
7568 p_cols.push(j);
7569 p_vals.push(-rd_diag[k] * 2.0);
7570 }
7571 }
7572 }
7573
7574 for t in 1..=T {
7576 let w = if t == T { qf_diag } else { q_diag };
7577 for k in 0..nx {
7578 let idx = x_idx(t, k);
7579 p_rows.push(idx);
7580 p_cols.push(idx);
7581 p_vals.push(w[k] * 2.0);
7582 }
7583 }
7584
7585 let p = CscMatrix::new_from_triplets(n, n, p_rows.clone(), p_cols.clone(), p_vals.clone());
7586
7587 let mut q_vec = vec![0.0; n];
7589 for t in 1..=T {
7590 let w = if t == T { qf_diag } else { q_diag };
7591 for k in 0..nx {
7592 q_vec[x_idx(t, k)] = -w[k] * 2.0 * xref[t][k];
7595 }
7596 }
7597
7598 let n_eq = nx * (T + 1); let n_steer_rate = T - 1; let n_u_box = 2 * nu * T; let n_steer_rate_box = 2 * n_steer_rate; let n_v_box = 2 * (T + 1); let n_ineq = n_u_box + n_steer_rate_box + n_v_box;
7609
7610 let mut a_rows = Vec::new();
7611 let mut a_cols = Vec::new();
7612 let mut a_vals = Vec::new();
7613 let mut b_vec = vec![0.0; n_eq + n_ineq];
7614
7615 let x0 = state.to_vector();
7617 for k in 0..nx {
7618 let row = k;
7619 a_rows.push(row);
7620 a_cols.push(x_idx(0, k));
7621 a_vals.push(1.0);
7622 b_vec[row] = x0[k];
7623 }
7624
7625 for t in 0..T {
7627 let (a_mat, b_mat, c_vec) = get_linear_model_matrix(xbar[t][2], xbar[t][3], 0.0);
7628 let base_row = nx * (t + 1);
7629
7630 for i in 0..nx {
7633 let row = base_row + i;
7634 a_rows.push(row);
7636 a_cols.push(x_idx(t + 1, i));
7637 a_vals.push(-1.0);
7638 for j in 0..nx {
7640 let val = a_mat[(i, j)];
7641 if val.abs() > 1e-15 {
7642 a_rows.push(row);
7643 a_cols.push(x_idx(t, j));
7644 a_vals.push(val);
7645 }
7646 }
7647 for j in 0..nu {
7649 let val = b_mat[(i, j)];
7650 if val.abs() > 1e-15 {
7651 a_rows.push(row);
7652 a_cols.push(u_idx(t, j));
7653 a_vals.push(val);
7654 }
7655 }
7656 b_vec[row] = -c_vec[i];
7657 }
7658 }
7659
7660 let ineq_offset = n_eq;
7663 let mut ineq_row = 0;
7664
7665 let u_bounds = [MAX_ACCEL, MAX_STEER];
7667 for t in 0..T {
7668 for (k, &bound) in u_bounds.iter().enumerate() {
7669 let row = ineq_offset + ineq_row;
7671 a_rows.push(row);
7672 a_cols.push(u_idx(t, k));
7673 a_vals.push(1.0);
7674 b_vec[row] = bound;
7675 ineq_row += 1;
7676
7677 let row = ineq_offset + ineq_row;
7679 a_rows.push(row);
7680 a_cols.push(u_idx(t, k));
7681 a_vals.push(-1.0);
7682 b_vec[row] = bound;
7683 ineq_row += 1;
7684 }
7685 }
7686
7687 let max_steer_delta = MAX_DSTEER * DT;
7689 for t in 0..(T - 1) {
7690 let row = ineq_offset + ineq_row;
7692 a_rows.push(row);
7693 a_cols.push(u_idx(t + 1, 1));
7694 a_vals.push(1.0);
7695 a_rows.push(row);
7696 a_cols.push(u_idx(t, 1));
7697 a_vals.push(-1.0);
7698 b_vec[row] = max_steer_delta;
7699 ineq_row += 1;
7700
7701 let row = ineq_offset + ineq_row;
7703 a_rows.push(row);
7704 a_cols.push(u_idx(t + 1, 1));
7705 a_vals.push(-1.0);
7706 a_rows.push(row);
7707 a_cols.push(u_idx(t, 1));
7708 a_vals.push(1.0);
7709 b_vec[row] = max_steer_delta;
7710 ineq_row += 1;
7711 }
7712
7713 for t in 0..=T {
7715 let row = ineq_offset + ineq_row;
7717 a_rows.push(row);
7718 a_cols.push(x_idx(t, 2));
7719 a_vals.push(1.0);
7720 b_vec[row] = MAX_SPEED;
7721 ineq_row += 1;
7722
7723 let row = ineq_offset + ineq_row;
7725 a_rows.push(row);
7726 a_cols.push(x_idx(t, 2));
7727 a_vals.push(-1.0);
7728 b_vec[row] = -MIN_SPEED;
7729 ineq_row += 1;
7730 }
7731
7732 let m = n_eq + n_ineq;
7733
7734 let a_csc = CscMatrix::new_from_triplets(m, n, a_rows, a_cols, a_vals);
7737
7738 let cones = vec![
7740 SupportedConeT::ZeroConeT(n_eq), SupportedConeT::NonnegativeConeT(n_ineq), ];
7743
7744 let settings = DefaultSettingsBuilder::default()
7745 .verbose(false)
7746 .build()
7747 .unwrap();
7748
7749 let mut solver = DefaultSolver::new(&p, &q_vec, &a_csc, &b_vec, &cones, settings).ok()?;
7750 solver.solve();
7751
7752 if solver.solution.status != SolverStatus::Solved {
7753 return None;
7754 }
7755
7756 let z = &solver.solution.x;
7757
7758 let controls: Vec<Vector2<f64>> = (0..T)
7759 .map(|t| Vector2::new(z[u_idx(t, 0)], z[u_idx(t, 1)]))
7760 .collect();
7761
7762 let predicted: Vec<Vector4<f64>> = (0..=T)
7763 .map(|t| {
7764 Vector4::new(
7765 z[x_idx(t, 0)],
7766 z[x_idx(t, 1)],
7767 z[x_idx(t, 2)],
7768 z[x_idx(t, 3)],
7769 )
7770 })
7771 .collect();
7772
7773 Some((controls, predicted))
7774 }
7775
7776 fn optimize_linearized_controls_qp(
7778 xref: &[Vector4<f64>],
7779 xbar: &[Vector4<f64>],
7780 state: &State,
7781 _initial_controls: &[Vector2<f64>],
7782 ) -> Vec<Vector2<f64>> {
7783 if let Some((controls, _)) = solve_mpc_qp_clarabel(xref, xbar, state) {
7784 controls
7785 } else {
7786 optimize_linearized_controls(xref, xbar, state, _initial_controls)
7788 }
7789 }
7790
7791 #[test]
7792 #[ignore = "long-running regression scenario"]
7793 fn test_mpc_qp_solver_matches_projected_gradient_on_representative_steps() {
7794 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7795 80,
7796 optimize_linearized_controls,
7797 );
7798 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
7799 80,
7800 optimize_linearized_controls,
7801 );
7802
7803 println!("=== QP Solver (clarabel) vs Projected-Gradient ===");
7804 println!();
7805
7806 for &step in &[20usize, 70, 75, 78] {
7807 let row = detailed_row_at_step(&default_trace, step);
7808 let ws = default_warm_start.get(&step).unwrap();
7809 let state = state_from_vector4(&row.state);
7810
7811 let mut controls_for_xbar = ws.to_vec();
7813 controls_for_xbar.resize(T, Vector2::zeros());
7814 apply_control_constraints(&mut controls_for_xbar);
7815 let xbar = predict_motion(state, &controls_for_xbar);
7816
7817 let pg_controls = optimize_linearized_controls(&row.xref, &xbar, &state, ws);
7819
7820 let qp_result = solve_mpc_qp_clarabel(&row.xref, &xbar, &state);
7822
7823 if let Some((qp_controls, _qp_predicted)) = qp_result {
7824 let control_diff = summarize_vec2_sequence_diff(
7825 &qp_controls,
7826 &pg_controls.iter().map(|c| [c[0], c[1]]).collect::<Vec<_>>(),
7827 );
7828 println!(
7829 "step {step}: control_diff=[{:.6e}, {:.6e}] pg_steer={:.6} qp_steer={:.6}",
7830 control_diff[0], control_diff[1], pg_controls[0][1], qp_controls[0][1],
7831 );
7832 } else {
7833 println!("step {step}: QP solver failed!");
7834 }
7835 }
7836 }
7837
7838 #[test]
7839 #[ignore = "long-running regression scenario"]
7840 fn test_mpc_qp_solver_closed_loop_parity() {
7841 let python_final_state = [
7842 -0.014_888_855_512_389,
7843 0.698_023_990_780_443,
7844 0.132_179_560_675_735,
7845 1.589_745_981_585_03,
7846 ];
7847 let python_history_length = 347usize;
7848
7849 let default_result = run_mpc_simulation();
7850 let default_final = default_result.final_state.to_vector();
7851
7852 let (cx, cy, cyaw) = generate_switch_back_course();
7854 let speed_profile = calc_speed_profile(&cx, &cy, &cyaw, TARGET_SPEED);
7855
7856 let mut state = State::new(cx[0], cy[0], 0.0, cyaw[0]);
7857 if state.yaw - cyaw[0] >= PI {
7858 state.yaw -= 2.0 * PI;
7859 } else if state.yaw - cyaw[0] <= -PI {
7860 state.yaw += 2.0 * PI;
7861 }
7862
7863 let final_index = cx.len() - 1;
7864 let goal = (cx[final_index], cy[final_index]);
7865 let mut target_index = 0;
7866 let mut warm_start = vec![Vector2::zeros(); T];
7867 let mut step_count = 0;
7868
7869 for _ in 0..MAX_SIM_STEPS {
7870 let (xref, new_index) = calc_ref_trajectory(
7871 &state,
7872 &cx,
7873 &cy,
7874 &cyaw,
7875 &speed_profile,
7876 UPSTREAM_SWITCH_BACK_TICK,
7877 target_index,
7878 );
7879 target_index = new_index;
7880
7881 let result = iterative_linear_mpc_control_with_test_optimizer(
7882 &xref,
7883 &state,
7884 &warm_start,
7885 optimize_linearized_controls_qp,
7886 );
7887
7888 state.update(result.controls[0][0], result.controls[0][1]);
7889 warm_start = next_warm_start(&result.controls);
7890 step_count += 1;
7891
7892 if check_goal(&state, goal, target_index, final_index) {
7893 break;
7894 }
7895 }
7896
7897 let qp_final = state.to_vector();
7898 let qp_len = step_count + 1;
7899
7900 let default_diffs: Vec<f64> = (0..4)
7901 .map(|i| (default_final[i] - python_final_state[i]).abs())
7902 .collect();
7903 let qp_diffs: Vec<f64> = (0..4)
7904 .map(|i| (qp_final[i] - python_final_state[i]).abs())
7905 .collect();
7906
7907 println!("=== Closed-Loop Parity: PG vs QP (clarabel) ===");
7908 println!(
7909 "Python ref: {:?} (len={})",
7910 python_final_state, python_history_length
7911 );
7912 println!();
7913 println!(
7914 "PG: [{:.6},{:.6},{:.6},{:.6}] len={} diffs=[{:.4e},{:.4e},{:.4e},{:.4e}]",
7915 default_final[0],
7916 default_final[1],
7917 default_final[2],
7918 default_final[3],
7919 default_result.hist_x.len(),
7920 default_diffs[0],
7921 default_diffs[1],
7922 default_diffs[2],
7923 default_diffs[3]
7924 );
7925 println!(
7926 "QP: [{:.6},{:.6},{:.6},{:.6}] len={} diffs=[{:.4e},{:.4e},{:.4e},{:.4e}]",
7927 qp_final[0],
7928 qp_final[1],
7929 qp_final[2],
7930 qp_final[3],
7931 qp_len,
7932 qp_diffs[0],
7933 qp_diffs[1],
7934 qp_diffs[2],
7935 qp_diffs[3]
7936 );
7937
7938 let pg_total: f64 = default_diffs.iter().sum();
7939 let qp_total: f64 = qp_diffs.iter().sum();
7940 println!(
7941 "Total: PG={:.4e} QP={:.4e} improvement={:.1}%",
7942 pg_total,
7943 qp_total,
7944 if pg_total > 0.0 {
7945 (1.0 - qp_total / pg_total) * 100.0
7946 } else {
7947 0.0
7948 }
7949 );
7950 }
7951
7952 #[test]
7953 #[ignore = "long-running regression scenario"]
7954 fn test_mpc_solver_comparison_qp_vs_pg_detailed() {
7955 let expected_trace =
7956 parse_switch_back_trace(include_str!("testdata/mpc_switch_back_prefix_trace.csv"));
7957
7958 let hybrid_trace = collect_switch_back_detailed_trace(350); let qp_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7961 350,
7962 optimize_linearized_controls_qp,
7963 );
7964 let pg_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7965 350,
7966 optimize_linearized_controls_with_full_candidate_expansion,
7967 );
7968 let pg_orig_trace = collect_switch_back_detailed_trace_with_test_optimizer(
7969 350,
7970 optimize_linearized_controls_original,
7971 );
7972
7973 println!("=== Solver Comparison (state diff sum vs Python) ===");
7974 println!(
7975 "{:>5} {:>10} {:>10} {:>10} {:>10} best",
7976 "step", "hybrid", "qp-only", "pg-exp", "pg-orig"
7977 );
7978
7979 let check_steps: Vec<usize> = (0..=80)
7980 .step_by(5)
7981 .chain([71, 72, 73, 74, 75, 76, 77, 78, 79, 80])
7982 .chain((85..350).step_by(10))
7983 .collect();
7984
7985 for &step in &check_steps {
7986 if step >= expected_trace.len() {
7987 continue;
7988 }
7989 let py = &expected_trace[step];
7990 let hy = hybrid_trace.iter().find(|r| r.step == step);
7991 let qp = qp_trace.iter().find(|r| r.step == step);
7992 let pg = pg_trace.iter().find(|r| r.step == step);
7993 let po = pg_orig_trace.iter().find(|r| r.step == step);
7994
7995 if let (Some(hy), Some(qp), Some(pg), Some(po)) = (hy, qp, pg, po) {
7996 let hy_s: f64 = trace_state_diff(&hy.state, &py.state).iter().sum();
7997 let qp_s: f64 = trace_state_diff(&qp.state, &py.state).iter().sum();
7998 let pg_s: f64 = trace_state_diff(&pg.state, &py.state).iter().sum();
7999 let po_s: f64 = trace_state_diff(&po.state, &py.state).iter().sum();
8000
8001 let min_s = hy_s.min(qp_s).min(pg_s).min(po_s);
8002 let best = if (hy_s - min_s).abs() < 1e-15 {
8003 "HYBRID"
8004 } else if (qp_s - min_s).abs() < 1e-15 {
8005 "QP"
8006 } else if (pg_s - min_s).abs() < 1e-15 {
8007 "PG-exp"
8008 } else {
8009 "PG-orig"
8010 };
8011
8012 println!(
8013 "{:5} {:10.4e} {:10.4e} {:10.4e} {:10.4e} {}",
8014 step, hy_s, qp_s, pg_s, po_s, best
8015 );
8016 }
8017 }
8018
8019 let mut wins = [0u32; 4]; let labels = ["HYBRID", "QP-only", "PG-expanded", "PG-original"];
8022 let min_steps = [
8023 hybrid_trace.len(),
8024 qp_trace.len(),
8025 pg_trace.len(),
8026 pg_orig_trace.len(),
8027 expected_trace.len(),
8028 ]
8029 .into_iter()
8030 .min()
8031 .unwrap();
8032 for (step, py) in expected_trace.iter().enumerate().take(min_steps) {
8033 let traces = [&hybrid_trace, &qp_trace, &pg_trace, &pg_orig_trace];
8034 let diffs: Vec<f64> = traces
8035 .iter()
8036 .map(|t| {
8037 t.iter()
8038 .find(|r| r.step == step)
8039 .map(|r| trace_state_diff(&r.state, &py.state).iter().sum())
8040 .unwrap_or(f64::INFINITY)
8041 })
8042 .collect();
8043 let min_d = diffs.iter().copied().fold(f64::INFINITY, f64::min);
8044 for (i, &d) in diffs.iter().enumerate() {
8045 if (d - min_d).abs() < 1e-15 {
8046 wins[i] += 1;
8047 break;
8048 }
8049 }
8050 }
8051 println!("\n=== Win count (closest to Python per step, out of {min_steps}) ===");
8052 for (i, label) in labels.iter().enumerate() {
8053 println!("{:12}: {} steps", label, wins[i]);
8054 }
8055 }
8056
8057 #[test]
8058 #[ignore = "long-running regression scenario"]
8059 fn test_mpc_switch_back_characterizes_v_injection_pull_ratio_threshold() {
8060 let default_trace = collect_switch_back_detailed_trace_with_test_optimizer(
8061 330,
8062 optimize_linearized_controls,
8063 );
8064 let expanded_trace = collect_switch_back_detailed_trace_with_test_optimizer(
8065 330,
8066 optimize_linearized_controls_with_full_candidate_expansion,
8067 );
8068 let default_warm_start = collect_switch_back_warm_start_trace_with_test_optimizer(
8069 330,
8070 optimize_linearized_controls,
8071 );
8072
8073 let d325 = detailed_row_at_step(&default_trace, 325);
8074 let e325 = detailed_row_at_step(&expanded_trace, 325);
8075 let e326 = detailed_row_at_step(&expanded_trace, 326);
8076 let ws = default_warm_start.get(&325).unwrap();
8077
8078 let d_v = d325.state[2]; let e325_v = e325.state[2]; let e326_v = e326.state[2]; let d_baseline_controls: Vec<[f64; 2]> =
8084 d325.controls.iter().map(|v| [v[0], v[1]]).collect();
8085 let e_baseline_controls: Vec<[f64; 2]> =
8086 e325.controls.iter().map(|v| [v[0], v[1]]).collect();
8087
8088 println!("=== V-Injection Pull Ratio Threshold Sweep ===");
8089 println!(
8090 "default v={:.6}, e325_v={:.6}, e326_v={:.6}",
8091 d_v, e325_v, e326_v
8092 );
8093 println!("Sweeping injected v from e325_v to e326_v in 0.02 steps");
8094 println!();
8095 println!(
8096 "{:>10} {:>10} {:>10} {:>10} {:>10}",
8097 "inj_v", "steer", "same_c0", "opp_c0", "ratio"
8098 );
8099
8100 let n_steps = 11;
8102 let mut prev_ratio = f64::INFINITY;
8103 let mut threshold_v = None;
8104
8105 for i in 0..=n_steps {
8106 let frac = i as f64 / n_steps as f64;
8107 let injected_v = e325_v + frac * (e326_v - e325_v);
8108
8109 let mut hybrid = d325.state;
8111 hybrid[2] = injected_v;
8112 let hybrid_state = state_from_vector4(&hybrid);
8113
8114 let result = iterative_linear_mpc_control_with_test_optimizer(
8115 &d325.xref,
8116 &hybrid_state,
8117 ws,
8118 optimize_linearized_controls,
8119 );
8120
8121 let same_c0 = summarize_vec2_sequence_diff(&result.controls, &d_baseline_controls)[0];
8122 let opp_c0 = summarize_vec2_sequence_diff(&result.controls, &e_baseline_controls)[0];
8123
8124 let ratio = if opp_c0 > 0.0 {
8125 same_c0 / opp_c0
8126 } else {
8127 f64::INFINITY
8128 };
8129
8130 println!(
8131 "{:10.6} {:10.6} {:10.4e} {:10.4e} {:10.3}",
8132 injected_v, result.controls[0][1], same_c0, opp_c0, ratio
8133 );
8134
8135 if threshold_v.is_none() && prev_ratio > 4.0 && ratio <= 4.0 {
8137 threshold_v = Some((injected_v, ratio));
8138 }
8139 prev_ratio = ratio;
8140 }
8141
8142 if let Some((v, r)) = threshold_v {
8143 println!(
8144 "\nThreshold: ratio drops below 4.0 at v={:.6} (ratio={:.3})",
8145 v, r
8146 );
8147 } else {
8148 println!("\nNo threshold crossing found in this range");
8149 }
8150 }
8151}