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_or(std::cmp::Ordering::Equal));
433 all_t.dedup();
434
435 let (t_min, t_max) = match (t1.first(), t1.last(), t2.first(), t2.last()) {
437 (Some(&a), Some(&b), Some(&c), Some(&d)) => (a.max(c), b.min(d)),
438 _ => return f64::NAN,
439 };
440
441 let common_t: Vec<f64> = all_t
442 .into_iter()
443 .filter(|&t| t >= t_min && t <= t_max)
444 .collect();
445
446 if common_t.len() < 2 {
447 return f64::NAN;
448 }
449
450 let y1: Vec<f64> = common_t.iter().map(|&t| linear_interp(t1, x1, t)).collect();
452 let y2: Vec<f64> = common_t.iter().map(|&t| linear_interp(t2, x2, t)).collect();
453
454 let mut integral = 0.0;
456 for j in 1..common_t.len() {
457 let h = common_t[j] - common_t[j - 1];
458 let val_left = (y1[j - 1] - y2[j - 1]).abs().powf(p);
459 let val_right = (y1[j] - y2[j]).abs().powf(p);
460 integral += 0.5 * h * (val_left + val_right);
461 }
462
463 integral.powf(1.0 / p)
464}
465
466fn linear_interp(argvals: &[f64], values: &[f64], t: f64) -> f64 {
468 if t <= argvals[0] {
469 return values[0];
470 }
471 if t >= argvals[argvals.len() - 1] {
472 return values[values.len() - 1];
473 }
474
475 let idx = argvals.iter().position(|&x| x > t).unwrap();
477 let t0 = argvals[idx - 1];
478 let t1 = argvals[idx];
479 let x0 = values[idx - 1];
480 let x1 = values[idx];
481
482 x0 + (x1 - x0) * (t - t0) / (t1 - t0)
484}
485
486pub fn metric_lp_irreg(offsets: &[usize], argvals: &[f64], values: &[f64], p: f64) -> Vec<f64> {
497 let n = offsets.len() - 1;
498 let mut dist = vec![0.0; n * n];
499
500 let pairs: Vec<(usize, usize)> = (0..n)
502 .flat_map(|i| (i + 1..n).map(move |j| (i, j)))
503 .collect();
504
505 let distances: Vec<f64> = slice_maybe_parallel!(pairs)
506 .map(|&(i, j)| {
507 let start_i = offsets[i];
508 let end_i = offsets[i + 1];
509 let start_j = offsets[j];
510 let end_j = offsets[j + 1];
511
512 lp_distance_pair(
513 &argvals[start_i..end_i],
514 &values[start_i..end_i],
515 &argvals[start_j..end_j],
516 &values[start_j..end_j],
517 p,
518 )
519 })
520 .collect();
521
522 for (k, &(i, j)) in pairs.iter().enumerate() {
524 dist[i + j * n] = distances[k];
525 dist[j + i * n] = distances[k];
526 }
527
528 dist
529}
530
531pub fn to_regular_grid(
548 offsets: &[usize],
549 argvals: &[f64],
550 values: &[f64],
551 target_grid: &[f64],
552) -> Vec<f64> {
553 let n = offsets.len() - 1;
554 let m = target_grid.len();
555
556 iter_maybe_parallel!(0..n)
557 .flat_map(|i| {
558 let start = offsets[i];
559 let end = offsets[i + 1];
560 let obs_t = &argvals[start..end];
561 let obs_x = &values[start..end];
562
563 target_grid
564 .iter()
565 .map(|&t| {
566 if obs_t.is_empty() || t < obs_t[0] || t > obs_t[obs_t.len() - 1] {
567 f64::NAN
568 } else {
569 linear_interp(obs_t, obs_x, t)
570 }
571 })
572 .collect::<Vec<f64>>()
573 })
574 .collect::<Vec<f64>>()
575 .chunks(m)
577 .enumerate()
578 .fold(vec![0.0; n * m], |mut acc, (i, row)| {
579 for (j, &val) in row.iter().enumerate() {
580 acc[i + j * n] = val;
581 }
582 acc
583 })
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589
590 #[test]
591 fn test_from_lists() {
592 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
593 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
594
595 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
596
597 assert_eq!(ifd.n_obs(), 2);
598 assert_eq!(ifd.n_points(0), 3);
599 assert_eq!(ifd.n_points(1), 2);
600 assert_eq!(ifd.total_points(), 5);
601 }
602
603 #[test]
604 fn test_get_obs() {
605 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
606 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
607
608 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
609
610 let (t0, x0) = ifd.get_obs(0);
611 assert_eq!(t0, &[0.0, 0.5, 1.0]);
612 assert_eq!(x0, &[1.0, 2.0, 3.0]);
613
614 let (t1, x1) = ifd.get_obs(1);
615 assert_eq!(t1, &[0.0, 1.0]);
616 assert_eq!(x1, &[1.0, 3.0]);
617 }
618
619 #[test]
620 fn test_integrate_irreg() {
621 let offsets = vec![0, 3, 6];
623 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
624 let values = vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0];
625
626 let integrals = integrate_irreg(&offsets, &argvals, &values);
627
628 assert!((integrals[0] - 1.0).abs() < 1e-10);
629 assert!((integrals[1] - 2.0).abs() < 1e-10);
630 }
631
632 #[test]
633 fn test_norm_lp_irreg() {
634 let offsets = vec![0, 3];
636 let argvals = vec![0.0, 0.5, 1.0];
637 let values = vec![2.0, 2.0, 2.0];
638
639 let norms = norm_lp_irreg(&offsets, &argvals, &values, 2.0);
640
641 assert!((norms[0] - 2.0).abs() < 1e-10);
642 }
643
644 #[test]
645 fn test_linear_interp() {
646 let t = vec![0.0, 1.0, 2.0];
647 let x = vec![0.0, 2.0, 4.0];
648
649 assert!((linear_interp(&t, &x, 0.5) - 1.0).abs() < 1e-10);
650 assert!((linear_interp(&t, &x, 1.5) - 3.0).abs() < 1e-10);
651 }
652
653 #[test]
654 fn test_mean_irreg() {
655 let offsets = vec![0, 3, 6];
657 let argvals = vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0];
658 let values = vec![0.0, 1.0, 2.0, 0.0, 1.0, 2.0];
659
660 let target = vec![0.0, 0.5, 1.0];
661 let mean = mean_irreg(&offsets, &argvals, &values, &target, 0.5, 1);
662
663 assert!((mean[1] - 1.0).abs() < 0.3);
665 }
666
667 #[test]
672 fn test_from_flat() {
673 let offsets = vec![0, 3, 5, 10];
674 let argvals = vec![0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.2, 0.4, 0.6, 0.8];
675 let values = vec![1.0, 2.0, 3.0, 1.0, 3.0, 0.0, 1.0, 2.0, 3.0, 4.0];
676 let rangeval = [0.0, 1.0];
677
678 let ifd = IrregFdata::from_flat(offsets.clone(), argvals.clone(), values.clone(), rangeval);
679
680 assert_eq!(ifd.n_obs(), 3);
681 assert_eq!(ifd.offsets, offsets);
682 assert_eq!(ifd.argvals, argvals);
683 assert_eq!(ifd.values, values);
684 assert_eq!(ifd.rangeval, rangeval);
685 }
686
687 #[test]
688 fn test_accessors_n_obs_n_points_total() {
689 let argvals_list = vec![
690 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
694 let values_list = vec![
695 vec![1.0, 2.0, 3.0],
696 vec![1.0, 3.0],
697 vec![0.0, 1.0, 2.0, 3.0, 4.0],
698 ];
699
700 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
701
702 assert_eq!(ifd.n_obs(), 3);
704
705 assert_eq!(ifd.n_points(0), 3);
707 assert_eq!(ifd.n_points(1), 2);
708 assert_eq!(ifd.n_points(2), 5);
709
710 assert_eq!(ifd.total_points(), 10);
712 }
713
714 #[test]
715 fn test_obs_counts() {
716 let argvals_list = vec![
717 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
721 let values_list = vec![
722 vec![1.0, 2.0, 3.0],
723 vec![1.0, 3.0],
724 vec![0.0, 1.0, 2.0, 3.0, 4.0],
725 ];
726
727 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
728 let counts = ifd.obs_counts();
729
730 assert_eq!(counts, vec![3, 2, 5]);
731 }
732
733 #[test]
734 fn test_min_max_obs() {
735 let argvals_list = vec![
736 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
740 let values_list = vec![
741 vec![1.0, 2.0, 3.0],
742 vec![1.0, 3.0],
743 vec![0.0, 1.0, 2.0, 3.0, 4.0],
744 ];
745
746 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
747
748 assert_eq!(ifd.min_obs(), 2);
749 assert_eq!(ifd.max_obs(), 5);
750 }
751
752 #[test]
753 fn test_min_max_obs_empty() {
754 let ifd = IrregFdata::from_lists(&[], &[]);
755 assert_eq!(ifd.min_obs(), 0);
756 assert_eq!(ifd.max_obs(), 0);
757 }
758
759 #[test]
764 fn test_cov_irreg_identical_curves() {
765 let offsets = vec![0, 5, 10];
767 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
768 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 2.0, 1.0];
769
770 let grid = vec![0.25, 0.5, 0.75];
771 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
772
773 assert_eq!(cov.len(), 9);
775 for i in 0..3 {
777 assert!(
778 cov[i + i * 3].abs() < 0.5,
779 "Diagonal cov[{},{}] = {} should be near 0",
780 i,
781 i,
782 cov[i + i * 3]
783 );
784 }
785 }
786
787 #[test]
788 fn test_cov_irreg_symmetry() {
789 let offsets = vec![0, 5, 10, 15];
791 let argvals = vec![
792 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,
793 ];
794 let values = vec![
795 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,
796 ];
797
798 let grid = vec![0.25, 0.5, 0.75];
799 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
800
801 for i in 0..3 {
803 for j in 0..3 {
804 assert!(
805 (cov[i + j * 3] - cov[j + i * 3]).abs() < 1e-10,
806 "Cov[{},{}] = {} != Cov[{},{}] = {}",
807 i,
808 j,
809 cov[i + j * 3],
810 j,
811 i,
812 cov[j + i * 3]
813 );
814 }
815 }
816 }
817
818 #[test]
819 fn test_cov_irreg_diagonal_positive() {
820 let offsets = vec![0, 5, 10, 15];
822 let argvals = vec![
823 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,
824 ];
825 let values = vec![
826 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,
827 ];
828
829 let grid = vec![0.25, 0.5, 0.75];
830 let cov = cov_irreg(&offsets, &argvals, &values, &grid, &grid, 0.3);
831
832 for i in 0..3 {
833 assert!(
834 cov[i + i * 3] >= -1e-10,
835 "Variance at {} should be non-negative: {}",
836 i,
837 cov[i + i * 3]
838 );
839 }
840 }
841
842 #[test]
843 fn test_cov_irreg_different_grids() {
844 let offsets = vec![0, 5, 10];
845 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
846 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
847
848 let s_grid = vec![0.25, 0.5];
849 let t_grid = vec![0.5, 0.75];
850 let cov = cov_irreg(&offsets, &argvals, &values, &s_grid, &t_grid, 0.3);
851
852 assert_eq!(cov.len(), 4);
854 }
855
856 #[test]
861 fn test_metric_lp_irreg_self_distance_zero() {
862 let offsets = vec![0, 5, 10];
864 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
865 let values = vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0];
866
867 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
868
869 let n = 2;
871 for i in 0..n {
872 assert!(
873 dist[i + i * n].abs() < 1e-10,
874 "Self-distance d[{},{}] = {} should be 0",
875 i,
876 i,
877 dist[i + i * n]
878 );
879 }
880 }
881
882 #[test]
883 fn test_metric_lp_irreg_symmetry() {
884 let offsets = vec![0, 5, 10, 15];
886 let argvals = vec![
887 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,
888 ];
889 let values = vec![
890 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,
891 ];
892
893 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
894 let n = 3;
895
896 for i in 0..n {
897 for j in 0..n {
898 assert!(
899 (dist[i + j * n] - dist[j + i * n]).abs() < 1e-10,
900 "Dist[{},{}] = {} != Dist[{},{}] = {}",
901 i,
902 j,
903 dist[i + j * n],
904 j,
905 i,
906 dist[j + i * n]
907 );
908 }
909 }
910 }
911
912 #[test]
913 fn test_metric_lp_irreg_triangle_inequality() {
914 let offsets = vec![0, 5, 10, 15];
916 let argvals = vec![
917 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,
918 ];
919 let values = vec![
920 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, ];
924
925 let dist = metric_lp_irreg(&offsets, &argvals, &values, 2.0);
926 let n = 3;
927
928 let d_ac = dist[2 * n];
930 let d_ab = dist[n];
931 let d_bc = dist[1 + 2 * n];
932
933 assert!(
934 d_ac <= d_ab + d_bc + 1e-10,
935 "Triangle inequality violated: {} > {} + {}",
936 d_ac,
937 d_ab,
938 d_bc
939 );
940 }
941
942 #[test]
947 fn test_to_regular_grid_basic() {
948 let offsets = vec![0, 5];
949 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0];
950 let values = vec![0.0, 1.0, 2.0, 3.0, 4.0];
951
952 let grid = vec![0.0, 0.5, 1.0];
953 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
954
955 assert_eq!(result.len(), 3);
957
958 assert!((result[0] - 0.0).abs() < 1e-10, "At t=0: {}", result[0]);
960 assert!((result[1] - 2.0).abs() < 1e-10, "At t=0.5: {}", result[1]);
961 assert!((result[2] - 4.0).abs() < 1e-10, "At t=1: {}", result[2]);
962 }
963
964 #[test]
965 fn test_to_regular_grid_multiple_curves() {
966 let offsets = vec![0, 5, 10];
967 let argvals = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0];
968 let values = vec![
969 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0, 0.0, ];
972
973 let grid = vec![0.0, 0.5, 1.0];
974 let result = to_regular_grid(&offsets, &argvals, &values, &grid);
975
976 assert_eq!(result.len(), 6);
978
979 assert!((result[2] - 2.0).abs() < 1e-10);
981 assert!((result[1 + 2] - 2.0).abs() < 1e-10);
983 }
984
985 #[test]
986 fn test_to_regular_grid_boundary_nan() {
987 let offsets = vec![0, 3];
988 let argvals = vec![0.2, 0.5, 0.8]; let values = vec![1.0, 2.0, 3.0];
990
991 let grid = vec![0.0, 0.5, 1.0]; let result = to_regular_grid(&offsets, &argvals, &values, &grid);
993
994 assert!(result[0].is_nan(), "t=0 should be NaN");
996 assert!((result[1] - 2.0).abs() < 1e-10, "t=0.5: {}", result[1]);
998 assert!(result[2].is_nan(), "t=1 should be NaN");
1000 }
1001}