1use crate::diag::PathError;
19use crate::path::OutOfRangeMode;
20use nalgebra::{DMatrix, DVector};
21use rayon::prelude::*;
22
23#[derive(Clone, Copy, Debug)]
26pub enum Parametrization {
27 Uniform,
28}
29
30pub struct SplineConfig {
31 pub order: usize,
33 pub parametrization: Parametrization,
34 pub s_min: f64,
35 pub s_max: f64,
36 pub out_of_range_mode: OutOfRangeMode,
37 pub start_state: Option<DMatrix<f64>>,
41 pub end_state: Option<DMatrix<f64>>,
44}
45
46impl Default for SplineConfig {
47 fn default() -> Self {
48 Self {
49 order: 5,
50 parametrization: Parametrization::Uniform,
51 s_min: 0.0,
52 s_max: 1.0,
53 out_of_range_mode: OutOfRangeMode::Error,
54 start_state: None,
55 end_state: None,
56 }
57 }
58}
59
60pub struct SplinePath {
67 pub order: usize,
69 pub s_min: f64,
70 pub s_max: f64,
71 pub out_of_range_mode: OutOfRangeMode,
72 n_coef: usize,
73 n_segments: usize,
74 inv_ds: f64,
78 coeffs: Vec<f64>,
81}
82
83impl SplinePath {
84 pub fn from_waypoints(waypoints: &DMatrix<f64>, cfg: &SplineConfig) -> Result<Self, PathError> {
86 let order = cfg.order;
87 if order < 3 || order.is_multiple_of(2) {
88 return Err(PathError::InvalidOrder { order });
89 }
90 let dim = waypoints.nrows();
91 let n_points = waypoints.ncols();
92 if n_points < 2 {
93 return Err(PathError::NotEnoughWaypoints { n: n_points });
94 }
95 let n_segments = n_points - 1;
96 let n_coef = order + 1;
97 let m = (order - 1) / 2;
98
99 let start = extract_boundary(cfg.start_state.as_ref(), dim, m)?;
101 let end = extract_boundary(cfg.end_state.as_ref(), dim, m)?;
102 let coeffs = solve_general_thomas(waypoints, order, m, &start, &end)?;
103
104 let range = cfg.s_max - cfg.s_min;
105 let inv_ds = n_segments as f64 / range;
106
107 Ok(Self {
108 order,
109 s_min: cfg.s_min,
110 s_max: cfg.s_max,
111 out_of_range_mode: cfg.out_of_range_mode,
112 n_coef,
113 n_segments,
114 inv_ds,
115 coeffs,
116 })
117 }
118
119 #[inline(always)]
120 fn segment_tau(&self, s: f64) -> (usize, f64) {
121 let scaled = (s - self.s_min) * self.inv_ds;
122 if scaled <= 0.0 {
123 return (0, 0.0);
124 }
125 let max_scaled = self.n_segments as f64;
126 if scaled >= max_scaled {
127 return (self.n_segments - 1, 1.0);
128 }
129 let seg = scaled.floor() as usize;
130 (seg, scaled - seg as f64)
131 }
132
133 #[inline(always)]
139 fn eval_poly<const ORDER: u8>(coeffs: &[f64], t: f64) -> (f64, f64, f64, f64) {
140 if coeffs.len() == 6 {
141 let [a0, a1, a2, a3, a4, a5] = [
142 coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5],
143 ];
144 let q = a5
145 .mul_add(t, a4)
146 .mul_add(t, a3)
147 .mul_add(t, a2)
148 .mul_add(t, a1)
149 .mul_add(t, a0);
150 if ORDER == 0 {
151 return (q, 0.0, 0.0, 0.0);
152 }
153 let dq = (5.0 * a5)
154 .mul_add(t, 4.0 * a4)
155 .mul_add(t, 3.0 * a3)
156 .mul_add(t, 2.0 * a2)
157 .mul_add(t, a1);
158 let ddq = (20.0 * a5)
159 .mul_add(t, 12.0 * a4)
160 .mul_add(t, 6.0 * a3)
161 .mul_add(t, 2.0 * a2);
162 if ORDER == 2 {
163 return (q, dq, ddq, 0.0);
164 }
165 let dddq = (60.0 * a5).mul_add(t, 24.0 * a4).mul_add(t, 6.0 * a3);
166 return (q, dq, ddq, dddq);
167 }
168 if coeffs.len() == 4 {
169 let [a0, a1, a2, a3] = [coeffs[0], coeffs[1], coeffs[2], coeffs[3]];
170 let q = a3.mul_add(t, a2).mul_add(t, a1).mul_add(t, a0);
171 if ORDER == 0 {
172 return (q, 0.0, 0.0, 0.0);
173 }
174 let dq = (3.0 * a3).mul_add(t, 2.0 * a2).mul_add(t, a1);
175 let ddq = (6.0 * a3).mul_add(t, 2.0 * a2);
176 if ORDER == 2 {
177 return (q, dq, ddq, 0.0);
178 }
179 return (q, dq, ddq, 6.0 * a3);
180 }
181 let n = coeffs.len() - 1;
183 let mut b0 = coeffs[n];
184 let mut b1 = 0.0_f64;
185 let mut b2 = 0.0_f64;
186 let mut b3 = 0.0_f64;
187 for k in (0..n).rev() {
188 if ORDER >= 3 {
189 b3 = b3 * t + 3.0 * b2;
190 }
191 if ORDER >= 2 {
192 b2 = b2 * t + 2.0 * b1;
193 }
194 if ORDER >= 1 {
195 b1 = b1 * t + b0;
196 }
197 b0 = b0 * t + coeffs[k];
198 }
199 (b0, b1, b2, b3)
200 }
201
202 #[inline(always)]
214 pub fn eval_at<const ORDER: u8>(
215 &self,
216 s: f64,
217 _dim: usize,
218 q_col: &mut [f64],
219 dq_col: &mut [f64],
220 ddq_col: &mut [f64],
221 dddq_col: &mut [f64],
222 ) {
223 let (seg, tau) = self.segment_tau(s);
224 let seg_offset = seg * self.n_coef;
225 let inv_ds2 = self.inv_ds * self.inv_ds;
226 let inv_ds3 = inv_ds2 * self.inv_ds;
227 let stride = self.n_segments * self.n_coef;
228 for (i, q_v) in q_col.iter_mut().enumerate() {
229 let start = i * stride + seg_offset;
230 let (q_val, dq_val, ddq_val, dddq_val) =
231 Self::eval_poly::<ORDER>(&self.coeffs[start..start + self.n_coef], tau);
232 *q_v = q_val;
233 if ORDER >= 2 {
234 dq_col[i] = dq_val * self.inv_ds;
235 ddq_col[i] = ddq_val * inv_ds2;
236 }
237 if ORDER >= 3 {
238 dddq_col[i] = dddq_val * inv_ds3;
239 }
240 }
241 }
242}
243
244fn extract_boundary(
249 state: Option<&DMatrix<f64>>,
250 dim: usize,
251 m: usize,
252) -> Result<Vec<f64>, PathError> {
253 match state {
254 None => Ok(vec![0.0; dim * m]),
255 Some(mat) => {
256 if mat.nrows() != dim || mat.ncols() != m {
257 return Err(PathError::DimensionMismatch);
258 }
259 let mut out = vec![0.0; dim * m];
261 for (d, row) in out.chunks_mut(m).enumerate() {
262 for (r, val) in row.iter_mut().enumerate() {
263 *val = mat[(d, r)];
264 }
265 }
266 Ok(out)
267 }
268 }
269}
270
271struct BlockMatrices {
306 m: usize,
307 a: DMatrix<f64>,
308 b: DMatrix<f64>,
309 c: DMatrix<f64>,
310 r: DMatrix<f64>,
312 h_coeff: DMatrix<f64>,
314}
315
316impl BlockMatrices {
317 fn for_order(m: usize) -> Self {
318 match m {
321 1 => Self::m1(),
322 2 => Self::m2(),
323 3 => Self::m3(),
324 _ => Self::general(m),
325 }
326 }
327
328 fn m1() -> Self {
331 BlockMatrices {
332 m: 1,
333 a: DMatrix::from_row_slice(1, 1, &[1.0]),
334 b: DMatrix::from_row_slice(1, 1, &[4.0]),
335 c: DMatrix::from_row_slice(1, 1, &[1.0]),
336 r: DMatrix::from_row_slice(1, 2, &[3.0, 3.0]),
337 h_coeff: DMatrix::from_row_slice(2, 3, &[3.0, -2.0, -1.0, -2.0, 1.0, 1.0]),
338 }
339 }
340
341 fn m2() -> Self {
348 BlockMatrices {
349 m: 2,
350 a: DMatrix::from_row_slice(2, 2, &[-4.0, -1.0, -7.0, -2.0]),
351 b: DMatrix::from_row_slice(2, 2, &[0.0, 6.0, -16.0, 0.0]),
352 c: DMatrix::from_row_slice(2, 2, &[4.0, -1.0, -7.0, 2.0]),
353 r: DMatrix::from_row_slice(2, 2, &[-10.0, 10.0, -15.0, -15.0]),
354 h_coeff: DMatrix::from_row_slice(
355 3,
356 5,
357 &[
358 10.0, -6.0, -3.0, -4.0, 1.0, -15.0, 8.0, 3.0, 7.0, -2.0, 6.0, -3.0, -1.0, -3.0,
359 1.0,
360 ],
361 ),
362 }
363 }
364
365 fn m3() -> Self {
371 BlockMatrices {
372 m: 3,
373 a: DMatrix::from_row_slice(3, 3, &[15.0, 5.0, 1.0, 39.0, 14.0, 3.0, 34.0, 13.0, 3.0]),
374 b: DMatrix::from_row_slice(3, 3, &[40.0, 0.0, 8.0, 0.0, -40.0, 0.0, 72.0, 0.0, 8.0]),
375 c: DMatrix::from_row_slice(
376 3,
377 3,
378 &[15.0, -5.0, 1.0, -39.0, 14.0, -3.0, 34.0, -13.0, 3.0],
379 ),
380 r: DMatrix::from_row_slice(3, 2, &[35.0, 35.0, 84.0, -84.0, 70.0, 70.0]),
381 h_coeff: DMatrix::from_row_slice(
382 4,
383 7,
384 &[
385 35.0, -20.0, -10.0, -4.0, -15.0, 5.0, -1.0, -84.0, 45.0, 20.0, 6.0, 39.0,
386 -14.0, 3.0, 70.0, -36.0, -15.0, -4.0, -34.0, 13.0, -3.0, -20.0, 10.0, 4.0, 1.0,
387 10.0, -4.0, 1.0,
388 ],
389 ),
390 }
391 }
392
393 fn general(m: usize) -> Self {
395 let p = 2 * m + 1;
396
397 let n = m + 1;
400
401 let binom = |nn: usize, k: usize| -> f64 {
402 if k > nn {
403 return 0.0;
404 }
405 (0..k).fold(1.0f64, |acc, i| acc * (nn - i) as f64 / (i + 1) as f64)
406 };
407
408 let mut aug = DMatrix::<f64>::zeros(n, 2 * n);
410 for r in 0..n {
411 for (col_idx, k) in (m + 1..=p).enumerate() {
412 aug[(r, col_idx)] = binom(k, r);
413 }
414 aug[(r, n + r)] = 1.0;
415 }
416 for col in 0..n {
417 let pivot_row = (col..n)
418 .max_by(|&a, &b| {
419 aug[(a, col)]
420 .abs()
421 .partial_cmp(&aug[(b, col)].abs())
422 .unwrap()
423 })
424 .unwrap();
425 if pivot_row != col {
426 aug.swap_rows(col, pivot_row);
427 }
428 let piv_inv = 1.0 / aug[(col, col)];
429 for j in 0..2 * n {
430 aug[(col, j)] *= piv_inv;
431 }
432 for r in 0..n {
433 if r == col {
434 continue;
435 }
436 let factor = aug[(r, col)];
437 if factor == 0.0 {
438 continue;
439 }
440 for j in 0..2 * n {
441 let sub = factor * aug[(col, j)];
442 aug[(r, j)] -= sub;
443 }
444 }
445 }
446 let minv = aug.columns(n, n).into_owned();
447
448 let basis = 1 + 2 * m;
450 let mut rhs_mat = DMatrix::<f64>::zeros(n, basis);
451 rhs_mat[(0, 0)] = 1.0;
452 for b in 1..=m {
453 rhs_mat[(0, b)] = -1.0;
454 }
455 for r in 1..n {
456 rhs_mat[(r, m + r)] = 1.0;
457 for k in r..n {
458 rhs_mat[(r, k)] -= binom(k, r);
459 }
460 }
461
462 let h_coeff = &minv * &rhs_mat;
464
465 let bs = 2 + 3 * m;
467 let get_coeff_vec = |k: usize, is_left: bool| -> DVector<f64> {
468 let mut v = DVector::<f64>::zeros(bs);
469 if k == 0 {
470 return v;
471 }
472 if k <= m {
473 let offset = if is_left { 2 } else { 2 + m };
474 v[offset + k - 1] = 1.0;
475 return v;
476 }
477 let ic = k - (m + 1);
478 for (b, &cv) in h_coeff.row(ic).iter().enumerate() {
479 if cv == 0.0 {
480 continue;
481 }
482 if b == 0 {
483 v[if is_left { 0 } else { 1 }] += cv;
484 } else if b <= m {
485 let offset = if is_left { 2 } else { 2 + m };
486 v[offset + b - 1] += cv;
487 } else {
488 let offset = if is_left { 2 + m } else { 2 + 2 * m };
489 v[offset + b - m - 1] += cv;
490 }
491 }
492 v
493 };
494
495 let mut a_mat = DMatrix::<f64>::zeros(m, m);
496 let mut b_mat = DMatrix::<f64>::zeros(m, m);
497 let mut c_mat = DMatrix::<f64>::zeros(m, m);
498 let mut r_mat = DMatrix::<f64>::zeros(m, 2);
499
500 for (eq_idx, r) in (m + 1..=2 * m).enumerate() {
501 let lhs = (r..=p).fold(DVector::<f64>::zeros(bs), |acc, k| {
502 acc + get_coeff_vec(k, true) * binom(k, r)
503 });
504 let rhs_v = get_coeff_vec(r, false);
505 let eq = lhs - rhs_v;
506 for j in 0..m {
507 a_mat[(eq_idx, j)] = eq[2 + j];
508 b_mat[(eq_idx, j)] = eq[2 + m + j];
509 c_mat[(eq_idx, j)] = eq[2 + 2 * m + j];
510 }
511 r_mat[(eq_idx, 0)] = -eq[0];
512 r_mat[(eq_idx, 1)] = -eq[1];
513 }
514
515 BlockMatrices {
516 m,
517 a: a_mat,
518 b: b_mat,
519 c: c_mat,
520 r: r_mat,
521 h_coeff,
522 }
523 }
524
525 #[inline(always)]
527 fn compute_rhs(&self, dy_prev: f64, dy_next: f64, out: &mut [f64]) {
528 let r0 = self.r.column(0);
529 let r1 = self.r.column(1);
530 for (o, (&c0, &c1)) in out.iter_mut().zip(r0.iter().zip(r1.iter())) {
531 *o = c0 * dy_prev + c1 * dy_next;
532 }
533 }
534
535 #[inline(always)]
539 fn upper_coeffs(&self, dy: f64, u0: &[f64], u1: &[f64], out: &mut [f64]) {
540 let m = self.m;
541 for (o, row) in out.iter_mut().zip(self.h_coeff.row_iter()) {
542 let dot_u0: f64 = row
544 .iter()
545 .skip(1)
546 .take(m)
547 .zip(u0.iter())
548 .map(|(&c, &v)| c * v)
549 .sum();
550 let dot_u1: f64 = row
551 .iter()
552 .skip(1 + m)
553 .zip(u1.iter())
554 .map(|(&c, &v)| c * v)
555 .sum();
556 *o = row[0] * dy + dot_u0 + dot_u1;
557 }
558 }
559}
560
561fn solve_general_thomas(
568 waypoints: &DMatrix<f64>,
569 order: usize,
570 m: usize,
571 start_bd: &[f64],
572 end_bd: &[f64],
573) -> Result<Vec<f64>, PathError> {
574 let dim = waypoints.nrows();
575 let n = waypoints.ncols(); let ns = n - 1; let n_coef = order + 1; let bm = BlockMatrices::for_order(m);
580
581 let chunk_len = ns * n_coef;
582 let mut coeffs = vec![0.0f64; dim * chunk_len];
583
584 coeffs.par_chunks_mut(chunk_len).enumerate().try_for_each(
585 |(d, chunk)| -> Result<(), PathError> {
586 let u_start = &start_bd[d * m..(d + 1) * m];
588 let u_end = &end_bd[d * m..(d + 1) * m];
589 thomas_row(
590 waypoints,
591 (d, n, ns, m, n_coef),
592 &bm,
593 (u_start, u_end),
594 chunk,
595 )
596 },
597 )?;
598
599 Ok(coeffs)
600}
601
602fn thomas_row(
617 waypoints: &DMatrix<f64>,
618 (d, n, ns, m, n_coef): (usize, usize, usize, usize, usize),
619 bm: &BlockMatrices,
620 (u_start, u_end): (&[f64], &[f64]),
621 chunk: &mut [f64],
622) -> Result<(), PathError> {
623 let row_d = waypoints.row(d);
625 let dy: Vec<f64> = row_d
626 .iter()
627 .zip(row_d.iter().skip(1))
628 .map(|(&a, &b)| b - a)
629 .collect();
630
631 let mut rhs = vec![0.0f64; m];
633 let mut upper_buf = vec![0.0f64; m + 1];
634
635 if n == 2 {
637 bm.upper_coeffs(dy[0], u_start, u_end, &mut upper_buf);
638 write_segment_coeffs(chunk, 0, n_coef, m, waypoints[(d, 0)], u_start, &upper_buf);
639 return Ok(());
640 }
641
642 let ni = n - 2; let mut b_inv_c_list: Vec<DMatrix<f64>> = Vec::with_capacity(ni);
649 let mut b_inv_r_list: Vec<DVector<f64>> = Vec::with_capacity(ni);
650
651 let u_start_vec = DVector::from_column_slice(u_start);
653 let u_end_vec = DVector::from_column_slice(u_end);
654
655 for i in 0..ni {
656 let node = i + 1;
657 bm.compute_rhs(dy[node - 1], dy[node], &mut rhs);
658 let mut rhs_curr = DVector::from_column_slice(&rhs);
659
660 if i == 0 {
662 rhs_curr -= &bm.a * &u_start_vec;
663 }
664 if i == ni - 1 {
665 rhs_curr -= &bm.c * &u_end_vec;
666 }
667
668 let b_cur = if i == 0 {
670 bm.b.clone()
671 } else {
672 &bm.b - &bm.a * &b_inv_c_list[i - 1]
673 };
674
675 if i > 0 {
677 rhs_curr -= &bm.a * &b_inv_r_list[i - 1];
678 }
679
680 let lu = b_cur.lu();
681 b_inv_c_list.push(lu.solve(&bm.c).ok_or(PathError::SingularSystem)?);
682 b_inv_r_list.push(lu.solve(&rhs_curr).ok_or(PathError::SingularSystem)?);
683 }
684
685 let mut u_list: Vec<DVector<f64>> = vec![DVector::zeros(m); ni];
688 u_list[ni - 1] = b_inv_r_list[ni - 1].clone();
689 for i in (0..ni - 1).rev() {
690 u_list[i] = &b_inv_r_list[i] - &b_inv_c_list[i] * &u_list[i + 1];
691 }
692
693 let u_at = |k: usize| -> &[f64] {
695 if k == 0 {
696 u_start
697 } else if k >= n - 1 {
698 u_end
699 } else {
700 u_list[k - 1].as_slice()
701 }
702 };
703
704 for seg in 0..ns {
705 let ul = u_at(seg);
706 let ur = u_at(seg + 1);
707 bm.upper_coeffs(dy[seg], ul, ur, &mut upper_buf);
708 write_segment_coeffs(chunk, seg, n_coef, m, waypoints[(d, seg)], ul, &upper_buf);
709 }
710
711 Ok(())
712}
713
714#[inline(always)]
720fn write_segment_coeffs(
721 chunk: &mut [f64],
722 seg: usize,
723 n_coef: usize,
724 m: usize,
725 y_left: f64,
726 u_left: &[f64],
727 upper: &[f64],
728) {
729 let base = seg * n_coef;
730 chunk[base] = y_left;
731 chunk[base + 1..base + 1 + m].copy_from_slice(u_left);
732 chunk[base + m + 1..base + m + 1 + (m + 1)].copy_from_slice(upper);
733}