1use super::Result;
2#[cfg(feature="plot")]
3use super::plot::plot_base;
4
5
6const DE_BOOR_SIZE: usize = 6;
9
10#[derive(Debug, Clone)]
21pub struct SplineCurve<const K: usize, const N: usize> {
22 pub t: Vec<f64>, pub c: Vec<f64>, i: Option<usize>, }
26
27impl<const K: usize, const N: usize> SplineCurve<K, N> {
28 pub fn new(t: Vec<f64>, c: Vec<f64>) -> Self {
36 assert!(K < DE_BOOR_SIZE, "Spline degree K={} exceeds maximum supported degree {}", K, DE_BOOR_SIZE - 1);
37 Self { t, c, i: None }
38 }
39
40 pub fn try_new(t: Vec<f64>, c: Vec<f64>) -> std::result::Result<Self, Box<dyn std::error::Error>> {
46 assert!(K < DE_BOOR_SIZE, "Spline degree K={} exceeds maximum supported degree {}", K, DE_BOOR_SIZE - 1);
47 let nc = c.len() / N;
48 if nc < (K+1) {
49 return Err(format!("Need at least {} coefficients for a degree-{} spline", N*(K+1), K).into());
50 }
51 Ok(Self { t, c, i: None })
52 }
53
54 #[cfg(feature="plot")]
63 pub fn plot(&self, filepath: &str, wxh: (u32, u32)) -> Result<()> {
64 assert!(N <= 2, "Plotting is only supported for 1D and 2D curves (N=1 or N=2), got N={}", N);
65 Ok(plot_base(self.clone(), filepath, wxh, None, None, false)?)
66 }
67
68 #[cfg(feature="plot")]
75 pub fn plot_with_parameter(&self, filepath: &str, wxh: (u32, u32), u:Option<&[f64]>) -> Result<()> {
76 assert!(N <= 2, "Plotting is only supported for 1D and 2D curves (N=1 or N=2), got N={}", N);
77 Ok(plot_base(self.clone(), filepath, wxh, u, None, false)?)
78 }
79
80 #[cfg(feature="plot")]
87 pub fn plot_with_control_points(&self, filepath: &str, wxh: (u32, u32)) -> Result<()> {
88 assert!(N <= 2, "Plotting is only supported for 1D and 2D curves (N=1 or N=2), got N={}", N);
89 Ok(plot_base(self.clone(), filepath, wxh, None, None, true)?)
90 }
91
92 #[cfg(feature="plot")]
99 pub fn plot_with_data(&self, filepath: &str, wxh: (u32, u32), xy: &[f64]) -> Result<()> {
100 assert!(N <= 2, "Plotting is only supported for 1D and 2D curves (N=1 or N=2), got N={}", N);
101 Ok(plot_base(self.clone(), filepath, wxh, None, Some(xy), false)?)
102 }
103
104 #[cfg(feature="plot")]
111 pub fn plot_with_control_points_and_data(&self, filepath: &str, wxh: (u32, u32), xy: &[f64]) -> Result<()> {
112 assert!(N <= 2, "Plotting is only supported for 1D and 2D curves (N=1 or N=2), got N={}", N);
113 Ok(plot_base(self.clone(), filepath, wxh, None, Some(xy), true)?)
114 }
115
116 pub fn evaluate(&self, u: &[f64]) -> Result<Vec<f64>> {
129 let n = self.t.len();
130 let nc = self.c.len() / N;
131 if nc < (K+1) {
132 return Err(format!("Need at least {} coefficients for a degree-{} spline", N*(K+1), K).into());
133 }
134 if nc != n-(K+1) {
135 return Err(format!("Expected {} coefficient values, got {}", N*(n - K - 1), N*nc).into());
136 }
137 let mut v: Vec<f64> = Vec::with_capacity(u.len() * N);
138
139 let mut i = K;
140 let mut u_prev = f64::NEG_INFINITY;
141 let mut d = [0.0; DE_BOOR_SIZE];
143
144 for &t in u {
145 if t <= u_prev {
146 return Err("parameter values must be in strictly increasing order".into());
147 } else {
148 u_prev = t;
149 };
150
151 let arg = if t < self.t[K] || t > self.t[n - K - 1] {
153 t.clamp(self.t[K], self.t[n - K - 1])
154 } else {
155 t
156 };
157
158 while !(arg >= self.t[i] && arg <= self.t[i + 1]) {
160 i += 1
161 }
162
163 for dim in 0..N {
165 for (j, dm) in d.iter_mut().enumerate().take(K + 1) {
167 *dm = self.c[dim * nc + j + i - K];
168 }
169
170 v.push(self.deboor(i, arg, &mut d))
171 }
172 }
173 Ok(v)
174 }
175
176
177 pub fn eval(&mut self, t: f64) -> std::result::Result<[f64; DE_BOOR_SIZE], f64> {
185 let n = self.t.len();
186 let nc = self.c.len() / N;
187
188 if t < self.t[K] {
189 Err(t - self.t[K])
190 } else if t > self.t[n - K - 1] {
191 Err(t - self.t[n - K - 1])
192 } else {
193 let mut i = if let Some(i_prev) = self.i {
194 if t > self.t[i_prev] {
195 i_prev
196 } else {
197 K
198 }
199 } else {
200 K
201 };
202
203 while !(t >= self.t[i] && t <= self.t[i + 1]) {
204 i += 1
205 }
206 self.i = Some(i);
207
208 let mut result = [0.0; DE_BOOR_SIZE];
209 let mut d = [0.0; DE_BOOR_SIZE];
210
211 for dim in 0..N {
212 for (j, dm) in d.iter_mut().enumerate().take(K + 1) {
213 *dm = self.c[dim * nc + j + i - K];
214 }
215 result[dim] = self.deboor(i, t, &mut d);
216 }
217 Ok(result)
218 }
219 }
220
221 pub(crate) fn deboor(&self, i: usize, x: f64, d: &mut [f64; DE_BOOR_SIZE]) -> f64 {
227
228 for r in 1..K + 1 {
229 for j in (r..=K).into_iter().rev() {
230 let alpha =
231 (x - self.t[j + i - K]) / (self.t[j + 1 + i - r] - self.t[j + i - K]);
232 d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j]
233 }
234 }
235 d[K]
236 }
237
238}
239
240pub fn transpose(xyn: &[f64], n: usize) -> Vec<Vec<f64>>{
246 let m = xyn.len()/n;
247 let mut vn: Vec<Vec<f64>> = std::iter::repeat(Vec::with_capacity(m)).take(n).collect();
248 for v in xyn.chunks(n) {
249 for (i,x) in v.iter().enumerate() {
250 vn[i].push(*x);
251 }
252 }
253 vn
254}
255
256#[cfg(test)]
257mod tests {
258 use super::{transpose, SplineCurve};
259 use approx::assert_abs_diff_eq;
260
261 #[test]
266 fn try_new_rejects_too_few_coefficients() {
267 let result: std::result::Result<SplineCurve<3, 1>, _> = SplineCurve::try_new(
269 vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
270 vec![0.0, 0.0, 0.0], );
272 assert!(result.is_err());
273 }
274
275 #[test]
276 fn evaluate_rejects_non_monotonic_params() {
277 let s: SplineCurve<1, 1> = SplineCurve::new(vec![0.0, 0.0, 1.0, 1.0], vec![0.0, 1.0]);
278 assert!(s.evaluate(&[0.2, 0.5, 0.4]).is_err()); }
280
281 #[test]
282 fn evaluate_rejects_coefficient_mismatch() {
283 let s: SplineCurve<1, 1> = SplineCurve::new(
285 vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0],
286 vec![0.0, 1.0, 2.0],
287 );
288 assert!(s.evaluate(&[0.5]).is_err());
289 }
290
291 #[test]
294 fn evaluate_clamps_out_of_range_params() {
295 let s: SplineCurve<1, 1> = SplineCurve::new(vec![0.0, 0.0, 1.0, 1.0], vec![0.0, 1.0]);
297 let yt = s.evaluate(&[-0.5, 1.5]).unwrap();
298 assert_abs_diff_eq!(yt[0], 0.0, epsilon = 1E-10); assert_abs_diff_eq!(yt[1], 1.0, epsilon = 1E-10); }
301
302 #[test]
305 fn eval_returns_err_with_signed_distance_when_out_of_range() {
306 let mut s: SplineCurve<3, 1> = SplineCurve::new(
307 vec![-2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0],
308 vec![0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0],
309 );
310 assert_abs_diff_eq!(s.eval(-3.0).unwrap_err(), -1.0, epsilon = 1E-10);
312 assert_abs_diff_eq!(s.eval(3.0).unwrap_err(), 1.0, epsilon = 1E-10);
314 }
315
316 #[test]
317 fn eval_cache_resets_correctly_on_backward_step() {
318 let mut s: SplineCurve<3, 1> = SplineCurve::new(
319 vec![-2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0],
320 vec![0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0],
321 );
322 assert_abs_diff_eq!(s.eval(0.5).unwrap()[0], 2.875, epsilon = 1E-7);
324 assert_abs_diff_eq!(s.eval(-1.0).unwrap()[0], 1.0, epsilon = 1E-7);
326 assert_abs_diff_eq!(s.eval(0.5).unwrap()[0], 2.875, epsilon = 1E-7);
328 }
329
330 #[test]
333 fn linear_2d_spline() {
334 let s: SplineCurve<1, 2> = SplineCurve::new(
337 vec![0.0, 0.0, 1.0, 1.0],
338 vec![0.0, 1.0, 0.0, 1.0],
339 );
340 let r = s.evaluate(&[0.0, 0.5, 1.0]).unwrap();
341 assert_abs_diff_eq!(r[0], 0.0, epsilon = 1E-10); assert_abs_diff_eq!(r[1], 0.0, epsilon = 1E-10); assert_abs_diff_eq!(r[2], 0.5, epsilon = 1E-10); assert_abs_diff_eq!(r[3], 0.5, epsilon = 1E-10); assert_abs_diff_eq!(r[4], 1.0, epsilon = 1E-10); assert_abs_diff_eq!(r[5], 1.0, epsilon = 1E-10); }
349
350 #[test]
353 fn transpose_splits_2d_coordinates() {
354 let flat = vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0]; let vecs = transpose(&flat, 2);
356 assert_eq!(vecs[0], vec![1.0, 3.0, 5.0]); assert_eq!(vecs[1], vec![2.0, 4.0, 6.0]); }
359
360 #[test]
361 fn transpose_splits_3d_coordinates() {
362 let flat = vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0]; let vecs = transpose(&flat, 3);
364 assert_eq!(vecs[0], vec![1.0, 4.0]);
365 assert_eq!(vecs[1], vec![2.0, 5.0]);
366 assert_eq!(vecs[2], vec![3.0, 6.0]);
367 }
368
369 #[test]
370 fn linear_bspline() {
371 let x = vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
372 let y = vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
373
374 let s: SplineCurve<1, 1> = SplineCurve::new(vec![0.0, 0.0, 1.0, 1.0], vec![0.0, 1.0]);
375 let yt = s.evaluate(&x).unwrap();
376 y.iter()
377 .zip(yt.iter())
378 .for_each(|(&a, &b)| assert_abs_diff_eq!(a, b, epsilon = 1E-8));
379
380 #[cfg(feature = "plot")]
381 s.plot("test.png", (1500,1000)).unwrap();
382 }
383 #[test]
384 fn quadratic_bspline() {
385 let x = [0.0, 0.5, 1.0, 1.4, 1.5, 1.6, 2.0, 2.5, 3.0];
386 let y = [0.0, 0.125, 0.5, 0.74, 0.75, 0.74, 0.5, 0.125, 0.0];
387
388 let s: SplineCurve<2, 1> = SplineCurve::new(
389 vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 3.0, 3.0],
390 vec![0.0, 0.0, 1.0, 0.0, 0.0],
391 );
392 let yt = s.evaluate(&x).unwrap();
393 y.iter()
394 .zip(yt.iter())
395 .for_each(|(&a, &b)| assert_abs_diff_eq!(a, b, epsilon = 1E-8));
396
397 #[cfg(feature = "plot")]
398 s.plot("test.png", (1500,1000)).unwrap();
399 }
400
401 #[test]
402 fn cubic_bspline() {
403 let x = vec![-2.0, -1.5, -1.0, -0.6, 0.0, 0.5, 1.5, 2.0];
405 let y = vec![0.0, 0.125, 1.0, 2.488, 4.0, 2.875, 0.12500001, 0.0];
406
407 let s: SplineCurve<3, 1> = SplineCurve::new(
408 vec![-2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0],
409 vec![0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0],
410 );
411
412 let yt = s.evaluate(&x).unwrap();
413 y.iter()
414 .zip(yt.iter())
415 .for_each(|(&a, &b)| assert_abs_diff_eq!(a, b, epsilon = 1E-7));
416
417 #[cfg(feature = "plot")]
418 s.plot("test.png", (1000,1000)).unwrap();
419
420 }
421
422 #[test]
423 fn cubic_bspline_single_values() {
424 let x = vec![-2.0, -1.5, -1.0, -0.6, 0.0, 0.5, 1.5, 2.0];
426 let y = vec![0.0, 0.125, 1.0, 2.488, 4.0, 2.875, 0.12500001, 0.0];
427
428 let mut s: SplineCurve<3, 1> = SplineCurve::new(
429 vec![-2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0],
430 vec![0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0],
431 );
432
433 let yt: Vec<f64> = x.into_iter().map(|x|s.eval(x).unwrap()[0]).collect();
434 y.iter()
435 .zip(yt.iter())
436 .for_each(|(&a, &b)| assert_abs_diff_eq!(a, b, epsilon = 1E-7));
437
438 #[cfg(feature = "plot")]
439 s.plot("test.png", (1000,1000)).unwrap();
440
441 }
442
443
444 #[test]
445 fn quartic_bspline() {
446 let x = vec![0.0, 0.4, 1.0, 1.5, 2.0, 2.5, 3.0, 3.2, 4.1, 4.5, 5.0];
447 let y = vec![
448 0.0,
449 0.0010666668,
450 0.041666668,
451 0.19791667,
452 0.4583333,
453 0.5989583,
454 0.4583333,
455 0.35206667,
456 0.02733751,
457 0.002604167,
458 0.0,
459 ];
460 let s: SplineCurve<4, 1> = SplineCurve::new(
461 vec![
462 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0,
463 ],
464 vec![0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
465 );
466 let yt = s.evaluate(&x).unwrap();
467 y.iter()
468 .zip(yt.iter())
469 .for_each(|(&a, &b)| assert_abs_diff_eq!(a, b, epsilon = 1E-7));
470
471 #[cfg(feature = "plot")]
472 s.plot("test.png", (2000,1000)).unwrap();
473 }
474}