Skip to main content

rust_robotics_planning/
quintic_polynomials.rs

1#![allow(
2    dead_code,
3    clippy::too_many_arguments,
4    clippy::needless_borrows_for_generic_args
5)]
6
7//! Quintic Polynomials path planning
8//!
9//! Generates smooth trajectories using quintic polynomial curves
10//! that satisfy position, velocity, and acceleration boundary conditions.
11
12// Parameters
13const MAX_T: f64 = 100.0; // maximum time to the goal [s]
14const MIN_T: f64 = 5.0; // minimum time to the goal [s]
15
16#[derive(Debug, Clone)]
17pub struct QuinticPolynomial {
18    pub a0: f64,
19    pub a1: f64,
20    pub a2: f64,
21    pub a3: f64,
22    pub a4: f64,
23    pub a5: f64,
24}
25
26impl QuinticPolynomial {
27    pub fn new(xs: f64, vxs: f64, axs: f64, xe: f64, vxe: f64, axe: f64, time: f64) -> Self {
28        let a0 = xs;
29        let a1 = vxs;
30        let a2 = axs / 2.0;
31
32        let t2 = time * time;
33        let t3 = t2 * time;
34        let t4 = t3 * time;
35        let t5 = t4 * time;
36
37        let a_matrix = [
38            [t3, t4, t5],
39            [3.0 * t2, 4.0 * t3, 5.0 * t4],
40            [6.0 * time, 12.0 * t2, 20.0 * t3],
41        ];
42
43        let b = [
44            xe - a0 - a1 * time - a2 * t2,
45            vxe - a1 - 2.0 * a2 * time,
46            axe - 2.0 * a2,
47        ];
48
49        let det_a = Self::determinant_3x3(&a_matrix);
50
51        let mut a3_matrix = a_matrix;
52        a3_matrix[0][0] = b[0];
53        a3_matrix[1][0] = b[1];
54        a3_matrix[2][0] = b[2];
55        let a3 = Self::determinant_3x3(&a3_matrix) / det_a;
56
57        let mut a4_matrix = a_matrix;
58        a4_matrix[0][1] = b[0];
59        a4_matrix[1][1] = b[1];
60        a4_matrix[2][1] = b[2];
61        let a4 = Self::determinant_3x3(&a4_matrix) / det_a;
62
63        let mut a5_matrix = a_matrix;
64        a5_matrix[0][2] = b[0];
65        a5_matrix[1][2] = b[1];
66        a5_matrix[2][2] = b[2];
67        let a5 = Self::determinant_3x3(&a5_matrix) / det_a;
68
69        QuinticPolynomial {
70            a0,
71            a1,
72            a2,
73            a3,
74            a4,
75            a5,
76        }
77    }
78
79    fn determinant_3x3(matrix: &[[f64; 3]; 3]) -> f64 {
80        matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1])
81            - matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0])
82            + matrix[0][2] * (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0])
83    }
84
85    pub fn calc_point(&self, t: f64) -> f64 {
86        self.a0
87            + self.a1 * t
88            + self.a2 * t.powi(2)
89            + self.a3 * t.powi(3)
90            + self.a4 * t.powi(4)
91            + self.a5 * t.powi(5)
92    }
93
94    pub fn calc_first_derivative(&self, t: f64) -> f64 {
95        self.a1
96            + 2.0 * self.a2 * t
97            + 3.0 * self.a3 * t.powi(2)
98            + 4.0 * self.a4 * t.powi(3)
99            + 5.0 * self.a5 * t.powi(4)
100    }
101
102    pub fn calc_second_derivative(&self, t: f64) -> f64 {
103        2.0 * self.a2 + 6.0 * self.a3 * t + 12.0 * self.a4 * t.powi(2) + 20.0 * self.a5 * t.powi(3)
104    }
105
106    pub fn calc_third_derivative(&self, t: f64) -> f64 {
107        6.0 * self.a3 + 24.0 * self.a4 * t + 60.0 * self.a5 * t.powi(2)
108    }
109}
110
111#[derive(Debug)]
112pub struct QuinticPolynomialsPlanner {
113    pub time: Vec<f64>,
114    pub rx: Vec<f64>,
115    pub ry: Vec<f64>,
116    pub ryaw: Vec<f64>,
117    pub rv: Vec<f64>,
118    pub ra: Vec<f64>,
119    pub rj: Vec<f64>,
120}
121
122impl QuinticPolynomialsPlanner {
123    pub fn new() -> Self {
124        QuinticPolynomialsPlanner {
125            time: Vec::new(),
126            rx: Vec::new(),
127            ry: Vec::new(),
128            ryaw: Vec::new(),
129            rv: Vec::new(),
130            ra: Vec::new(),
131            rj: Vec::new(),
132        }
133    }
134
135    pub fn planning(
136        &mut self,
137        sx: f64,
138        sy: f64,
139        syaw: f64,
140        sv: f64,
141        sa: f64,
142        gx: f64,
143        gy: f64,
144        gyaw: f64,
145        gv: f64,
146        ga: f64,
147        max_accel: f64,
148        max_jerk: f64,
149        dt: f64,
150    ) -> bool {
151        let vxs = sv * syaw.cos();
152        let vys = sv * syaw.sin();
153        let vxg = gv * gyaw.cos();
154        let vyg = gv * gyaw.sin();
155
156        let axs = sa * syaw.cos();
157        let ays = sa * syaw.sin();
158        let axg = ga * gyaw.cos();
159        let ayg = ga * gyaw.sin();
160
161        let mut t = MIN_T;
162        while t < MAX_T {
163            let xqp = QuinticPolynomial::new(sx, vxs, axs, gx, vxg, axg, t);
164            let yqp = QuinticPolynomial::new(sy, vys, ays, gy, vyg, ayg, t);
165
166            self.time.clear();
167            self.rx.clear();
168            self.ry.clear();
169            self.ryaw.clear();
170            self.rv.clear();
171            self.ra.clear();
172            self.rj.clear();
173
174            let mut current_t = 0.0;
175            while current_t < t + dt - 1e-12 {
176                self.time.push(current_t);
177                self.rx.push(xqp.calc_point(current_t));
178                self.ry.push(yqp.calc_point(current_t));
179
180                let vx = xqp.calc_first_derivative(current_t);
181                let vy = yqp.calc_first_derivative(current_t);
182                let v = (vx * vx + vy * vy).sqrt();
183                let yaw = vy.atan2(vx);
184                self.rv.push(v);
185                self.ryaw.push(yaw);
186
187                let ax = xqp.calc_second_derivative(current_t);
188                let ay = yqp.calc_second_derivative(current_t);
189                let mut a = (ax * ax + ay * ay).sqrt();
190                if self.rv.len() >= 2
191                    && self.rv[self.rv.len() - 1] - self.rv[self.rv.len() - 2] < 0.0
192                {
193                    a *= -1.0;
194                }
195                self.ra.push(a);
196
197                let jx = xqp.calc_third_derivative(current_t);
198                let jy = yqp.calc_third_derivative(current_t);
199                let mut j = (jx * jx + jy * jy).sqrt();
200                if self.ra.len() >= 2
201                    && self.ra[self.ra.len() - 1] - self.ra[self.ra.len() - 2] < 0.0
202                {
203                    j *= -1.0;
204                }
205                self.rj.push(j);
206
207                current_t += dt;
208            }
209
210            let max_a = self.ra.iter().map(|x| x.abs()).fold(0.0, f64::max);
211            let max_j = self.rj.iter().map(|x| x.abs()).fold(0.0, f64::max);
212
213            if max_a <= max_accel && max_j <= max_jerk {
214                return true;
215            }
216
217            t += MIN_T;
218        }
219
220        false
221    }
222}
223
224impl Default for QuinticPolynomialsPlanner {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230#[cfg(test)]
231#[allow(clippy::excessive_precision)]
232mod tests {
233    use super::*;
234
235    fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
236        (a - b).abs() < tol
237    }
238
239    fn scalar_fingerprint(values: &[f64]) -> (f64, f64) {
240        let sum: f64 = values.iter().sum();
241        let weighted_sum: f64 = values
242            .iter()
243            .enumerate()
244            .map(|(index, value)| (index + 1) as f64 * value)
245            .sum();
246        (sum, weighted_sum)
247    }
248
249    #[test]
250    fn test_quintic_polynomial() {
251        let qp = QuinticPolynomial::new(0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 10.0);
252        assert!((qp.calc_point(0.0) - 0.0).abs() < 1e-10);
253        assert!((qp.calc_point(10.0) - 10.0).abs() < 1e-6);
254    }
255
256    #[test]
257    fn test_quintic_polynomial_matches_upstream_simple_reference() {
258        let qp = QuinticPolynomial::new(0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 10.0);
259
260        assert!(approx_eq(qp.a0, 0.0, 1e-12));
261        assert!(approx_eq(qp.a1, 0.0, 1e-12));
262        assert!(approx_eq(qp.a2, 0.0, 1e-12));
263        assert!(approx_eq(qp.a3, 0.099_999_999_999_999_99, 1e-12));
264        assert!(approx_eq(qp.a4, -0.014_999_999_999_999_996, 1e-12));
265        assert!(approx_eq(qp.a5, 0.000_599_999_999_999_999_8, 1e-12));
266
267        let expected_states = [
268            (0.0, 0.0, 0.0, 0.0, 0.6),
269            (
270                5.0,
271                5.0,
272                1.875_000_000_000_001_3,
273                1.332_267_629_550_187_8e-15,
274                -0.299_999_999_999_999_5,
275            ),
276            (
277                10.0,
278                10.0,
279                7.105_427_357_601_002e-15,
280                3.552_713_678_800_501e-15,
281                0.600_000_000_000_000_5,
282            ),
283        ];
284
285        for (t, point, velocity, accel, jerk) in expected_states {
286            assert!(approx_eq(qp.calc_point(t), point, 1e-12));
287            assert!(approx_eq(qp.calc_first_derivative(t), velocity, 1e-12));
288            assert!(approx_eq(qp.calc_second_derivative(t), accel, 1e-12));
289            assert!(approx_eq(qp.calc_third_derivative(t), jerk, 1e-12));
290        }
291    }
292
293    #[test]
294    fn test_quintic_polynomials_planner() {
295        let mut planner = QuinticPolynomialsPlanner::new();
296        let result = planner.planning(
297            10.0,
298            10.0,
299            10.0_f64.to_radians(),
300            1.0,
301            0.1,
302            30.0,
303            -10.0,
304            20.0_f64.to_radians(),
305            1.0,
306            0.1,
307            1.0,
308            0.5,
309            0.1,
310        );
311        assert!(result);
312        assert!(!planner.rx.is_empty());
313    }
314
315    #[test]
316    fn test_quintic_polynomials_planner_matches_upstream_main_example() {
317        let mut planner = QuinticPolynomialsPlanner::new();
318        let result = planner.planning(
319            10.0,
320            10.0,
321            10.0_f64.to_radians(),
322            1.0,
323            0.1,
324            30.0,
325            -10.0,
326            20.0_f64.to_radians(),
327            1.0,
328            0.1,
329            1.0,
330            0.5,
331            0.1,
332        );
333
334        assert!(result);
335        assert_eq!(planner.time.len(), 151);
336        assert_eq!(planner.rx.len(), 151);
337        assert_eq!(planner.ry.len(), 151);
338        assert_eq!(planner.ryaw.len(), 151);
339        assert_eq!(planner.rv.len(), 151);
340        assert_eq!(planner.ra.len(), 151);
341        assert_eq!(planner.rj.len(), 151);
342        assert!(approx_eq(*planner.time.last().unwrap(), 15.0, 1e-12));
343
344        let expected_samples = [
345            (
346                0usize,
347                0.0,
348                10.0,
349                10.0,
350                10.0_f64.to_radians(),
351                1.0,
352                0.099_999_999_999_999_99,
353                0.427_280_794_153_487_6,
354            ),
355            (
356                1,
357                0.1,
358                10.098_982_615_546_902,
359                10.017_381_774_411_378,
360                0.172_447_377_225_515_92,
361                1.009_916_853_081_542_3,
362                0.106_821_796_693_630_91,
363                0.410_270_512_683_564_35,
364            ),
365            (
366                10,
367                1.0,
368                11.042_264_078_940_175,
369                10.118_588_516_101_896,
370                0.005_806_154_972_050_835,
371                1.106_637_942_606_725_2,
372                0.354_433_544_260_436_83,
373                0.267_611_399_963_759_2,
374            ),
375            (
376                50,
377                5.0,
378                16.610_137_614_251_27,
379                6.064_611_076_798_1,
380                -0.912_947_184_636_906_5,
381                2.664_216_406_735_696_4,
382                0.467_382_916_097_198_84,
383                -0.146_179_174_681_691_67,
384            ),
385            (
386                150,
387                15.0,
388                30.000_000_000_000_007,
389                -10.0,
390                20.0_f64.to_radians(),
391                1.000_000_000_000_003,
392                0.099_999_999_999_999_62,
393                -0.433_897_236_756_954_63,
394            ),
395        ];
396
397        for (index, time, rx, ry, yaw, v, a, j) in expected_samples {
398            assert!(approx_eq(planner.time[index], time, 1e-12));
399            assert!(approx_eq(planner.rx[index], rx, 1e-12));
400            assert!(approx_eq(planner.ry[index], ry, 1e-12));
401            assert!(approx_eq(planner.ryaw[index], yaw, 1e-12));
402            assert!(approx_eq(planner.rv[index], v, 1e-12));
403            assert!(approx_eq(planner.ra[index], a, 1e-12));
404            assert!(approx_eq(planner.rj[index], j, 1e-12));
405        }
406
407        let time_fp = scalar_fingerprint(&planner.time);
408        assert!(approx_eq(time_fp.0, 1_132.500_000_000_000_2, 1e-9));
409        assert!(approx_eq(time_fp.1, 114_759.999_999_999_97, 1e-6));
410
411        let rx_fp = scalar_fingerprint(&planner.rx);
412        assert!(approx_eq(rx_fp.0, 3_084.277_101_694_296, 1e-9));
413        assert!(approx_eq(rx_fp.1, 275_626.252_367_386_25, 1e-6));
414
415        let ry_fp = scalar_fingerprint(&planner.ry);
416        assert!(approx_eq(ry_fp.0, -23.379_117_661_761_498, 1e-9));
417        assert!(approx_eq(ry_fp.1, -52_763.587_503_238_03, 1e-6));
418
419        let yaw_fp = scalar_fingerprint(&planner.ryaw);
420        assert!(approx_eq(yaw_fp.0, -91.931_046_512_332_49, 1e-9));
421        assert!(approx_eq(yaw_fp.1, -6_931.617_159_195_794, 1e-6));
422
423        let velocity_fp = scalar_fingerprint(&planner.rv);
424        assert!(approx_eq(velocity_fp.0, 302.345_686_787_654_7, 1e-9));
425        assert!(approx_eq(velocity_fp.1, 22_418.061_584_864_976, 1e-6));
426
427        let accel_fp = scalar_fingerprint(&planner.ra);
428        assert!(approx_eq(accel_fp.0, 3.130_947_174_813_961, 1e-9));
429        assert!(approx_eq(accel_fp.1, -1_921.398_399_412_191_8, 1e-6));
430
431        let jerk_fp = scalar_fingerprint(&planner.rj);
432        assert!(approx_eq(jerk_fp.0, -6.835_206_957_044_357, 1e-9));
433        assert!(approx_eq(jerk_fp.1, -1_017.425_338_206_766_7, 1e-6));
434    }
435}