1use crate::matrix::FdMatrix;
24use crate::{iter_maybe_parallel, slice_maybe_parallel};
25#[cfg(feature = "parallel")]
26use rayon::iter::ParallelIterator;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum KernelType {
31 Epanechnikov,
33 Gaussian,
35}
36
37#[derive(Clone, Debug)]
42pub struct IrregFdata {
43 pub offsets: Vec<usize>,
46 pub argvals: Vec<f64>,
48 pub values: Vec<f64>,
50 pub rangeval: [f64; 2],
52}
53
54impl IrregFdata {
55 pub fn from_lists(argvals_list: &[Vec<f64>], values_list: &[Vec<f64>]) -> Self {
64 let n = argvals_list.len();
65 assert_eq!(
66 n,
67 values_list.len(),
68 "argvals_list and values_list must have same length"
69 );
70
71 let mut offsets = Vec::with_capacity(n + 1);
72 offsets.push(0);
73
74 let total_points: usize = argvals_list.iter().map(|v| v.len()).sum();
75 let mut argvals = Vec::with_capacity(total_points);
76 let mut values = Vec::with_capacity(total_points);
77
78 let mut range_min = f64::INFINITY;
79 let mut range_max = f64::NEG_INFINITY;
80
81 for i in 0..n {
82 assert_eq!(
83 argvals_list[i].len(),
84 values_list[i].len(),
85 "Observation {} has mismatched argvals/values lengths",
86 i
87 );
88
89 argvals.extend_from_slice(&argvals_list[i]);
90 values.extend_from_slice(&values_list[i]);
91 offsets.push(argvals.len());
92
93 if let (Some(&min), Some(&max)) = (argvals_list[i].first(), argvals_list[i].last()) {
94 range_min = range_min.min(min);
95 range_max = range_max.max(max);
96 }
97 }
98
99 IrregFdata {
100 offsets,
101 argvals,
102 values,
103 rangeval: [range_min, range_max],
104 }
105 }
106
107 pub fn from_flat(
118 offsets: Vec<usize>,
119 argvals: Vec<f64>,
120 values: Vec<f64>,
121 rangeval: [f64; 2],
122 ) -> Option<Self> {
123 if offsets.is_empty()
124 || argvals.len() != values.len()
125 || *offsets.last()? != argvals.len()
126 || offsets.windows(2).any(|w| w[0] > w[1])
127 {
128 return None;
129 }
130 Some(IrregFdata {
131 offsets,
132 argvals,
133 values,
134 rangeval,
135 })
136 }
137
138 #[inline]
140 pub fn n_obs(&self) -> usize {
141 self.offsets.len().saturating_sub(1)
142 }
143
144 #[inline]
146 pub fn n_points(&self, i: usize) -> usize {
147 self.offsets[i + 1] - self.offsets[i]
148 }
149
150 #[inline]
152 pub fn get_obs(&self, i: usize) -> (&[f64], &[f64]) {
153 let start = self.offsets[i];
154 let end = self.offsets[i + 1];
155 (&self.argvals[start..end], &self.values[start..end])
156 }
157
158 #[inline]
160 pub fn total_points(&self) -> usize {
161 self.argvals.len()
162 }
163
164 pub fn obs_counts(&self) -> Vec<usize> {
166 (0..self.n_obs()).map(|i| self.n_points(i)).collect()
167 }
168
169 pub fn min_obs(&self) -> usize {
171 (0..self.n_obs())
172 .map(|i| self.n_points(i))
173 .min()
174 .unwrap_or(0)
175 }
176
177 pub fn max_obs(&self) -> usize {
179 (0..self.n_obs())
180 .map(|i| self.n_points(i))
181 .max()
182 .unwrap_or(0)
183 }
184}
185
186pub fn integrate_irreg(ifd: &IrregFdata) -> Vec<f64> {
198 let n = ifd.n_obs();
199
200 iter_maybe_parallel!(0..n)
201 .map(|i| {
202 let (t, x) = ifd.get_obs(i);
203
204 if t.len() < 2 {
205 return 0.0;
206 }
207
208 let mut integral = 0.0;
209 for j in 1..t.len() {
210 let h = t[j] - t[j - 1];
211 integral += 0.5 * h * (x[j] + x[j - 1]);
212 }
213 integral
214 })
215 .collect()
216}
217
218pub fn norm_lp_irreg(ifd: &IrregFdata, p: f64) -> Vec<f64> {
235 let n = ifd.n_obs();
236
237 iter_maybe_parallel!(0..n)
238 .map(|i| {
239 let (t, x) = ifd.get_obs(i);
240
241 if t.len() < 2 {
242 return 0.0;
243 }
244
245 let mut integral = 0.0;
246 if (p - 1.0).abs() < 1e-14 {
247 for j in 1..t.len() {
248 let h = t[j] - t[j - 1];
249 integral += 0.5 * h * (x[j - 1].abs() + x[j].abs());
250 }
251 integral
252 } else if (p - 2.0).abs() < 1e-14 {
253 for j in 1..t.len() {
254 let h = t[j] - t[j - 1];
255 integral += 0.5 * h * (x[j - 1] * x[j - 1] + x[j] * x[j]);
256 }
257 integral.sqrt()
258 } else {
259 for j in 1..t.len() {
260 let h = t[j] - t[j - 1];
261 let val_left = x[j - 1].abs().powf(p);
262 let val_right = x[j].abs().powf(p);
263 integral += 0.5 * h * (val_left + val_right);
264 }
265 integral.powf(1.0 / p)
266 }
267 })
268 .collect()
269}
270
271#[inline]
277fn kernel_epanechnikov(u: f64) -> f64 {
278 if u.abs() <= 1.0 {
279 0.75 * (1.0 - u * u)
280 } else {
281 0.0
282 }
283}
284
285#[inline]
287fn kernel_gaussian(u: f64) -> f64 {
288 (-0.5 * u * u).exp() / (2.0 * std::f64::consts::PI).sqrt()
289}
290
291impl KernelType {
292 #[inline]
293 fn as_fn(self) -> fn(f64) -> f64 {
294 match self {
295 KernelType::Epanechnikov => kernel_epanechnikov,
296 KernelType::Gaussian => kernel_gaussian,
297 }
298 }
299}
300
301pub fn mean_irreg(
315 ifd: &IrregFdata,
316 target_argvals: &[f64],
317 bandwidth: f64,
318 kernel_type: KernelType,
319) -> Vec<f64> {
320 let n = ifd.n_obs();
321 let kernel = kernel_type.as_fn();
322
323 slice_maybe_parallel!(target_argvals)
324 .map(|&t| {
325 let mut sum_weights = 0.0;
326 let mut sum_values = 0.0;
327
328 for i in 0..n {
329 let (obs_t, obs_x) = ifd.get_obs(i);
330
331 for (&ti, &xi) in obs_t.iter().zip(obs_x.iter()) {
332 let u = (ti - t) / bandwidth;
333 let w = kernel(u);
334 sum_weights += w;
335 sum_values += w * xi;
336 }
337 }
338
339 if sum_weights > 0.0 {
340 sum_values / sum_weights
341 } else {
342 f64::NAN
343 }
344 })
345 .collect()
346}
347
348pub fn cov_irreg(ifd: &IrregFdata, s_grid: &[f64], t_grid: &[f64], bandwidth: f64) -> FdMatrix {
363 let n = ifd.n_obs();
364 let ns = s_grid.len();
365 let nt = t_grid.len();
366
367 let mean_at_obs = mean_irreg(ifd, &ifd.argvals, bandwidth, KernelType::Gaussian);
369
370 let centered: Vec<f64> = ifd
372 .values
373 .iter()
374 .zip(mean_at_obs.iter())
375 .map(|(&v, &m)| v - m)
376 .collect();
377
378 let mut cov = vec![0.0; ns * nt];
380
381 for (si, &s) in s_grid.iter().enumerate() {
382 for (ti, &t) in t_grid.iter().enumerate() {
383 cov[si + ti * ns] =
384 accumulate_cov_at_point(&ifd.offsets, &ifd.argvals, ¢ered, n, s, t, bandwidth);
385 }
386 }
387
388 FdMatrix::from_column_major(cov, ns, nt).unwrap()
389}
390
391fn accumulate_cov_at_point(
393 offsets: &[usize],
394 obs_times: &[f64],
395 centered: &[f64],
396 n: usize,
397 s: f64,
398 t: f64,
399 bandwidth: f64,
400) -> f64 {
401 let mut sum_weights = 0.0;
402 let mut sum_products = 0.0;
403
404 for i in 0..n {
405 let start = offsets[i];
406 let end = offsets[i + 1];
407 let obs_t = &obs_times[start..end];
408 let obs_c = ¢ered[start..end];
409
410 for j1 in 0..obs_t.len() {
411 for j2 in 0..obs_t.len() {
412 let w1 = kernel_gaussian((obs_t[j1] - s) / bandwidth);
413 let w2 = kernel_gaussian((obs_t[j2] - t) / bandwidth);
414 let w = w1 * w2;
415
416 sum_weights += w;
417 sum_products += w * obs_c[j1] * obs_c[j2];
418 }
419 }
420 }
421
422 if sum_weights > 0.0 {
423 sum_products / sum_weights
424 } else {
425 0.0
426 }
427}
428
429fn lp_distance_pair(t1: &[f64], x1: &[f64], t2: &[f64], x2: &[f64], p: f64) -> f64 {
437 let mut all_t: Vec<f64> = t1.iter().chain(t2.iter()).copied().collect();
439 all_t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
440 all_t.dedup();
441
442 let (t_min, t_max) = match (t1.first(), t1.last(), t2.first(), t2.last()) {
444 (Some(&a), Some(&b), Some(&c), Some(&d)) => (a.max(c), b.min(d)),
445 _ => return f64::NAN,
446 };
447
448 let common_t: Vec<f64> = all_t
449 .into_iter()
450 .filter(|&t| t >= t_min && t <= t_max)
451 .collect();
452
453 if common_t.len() < 2 {
454 return f64::NAN;
455 }
456
457 let y1: Vec<f64> = common_t.iter().map(|&t| linear_interp(t1, x1, t)).collect();
459 let y2: Vec<f64> = common_t.iter().map(|&t| linear_interp(t2, x2, t)).collect();
460
461 let mut integral = 0.0;
463 if (p - 1.0).abs() < 1e-14 {
464 for j in 1..common_t.len() {
465 let h = common_t[j] - common_t[j - 1];
466 integral += 0.5 * h * ((y1[j - 1] - y2[j - 1]).abs() + (y1[j] - y2[j]).abs());
467 }
468 integral
469 } else if (p - 2.0).abs() < 1e-14 {
470 for j in 1..common_t.len() {
471 let h = common_t[j] - common_t[j - 1];
472 let d_left = y1[j - 1] - y2[j - 1];
473 let d_right = y1[j] - y2[j];
474 integral += 0.5 * h * (d_left * d_left + d_right * d_right);
475 }
476 integral.sqrt()
477 } else {
478 for j in 1..common_t.len() {
479 let h = common_t[j] - common_t[j - 1];
480 let val_left = (y1[j - 1] - y2[j - 1]).abs().powf(p);
481 let val_right = (y1[j] - y2[j]).abs().powf(p);
482 integral += 0.5 * h * (val_left + val_right);
483 }
484 integral.powf(1.0 / p)
485 }
486}
487
488fn linear_interp(argvals: &[f64], values: &[f64], t: f64) -> f64 {
490 if t <= argvals[0] {
491 return values[0];
492 }
493 if t >= argvals[argvals.len() - 1] {
494 return values[values.len() - 1];
495 }
496
497 let idx = argvals.iter().position(|&x| x > t).unwrap();
499 let t0 = argvals[idx - 1];
500 let t1 = argvals[idx];
501 let x0 = values[idx - 1];
502 let x1 = values[idx];
503
504 x0 + (x1 - x0) * (t - t0) / (t1 - t0)
506}
507
508pub fn metric_lp_irreg(ifd: &IrregFdata, p: f64) -> FdMatrix {
517 let n = ifd.n_obs();
518 let mut dist = vec![0.0; n * n];
519
520 let pairs: Vec<(usize, usize)> = (0..n)
522 .flat_map(|i| (i + 1..n).map(move |j| (i, j)))
523 .collect();
524
525 let distances: Vec<f64> = slice_maybe_parallel!(pairs)
526 .map(|&(i, j)| {
527 let (t_i, x_i) = ifd.get_obs(i);
528 let (t_j, x_j) = ifd.get_obs(j);
529 lp_distance_pair(t_i, x_i, t_j, x_j, p)
530 })
531 .collect();
532
533 for (k, &(i, j)) in pairs.iter().enumerate() {
535 dist[i + j * n] = distances[k];
536 dist[j + i * n] = distances[k];
537 }
538
539 FdMatrix::from_column_major(dist, n, n).unwrap()
540}
541
542pub fn to_regular_grid(ifd: &IrregFdata, target_grid: &[f64]) -> FdMatrix {
557 let n = ifd.n_obs();
558 let m = target_grid.len();
559
560 let result = iter_maybe_parallel!(0..n)
561 .flat_map(|i| {
562 let (obs_t, obs_x) = ifd.get_obs(i);
563
564 target_grid
565 .iter()
566 .map(|&t| {
567 if obs_t.is_empty() || t < obs_t[0] || t > obs_t[obs_t.len() - 1] {
568 f64::NAN
569 } else {
570 linear_interp(obs_t, obs_x, t)
571 }
572 })
573 .collect::<Vec<f64>>()
574 })
575 .collect::<Vec<f64>>()
576 .chunks(m)
578 .enumerate()
579 .fold(vec![0.0; n * m], |mut acc, (i, row)| {
580 for (j, &val) in row.iter().enumerate() {
581 acc[i + j * n] = val;
582 }
583 acc
584 });
585
586 FdMatrix::from_column_major(result, n, m).unwrap()
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 fn make_ifd(offsets: Vec<usize>, argvals: Vec<f64>, values: Vec<f64>) -> IrregFdata {
594 let range_min = argvals.iter().cloned().fold(f64::INFINITY, f64::min);
595 let range_max = argvals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
596 IrregFdata::from_flat(offsets, argvals, values, [range_min, range_max]).unwrap()
597 }
598
599 #[test]
600 fn test_from_lists() {
601 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
602 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
603
604 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
605
606 assert_eq!(ifd.n_obs(), 2);
607 assert_eq!(ifd.n_points(0), 3);
608 assert_eq!(ifd.n_points(1), 2);
609 assert_eq!(ifd.total_points(), 5);
610 }
611
612 #[test]
613 fn test_get_obs() {
614 let argvals_list = vec![vec![0.0, 0.5, 1.0], vec![0.0, 1.0]];
615 let values_list = vec![vec![1.0, 2.0, 3.0], vec![1.0, 3.0]];
616
617 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
618
619 let (t0, x0) = ifd.get_obs(0);
620 assert_eq!(t0, &[0.0, 0.5, 1.0]);
621 assert_eq!(x0, &[1.0, 2.0, 3.0]);
622
623 let (t1, x1) = ifd.get_obs(1);
624 assert_eq!(t1, &[0.0, 1.0]);
625 assert_eq!(x1, &[1.0, 3.0]);
626 }
627
628 #[test]
629 fn test_integrate_irreg() {
630 let ifd = make_ifd(
632 vec![0, 3, 6],
633 vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0],
634 vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0],
635 );
636
637 let integrals = integrate_irreg(&ifd);
638
639 assert!((integrals[0] - 1.0).abs() < 1e-10);
640 assert!((integrals[1] - 2.0).abs() < 1e-10);
641 }
642
643 #[test]
644 fn test_norm_lp_irreg() {
645 let ifd = make_ifd(vec![0, 3], vec![0.0, 0.5, 1.0], vec![2.0, 2.0, 2.0]);
647
648 let norms = norm_lp_irreg(&ifd, 2.0);
649
650 assert!((norms[0] - 2.0).abs() < 1e-10);
651 }
652
653 #[test]
654 fn test_linear_interp() {
655 let t = vec![0.0, 1.0, 2.0];
656 let x = vec![0.0, 2.0, 4.0];
657
658 assert!((linear_interp(&t, &x, 0.5) - 1.0).abs() < 1e-10);
659 assert!((linear_interp(&t, &x, 1.5) - 3.0).abs() < 1e-10);
660 }
661
662 #[test]
663 fn test_mean_irreg() {
664 let ifd = make_ifd(
666 vec![0, 3, 6],
667 vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0],
668 vec![0.0, 1.0, 2.0, 0.0, 1.0, 2.0],
669 );
670
671 let target = vec![0.0, 0.5, 1.0];
672 let mean = mean_irreg(&ifd, &target, 0.5, KernelType::Gaussian);
673
674 assert!((mean[1] - 1.0).abs() < 0.3);
676 }
677
678 #[test]
683 fn test_from_flat() {
684 let offsets = vec![0, 3, 5, 10];
685 let argvals = vec![0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.2, 0.4, 0.6, 0.8];
686 let values = vec![1.0, 2.0, 3.0, 1.0, 3.0, 0.0, 1.0, 2.0, 3.0, 4.0];
687 let rangeval = [0.0, 1.0];
688
689 let ifd = IrregFdata::from_flat(offsets.clone(), argvals.clone(), values.clone(), rangeval)
690 .unwrap();
691
692 assert_eq!(ifd.n_obs(), 3);
693 assert_eq!(ifd.offsets, offsets);
694 assert_eq!(ifd.argvals, argvals);
695 assert_eq!(ifd.values, values);
696 assert_eq!(ifd.rangeval, rangeval);
697 }
698
699 #[test]
700 fn test_from_flat_invalid() {
701 assert!(IrregFdata::from_flat(vec![], vec![], vec![], [0.0, 1.0]).is_none());
703 assert!(IrregFdata::from_flat(vec![0, 2], vec![0.0, 1.0], vec![1.0], [0.0, 1.0]).is_none());
705 assert!(
707 IrregFdata::from_flat(vec![0, 5], vec![0.0, 1.0], vec![1.0, 2.0], [0.0, 1.0]).is_none()
708 );
709 assert!(IrregFdata::from_flat(
711 vec![0, 3, 1],
712 vec![0.0, 1.0, 2.0],
713 vec![1.0, 2.0, 3.0],
714 [0.0, 2.0]
715 )
716 .is_none());
717 }
718
719 #[test]
720 fn test_accessors_n_obs_n_points_total() {
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.n_obs(), 3);
736
737 assert_eq!(ifd.n_points(0), 3);
739 assert_eq!(ifd.n_points(1), 2);
740 assert_eq!(ifd.n_points(2), 5);
741
742 assert_eq!(ifd.total_points(), 10);
744 }
745
746 #[test]
747 fn test_obs_counts() {
748 let argvals_list = vec![
749 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
753 let values_list = vec![
754 vec![1.0, 2.0, 3.0],
755 vec![1.0, 3.0],
756 vec![0.0, 1.0, 2.0, 3.0, 4.0],
757 ];
758
759 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
760 let counts = ifd.obs_counts();
761
762 assert_eq!(counts, vec![3, 2, 5]);
763 }
764
765 #[test]
766 fn test_min_max_obs() {
767 let argvals_list = vec![
768 vec![0.0, 0.5, 1.0], vec![0.0, 1.0], vec![0.0, 0.25, 0.5, 0.75, 1.0], ];
772 let values_list = vec![
773 vec![1.0, 2.0, 3.0],
774 vec![1.0, 3.0],
775 vec![0.0, 1.0, 2.0, 3.0, 4.0],
776 ];
777
778 let ifd = IrregFdata::from_lists(&argvals_list, &values_list);
779
780 assert_eq!(ifd.min_obs(), 2);
781 assert_eq!(ifd.max_obs(), 5);
782 }
783
784 #[test]
785 fn test_min_max_obs_empty() {
786 let ifd = IrregFdata::from_lists(&[], &[]);
787 assert_eq!(ifd.min_obs(), 0);
788 assert_eq!(ifd.max_obs(), 0);
789 }
790
791 #[test]
796 fn test_cov_irreg_identical_curves() {
797 let ifd = make_ifd(
799 vec![0, 5, 10],
800 vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0],
801 vec![1.0, 2.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 2.0, 1.0],
802 );
803
804 let grid = vec![0.25, 0.5, 0.75];
805 let cov = cov_irreg(&ifd, &grid, &grid, 0.3);
806
807 assert_eq!(cov.nrows(), 3);
809 assert_eq!(cov.ncols(), 3);
810 for i in 0..3 {
812 assert!(
813 cov[(i, i)].abs() < 0.5,
814 "Diagonal cov[{},{}] = {} should be near 0",
815 i,
816 i,
817 cov[(i, i)]
818 );
819 }
820 }
821
822 #[test]
823 fn test_cov_irreg_symmetry() {
824 let ifd = make_ifd(
826 vec![0, 5, 10, 15],
827 vec![
828 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,
829 ],
830 vec![
831 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,
832 ],
833 );
834
835 let grid = vec![0.25, 0.5, 0.75];
836 let cov = cov_irreg(&ifd, &grid, &grid, 0.3);
837
838 for i in 0..3 {
840 for j in 0..3 {
841 assert!(
842 (cov[(i, j)] - cov[(j, i)]).abs() < 1e-10,
843 "Cov[{},{}] = {} != Cov[{},{}] = {}",
844 i,
845 j,
846 cov[(i, j)],
847 j,
848 i,
849 cov[(j, i)]
850 );
851 }
852 }
853 }
854
855 #[test]
856 fn test_cov_irreg_diagonal_positive() {
857 let ifd = make_ifd(
859 vec![0, 5, 10, 15],
860 vec![
861 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,
862 ],
863 vec![
864 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,
865 ],
866 );
867
868 let grid = vec![0.25, 0.5, 0.75];
869 let cov = cov_irreg(&ifd, &grid, &grid, 0.3);
870
871 for i in 0..3 {
872 assert!(
873 cov[(i, i)] >= -1e-10,
874 "Variance at {} should be non-negative: {}",
875 i,
876 cov[(i, i)]
877 );
878 }
879 }
880
881 #[test]
882 fn test_cov_irreg_different_grids() {
883 let ifd = make_ifd(
884 vec![0, 5, 10],
885 vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0],
886 vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0],
887 );
888
889 let s_grid = vec![0.25, 0.5];
890 let t_grid = vec![0.5, 0.75];
891 let cov = cov_irreg(&ifd, &s_grid, &t_grid, 0.3);
892
893 assert_eq!(cov.nrows(), 2);
895 assert_eq!(cov.ncols(), 2);
896 }
897
898 #[test]
903 fn test_metric_lp_irreg_self_distance_zero() {
904 let ifd = make_ifd(
906 vec![0, 5, 10],
907 vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0],
908 vec![1.0, 2.0, 3.0, 2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0],
909 );
910
911 let dist = metric_lp_irreg(&ifd, 2.0);
912
913 let n = 2;
915 for i in 0..n {
916 assert!(
917 dist[(i, i)].abs() < 1e-10,
918 "Self-distance d[{},{}] = {} should be 0",
919 i,
920 i,
921 dist[(i, i)]
922 );
923 }
924 }
925
926 #[test]
927 fn test_metric_lp_irreg_symmetry() {
928 let ifd = make_ifd(
930 vec![0, 5, 10, 15],
931 vec![
932 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,
933 ],
934 vec![
935 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,
936 ],
937 );
938
939 let dist = metric_lp_irreg(&ifd, 2.0);
940 let n = 3;
941
942 for i in 0..n {
943 for j in 0..n {
944 assert!(
945 (dist[(i, j)] - dist[(j, i)]).abs() < 1e-10,
946 "Dist[{},{}] = {} != Dist[{},{}] = {}",
947 i,
948 j,
949 dist[(i, j)],
950 j,
951 i,
952 dist[(j, i)]
953 );
954 }
955 }
956 }
957
958 #[test]
959 fn test_metric_lp_irreg_triangle_inequality() {
960 let ifd = make_ifd(
962 vec![0, 5, 10, 15],
963 vec![
964 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,
965 ],
966 vec![
967 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, ],
971 );
972
973 let dist = metric_lp_irreg(&ifd, 2.0);
974
975 let d_ac = dist[(0, 2)];
977 let d_ab = dist[(0, 1)];
978 let d_bc = dist[(1, 2)];
979
980 assert!(
981 d_ac <= d_ab + d_bc + 1e-10,
982 "Triangle inequality violated: {} > {} + {}",
983 d_ac,
984 d_ab,
985 d_bc
986 );
987 }
988
989 #[test]
994 fn test_to_regular_grid_basic() {
995 let ifd = make_ifd(
996 vec![0, 5],
997 vec![0.0, 0.25, 0.5, 0.75, 1.0],
998 vec![0.0, 1.0, 2.0, 3.0, 4.0],
999 );
1000
1001 let grid = vec![0.0, 0.5, 1.0];
1002 let result = to_regular_grid(&ifd, &grid);
1003
1004 assert_eq!(result.nrows(), 1);
1006 assert_eq!(result.ncols(), 3);
1007
1008 assert!(
1010 (result[(0, 0)] - 0.0).abs() < 1e-10,
1011 "At t=0: {}",
1012 result[(0, 0)]
1013 );
1014 assert!(
1015 (result[(0, 1)] - 2.0).abs() < 1e-10,
1016 "At t=0.5: {}",
1017 result[(0, 1)]
1018 );
1019 assert!(
1020 (result[(0, 2)] - 4.0).abs() < 1e-10,
1021 "At t=1: {}",
1022 result[(0, 2)]
1023 );
1024 }
1025
1026 #[test]
1027 fn test_to_regular_grid_multiple_curves() {
1028 let ifd = make_ifd(
1029 vec![0, 5, 10],
1030 vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.0, 0.25, 0.5, 0.75, 1.0],
1031 vec![
1032 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0, 0.0, ],
1035 );
1036
1037 let grid = vec![0.0, 0.5, 1.0];
1038 let result = to_regular_grid(&ifd, &grid);
1039
1040 assert_eq!(result.nrows(), 2);
1042 assert_eq!(result.ncols(), 3);
1043
1044 assert!((result[(0, 1)] - 2.0).abs() < 1e-10);
1046 assert!((result[(1, 1)] - 2.0).abs() < 1e-10);
1048 }
1049
1050 #[test]
1051 fn test_to_regular_grid_boundary_nan() {
1052 let ifd = make_ifd(
1053 vec![0, 3],
1054 vec![0.2, 0.5, 0.8], vec![1.0, 2.0, 3.0],
1056 );
1057
1058 let grid = vec![0.0, 0.5, 1.0]; let result = to_regular_grid(&ifd, &grid);
1060
1061 assert!(result[(0, 0)].is_nan(), "t=0 should be NaN");
1063 assert!(
1065 (result[(0, 1)] - 2.0).abs() < 1e-10,
1066 "t=0.5: {}",
1067 result[(0, 1)]
1068 );
1069 assert!(result[(0, 2)].is_nan(), "t=1 should be NaN");
1071 }
1072
1073 #[test]
1074 fn test_empty_from_lists() {
1075 let ifd = IrregFdata::from_lists(&[], &[]);
1076 assert_eq!(ifd.n_obs(), 0);
1077 }
1078
1079 #[test]
1080 fn test_single_point_curve() {
1081 let argvals = vec![vec![0.5]];
1082 let values = vec![vec![1.0]];
1083 let ifd = IrregFdata::from_lists(&argvals, &values);
1084 assert_eq!(ifd.n_obs(), 1);
1085 assert_eq!(ifd.n_points(0), 1);
1086 let (a, v) = ifd.get_obs(0);
1087 assert_eq!(a, &[0.5]);
1088 assert_eq!(v, &[1.0]);
1089 }
1090
1091 #[test]
1092 fn test_nan_values_irreg() {
1093 let argvals = vec![vec![0.0, 0.5, 1.0]];
1094 let values = vec![vec![1.0, f64::NAN, 3.0]];
1095 let ifd = IrregFdata::from_lists(&argvals, &values);
1096 let integrals = integrate_irreg(&ifd);
1097 assert_eq!(integrals.len(), 1);
1098 assert!(integrals[0].is_nan());
1100 }
1101}