1use crate::{iter_maybe_parallel, slice_maybe_parallel};
24#[cfg(feature = "parallel")]
25use rayon::iter::ParallelIterator;
26
27#[derive(Clone, Debug)]
32pub struct IrregFdata {
33 pub offsets: Vec<usize>,
36 pub argvals: Vec<f64>,
38 pub values: Vec<f64>,
40 pub rangeval: [f64; 2],
42}
43
44impl IrregFdata {
45 pub fn from_lists(argvals_list: &[Vec<f64>], values_list: &[Vec<f64>]) -> Self {
54 let n = argvals_list.len();
55 assert_eq!(
56 n,
57 values_list.len(),
58 "argvals_list and values_list must have same length"
59 );
60
61 let mut offsets = Vec::with_capacity(n + 1);
62 offsets.push(0);
63
64 let total_points: usize = argvals_list.iter().map(|v| v.len()).sum();
65 let mut argvals = Vec::with_capacity(total_points);
66 let mut values = Vec::with_capacity(total_points);
67
68 let mut range_min = f64::INFINITY;
69 let mut range_max = f64::NEG_INFINITY;
70
71 for i in 0..n {
72 assert_eq!(
73 argvals_list[i].len(),
74 values_list[i].len(),
75 "Observation {} has mismatched argvals/values lengths",
76 i
77 );
78
79 argvals.extend_from_slice(&argvals_list[i]);
80 values.extend_from_slice(&values_list[i]);
81 offsets.push(argvals.len());
82
83 if let (Some(&min), Some(&max)) = (argvals_list[i].first(), argvals_list[i].last()) {
84 range_min = range_min.min(min);
85 range_max = range_max.max(max);
86 }
87 }
88
89 IrregFdata {
90 offsets,
91 argvals,
92 values,
93 rangeval: [range_min, range_max],
94 }
95 }
96
97 pub fn from_flat(
105 offsets: Vec<usize>,
106 argvals: Vec<f64>,
107 values: Vec<f64>,
108 rangeval: [f64; 2],
109 ) -> Self {
110 IrregFdata {
111 offsets,
112 argvals,
113 values,
114 rangeval,
115 }
116 }
117
118 #[inline]
120 pub fn n_obs(&self) -> usize {
121 self.offsets.len().saturating_sub(1)
122 }
123
124 #[inline]
126 pub fn n_points(&self, i: usize) -> usize {
127 self.offsets[i + 1] - self.offsets[i]
128 }
129
130 #[inline]
132 pub fn get_obs(&self, i: usize) -> (&[f64], &[f64]) {
133 let start = self.offsets[i];
134 let end = self.offsets[i + 1];
135 (&self.argvals[start..end], &self.values[start..end])
136 }
137
138 #[inline]
140 pub fn total_points(&self) -> usize {
141 self.argvals.len()
142 }
143
144 pub fn obs_counts(&self) -> Vec<usize> {
146 (0..self.n_obs()).map(|i| self.n_points(i)).collect()
147 }
148
149 pub fn min_obs(&self) -> usize {
151 (0..self.n_obs())
152 .map(|i| self.n_points(i))
153 .min()
154 .unwrap_or(0)
155 }
156
157 pub fn max_obs(&self) -> usize {
159 (0..self.n_obs())
160 .map(|i| self.n_points(i))
161 .max()
162 .unwrap_or(0)
163 }
164}
165
166pub fn integrate_irreg(offsets: &[usize], argvals: &[f64], values: &[f64]) -> Vec<f64> {
183 let n = offsets.len() - 1;
184
185 iter_maybe_parallel!(0..n)
186 .map(|i| {
187 let start = offsets[i];
188 let end = offsets[i + 1];
189 let t = &argvals[start..end];
190 let x = &values[start..end];
191
192 if t.len() < 2 {
193 return 0.0;
194 }
195
196 let mut integral = 0.0;
197 for j in 1..t.len() {
198 let h = t[j] - t[j - 1];
199 integral += 0.5 * h * (x[j] + x[j - 1]);
200 }
201 integral
202 })
203 .collect()
204}
205
206pub fn integrate_irreg_struct(ifd: &IrregFdata) -> Vec<f64> {
208 integrate_irreg(&ifd.offsets, &ifd.argvals, &ifd.values)
209}
210
211pub fn norm_lp_irreg(offsets: &[usize], argvals: &[f64], values: &[f64], p: f64) -> Vec<f64> {
230 let n = offsets.len() - 1;
231
232 iter_maybe_parallel!(0..n)
233 .map(|i| {
234 let start = offsets[i];
235 let end = offsets[i + 1];
236 let t = &argvals[start..end];
237 let x = &values[start..end];
238
239 if t.len() < 2 {
240 return 0.0;
241 }
242
243 let mut integral = 0.0;
244 for j in 1..t.len() {
245 let h = t[j] - t[j - 1];
246 let val_left = x[j - 1].abs().powf(p);
247 let val_right = x[j].abs().powf(p);
248 integral += 0.5 * h * (val_left + val_right);
249 }
250 integral.powf(1.0 / p)
251 })
252 .collect()
253}
254
255#[inline]
261fn kernel_epanechnikov(u: f64) -> f64 {
262 if u.abs() <= 1.0 {
263 0.75 * (1.0 - u * u)
264 } else {
265 0.0
266 }
267}
268
269#[inline]
271fn kernel_gaussian(u: f64) -> f64 {
272 (-0.5 * u * u).exp() / (2.0 * std::f64::consts::PI).sqrt()
273}
274
275pub fn mean_irreg(
291 offsets: &[usize],
292 argvals: &[f64],
293 values: &[f64],
294 target_argvals: &[f64],
295 bandwidth: f64,
296 kernel_type: i32,
297) -> Vec<f64> {
298 let n = offsets.len() - 1;
299 let kernel = match kernel_type {
300 0 => kernel_epanechnikov,
301 _ => kernel_gaussian,
302 };
303
304 slice_maybe_parallel!(target_argvals)
305 .map(|&t| {
306 let mut sum_weights = 0.0;
307 let mut sum_values = 0.0;
308
309 for i in 0..n {
310 let start = offsets[i];
311 let end = offsets[i + 1];
312 let obs_t = &argvals[start..end];
313 let obs_x = &values[start..end];
314
315 for (&ti, &xi) in obs_t.iter().zip(obs_x.iter()) {
316 let u = (ti - t) / bandwidth;
317 let w = kernel(u);
318 sum_weights += w;
319 sum_values += w * xi;
320 }
321 }
322
323 if sum_weights > 0.0 {
324 sum_values / sum_weights
325 } else {
326 f64::NAN
327 }
328 })
329 .collect()
330}
331
332pub fn cov_irreg(
350 offsets: &[usize],
351 argvals: &[f64],
352 values: &[f64],
353 s_grid: &[f64],
354 t_grid: &[f64],
355 bandwidth: f64,
356) -> Vec<f64> {
357 let n = offsets.len() - 1;
358 let ns = s_grid.len();
359 let nt = t_grid.len();
360
361 let mean_at_obs = mean_irreg(offsets, argvals, values, argvals, bandwidth, 1);
363
364 let centered: Vec<f64> = values
366 .iter()
367 .zip(mean_at_obs.iter())
368 .map(|(&v, &m)| v - m)
369 .collect();
370
371 let mut cov = vec![0.0; ns * nt];
373
374 for (si, &s) in s_grid.iter().enumerate() {
375 for (ti, &t) in t_grid.iter().enumerate() {
376 let mut sum_weights = 0.0;
377 let mut sum_products = 0.0;
378
379 for i in 0..n {
381 let start = offsets[i];
382 let end = offsets[i + 1];
383 let obs_t = &argvals[start..end];
384 let obs_c = ¢ered[start..end];
385
386 for j1 in 0..obs_t.len() {
387 for j2 in 0..obs_t.len() {
388 let u1 = (obs_t[j1] - s) / bandwidth;
389 let u2 = (obs_t[j2] - t) / bandwidth;
390
391 let w1 = kernel_gaussian(u1);
392 let w2 = kernel_gaussian(u2);
393 let w = w1 * w2;
394
395 sum_weights += w;
396 sum_products += w * obs_c[j1] * obs_c[j2];
397 }
398 }
399 }
400
401 if sum_weights > 0.0 {
402 cov[si + ti * ns] = sum_products / sum_weights;
403 }
404 }
405 }
406
407 cov
408}
409
410fn lp_distance_pair(t1: &[f64], x1: &[f64], t2: &[f64], x2: &[f64], p: f64) -> f64 {
418 let mut all_t: Vec<f64> = t1.iter().chain(t2.iter()).copied().collect();
420 all_t.sort_by(|a, b| a.partial_cmp(b).unwrap());
421 all_t.dedup();
422
423 let t_min = t1.first().unwrap().max(*t2.first().unwrap());
425 let t_max = t1.last().unwrap().min(*t2.last().unwrap());
426
427 let common_t: Vec<f64> = all_t
428 .into_iter()
429 .filter(|&t| t >= t_min && t <= t_max)
430 .collect();
431
432 if common_t.len() < 2 {
433 return f64::NAN;
434 }
435
436 let y1: Vec<f64> = common_t.iter().map(|&t| linear_interp(t1, x1, t)).collect();
438 let y2: Vec<f64> = common_t.iter().map(|&t| linear_interp(t2, x2, t)).collect();
439
440 let mut integral = 0.0;
442 for j in 1..common_t.len() {
443 let h = common_t[j] - common_t[j - 1];
444 let val_left = (y1[j - 1] - y2[j - 1]).abs().powf(p);
445 let val_right = (y1[j] - y2[j]).abs().powf(p);
446 integral += 0.5 * h * (val_left + val_right);
447 }
448
449 integral.powf(1.0 / p)
450}
451
452fn linear_interp(argvals: &[f64], values: &[f64], t: f64) -> f64 {
454 if t <= argvals[0] {
455 return values[0];
456 }
457 if t >= argvals[argvals.len() - 1] {
458 return values[values.len() - 1];
459 }
460
461 let idx = argvals.iter().position(|&x| x > t).unwrap();
463 let t0 = argvals[idx - 1];
464 let t1 = argvals[idx];
465 let x0 = values[idx - 1];
466 let x1 = values[idx];
467
468 x0 + (x1 - x0) * (t - t0) / (t1 - t0)
470}
471
472pub fn metric_lp_irreg(offsets: &[usize], argvals: &[f64], values: &[f64], p: f64) -> Vec<f64> {
483 let n = offsets.len() - 1;
484 let mut dist = vec![0.0; n * n];
485
486 let pairs: Vec<(usize, usize)> = (0..n)
488 .flat_map(|i| (i + 1..n).map(move |j| (i, j)))
489 .collect();
490
491 let distances: Vec<f64> = slice_maybe_parallel!(pairs)
492 .map(|&(i, j)| {
493 let start_i = offsets[i];
494 let end_i = offsets[i + 1];
495 let start_j = offsets[j];
496 let end_j = offsets[j + 1];
497
498 lp_distance_pair(
499 &argvals[start_i..end_i],
500 &values[start_i..end_i],
501 &argvals[start_j..end_j],
502 &values[start_j..end_j],
503 p,
504 )
505 })
506 .collect();
507
508 for (k, &(i, j)) in pairs.iter().enumerate() {
510 dist[i + j * n] = distances[k];
511 dist[j + i * n] = distances[k];
512 }
513
514 dist
515}
516
517pub fn to_regular_grid(
534 offsets: &[usize],
535 argvals: &[f64],
536 values: &[f64],
537 target_grid: &[f64],
538) -> Vec<f64> {
539 let n = offsets.len() - 1;
540 let m = target_grid.len();
541
542 iter_maybe_parallel!(0..n)
543 .flat_map(|i| {
544 let start = offsets[i];
545 let end = offsets[i + 1];
546 let obs_t = &argvals[start..end];
547 let obs_x = &values[start..end];
548
549 target_grid
550 .iter()
551 .map(|&t| {
552 if obs_t.is_empty() || t < obs_t[0] || t > obs_t[obs_t.len() - 1] {
553 f64::NAN
554 } else {
555 linear_interp(obs_t, obs_x, t)
556 }
557 })
558 .collect::<Vec<f64>>()
559 })
560 .collect::<Vec<f64>>()
561 .chunks(m)
563 .enumerate()
564 .fold(vec![0.0; n * m], |mut acc, (i, row)| {
565 for (j, &val) in row.iter().enumerate() {
566 acc[i + j * n] = val;
567 }
568 acc
569 })
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575
576 #[test]
577 fn test_from_lists() {
578 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
579 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
580
581 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
582
583 assert_eq!(ifd.n_obs(), 2);
584 assert_eq!(ifd.n_points(0), 3);
585 assert_eq!(ifd.n_points(1), 2);
586 assert_eq!(ifd.total_points(), 5);
587 }
588
589 #[test]
590 fn test_get_obs() {
591 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
592 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
593
594 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
595
596 let (t0, x0) = ifd.get_obs(0);
597 assert_eq!(t0, &[0.0, 0.5, 1.0]);
598 assert_eq!(x0, &[1.0, 2.0, 3.0]);
599
600 let (t1, x1) = ifd.get_obs(1);
601 assert_eq!(t1, &[0.0, 1.0]);
602 assert_eq!(x1, &[1.0, 3.0]);
603 }
604
605 #[test]
606 fn test_integrate_irreg() {
607 let offsets = vec![0, 3, 6];
609 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
610 let values = vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0];
611
612 let integrals = integrate_irreg(&offsets, &argvals, &values);
613
614 assert!((integrals[0] - 1.0).abs() < 1e-10);
615 assert!((integrals[1] - 2.0).abs() < 1e-10);
616 }
617
618 #[test]
619 fn test_norm_lp_irreg() {
620 let offsets = vec![0, 3];
622 let argvals = vec![0.0, 0.5, 1.0];
623 let values = vec![2.0, 2.0, 2.0];
624
625 let norms = norm_lp_irreg(&offsets, &argvals, &values, 2.0);
626
627 assert!((norms[0] - 2.0).abs() < 1e-10);
628 }
629
630 #[test]
631 fn test_linear_interp() {
632 let t = vec![0.0, 1.0, 2.0];
633 let x = vec![0.0, 2.0, 4.0];
634
635 assert!((linear_interp(&t, &x, 0.5) - 1.0).abs() < 1e-10);
636 assert!((linear_interp(&t, &x, 1.5) - 3.0).abs() < 1e-10);
637 }
638
639 #[test]
640 fn test_mean_irreg() {
641 let offsets = vec![0, 3, 6];
643 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
644 let values = vec![0.0, 1.0, 2.0, 0.0, 1.0, 2.0];
645
646 let target = vec![0.0, 0.5, 1.0];
647 let mean = mean_irreg(&offsets, &argvals, &values, &target, 0.5, 1);
648
649 assert!((mean[1] - 1.0).abs() < 0.3);
651 }
652
653 #[test]
658 fn test_from_flat() {
659 let offsets = vec![0, 3, 5, 10];
660 let argvals = vec![0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.2, 0.4, 0.6, 0.8];
661 let values = vec![1.0, 2.0, 3.0, 1.0, 3.0, 0.0, 1.0, 2.0, 3.0, 4.0];
662 let rangeval = [0.0, 1.0];
663
664 let ifd = IrregFdata::from_flat(offsets.clone(), argvals.clone(), values.clone(), rangeval);
665
666 assert_eq!(ifd.n_obs(), 3);
667 assert_eq!(ifd.offsets, offsets);
668 assert_eq!(ifd.argvals, argvals);
669 assert_eq!(ifd.values, values);
670 assert_eq!(ifd.rangeval, rangeval);
671 }
672
673 #[test]
674 fn test_accessors_n_obs_n_points_total() {
675 let argvals_list = vec![
676 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
680 let values_list = vec![
681 vec![1.0, 2.0, 3.0],
682 vec![1.0, 3.0],
683 vec![0.0, 1.0, 2.0, 3.0, 4.0],
684 ];
685
686 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
687
688 assert_eq!(ifd.n_obs(), 3);
690
691 assert_eq!(ifd.n_points(0), 3);
693 assert_eq!(ifd.n_points(1), 2);
694 assert_eq!(ifd.n_points(2), 5);
695
696 assert_eq!(ifd.total_points(), 10);
698 }
699
700 #[test]
701 fn test_obs_counts() {
702 let argvals_list = vec![
703 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
707 let values_list = vec![
708 vec![1.0, 2.0, 3.0],
709 vec![1.0, 3.0],
710 vec![0.0, 1.0, 2.0, 3.0, 4.0],
711 ];
712
713 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
714 let counts = ifd.obs_counts();
715
716 assert_eq!(counts, vec![3, 2, 5]);
717 }
718
719 #[test]
720 fn test_min_max_obs() {
721 let argvals_list = vec![
722 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
726 let values_list = vec![
727 vec![1.0, 2.0, 3.0],
728 vec![1.0, 3.0],
729 vec![0.0, 1.0, 2.0, 3.0, 4.0],
730 ];
731
732 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
733
734 assert_eq!(ifd.min_obs(), 2);
735 assert_eq!(ifd.max_obs(), 5);
736 }
737
738 #[test]
739 fn test_min_max_obs_empty() {
740 let ifd = IrregFdata::from_lists(&[], &[]);
741 assert_eq!(ifd.min_obs(), 0);
742 assert_eq!(ifd.max_obs(), 0);
743 }
744
745 #[test]
750 fn test_cov_irreg_identical_curves() {
751 let offsets = vec![0, 5, 10];
753 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
754 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 2.0, 1.0];
755
756 let grid = vec![0.25, 0.5, 0.75];
757 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
758
759 assert_eq!(cov.len(), 9);
761 for i in 0..3 {
763 assert!(
764 cov[i + i * 3].abs() < 0.5,
765 "Diagonal cov[{},{}] = {} should be near 0",
766 i,
767 i,
768 cov[i + i * 3]
769 );
770 }
771 }
772
773 #[test]
774 fn test_cov_irreg_symmetry() {
775 let offsets = vec![0, 5, 10, 15];
777 let argvals = vec![
778 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0,
779 ];
780 let values = vec![
781 1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 4.0, 1.0, 0.0, 2.0, 3.0, 2.0, 3.0, 2.0,
782 ];
783
784 let grid = vec![0.25, 0.5, 0.75];
785 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
786
787 for i in 0..3 {
789 for j in 0..3 {
790 assert!(
791 (cov[i + j * 3] - cov[j + i * 3]).abs() < 1e-10,
792 "Cov[{},{}] = {} != Cov[{},{}] = {}",
793 i,
794 j,
795 cov[i + j * 3],
796 j,
797 i,
798 cov[j + i * 3]
799 );
800 }
801 }
802 }
803
804 #[test]
805 fn test_cov_irreg_diagonal_positive() {
806 let offsets = vec![0, 5, 10, 15];
808 let argvals = vec![
809 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0,
810 ];
811 let values = vec![
812 1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 4.0, 1.0, 0.0, 2.0, 3.0, 2.0, 3.0, 2.0,
813 ];
814
815 let grid = vec![0.25, 0.5, 0.75];
816 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
817
818 for i in 0..3 {
819 assert!(
820 cov[i + i * 3] >= -1e-10,
821 "Variance at {} should be non-negative: {}",
822 i,
823 cov[i + i * 3]
824 );
825 }
826 }
827
828 #[test]
829 fn test_cov_irreg_different_grids() {
830 let offsets = vec![0, 5, 10];
831 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
832 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
833
834 let s_grid = vec![0.25, 0.5];
835 let t_grid = vec![0.5, 0.75];
836 let cov = cov_irreg(&offsets, &argvals, &values, &s_grid, &t_grid, 0.3);
837
838 assert_eq!(cov.len(), 4);
840 }
841
842 #[test]
847 fn test_metric_lp_irreg_self_distance_zero() {
848 let offsets = vec![0, 5, 10];
850 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
851 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
852
853 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
854
855 let n = 2;
857 for i in 0..n {
858 assert!(
859 dist[i + i * n].abs() < 1e-10,
860 "Self-distance d[{},{}] = {} should be 0",
861 i,
862 i,
863 dist[i + i * n]
864 );
865 }
866 }
867
868 #[test]
869 fn test_metric_lp_irreg_symmetry() {
870 let offsets = vec![0, 5, 10, 15];
872 let argvals = vec![
873 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0,
874 ];
875 let values = vec![
876 1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0, 2.0, 3.0, 4.0, 3.0, 2.0,
877 ];
878
879 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
880 let n = 3;
881
882 for i in 0..n {
883 for j in 0..n {
884 assert!(
885 (dist[i + j * n] - dist[j + i * n]).abs() < 1e-10,
886 "Dist[{},{}] = {} != Dist[{},{}] = {}",
887 i,
888 j,
889 dist[i + j * n],
890 j,
891 i,
892 dist[j + i * n]
893 );
894 }
895 }
896 }
897
898 #[test]
899 fn test_metric_lp_irreg_triangle_inequality() {
900 let offsets = vec![0, 5, 10, 15];
902 let argvals = vec![
903 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0,
904 ];
905 let values = vec![
906 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, ];
910
911 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
912 let n = 3;
913
914 let d_ac = dist[2 * n];
916 let d_ab = dist[n];
917 let d_bc = dist[1 + 2 * n];
918
919 assert!(
920 d_ac <= d_ab + d_bc + 1e-10,
921 "Triangle inequality violated: {} > {} + {}",
922 d_ac,
923 d_ab,
924 d_bc
925 );
926 }
927
928 #[test]
933 fn test_to_regular_grid_basic() {
934 let offsets = vec![0, 5];
935 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0];
936 let values = vec![0.0, 1.0, 2.0, 3.0, 4.0];
937
938 let grid = vec![0.0, 0.5, 1.0];
939 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
940
941 assert_eq!(result.len(), 3);
943
944 assert!((result[0] - 0.0).abs() < 1e-10, "At t=0: {}", result[0]);
946 assert!((result[1] - 2.0).abs() < 1e-10, "At t=0.5: {}", result[1]);
947 assert!((result[2] - 4.0).abs() < 1e-10, "At t=1: {}", result[2]);
948 }
949
950 #[test]
951 fn test_to_regular_grid_multiple_curves() {
952 let offsets = vec![0, 5, 10];
953 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
954 let values = vec![
955 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0, 0.0, ];
958
959 let grid = vec![0.0, 0.5, 1.0];
960 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
961
962 assert_eq!(result.len(), 6);
964
965 assert!((result[2] - 2.0).abs() < 1e-10);
967 assert!((result[1 + 2] - 2.0).abs() < 1e-10);
969 }
970
971 #[test]
972 fn test_to_regular_grid_boundary_nan() {
973 let offsets = vec![0, 3];
974 let argvals = vec![0.2, 0.5, 0.8]; let values = vec![1.0, 2.0, 3.0];
976
977 let grid = vec![0.0, 0.5, 1.0]; let result = to_regular_grid(&offsets, &argvals, &values, &grid);
979
980 assert!(result[0].is_nan(), "t=0 should be NaN");
982 assert!((result[1] - 2.0).abs() < 1e-10, "t=0.5: {}", result[1]);
984 assert!(result[2].is_nan(), "t=1 should be NaN");
986 }
987}