1use nalgebra::{Matrix1, Matrix1x4, Matrix4, Vector4};
10use rust_robotics_core::{ControlInput, Path2D, PathTracker, Point2D, State2D};
11use std::f64::consts::PI;
12
13#[derive(Debug, Clone, Copy)]
15pub struct LQRVehicleState {
16 pub x: f64,
17 pub y: f64,
18 pub yaw: f64,
19 pub v: f64,
20 pub wheelbase: f64,
21 pub max_steer: f64,
22}
23
24impl LQRVehicleState {
25 pub fn new(x: f64, y: f64, yaw: f64, v: f64, wheelbase: f64, max_steer: f64) -> Self {
26 LQRVehicleState {
27 x,
28 y,
29 yaw,
30 v,
31 wheelbase,
32 max_steer,
33 }
34 }
35
36 pub fn update(&mut self, a: f64, mut delta: f64, dt: f64) {
37 delta = delta.clamp(-self.max_steer, self.max_steer);
38 self.x += self.v * self.yaw.cos() * dt;
39 self.y += self.v * self.yaw.sin() * dt;
40 self.yaw += self.v / self.wheelbase * delta.tan() * dt;
41 self.v += a * dt;
42 }
43
44 pub fn to_state2d(&self) -> State2D {
45 State2D::new(self.x, self.y, self.yaw, self.v)
46 }
47}
48
49impl From<State2D> for LQRVehicleState {
50 fn from(s: State2D) -> Self {
51 LQRVehicleState::new(s.x, s.y, s.yaw, s.v, 0.5, 45.0_f64.to_radians())
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct LQRSteerConfig {
58 pub wheelbase: f64,
60 pub max_steer: f64,
62 pub kp: f64,
64 pub q_diag: [f64; 4],
66 pub r: f64,
68 pub dt: f64,
70 pub goal_threshold: f64,
72}
73
74impl Default for LQRSteerConfig {
75 fn default() -> Self {
76 Self {
77 wheelbase: 0.5,
78 max_steer: 45.0_f64.to_radians(),
79 kp: 1.0,
80 q_diag: [1.0, 1.0, 1.0, 1.0],
81 r: 1.0,
82 dt: 0.1,
83 goal_threshold: 0.3,
84 }
85 }
86}
87
88pub struct LQRSteerController {
90 config: LQRSteerConfig,
91 path: Path2D,
92 path_yaw: Vec<f64>,
93 path_curvature: Vec<f64>,
94 speed_profile: Vec<f64>,
95 prev_error: f64,
96 prev_theta_error: f64,
97}
98
99impl LQRSteerController {
100 pub fn new(config: LQRSteerConfig) -> Self {
102 LQRSteerController {
103 config,
104 path: Path2D::new(),
105 path_yaw: Vec::new(),
106 path_curvature: Vec::new(),
107 speed_profile: Vec::new(),
108 prev_error: 0.0,
109 prev_theta_error: 0.0,
110 }
111 }
112
113 pub fn with_defaults() -> Self {
115 Self::new(LQRSteerConfig::default())
116 }
117
118 pub fn set_path(&mut self, path: Path2D) {
120 let (yaw, curvature) = self.compute_path_derivatives(&path);
121 self.path_yaw = yaw;
122 self.path_curvature = curvature;
123 self.speed_profile = self.calc_speed_profile(&path, 2.78); self.path = path;
125 self.prev_error = 0.0;
126 self.prev_theta_error = 0.0;
127 }
128
129 pub fn set_path_with_speed(&mut self, path: Path2D, target_speed: f64) {
131 let (yaw, curvature) = self.compute_path_derivatives(&path);
132 self.path_yaw = yaw;
133 self.path_curvature = curvature;
134 self.speed_profile = self.calc_speed_profile(&path, target_speed);
135 self.path = path;
136 self.prev_error = 0.0;
137 self.prev_theta_error = 0.0;
138 }
139
140 pub fn get_path(&self) -> &Path2D {
142 &self.path
143 }
144
145 fn compute_path_derivatives(&self, path: &Path2D) -> (Vec<f64>, Vec<f64>) {
147 let n = path.len();
148 let yaw = path.yaw_profile();
149 let mut curvature = Vec::with_capacity(n);
150
151 for i in 0..n {
152 if i > 0 && i < n - 1 {
154 let dyaw = Self::normalize_angle(yaw[i] - yaw[i - 1]);
155 let dx = path.points[i].x - path.points[i - 1].x;
156 let dy = path.points[i].y - path.points[i - 1].y;
157 let ds = (dx * dx + dy * dy).sqrt();
158 curvature.push(if ds > 0.001 { dyaw / ds } else { 0.0 });
159 } else {
160 curvature.push(0.0);
161 }
162 }
163
164 (yaw, curvature)
165 }
166
167 fn calc_speed_profile(&self, path: &Path2D, target_speed: f64) -> Vec<f64> {
169 let n = path.len();
170 let mut profile = Vec::with_capacity(n);
171 let mut direction = 1.0;
172
173 for i in 0..n {
174 if i < n - 1 && i < self.path_yaw.len() - 1 {
175 let dyaw = (self.path_yaw[i + 1] - self.path_yaw[i]).abs();
176 if (PI / 4.0..PI / 2.0).contains(&dyaw) {
177 direction *= -1.0;
178 profile.push(0.0);
179 } else {
180 profile.push(direction * target_speed);
181 }
182 } else {
183 profile.push(0.0);
184 }
185 }
186
187 profile
188 }
189
190 fn normalize_angle(mut angle: f64) -> f64 {
192 while angle > PI {
193 angle -= 2.0 * PI;
194 }
195 while angle < -PI {
196 angle += 2.0 * PI;
197 }
198 angle
199 }
200
201 fn calc_target_index(&self, state: &LQRVehicleState) -> (usize, f64) {
203 let query = Point2D::new(state.x, state.y);
204 let min_idx = self.path.nearest_point_index(query).unwrap_or(0);
205 let target = &self.path.points[min_idx];
206 let min_dist = ((state.x - target.x).powi(2) + (state.y - target.y).powi(2)).sqrt();
207
208 let diff_x = target.x - state.x;
210 let diff_y = target.y - state.y;
211 let arcang = self.path_yaw[min_idx] - diff_y.atan2(diff_x);
212 let angle = Self::normalize_angle(arcang);
213
214 let error = if angle < 0.0 { -min_dist } else { min_dist };
215 (min_idx, error)
216 }
217
218 fn solve_dare(
220 a: Matrix4<f64>,
221 b: Vector4<f64>,
222 q: Matrix4<f64>,
223 r: Matrix1<f64>,
224 ) -> Matrix4<f64> {
225 let mut x = q;
226 let max_iter = 150;
227 let eps = 0.01;
228
229 for _ in 0..max_iter {
230 let bt_x_b = b.transpose() * x * b;
231 let inv = (r + bt_x_b).try_inverse().unwrap_or(Matrix1::identity());
232 let xn =
233 a.transpose() * x * a - a.transpose() * x * b * inv * b.transpose() * x * a + q;
234
235 if (xn - x).abs().max() < eps {
236 break;
237 }
238 x = xn;
239 }
240 x
241 }
242
243 fn dlqr(a: Matrix4<f64>, b: Vector4<f64>, q: Matrix4<f64>, r: Matrix1<f64>) -> Matrix1x4<f64> {
245 let x = Self::solve_dare(a, b, q, r);
246 let bt_x_b = b.transpose() * x * b;
247 let inv = (bt_x_b + r).try_inverse().unwrap_or(Matrix1::identity());
248 inv * (b.transpose() * x * a)
249 }
250
251 pub fn compute_steering(&mut self, state: &LQRVehicleState) -> f64 {
253 if self.path.is_empty() {
254 return 0.0;
255 }
256
257 let (ind, e) = self.calc_target_index(state);
258 let k = if ind < self.path_curvature.len() {
259 self.path_curvature[ind]
260 } else {
261 0.0
262 };
263
264 let th_e = Self::normalize_angle(state.yaw - self.path_yaw[ind]);
265 let dt = self.config.dt;
266
267 let mut a = Matrix4::zeros();
269 a[(0, 0)] = 1.0;
270 a[(0, 1)] = dt;
271 a[(1, 2)] = state.v;
272 a[(2, 2)] = 1.0;
273 a[(2, 3)] = dt;
274
275 let mut b = Vector4::zeros();
276 b[3] = state.v / state.wheelbase;
277
278 let q = Matrix4::from_diagonal(&Vector4::new(
280 self.config.q_diag[0],
281 self.config.q_diag[1],
282 self.config.q_diag[2],
283 self.config.q_diag[3],
284 ));
285 let r = Matrix1::new(self.config.r);
286
287 let gain = Self::dlqr(a, b, q, r);
288
289 let x_err = Vector4::new(
291 e,
292 if dt > 0.0 {
293 (e - self.prev_error) / dt
294 } else {
295 0.0
296 },
297 th_e,
298 if dt > 0.0 {
299 (th_e - self.prev_theta_error) / dt
300 } else {
301 0.0
302 },
303 );
304
305 let ff = (state.wheelbase * k).atan2(1.0);
307 let fb = Self::normalize_angle((-gain * x_err)[0]);
308 let delta = ff + fb;
309
310 self.prev_error = e;
311 self.prev_theta_error = th_e;
312
313 delta.clamp(-state.max_steer, state.max_steer)
314 }
315
316 pub fn compute_acceleration(&self, target_speed: f64, current_speed: f64) -> f64 {
318 self.config.kp * (target_speed - current_speed)
319 }
320
321 pub fn get_target_speed(&self, index: usize) -> f64 {
323 if index < self.speed_profile.len() {
324 self.speed_profile[index].abs()
325 } else {
326 0.0
327 }
328 }
329
330 pub fn is_goal_reached_vehicle(&self, state: &LQRVehicleState) -> bool {
332 if let Some(goal) = self.path.points.last() {
333 let dx = state.x - goal.x;
334 let dy = state.y - goal.y;
335 (dx * dx + dy * dy).sqrt() < self.config.goal_threshold
336 } else {
337 true
338 }
339 }
340
341 pub fn planning(
343 &mut self,
344 waypoints: Vec<(f64, f64)>,
345 target_speed: f64,
346 ds: f64,
347 ) -> Option<Vec<(f64, f64)>> {
348 if waypoints.len() < 2 {
349 return None;
350 }
351
352 let ax: Vec<f64> = waypoints.iter().map(|p| p.0).collect();
354 let ay: Vec<f64> = waypoints.iter().map(|p| p.1).collect();
355 let (cx, cy, cyaw, ck, _) = calc_spline_course(&ax, &ay, ds);
356
357 let path = Path2D::from_points(
359 cx.iter()
360 .zip(cy.iter())
361 .map(|(&x, &y)| Point2D::new(x, y))
362 .collect(),
363 );
364 self.path_yaw = cyaw;
365 self.path_curvature = ck;
366 self.speed_profile = self.calc_speed_profile(&path, target_speed);
367 self.path = path;
368
369 let mut state = LQRVehicleState::new(
371 0.0,
372 0.0,
373 0.0,
374 0.0,
375 self.config.wheelbase,
376 self.config.max_steer,
377 );
378
379 let mut trajectory = vec![(state.x, state.y)];
380 let dt = self.config.dt;
381 let t_max = 500.0;
382 let mut time = 0.0;
383
384 while time < t_max {
385 let (target_idx, _) = self.calc_target_index(&state);
386 let target_v = self.get_target_speed(target_idx);
387
388 let ai = self.compute_acceleration(target_v, state.v);
389 let di = self.compute_steering(&state);
390 state.update(ai, di, dt);
391 time += dt;
392
393 trajectory.push((state.x, state.y));
394
395 if self.is_goal_reached_vehicle(&state) {
396 break;
397 }
398 }
399
400 Some(trajectory)
401 }
402}
403
404impl PathTracker for LQRSteerController {
405 fn compute_control(&mut self, current_state: &State2D, path: &Path2D) -> ControlInput {
406 if self.path.len() != path.len() {
408 self.set_path(path.clone());
409 }
410
411 let vehicle_state = LQRVehicleState::new(
412 current_state.x,
413 current_state.y,
414 current_state.yaw,
415 current_state.v,
416 self.config.wheelbase,
417 self.config.max_steer,
418 );
419
420 let delta = self.compute_steering(&vehicle_state);
421 let (target_idx, _) = self.calc_target_index(&vehicle_state);
422 let target_v = self.get_target_speed(target_idx);
423
424 let v =
425 current_state.v + self.compute_acceleration(target_v, current_state.v) * self.config.dt;
426 let omega = v * delta.tan() / self.config.wheelbase;
427
428 ControlInput::new(v, omega)
429 }
430
431 fn is_goal_reached(&self, current_state: &State2D, goal: Point2D) -> bool {
432 let dx = current_state.x - goal.x;
433 let dy = current_state.y - goal.y;
434 (dx * dx + dy * dy).sqrt() < self.config.goal_threshold
435 }
436}
437
438fn calc_spline_course(x: &[f64], y: &[f64], ds: f64) -> SplineCourse {
441 let sp = CubicSpline2D::new(x, y);
442 let mut s = 0.0;
443 let mut course_x = Vec::new();
444 let mut course_y = Vec::new();
445 let mut course_yaw = Vec::new();
446 let mut course_k = Vec::new();
447 let mut course_s = Vec::new();
448
449 let s_max = *sp
451 .s
452 .last()
453 .expect("spline s is non-empty after construction")
454 - ds;
455 while s < s_max {
456 let (ix, iy) = sp.calc_position(s);
457 let iyaw = sp.calc_yaw(s);
458 let ik = sp.calc_curvature(s);
459 course_x.push(ix);
460 course_y.push(iy);
461 course_yaw.push(iyaw);
462 course_k.push(ik);
463 course_s.push(s);
464 s += ds;
465 }
466
467 (course_x, course_y, course_yaw, course_k, course_s)
468}
469
470struct CubicSpline {
471 a: Vec<f64>,
472 b: Vec<f64>,
473 c: Vec<f64>,
474 d: Vec<f64>,
475 x: Vec<f64>,
476}
477
478type SplineCourse = (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>);
479
480impl CubicSpline {
481 fn new(x: &[f64], y: &[f64]) -> Self {
482 let n = x.len();
483 let mut h = vec![0.0; n - 1];
484 for i in 0..n - 1 {
485 h[i] = x[i + 1] - x[i];
486 }
487
488 let mut a = vec![0.0; n];
489 let mut b = vec![0.0; n];
490 let mut c = vec![0.0; n];
491 let mut d = vec![0.0; n];
492
493 a[..n].copy_from_slice(&y[..n]);
494
495 let mut alpha = vec![0.0; n - 1];
496 for i in 1..n - 1 {
497 alpha[i] = 3.0 * (a[i + 1] - a[i]) / h[i] - 3.0 * (a[i] - a[i - 1]) / h[i - 1];
498 }
499
500 let mut l = vec![1.0; n];
501 let mut mu = vec![0.0; n];
502 let mut z = vec![0.0; n];
503
504 for i in 1..n - 1 {
505 l[i] = 2.0 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1];
506 mu[i] = h[i] / l[i];
507 z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
508 }
509
510 for j in (0..n - 1).rev() {
511 c[j] = z[j] - mu[j] * c[j + 1];
512 b[j] = (a[j + 1] - a[j]) / h[j] - h[j] * (c[j + 1] + 2.0 * c[j]) / 3.0;
513 d[j] = (c[j + 1] - c[j]) / (3.0 * h[j]);
514 }
515
516 CubicSpline {
517 a,
518 b,
519 c,
520 d,
521 x: x.to_vec(),
522 }
523 }
524
525 fn calc(&self, t: f64) -> f64 {
526 if t < self.x[0] {
527 return self.a[0];
528 } else if t > self.x[self.x.len() - 1] {
529 return self.a[self.a.len() - 1];
530 }
531
532 let mut i = self.search_index(t);
533 if i >= self.x.len() - 1 {
534 i = self.x.len() - 2;
535 }
536
537 let dx = t - self.x[i];
538 self.a[i] + self.b[i] * dx + self.c[i] * dx * dx + self.d[i] * dx * dx * dx
539 }
540
541 fn calc_d(&self, t: f64) -> f64 {
542 if t < self.x[0] {
543 return self.b[0];
544 } else if t > self.x[self.x.len() - 1] {
545 return self.b[self.b.len() - 1];
546 }
547
548 let mut i = self.search_index(t);
549 if i >= self.x.len() - 1 {
550 i = self.x.len() - 2;
551 }
552
553 let dx = t - self.x[i];
554 self.b[i] + 2.0 * self.c[i] * dx + 3.0 * self.d[i] * dx * dx
555 }
556
557 fn calc_dd(&self, t: f64) -> f64 {
558 if t < self.x[0] {
559 return 2.0 * self.c[0];
560 } else if t > self.x[self.x.len() - 1] {
561 return 2.0 * self.c[self.c.len() - 1];
562 }
563
564 let mut i = self.search_index(t);
565 if i >= self.x.len() - 1 {
566 i = self.x.len() - 2;
567 }
568
569 let dx = t - self.x[i];
570 2.0 * self.c[i] + 6.0 * self.d[i] * dx
571 }
572
573 fn search_index(&self, x: f64) -> usize {
574 for i in 0..self.x.len() - 1 {
575 if self.x[i] <= x && x <= self.x[i + 1] {
576 return i;
577 }
578 }
579 self.x.len() - 2
580 }
581}
582
583struct CubicSpline2D {
584 s: Vec<f64>,
585 sx: CubicSpline,
586 sy: CubicSpline,
587}
588
589impl CubicSpline2D {
590 fn new(x: &[f64], y: &[f64]) -> Self {
591 let mut s = vec![0.0];
592 for i in 1..x.len() {
593 let dx = x[i] - x[i - 1];
594 let dy = y[i] - y[i - 1];
595 s.push(s[i - 1] + (dx * dx + dy * dy).sqrt());
596 }
597
598 let sx = CubicSpline::new(&s, x);
599 let sy = CubicSpline::new(&s, y);
600
601 CubicSpline2D { s, sx, sy }
602 }
603
604 fn calc_position(&self, s: f64) -> (f64, f64) {
605 let x = self.sx.calc(s);
606 let y = self.sy.calc(s);
607 (x, y)
608 }
609
610 fn calc_curvature(&self, s: f64) -> f64 {
611 let dx = self.sx.calc_d(s);
612 let ddx = self.sx.calc_dd(s);
613 let dy = self.sy.calc_d(s);
614 let ddy = self.sy.calc_dd(s);
615 let denom = (dx * dx + dy * dy).powf(1.5);
616 if denom.abs() > 1e-6 {
617 (ddy * dx - ddx * dy) / denom
618 } else {
619 0.0
620 }
621 }
622
623 fn calc_yaw(&self, s: f64) -> f64 {
624 let dx = self.sx.calc_d(s);
625 let dy = self.sy.calc_d(s);
626 dy.atan2(dx)
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 #[test]
635 fn test_lqr_creation() {
636 let config = LQRSteerConfig::default();
637 let controller = LQRSteerController::new(config);
638 assert!(controller.path.is_empty());
639 }
640
641 #[test]
642 fn test_lqr_set_path() {
643 let mut controller = LQRSteerController::with_defaults();
644 let path = Path2D::from_points(vec![
645 Point2D::new(0.0, 0.0),
646 Point2D::new(1.0, 0.0),
647 Point2D::new(2.0, 0.0),
648 ]);
649 controller.set_path(path);
650 assert_eq!(controller.get_path().len(), 3);
651 }
652
653 #[test]
654 fn test_lqr_normalize_angle() {
655 assert!((LQRSteerController::normalize_angle(3.0 * PI) - PI).abs() < 0.01);
656 assert!((LQRSteerController::normalize_angle(-3.0 * PI) + PI).abs() < 0.01);
657 }
658
659 #[test]
660 fn test_lqr_planning() {
661 let mut controller = LQRSteerController::with_defaults();
662 let waypoints = vec![(0.0, 0.0), (5.0, 0.0), (10.0, 0.0)];
663
664 let result = controller.planning(waypoints, 2.0, 0.5);
665 assert!(result.is_some());
666 assert!(!result.unwrap().is_empty());
667 }
668
669 #[test]
670 fn test_lqr_solve_dare() {
671 let a = Matrix4::identity();
672 let b = Vector4::new(0.0, 0.0, 0.0, 1.0);
673 let q = Matrix4::identity();
674 let r = Matrix1::new(1.0);
675
676 let x = LQRSteerController::solve_dare(a, b, q, r);
677 assert!(x[(0, 0)] >= 0.0);
679 }
680}