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 cov[si + ti * ns] =
377 accumulate_cov_at_point(offsets, argvals, ¢ered, n, s, t, bandwidth);
378 }
379 }
380
381 cov
382}
383
384fn accumulate_cov_at_point(
386 offsets: &[usize],
387 obs_times: &[f64],
388 centered: &[f64],
389 n: usize,
390 s: f64,
391 t: f64,
392 bandwidth: f64,
393) -> f64 {
394 let mut sum_weights = 0.0;
395 let mut sum_products = 0.0;
396
397 for i in 0..n {
398 let start = offsets[i];
399 let end = offsets[i + 1];
400 let obs_t = &obs_times[start..end];
401 let obs_c = ¢ered[start..end];
402
403 for j1 in 0..obs_t.len() {
404 for j2 in 0..obs_t.len() {
405 let w1 = kernel_gaussian((obs_t[j1] - s) / bandwidth);
406 let w2 = kernel_gaussian((obs_t[j2] - t) / bandwidth);
407 let w = w1 * w2;
408
409 sum_weights += w;
410 sum_products += w * obs_c[j1] * obs_c[j2];
411 }
412 }
413 }
414
415 if sum_weights > 0.0 {
416 sum_products / sum_weights
417 } else {
418 0.0
419 }
420}
421
422fn lp_distance_pair(t1: &[f64], x1: &[f64], t2: &[f64], x2: &[f64], p: f64) -> f64 {
430 let mut all_t: Vec<f64> = t1.iter().chain(t2.iter()).copied().collect();
432 all_t.sort_by(|a, b| a.partial_cmp(b).unwrap());
433 all_t.dedup();
434
435 let t_min = t1.first().unwrap().max(*t2.first().unwrap());
437 let t_max = t1.last().unwrap().min(*t2.last().unwrap());
438
439 let common_t: Vec<f64> = all_t
440 .into_iter()
441 .filter(|&t| t >= t_min && t <= t_max)
442 .collect();
443
444 if common_t.len() < 2 {
445 return f64::NAN;
446 }
447
448 let y1: Vec<f64> = common_t.iter().map(|&t| linear_interp(t1, x1, t)).collect();
450 let y2: Vec<f64> = common_t.iter().map(|&t| linear_interp(t2, x2, t)).collect();
451
452 let mut integral = 0.0;
454 for j in 1..common_t.len() {
455 let h = common_t[j] - common_t[j - 1];
456 let val_left = (y1[j - 1] - y2[j - 1]).abs().powf(p);
457 let val_right = (y1[j] - y2[j]).abs().powf(p);
458 integral += 0.5 * h * (val_left + val_right);
459 }
460
461 integral.powf(1.0 / p)
462}
463
464fn linear_interp(argvals: &[f64], values: &[f64], t: f64) -> f64 {
466 if t <= argvals[0] {
467 return values[0];
468 }
469 if t >= argvals[argvals.len() - 1] {
470 return values[values.len() - 1];
471 }
472
473 let idx = argvals.iter().position(|&x| x > t).unwrap();
475 let t0 = argvals[idx - 1];
476 let t1 = argvals[idx];
477 let x0 = values[idx - 1];
478 let x1 = values[idx];
479
480 x0 + (x1 - x0) * (t - t0) / (t1 - t0)
482}
483
484pub fn metric_lp_irreg(offsets: &[usize], argvals: &[f64], values: &[f64], p: f64) -> Vec<f64> {
495 let n = offsets.len() - 1;
496 let mut dist = vec![0.0; n * n];
497
498 let pairs: Vec<(usize, usize)> = (0..n)
500 .flat_map(|i| (i + 1..n).map(move |j| (i, j)))
501 .collect();
502
503 let distances: Vec<f64> = slice_maybe_parallel!(pairs)
504 .map(|&(i, j)| {
505 let start_i = offsets[i];
506 let end_i = offsets[i + 1];
507 let start_j = offsets[j];
508 let end_j = offsets[j + 1];
509
510 lp_distance_pair(
511 &argvals[start_i..end_i],
512 &values[start_i..end_i],
513 &argvals[start_j..end_j],
514 &values[start_j..end_j],
515 p,
516 )
517 })
518 .collect();
519
520 for (k, &(i, j)) in pairs.iter().enumerate() {
522 dist[i + j * n] = distances[k];
523 dist[j + i * n] = distances[k];
524 }
525
526 dist
527}
528
529pub fn to_regular_grid(
546 offsets: &[usize],
547 argvals: &[f64],
548 values: &[f64],
549 target_grid: &[f64],
550) -> Vec<f64> {
551 let n = offsets.len() - 1;
552 let m = target_grid.len();
553
554 iter_maybe_parallel!(0..n)
555 .flat_map(|i| {
556 let start = offsets[i];
557 let end = offsets[i + 1];
558 let obs_t = &argvals[start..end];
559 let obs_x = &values[start..end];
560
561 target_grid
562 .iter()
563 .map(|&t| {
564 if obs_t.is_empty() || t < obs_t[0] || t > obs_t[obs_t.len() - 1] {
565 f64::NAN
566 } else {
567 linear_interp(obs_t, obs_x, t)
568 }
569 })
570 .collect::<Vec<f64>>()
571 })
572 .collect::<Vec<f64>>()
573 .chunks(m)
575 .enumerate()
576 .fold(vec![0.0; n * m], |mut acc, (i, row)| {
577 for (j, &val) in row.iter().enumerate() {
578 acc[i + j * n] = val;
579 }
580 acc
581 })
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn test_from_lists() {
590 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
591 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
592
593 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
594
595 assert_eq!(ifd.n_obs(), 2);
596 assert_eq!(ifd.n_points(0), 3);
597 assert_eq!(ifd.n_points(1), 2);
598 assert_eq!(ifd.total_points(), 5);
599 }
600
601 #[test]
602 fn test_get_obs() {
603 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
604 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
605
606 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
607
608 let (t0, x0) = ifd.get_obs(0);
609 assert_eq!(t0, &[0.0, 0.5, 1.0]);
610 assert_eq!(x0, &[1.0, 2.0, 3.0]);
611
612 let (t1, x1) = ifd.get_obs(1);
613 assert_eq!(t1, &[0.0, 1.0]);
614 assert_eq!(x1, &[1.0, 3.0]);
615 }
616
617 #[test]
618 fn test_integrate_irreg() {
619 let offsets = vec![0, 3, 6];
621 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
622 let values = vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0];
623
624 let integrals = integrate_irreg(&offsets, &argvals, &values);
625
626 assert!((integrals[0] - 1.0).abs() < 1e-10);
627 assert!((integrals[1] - 2.0).abs() < 1e-10);
628 }
629
630 #[test]
631 fn test_norm_lp_irreg() {
632 let offsets = vec![0, 3];
634 let argvals = vec![0.0, 0.5, 1.0];
635 let values = vec![2.0, 2.0, 2.0];
636
637 let norms = norm_lp_irreg(&offsets, &argvals, &values, 2.0);
638
639 assert!((norms[0] - 2.0).abs() < 1e-10);
640 }
641
642 #[test]
643 fn test_linear_interp() {
644 let t = vec![0.0, 1.0, 2.0];
645 let x = vec![0.0, 2.0, 4.0];
646
647 assert!((linear_interp(&t, &x, 0.5) - 1.0).abs() < 1e-10);
648 assert!((linear_interp(&t, &x, 1.5) - 3.0).abs() < 1e-10);
649 }
650
651 #[test]
652 fn test_mean_irreg() {
653 let offsets = vec![0, 3, 6];
655 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
656 let values = vec![0.0, 1.0, 2.0, 0.0, 1.0, 2.0];
657
658 let target = vec![0.0, 0.5, 1.0];
659 let mean = mean_irreg(&offsets, &argvals, &values, &target, 0.5, 1);
660
661 assert!((mean[1] - 1.0).abs() < 0.3);
663 }
664
665 #[test]
670 fn test_from_flat() {
671 let offsets = vec![0, 3, 5, 10];
672 let argvals = vec![0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.2, 0.4, 0.6, 0.8];
673 let values = vec![1.0, 2.0, 3.0, 1.0, 3.0, 0.0, 1.0, 2.0, 3.0, 4.0];
674 let rangeval = [0.0, 1.0];
675
676 let ifd = IrregFdata::from_flat(offsets.clone(), argvals.clone(), values.clone(), rangeval);
677
678 assert_eq!(ifd.n_obs(), 3);
679 assert_eq!(ifd.offsets, offsets);
680 assert_eq!(ifd.argvals, argvals);
681 assert_eq!(ifd.values, values);
682 assert_eq!(ifd.rangeval, rangeval);
683 }
684
685 #[test]
686 fn test_accessors_n_obs_n_points_total() {
687 let argvals_list = vec![
688 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
692 let values_list = vec![
693 vec![1.0, 2.0, 3.0],
694 vec![1.0, 3.0],
695 vec![0.0, 1.0, 2.0, 3.0, 4.0],
696 ];
697
698 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
699
700 assert_eq!(ifd.n_obs(), 3);
702
703 assert_eq!(ifd.n_points(0), 3);
705 assert_eq!(ifd.n_points(1), 2);
706 assert_eq!(ifd.n_points(2), 5);
707
708 assert_eq!(ifd.total_points(), 10);
710 }
711
712 #[test]
713 fn test_obs_counts() {
714 let argvals_list = vec![
715 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
719 let values_list = vec![
720 vec![1.0, 2.0, 3.0],
721 vec![1.0, 3.0],
722 vec![0.0, 1.0, 2.0, 3.0, 4.0],
723 ];
724
725 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
726 let counts = ifd.obs_counts();
727
728 assert_eq!(counts, vec![3, 2, 5]);
729 }
730
731 #[test]
732 fn test_min_max_obs() {
733 let argvals_list = vec![
734 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
738 let values_list = vec![
739 vec![1.0, 2.0, 3.0],
740 vec![1.0, 3.0],
741 vec![0.0, 1.0, 2.0, 3.0, 4.0],
742 ];
743
744 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
745
746 assert_eq!(ifd.min_obs(), 2);
747 assert_eq!(ifd.max_obs(), 5);
748 }
749
750 #[test]
751 fn test_min_max_obs_empty() {
752 let ifd = IrregFdata::from_lists(&[], &[]);
753 assert_eq!(ifd.min_obs(), 0);
754 assert_eq!(ifd.max_obs(), 0);
755 }
756
757 #[test]
762 fn test_cov_irreg_identical_curves() {
763 let offsets = vec![0, 5, 10];
765 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
766 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 2.0, 1.0];
767
768 let grid = vec![0.25, 0.5, 0.75];
769 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
770
771 assert_eq!(cov.len(), 9);
773 for i in 0..3 {
775 assert!(
776 cov[i + i * 3].abs() < 0.5,
777 "Diagonal cov[{},{}] = {} should be near 0",
778 i,
779 i,
780 cov[i + i * 3]
781 );
782 }
783 }
784
785 #[test]
786 fn test_cov_irreg_symmetry() {
787 let offsets = vec![0, 5, 10, 15];
789 let argvals = vec![
790 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,
791 ];
792 let values = vec![
793 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,
794 ];
795
796 let grid = vec![0.25, 0.5, 0.75];
797 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
798
799 for i in 0..3 {
801 for j in 0..3 {
802 assert!(
803 (cov[i + j * 3] - cov[j + i * 3]).abs() < 1e-10,
804 "Cov[{},{}] = {} != Cov[{},{}] = {}",
805 i,
806 j,
807 cov[i + j * 3],
808 j,
809 i,
810 cov[j + i * 3]
811 );
812 }
813 }
814 }
815
816 #[test]
817 fn test_cov_irreg_diagonal_positive() {
818 let offsets = vec![0, 5, 10, 15];
820 let argvals = vec![
821 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,
822 ];
823 let values = vec![
824 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,
825 ];
826
827 let grid = vec![0.25, 0.5, 0.75];
828 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
829
830 for i in 0..3 {
831 assert!(
832 cov[i + i * 3] >= -1e-10,
833 "Variance at {} should be non-negative: {}",
834 i,
835 cov[i + i * 3]
836 );
837 }
838 }
839
840 #[test]
841 fn test_cov_irreg_different_grids() {
842 let offsets = vec![0, 5, 10];
843 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
844 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
845
846 let s_grid = vec![0.25, 0.5];
847 let t_grid = vec![0.5, 0.75];
848 let cov = cov_irreg(&offsets, &argvals, &values, &s_grid, &t_grid, 0.3);
849
850 assert_eq!(cov.len(), 4);
852 }
853
854 #[test]
859 fn test_metric_lp_irreg_self_distance_zero() {
860 let offsets = vec![0, 5, 10];
862 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
863 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
864
865 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
866
867 let n = 2;
869 for i in 0..n {
870 assert!(
871 dist[i + i * n].abs() < 1e-10,
872 "Self-distance d[{},{}] = {} should be 0",
873 i,
874 i,
875 dist[i + i * n]
876 );
877 }
878 }
879
880 #[test]
881 fn test_metric_lp_irreg_symmetry() {
882 let offsets = vec![0, 5, 10, 15];
884 let argvals = vec![
885 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,
886 ];
887 let values = vec![
888 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,
889 ];
890
891 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
892 let n = 3;
893
894 for i in 0..n {
895 for j in 0..n {
896 assert!(
897 (dist[i + j * n] - dist[j + i * n]).abs() < 1e-10,
898 "Dist[{},{}] = {} != Dist[{},{}] = {}",
899 i,
900 j,
901 dist[i + j * n],
902 j,
903 i,
904 dist[j + i * n]
905 );
906 }
907 }
908 }
909
910 #[test]
911 fn test_metric_lp_irreg_triangle_inequality() {
912 let offsets = vec![0, 5, 10, 15];
914 let argvals = vec![
915 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,
916 ];
917 let values = vec![
918 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, ];
922
923 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
924 let n = 3;
925
926 let d_ac = dist[2 * n];
928 let d_ab = dist[n];
929 let d_bc = dist[1 + 2 * n];
930
931 assert!(
932 d_ac <= d_ab + d_bc + 1e-10,
933 "Triangle inequality violated: {} > {} + {}",
934 d_ac,
935 d_ab,
936 d_bc
937 );
938 }
939
940 #[test]
945 fn test_to_regular_grid_basic() {
946 let offsets = vec![0, 5];
947 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0];
948 let values = vec![0.0, 1.0, 2.0, 3.0, 4.0];
949
950 let grid = vec![0.0, 0.5, 1.0];
951 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
952
953 assert_eq!(result.len(), 3);
955
956 assert!((result[0] - 0.0).abs() < 1e-10, "At t=0: {}", result[0]);
958 assert!((result[1] - 2.0).abs() < 1e-10, "At t=0.5: {}", result[1]);
959 assert!((result[2] - 4.0).abs() < 1e-10, "At t=1: {}", result[2]);
960 }
961
962 #[test]
963 fn test_to_regular_grid_multiple_curves() {
964 let offsets = vec![0, 5, 10];
965 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
966 let values = vec![
967 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0, 0.0, ];
970
971 let grid = vec![0.0, 0.5, 1.0];
972 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
973
974 assert_eq!(result.len(), 6);
976
977 assert!((result[2] - 2.0).abs() < 1e-10);
979 assert!((result[1 + 2] - 2.0).abs() < 1e-10);
981 }
982
983 #[test]
984 fn test_to_regular_grid_boundary_nan() {
985 let offsets = vec![0, 3];
986 let argvals = vec![0.2, 0.5, 0.8]; let values = vec![1.0, 2.0, 3.0];
988
989 let grid = vec![0.0, 0.5, 1.0]; let result = to_regular_grid(&offsets, &argvals, &values, &grid);
991
992 assert!(result[0].is_nan(), "t=0 should be NaN");
994 assert!((result[1] - 2.0).abs() < 1e-10, "t=0.5: {}", result[1]);
996 assert!(result[2].is_nan(), "t=1 should be NaN");
998 }
999}