1use crate::matrix::FdMatrix;
24use crate::maybe_par_chunks_mut_enumerate;
25use rand::prelude::*;
26use rand_distr::Normal;
27use std::f64::consts::PI;
28
29#[derive(Clone, Copy, Debug, PartialEq)]
31#[non_exhaustive]
32pub enum EFunType {
33 Fourier = 0,
35 Poly = 1,
37 PolyHigh = 2,
39 Wiener = 3,
41}
42
43impl EFunType {
44 pub fn from_i32(value: i32) -> Result<Self, crate::FdarError> {
46 match value {
47 0 => Ok(EFunType::Fourier),
48 1 => Ok(EFunType::Poly),
49 2 => Ok(EFunType::PolyHigh),
50 3 => Ok(EFunType::Wiener),
51 _ => Err(crate::FdarError::InvalidEnumValue {
52 enum_name: "EFunType",
53 value,
54 }),
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq)]
61#[non_exhaustive]
62pub enum EValType {
63 Linear = 0,
65 Exponential = 1,
67 Wiener = 2,
69}
70
71impl EValType {
72 pub fn from_i32(value: i32) -> Result<Self, crate::FdarError> {
74 match value {
75 0 => Ok(EValType::Linear),
76 1 => Ok(EValType::Exponential),
77 2 => Ok(EValType::Wiener),
78 _ => Err(crate::FdarError::InvalidEnumValue {
79 enum_name: "EValType",
80 value,
81 }),
82 }
83 }
84}
85
86pub fn fourier_eigenfunctions(t: &[f64], m: usize) -> FdMatrix {
104 let n = t.len();
105 let mut phi = FdMatrix::zeros(n, m);
106 let sqrt2 = 2.0_f64.sqrt();
107
108 for (i, &ti) in t.iter().enumerate() {
109 phi[(i, 0)] = 1.0;
111
112 let mut k = 1; let mut freq = 1; while k < m {
116 if k < m {
118 phi[(i, k)] = sqrt2 * (2.0 * PI * f64::from(freq) * ti).sin();
119 k += 1;
120 }
121 if k < m {
123 phi[(i, k)] = sqrt2 * (2.0 * PI * f64::from(freq) * ti).cos();
124 k += 1;
125 }
126 freq += 1;
127 }
128 }
129 phi
130}
131
132pub fn legendre_eigenfunctions(t: &[f64], m: usize, high: bool) -> FdMatrix {
145 let n = t.len();
146 let mut phi = FdMatrix::zeros(n, m);
147 let start_deg = if high { 2 } else { 0 };
148
149 for (i, &ti) in t.iter().enumerate() {
150 let x = 2.0 * ti - 1.0;
152
153 for j in 0..m {
154 let deg = start_deg + j;
155 let p = legendre_p(x, deg);
157 let norm = ((2 * deg + 1) as f64).sqrt();
159 phi[(i, j)] = p * norm;
160 }
161 }
162 phi
163}
164
165fn legendre_p(x: f64, n: usize) -> f64 {
170 if n == 0 {
171 return 1.0;
172 }
173 if n == 1 {
174 return x;
175 }
176
177 let mut p_prev = 1.0;
178 let mut p_curr = x;
179
180 for k in 2..=n {
181 let p_next = ((2 * k - 1) as f64 * x * p_curr - (k - 1) as f64 * p_prev) / k as f64;
182 p_prev = p_curr;
183 p_curr = p_next;
184 }
185 p_curr
186}
187
188pub fn wiener_eigenfunctions(t: &[f64], m: usize) -> FdMatrix {
202 let n = t.len();
203 let mut phi = FdMatrix::zeros(n, m);
204 let sqrt2 = 2.0_f64.sqrt();
205
206 for (i, &ti) in t.iter().enumerate() {
207 for j in 0..m {
208 let k = (j + 1) as f64;
209 phi[(i, j)] = sqrt2 * ((k - 0.5) * PI * ti).sin();
211 }
212 }
213 phi
214}
215
216pub fn eigenfunctions(t: &[f64], m: usize, efun_type: EFunType) -> FdMatrix {
226 match efun_type {
227 EFunType::Fourier => fourier_eigenfunctions(t, m),
228 EFunType::Poly => legendre_eigenfunctions(t, m, false),
229 EFunType::PolyHigh => legendre_eigenfunctions(t, m, true),
230 EFunType::Wiener => wiener_eigenfunctions(t, m),
231 }
232}
233
234pub fn eigenvalues_linear(m: usize) -> Vec<f64> {
242 (1..=m).map(|k| 1.0 / k as f64).collect()
243}
244
245pub fn eigenvalues_exponential(m: usize) -> Vec<f64> {
249 (1..=m).map(|k| (-(k as f64)).exp()).collect()
250}
251
252pub fn eigenvalues_wiener(m: usize) -> Vec<f64> {
258 (1..=m)
259 .map(|k| {
260 let denom = (k as f64 - 0.5) * PI;
261 1.0 / (denom * denom)
262 })
263 .collect()
264}
265
266pub fn eigenvalues(m: usize, eval_type: EValType) -> Vec<f64> {
275 match eval_type {
276 EValType::Linear => eigenvalues_linear(m),
277 EValType::Exponential => eigenvalues_exponential(m),
278 EValType::Wiener => eigenvalues_wiener(m),
279 }
280}
281
282pub fn sim_kl(
302 n: usize,
303 phi: &FdMatrix,
304 big_m: usize,
305 lambda: &[f64],
306 seed: Option<u64>,
307) -> FdMatrix {
308 let m = phi.nrows();
309
310 let mut rng = match seed {
312 Some(s) => StdRng::seed_from_u64(s),
313 None => StdRng::from_entropy(),
314 };
315
316 let normal = Normal::new(0.0, 1.0).expect("valid distribution parameters");
317
318 let mut xi = vec![0.0; n * big_m];
321 for k in 0..big_m {
322 let sd = lambda[k].sqrt();
323 for i in 0..n {
324 xi[i + k * n] = rng.sample::<f64, _>(normal) * sd;
325 }
326 }
327
328 let mut data = vec![0.0; n * m];
331
332 maybe_par_chunks_mut_enumerate!(data, n, |(j, col)| {
334 for i in 0..n {
335 let mut sum = 0.0;
336 for k in 0..big_m {
337 sum += xi[i + k * n] * phi[(j, k)];
340 }
341 col[i] = sum;
342 }
343 });
344
345 FdMatrix::from_column_major(data, n, m).expect("dimension invariant: data.len() == n * m")
346}
347
348pub fn sim_fundata(
375 n: usize,
376 t: &[f64],
377 big_m: usize,
378 efun_type: EFunType,
379 eval_type: EValType,
380 seed: Option<u64>,
381) -> FdMatrix {
382 let phi = eigenfunctions(t, big_m, efun_type);
383 let lambda = eigenvalues(big_m, eval_type);
384 sim_kl(n, &phi, big_m, &lambda, seed)
385}
386
387pub fn add_error_pointwise(data: &FdMatrix, sd: f64, seed: Option<u64>) -> FdMatrix {
403 let mut rng = match seed {
404 Some(s) => StdRng::seed_from_u64(s),
405 None => StdRng::from_entropy(),
406 };
407
408 let normal = Normal::new(0.0, sd).expect("valid distribution parameters: sd > 0");
409
410 let noisy: Vec<f64> = data
411 .as_slice()
412 .iter()
413 .map(|&x| x + rng.sample::<f64, _>(normal))
414 .collect();
415
416 FdMatrix::from_column_major(noisy, data.nrows(), data.ncols())
417 .expect("dimension invariant: data.len() == n * m")
418}
419
420pub fn add_error_curve(data: &FdMatrix, sd: f64, seed: Option<u64>) -> FdMatrix {
433 let n = data.nrows();
434 let m = data.ncols();
435
436 let mut rng = match seed {
437 Some(s) => StdRng::seed_from_u64(s),
438 None => StdRng::from_entropy(),
439 };
440
441 let normal = Normal::new(0.0, sd).expect("valid distribution parameters: sd > 0");
442
443 let curve_noise: Vec<f64> = (0..n).map(|_| rng.sample::<f64, _>(normal)).collect();
445
446 let mut result = data.as_slice().to_vec();
448 for j in 0..m {
449 for i in 0..n {
450 result[i + j * n] += curve_noise[i];
451 }
452 }
453 FdMatrix::from_column_major(result, n, m).expect("dimension invariant: data.len() == n * m")
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_fourier_eigenfunctions_dimensions() {
462 let t: Vec<f64> = (0..100).map(|i| i as f64 / 99.0).collect();
463 let phi = fourier_eigenfunctions(&t, 5);
464 assert_eq!(phi.nrows(), 100);
465 assert_eq!(phi.ncols(), 5);
466 assert_eq!(phi.len(), 100 * 5);
467 }
468
469 #[test]
470 fn test_fourier_eigenfunctions_first_is_constant() {
471 let t: Vec<f64> = (0..100).map(|i| i as f64 / 99.0).collect();
472 let phi = fourier_eigenfunctions(&t, 3);
473
474 for i in 0..100 {
476 assert!((phi[(i, 0)] - 1.0).abs() < 1e-10);
477 }
478 }
479
480 #[test]
481 fn test_eigenvalues_linear() {
482 let lambda = eigenvalues_linear(5);
483 assert_eq!(lambda.len(), 5);
484 assert!((lambda[0] - 1.0).abs() < 1e-10);
485 assert!((lambda[1] - 0.5).abs() < 1e-10);
486 assert!((lambda[2] - 1.0 / 3.0).abs() < 1e-10);
487 }
488
489 #[test]
490 fn test_eigenvalues_exponential() {
491 let lambda = eigenvalues_exponential(3);
492 assert_eq!(lambda.len(), 3);
493 assert!((lambda[0] - (-1.0_f64).exp()).abs() < 1e-10);
494 assert!((lambda[1] - (-2.0_f64).exp()).abs() < 1e-10);
495 }
496
497 #[test]
498 fn test_sim_kl_dimensions() {
499 let t: Vec<f64> = (0..50).map(|i| i as f64 / 49.0).collect();
500 let phi = fourier_eigenfunctions(&t, 5);
501 let lambda = eigenvalues_linear(5);
502
503 let data = sim_kl(10, &phi, 5, &lambda, Some(42));
504 assert_eq!(data.nrows(), 10);
505 assert_eq!(data.ncols(), 50);
506 assert_eq!(data.len(), 10 * 50);
507 }
508
509 #[test]
510 fn test_sim_fundata_dimensions() {
511 let t: Vec<f64> = (0..100).map(|i| i as f64 / 99.0).collect();
512 let data = sim_fundata(20, &t, 5, EFunType::Fourier, EValType::Linear, Some(42));
513 assert_eq!(data.nrows(), 20);
514 assert_eq!(data.ncols(), 100);
515 assert_eq!(data.len(), 20 * 100);
516 }
517
518 #[test]
519 fn test_add_error_pointwise() {
520 let raw = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let data = FdMatrix::from_column_major(raw.clone(), 2, 3).unwrap();
522 let noisy = add_error_pointwise(&data, 0.1, Some(42));
523 assert_eq!(noisy.len(), 6);
524 let noisy_slice = noisy.as_slice();
526 for i in 0..6 {
527 assert!((noisy_slice[i] - raw[i]).abs() < 1.0);
528 }
529 }
530
531 #[test]
532 fn test_legendre_orthonormality() {
533 let n = 1000;
535 let t: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
536 let m = 5;
537 let phi = legendre_eigenfunctions(&t, m, false);
538 let dt = 1.0 / (n - 1) as f64;
539
540 for j1 in 0..m {
542 for j2 in 0..m {
543 let mut integral = 0.0;
544 for i in 0..n {
545 integral += phi[(i, j1)] * phi[(i, j2)] * dt;
546 }
547 let expected = if j1 == j2 { 1.0 } else { 0.0 };
548 assert!(
549 (integral - expected).abs() < 0.05,
550 "Orthonormality check failed for ({}, {}): {} vs {}",
551 j1,
552 j2,
553 integral,
554 expected
555 );
556 }
557 }
558 }
559
560 #[test]
565 fn test_wiener_eigenfunctions_dimensions() {
566 let t: Vec<f64> = (0..100).map(|i| i as f64 / 99.0).collect();
567 let phi = wiener_eigenfunctions(&t, 7);
568 assert_eq!(phi.nrows(), 100);
569 assert_eq!(phi.ncols(), 7);
570 assert_eq!(phi.len(), 100 * 7);
571 }
572
573 #[test]
574 fn test_wiener_eigenfunctions_orthonormality() {
575 let n = 1000;
578 let t: Vec<f64> = (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
579 let m = 5;
580 let phi = wiener_eigenfunctions(&t, m);
581 let dt = 1.0 / (n - 1) as f64;
582
583 for j1 in 0..m {
584 for j2 in 0..m {
585 let mut integral = 0.0;
586 for i in 0..n {
587 integral += phi[(i, j1)] * phi[(i, j2)] * dt;
588 }
589 let expected = if j1 == j2 { 1.0 } else { 0.0 };
590 assert!(
591 (integral - expected).abs() < 0.05,
592 "Wiener orthonormality failed for ({}, {}): {} vs {}",
593 j1,
594 j2,
595 integral,
596 expected
597 );
598 }
599 }
600 }
601
602 #[test]
603 fn test_wiener_eigenfunctions_analytical_form() {
604 let t = vec![0.0, 0.25, 0.5, 0.75, 1.0];
606 let phi = wiener_eigenfunctions(&t, 2);
607 let sqrt2 = 2.0_f64.sqrt();
608
609 for (i, &ti) in t.iter().enumerate() {
611 let expected = sqrt2 * (0.5 * PI * ti).sin();
612 assert!(
613 (phi[(i, 0)] - expected).abs() < 1e-10,
614 "k=1 at t={}: got {} expected {}",
615 ti,
616 phi[(i, 0)],
617 expected
618 );
619 }
620
621 for (i, &ti) in t.iter().enumerate() {
623 let expected = sqrt2 * (1.5 * PI * ti).sin();
624 assert!(
625 (phi[(i, 1)] - expected).abs() < 1e-10,
626 "k=2 at t={}: got {} expected {}",
627 ti,
628 phi[(i, 1)],
629 expected
630 );
631 }
632 }
633
634 #[test]
639 fn test_eigenvalues_wiener_decay_rate() {
640 let lambda = eigenvalues_wiener(5);
642 assert_eq!(lambda.len(), 5);
643
644 for k in 1..=5 {
645 let denom = (k as f64 - 0.5) * PI;
646 let expected = 1.0 / (denom * denom);
647 assert!(
648 (lambda[k - 1] - expected).abs() < 1e-12,
649 "Wiener eigenvalue k={}: got {} expected {}",
650 k,
651 lambda[k - 1],
652 expected
653 );
654 }
655 }
656
657 #[test]
658 fn test_eigenvalues_wiener_decreasing() {
659 let lambda = eigenvalues_wiener(10);
661
662 for i in 1..lambda.len() {
663 assert!(
664 lambda[i] < lambda[i - 1],
665 "Eigenvalues not decreasing at {}: {} >= {}",
666 i,
667 lambda[i],
668 lambda[i - 1]
669 );
670 }
671 }
672
673 #[test]
678 fn test_add_error_curve_properties() {
679 let raw = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let n = 2;
682 let data = FdMatrix::from_column_major(raw.clone(), n, 3).unwrap();
683 let noisy = add_error_curve(&data, 0.5, Some(42));
684
685 assert_eq!(noisy.len(), 6);
686
687 let diff0_j0 = noisy[(0, 0)] - raw[0]; let diff0_j1 = noisy[(0, 1)] - raw[n]; let diff0_j2 = noisy[(0, 2)] - raw[2 * n]; assert!(
694 (diff0_j0 - diff0_j1).abs() < 1e-10,
695 "Curve 0 noise differs: {} vs {}",
696 diff0_j0,
697 diff0_j1
698 );
699 assert!(
700 (diff0_j0 - diff0_j2).abs() < 1e-10,
701 "Curve 0 noise differs: {} vs {}",
702 diff0_j0,
703 diff0_j2
704 );
705
706 let diff1_j0 = noisy[(1, 0)] - raw[1];
708 assert!(
711 (diff0_j0 - diff1_j0).abs() > 1e-10,
712 "Different curves got same noise"
713 );
714 }
715
716 #[test]
717 fn test_add_error_curve_reproducibility() {
718 let raw = vec![1.0, 2.0, 3.0, 4.0];
719 let data = FdMatrix::from_column_major(raw, 2, 2).unwrap();
720 let noisy1 = add_error_curve(&data, 1.0, Some(123));
721 let noisy2 = add_error_curve(&data, 1.0, Some(123));
722
723 let s1 = noisy1.as_slice();
724 let s2 = noisy2.as_slice();
725 for i in 0..4 {
726 assert!(
727 (s1[i] - s2[i]).abs() < 1e-10,
728 "Reproducibility failed at {}: {} vs {}",
729 i,
730 s1[i],
731 s2[i]
732 );
733 }
734 }
735
736 #[test]
741 fn test_efun_type_from_i32() {
742 assert_eq!(EFunType::from_i32(0), Ok(EFunType::Fourier));
743 assert_eq!(EFunType::from_i32(1), Ok(EFunType::Poly));
744 assert_eq!(EFunType::from_i32(2), Ok(EFunType::PolyHigh));
745 assert_eq!(EFunType::from_i32(3), Ok(EFunType::Wiener));
746 assert!(EFunType::from_i32(-1).is_err());
747 assert!(EFunType::from_i32(4).is_err());
748 assert!(EFunType::from_i32(100).is_err());
749 }
750
751 #[test]
752 fn test_eval_type_from_i32() {
753 assert_eq!(EValType::from_i32(0), Ok(EValType::Linear));
754 assert_eq!(EValType::from_i32(1), Ok(EValType::Exponential));
755 assert_eq!(EValType::from_i32(2), Ok(EValType::Wiener));
756 assert!(EValType::from_i32(-1).is_err());
757 assert!(EValType::from_i32(3).is_err());
758 assert!(EValType::from_i32(99).is_err());
759 }
760
761 #[test]
762 fn test_eigenfunctions_dispatcher() {
763 let t: Vec<f64> = (0..50).map(|i| i as f64 / 49.0).collect();
764 let m = 4;
765
766 let phi_fourier = eigenfunctions(&t, m, EFunType::Fourier);
768 let phi_fourier_direct = fourier_eigenfunctions(&t, m);
769 assert_eq!(phi_fourier, phi_fourier_direct);
770
771 let phi_poly = eigenfunctions(&t, m, EFunType::Poly);
772 let phi_poly_direct = legendre_eigenfunctions(&t, m, false);
773 assert_eq!(phi_poly, phi_poly_direct);
774
775 let phi_poly_high = eigenfunctions(&t, m, EFunType::PolyHigh);
776 let phi_poly_high_direct = legendre_eigenfunctions(&t, m, true);
777 assert_eq!(phi_poly_high, phi_poly_high_direct);
778
779 let phi_wiener = eigenfunctions(&t, m, EFunType::Wiener);
780 let phi_wiener_direct = wiener_eigenfunctions(&t, m);
781 assert_eq!(phi_wiener, phi_wiener_direct);
782 }
783
784 #[test]
785 fn test_sigma_zero_error() {
786 let t: Vec<f64> = (0..20).map(|i| i as f64 / 19.0).collect();
787 let data = sim_fundata(5, &t, 3, EFunType::Fourier, EValType::Exponential, Some(42));
788 let noisy = add_error_pointwise(&data, 0.0, Some(42));
789 for i in 0..5 {
791 for j in 0..20 {
792 assert!(
793 (noisy[(i, j)] - data[(i, j)]).abs() < 1e-12,
794 "Zero-sigma error should not change data"
795 );
796 }
797 }
798 }
799
800 #[test]
801 fn test_ncomp1_eigenfunctions() {
802 let t: Vec<f64> = (0..50).map(|i| i as f64 / 49.0).collect();
803 let phi = fourier_eigenfunctions(&t, 1);
804 assert_eq!(phi.nrows(), t.len());
805 assert_eq!(phi.ncols(), 1);
806 let first_val = phi[(0, 0)];
808 for i in 1..t.len() {
809 assert!((phi[(i, 0)] - first_val).abs() < 1e-10);
810 }
811 }
812
813 #[test]
814 fn test_deterministic_seed() {
815 let t: Vec<f64> = (0..30).map(|i| i as f64 / 29.0).collect();
816 let d1 = sim_fundata(10, &t, 3, EFunType::Fourier, EValType::Linear, Some(123));
817 let d2 = sim_fundata(10, &t, 3, EFunType::Fourier, EValType::Linear, Some(123));
818 for i in 0..10 {
819 for j in 0..30 {
820 assert!(
821 (d1[(i, j)] - d2[(i, j)]).abs() < 1e-12,
822 "Same seed should produce identical results"
823 );
824 }
825 }
826 }
827}