1use crate::helpers::{simpsons_weights, simpsons_weights_2d, NUMERICAL_EPS};
4use crate::iter_maybe_parallel;
5use crate::matrix::FdMatrix;
6#[cfg(feature = "parallel")]
7use rayon::iter::ParallelIterator;
8
9fn finite_diff_1d(
14 values: impl Fn(usize) -> f64,
15 idx: usize,
16 n_points: usize,
17 step_sizes: &[f64],
18) -> f64 {
19 if idx == 0 {
20 (values(1) - values(0)) / step_sizes[0]
21 } else if idx == n_points - 1 {
22 (values(n_points - 1) - values(n_points - 2)) / step_sizes[n_points - 1]
23 } else {
24 (values(idx + 1) - values(idx - 1)) / step_sizes[idx]
25 }
26}
27
28fn compute_2d_derivatives(
32 get_val: impl Fn(usize, usize) -> f64,
33 si: usize,
34 ti: usize,
35 m1: usize,
36 m2: usize,
37 hs: &[f64],
38 ht: &[f64],
39) -> (f64, f64, f64) {
40 let ds = finite_diff_1d(|s| get_val(s, ti), si, m1, hs);
42
43 let dt = finite_diff_1d(|t| get_val(si, t), ti, m2, ht);
45
46 let denom = hs[si] * ht[ti];
48
49 let (s_lo, s_hi) = if si == 0 {
51 (0, 1)
52 } else if si == m1 - 1 {
53 (m1 - 2, m1 - 1)
54 } else {
55 (si - 1, si + 1)
56 };
57
58 let (t_lo, t_hi) = if ti == 0 {
59 (0, 1)
60 } else if ti == m2 - 1 {
61 (m2 - 2, m2 - 1)
62 } else {
63 (ti - 1, ti + 1)
64 };
65
66 let dsdt = (get_val(s_hi, t_hi) - get_val(s_lo, t_hi) - get_val(s_hi, t_lo)
67 + get_val(s_lo, t_lo))
68 / denom;
69
70 (ds, dt, dsdt)
71}
72
73fn weiszfeld_iteration(data: &FdMatrix, weights: &[f64], max_iter: usize, tol: f64) -> Vec<f64> {
77 let (n, m) = data.shape();
78
79 let mut median: Vec<f64> = (0..m)
81 .map(|j| {
82 let col = data.column(j);
83 col.iter().sum::<f64>() / n as f64
84 })
85 .collect();
86
87 for _ in 0..max_iter {
88 let distances: Vec<f64> = (0..n)
90 .map(|i| {
91 let mut dist_sq = 0.0;
92 for j in 0..m {
93 let diff = data[(i, j)] - median[j];
94 dist_sq += diff * diff * weights[j];
95 }
96 dist_sq.sqrt()
97 })
98 .collect();
99
100 let inv_distances: Vec<f64> = distances
102 .iter()
103 .map(|d| {
104 if *d > NUMERICAL_EPS {
105 1.0 / d
106 } else {
107 1.0 / NUMERICAL_EPS
108 }
109 })
110 .collect();
111
112 let sum_inv_dist: f64 = inv_distances.iter().sum();
113
114 let new_median: Vec<f64> = (0..m)
116 .map(|j| {
117 let mut weighted_sum = 0.0;
118 for i in 0..n {
119 weighted_sum += data[(i, j)] * inv_distances[i];
120 }
121 weighted_sum / sum_inv_dist
122 })
123 .collect();
124
125 let diff: f64 = median
127 .iter()
128 .zip(new_median.iter())
129 .map(|(a, b)| (a - b).abs())
130 .sum::<f64>()
131 / m as f64;
132
133 median = new_median;
134
135 if diff < tol {
136 break;
137 }
138 }
139
140 median
141}
142
143pub fn mean_1d(data: &FdMatrix) -> Vec<f64> {
151 let (n, m) = data.shape();
152 if n == 0 || m == 0 {
153 return Vec::new();
154 }
155
156 iter_maybe_parallel!(0..m)
157 .map(|j| {
158 let col = data.column(j);
159 col.iter().sum::<f64>() / n as f64
160 })
161 .collect()
162}
163
164pub fn mean_2d(data: &FdMatrix) -> Vec<f64> {
168 mean_1d(data)
170}
171
172pub fn center_1d(data: &FdMatrix) -> FdMatrix {
180 let (n, m) = data.shape();
181 if n == 0 || m == 0 {
182 return FdMatrix::zeros(0, 0);
183 }
184
185 let means: Vec<f64> = iter_maybe_parallel!(0..m)
187 .map(|j| {
188 let col = data.column(j);
189 col.iter().sum::<f64>() / n as f64
190 })
191 .collect();
192
193 let mut centered = FdMatrix::zeros(n, m);
195 for j in 0..m {
196 let col = centered.column_mut(j);
197 let src = data.column(j);
198 for i in 0..n {
199 col[i] = src[i] - means[j];
200 }
201 }
202
203 centered
204}
205
206pub fn norm_lp_1d(data: &FdMatrix, argvals: &[f64], p: f64) -> Vec<f64> {
216 let (n, m) = data.shape();
217 if n == 0 || m == 0 || argvals.len() != m {
218 return Vec::new();
219 }
220
221 let weights = simpsons_weights(argvals);
222
223 if (p - 2.0).abs() < 1e-14 {
224 iter_maybe_parallel!(0..n)
225 .map(|i| {
226 let mut integral = 0.0;
227 for j in 0..m {
228 let v = data[(i, j)];
229 integral += v * v * weights[j];
230 }
231 integral.sqrt()
232 })
233 .collect()
234 } else if (p - 1.0).abs() < 1e-14 {
235 iter_maybe_parallel!(0..n)
236 .map(|i| {
237 let mut integral = 0.0;
238 for j in 0..m {
239 integral += data[(i, j)].abs() * weights[j];
240 }
241 integral
242 })
243 .collect()
244 } else {
245 iter_maybe_parallel!(0..n)
246 .map(|i| {
247 let mut integral = 0.0;
248 for j in 0..m {
249 integral += data[(i, j)].abs().powf(p) * weights[j];
250 }
251 integral.powf(1.0 / p)
252 })
253 .collect()
254 }
255}
256
257fn deriv_1d_step(
268 current: &FdMatrix,
269 n: usize,
270 m: usize,
271 h0: f64,
272 hn: f64,
273 h_central: &[f64],
274) -> FdMatrix {
275 let mut next = FdMatrix::zeros(n, m);
276 let src_col0 = current.column(0);
278 let src_col1 = current.column(1);
279 let dst = next.column_mut(0);
280 for i in 0..n {
281 dst[i] = (src_col1[i] - src_col0[i]) / h0;
282 }
283 for j in 1..(m - 1) {
285 let src_prev = current.column(j - 1);
286 let src_next = current.column(j + 1);
287 let dst = next.column_mut(j);
288 let h = h_central[j - 1];
289 for i in 0..n {
290 dst[i] = (src_next[i] - src_prev[i]) / h;
291 }
292 }
293 let src_colm2 = current.column(m - 2);
295 let src_colm1 = current.column(m - 1);
296 let dst = next.column_mut(m - 1);
297 for i in 0..n {
298 dst[i] = (src_colm1[i] - src_colm2[i]) / hn;
299 }
300 next
301}
302
303pub fn deriv_1d(data: &FdMatrix, argvals: &[f64], nderiv: usize) -> FdMatrix {
304 let (n, m) = data.shape();
305 if n == 0 || m < 2 || argvals.len() != m {
306 return FdMatrix::zeros(n, m);
307 }
308 if nderiv == 0 {
309 return data.clone();
310 }
311
312 let mut current = data.clone();
313
314 let h0 = argvals[1] - argvals[0];
316 let hn = argvals[m - 1] - argvals[m - 2];
317 let h_central: Vec<f64> = (1..(m - 1))
318 .map(|j| argvals[j + 1] - argvals[j - 1])
319 .collect();
320
321 for _ in 0..nderiv {
322 current = deriv_1d_step(¤t, n, m, h0, hn, &h_central);
323 }
324
325 current
326}
327
328pub struct Deriv2DResult {
330 pub ds: FdMatrix,
332 pub dt: FdMatrix,
334 pub dsdt: FdMatrix,
336}
337
338fn compute_step_sizes(argvals: &[f64]) -> Vec<f64> {
342 let m = argvals.len();
343 if m < 2 {
344 return vec![1.0; m];
345 }
346 (0..m)
347 .map(|j| {
348 if j == 0 {
349 argvals[1] - argvals[0]
350 } else if j == m - 1 {
351 argvals[m - 1] - argvals[m - 2]
352 } else {
353 argvals[j + 1] - argvals[j - 1]
354 }
355 })
356 .collect()
357}
358
359fn reassemble_colmajor(rows: &[Vec<f64>], n: usize, ncol: usize) -> FdMatrix {
361 let mut mat = FdMatrix::zeros(n, ncol);
362 for i in 0..n {
363 for j in 0..ncol {
364 mat[(i, j)] = rows[i][j];
365 }
366 }
367 mat
368}
369
370pub fn deriv_2d(
384 data: &FdMatrix,
385 argvals_s: &[f64],
386 argvals_t: &[f64],
387 m1: usize,
388 m2: usize,
389) -> Option<Deriv2DResult> {
390 let n = data.nrows();
391 let ncol = m1 * m2;
392 if n == 0
393 || ncol == 0
394 || m1 < 2
395 || m2 < 2
396 || data.ncols() != ncol
397 || argvals_s.len() != m1
398 || argvals_t.len() != m2
399 {
400 return None;
401 }
402
403 let hs = compute_step_sizes(argvals_s);
404 let ht = compute_step_sizes(argvals_t);
405
406 let results: Vec<(Vec<f64>, Vec<f64>, Vec<f64>)> = iter_maybe_parallel!(0..n)
408 .map(|i| {
409 let mut ds = vec![0.0; ncol];
410 let mut dt = vec![0.0; ncol];
411 let mut dsdt = vec![0.0; ncol];
412
413 let get_val = |si: usize, ti: usize| -> f64 { data[(i, si + ti * m1)] };
414
415 for ti in 0..m2 {
416 for si in 0..m1 {
417 let idx = si + ti * m1;
418 let (ds_val, dt_val, dsdt_val) =
419 compute_2d_derivatives(get_val, si, ti, m1, m2, &hs, &ht);
420 ds[idx] = ds_val;
421 dt[idx] = dt_val;
422 dsdt[idx] = dsdt_val;
423 }
424 }
425
426 (ds, dt, dsdt)
427 })
428 .collect();
429
430 let (ds_vecs, (dt_vecs, dsdt_vecs)): (Vec<Vec<f64>>, (Vec<Vec<f64>>, Vec<Vec<f64>>)) =
431 results.into_iter().map(|(a, b, c)| (a, (b, c))).unzip();
432
433 Some(Deriv2DResult {
434 ds: reassemble_colmajor(&ds_vecs, n, ncol),
435 dt: reassemble_colmajor(&dt_vecs, n, ncol),
436 dsdt: reassemble_colmajor(&dsdt_vecs, n, ncol),
437 })
438}
439
440pub fn geometric_median_1d(
450 data: &FdMatrix,
451 argvals: &[f64],
452 max_iter: usize,
453 tol: f64,
454) -> Vec<f64> {
455 let (n, m) = data.shape();
456 if n == 0 || m == 0 || argvals.len() != m {
457 return Vec::new();
458 }
459
460 let weights = simpsons_weights(argvals);
461 weiszfeld_iteration(data, &weights, max_iter, tol)
462}
463
464pub fn geometric_median_2d(
475 data: &FdMatrix,
476 argvals_s: &[f64],
477 argvals_t: &[f64],
478 max_iter: usize,
479 tol: f64,
480) -> Vec<f64> {
481 let (n, m) = data.shape();
482 let expected_cols = argvals_s.len() * argvals_t.len();
483 if n == 0 || m == 0 || m != expected_cols {
484 return Vec::new();
485 }
486
487 let weights = simpsons_weights_2d(argvals_s, argvals_t);
488 weiszfeld_iteration(data, &weights, max_iter, tol)
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use std::f64::consts::PI;
495
496 fn uniform_grid(n: usize) -> Vec<f64> {
497 (0..n).map(|i| i as f64 / (n - 1) as f64).collect()
498 }
499
500 #[test]
503 fn test_mean_1d() {
504 let data = vec![1.0, 3.0, 2.0, 4.0, 3.0, 5.0]; let mat = FdMatrix::from_column_major(data, 2, 3).unwrap();
510 let mean = mean_1d(&mat);
511 assert_eq!(mean, vec![2.0, 3.0, 4.0]);
512 }
513
514 #[test]
515 fn test_mean_1d_single_sample() {
516 let data = vec![1.0, 2.0, 3.0];
517 let mat = FdMatrix::from_column_major(data, 1, 3).unwrap();
518 let mean = mean_1d(&mat);
519 assert_eq!(mean, vec![1.0, 2.0, 3.0]);
520 }
521
522 #[test]
523 fn test_mean_1d_invalid() {
524 assert!(mean_1d(&FdMatrix::zeros(0, 0)).is_empty());
525 }
526
527 #[test]
528 fn test_mean_2d_delegates() {
529 let data = vec![1.0, 3.0, 2.0, 4.0];
530 let mat = FdMatrix::from_column_major(data, 2, 2).unwrap();
531 let mean1d = mean_1d(&mat);
532 let mean2d = mean_2d(&mat);
533 assert_eq!(mean1d, mean2d);
534 }
535
536 #[test]
539 fn test_center_1d() {
540 let data = vec![1.0, 3.0, 2.0, 4.0, 3.0, 5.0]; let mat = FdMatrix::from_column_major(data, 2, 3).unwrap();
542 let centered = center_1d(&mat);
543 assert_eq!(centered.as_slice(), &[-1.0, 1.0, -1.0, 1.0, -1.0, 1.0]);
545 }
546
547 #[test]
548 fn test_center_1d_mean_zero() {
549 let data = vec![1.0, 3.0, 2.0, 4.0, 3.0, 5.0];
550 let mat = FdMatrix::from_column_major(data, 2, 3).unwrap();
551 let centered = center_1d(&mat);
552 let centered_mean = mean_1d(¢ered);
553 for m in centered_mean {
554 assert!(m.abs() < 1e-10, "Centered data should have zero mean");
555 }
556 }
557
558 #[test]
559 fn test_center_1d_invalid() {
560 let centered = center_1d(&FdMatrix::zeros(0, 0));
561 assert!(centered.is_empty());
562 }
563
564 #[test]
567 fn test_norm_lp_1d_constant() {
568 let argvals = uniform_grid(21);
570 let data: Vec<f64> = vec![2.0; 21];
571 let mat = FdMatrix::from_column_major(data, 1, 21).unwrap();
572 let norms = norm_lp_1d(&mat, &argvals, 2.0);
573 assert_eq!(norms.len(), 1);
574 assert!(
575 (norms[0] - 2.0).abs() < 0.1,
576 "L2 norm of constant 2 should be 2"
577 );
578 }
579
580 #[test]
581 fn test_norm_lp_1d_sine() {
582 let argvals = uniform_grid(101);
584 let data: Vec<f64> = argvals.iter().map(|&x| (PI * x).sin()).collect();
585 let mat = FdMatrix::from_column_major(data, 1, 101).unwrap();
586 let norms = norm_lp_1d(&mat, &argvals, 2.0);
587 let expected = 0.5_f64.sqrt();
588 assert!(
589 (norms[0] - expected).abs() < 0.05,
590 "Expected {}, got {}",
591 expected,
592 norms[0]
593 );
594 }
595
596 #[test]
597 fn test_norm_lp_1d_invalid() {
598 assert!(norm_lp_1d(&FdMatrix::zeros(0, 0), &[], 2.0).is_empty());
599 }
600
601 #[test]
604 fn test_deriv_1d_linear() {
605 let argvals = uniform_grid(21);
607 let data = argvals.clone();
608 let mat = FdMatrix::from_column_major(data, 1, 21).unwrap();
609 let deriv = deriv_1d(&mat, &argvals, 1);
610 for j in 2..19 {
612 assert!(
613 (deriv[(0, j)] - 1.0).abs() < 0.1,
614 "Derivative of x should be 1"
615 );
616 }
617 }
618
619 #[test]
620 fn test_deriv_1d_quadratic() {
621 let argvals = uniform_grid(51);
623 let data: Vec<f64> = argvals.iter().map(|&x| x * x).collect();
624 let mat = FdMatrix::from_column_major(data, 1, 51).unwrap();
625 let deriv = deriv_1d(&mat, &argvals, 1);
626 for j in 5..45 {
628 let expected = 2.0 * argvals[j];
629 assert!(
630 (deriv[(0, j)] - expected).abs() < 0.1,
631 "Derivative of x^2 should be 2x"
632 );
633 }
634 }
635
636 #[test]
637 fn test_deriv_1d_invalid() {
638 let result = deriv_1d(&FdMatrix::zeros(0, 0), &[], 1);
639 assert!(result.is_empty() || result.as_slice().iter().all(|&x| x == 0.0));
640 }
641
642 #[test]
645 fn test_geometric_median_identical_curves() {
646 let argvals = uniform_grid(21);
648 let n = 5;
649 let m = 21;
650 let mut data = vec![0.0; n * m];
651 for i in 0..n {
652 for j in 0..m {
653 data[i + j * n] = (2.0 * PI * argvals[j]).sin();
654 }
655 }
656 let mat = FdMatrix::from_column_major(data, n, m).unwrap();
657 let median = geometric_median_1d(&mat, &argvals, 100, 1e-6);
658 for j in 0..m {
659 let expected = (2.0 * PI * argvals[j]).sin();
660 assert!(
661 (median[j] - expected).abs() < 0.01,
662 "Median should equal all curves"
663 );
664 }
665 }
666
667 #[test]
668 fn test_geometric_median_converges() {
669 let argvals = uniform_grid(21);
670 let n = 10;
671 let m = 21;
672 let mut data = vec![0.0; n * m];
673 for i in 0..n {
674 for j in 0..m {
675 data[i + j * n] = (i as f64 / n as f64) * argvals[j];
676 }
677 }
678 let mat = FdMatrix::from_column_major(data, n, m).unwrap();
679 let median = geometric_median_1d(&mat, &argvals, 100, 1e-6);
680 assert_eq!(median.len(), m);
681 assert!(median.iter().all(|&x| x.is_finite()));
682 }
683
684 #[test]
685 fn test_geometric_median_invalid() {
686 assert!(geometric_median_1d(&FdMatrix::zeros(0, 0), &[], 100, 1e-6).is_empty());
687 }
688
689 #[test]
692 fn test_deriv_2d_linear_surface() {
693 let m1 = 11;
696 let m2 = 11;
697 let argvals_s: Vec<f64> = (0..m1).map(|i| i as f64 / (m1 - 1) as f64).collect();
698 let argvals_t: Vec<f64> = (0..m2).map(|i| i as f64 / (m2 - 1) as f64).collect();
699
700 let n = 1; let ncol = m1 * m2;
702 let mut data = vec![0.0; n * ncol];
703
704 for si in 0..m1 {
705 for ti in 0..m2 {
706 let s = argvals_s[si];
707 let t = argvals_t[ti];
708 let idx = si + ti * m1;
709 data[idx] = 2.0 * s + 3.0 * t;
710 }
711 }
712
713 let mat = FdMatrix::from_column_major(data, n, ncol).unwrap();
714 let result = deriv_2d(&mat, &argvals_s, &argvals_t, m1, m2).unwrap();
715
716 for si in 2..(m1 - 2) {
718 for ti in 2..(m2 - 2) {
719 let idx = si + ti * m1;
720 assert!(
721 (result.ds[(0, idx)] - 2.0).abs() < 0.2,
722 "∂f/∂s at ({}, {}) = {}, expected 2",
723 si,
724 ti,
725 result.ds[(0, idx)]
726 );
727 }
728 }
729
730 for si in 2..(m1 - 2) {
732 for ti in 2..(m2 - 2) {
733 let idx = si + ti * m1;
734 assert!(
735 (result.dt[(0, idx)] - 3.0).abs() < 0.2,
736 "∂f/∂t at ({}, {}) = {}, expected 3",
737 si,
738 ti,
739 result.dt[(0, idx)]
740 );
741 }
742 }
743
744 for si in 2..(m1 - 2) {
746 for ti in 2..(m2 - 2) {
747 let idx = si + ti * m1;
748 assert!(
749 result.dsdt[(0, idx)].abs() < 0.5,
750 "∂²f/∂s∂t at ({}, {}) = {}, expected 0",
751 si,
752 ti,
753 result.dsdt[(0, idx)]
754 );
755 }
756 }
757 }
758
759 #[test]
760 fn test_deriv_2d_quadratic_surface() {
761 let m1 = 21;
764 let m2 = 21;
765 let argvals_s: Vec<f64> = (0..m1).map(|i| i as f64 / (m1 - 1) as f64).collect();
766 let argvals_t: Vec<f64> = (0..m2).map(|i| i as f64 / (m2 - 1) as f64).collect();
767
768 let n = 1;
769 let ncol = m1 * m2;
770 let mut data = vec![0.0; n * ncol];
771
772 for si in 0..m1 {
773 for ti in 0..m2 {
774 let s = argvals_s[si];
775 let t = argvals_t[ti];
776 let idx = si + ti * m1;
777 data[idx] = s * t;
778 }
779 }
780
781 let mat = FdMatrix::from_column_major(data, n, ncol).unwrap();
782 let result = deriv_2d(&mat, &argvals_s, &argvals_t, m1, m2).unwrap();
783
784 for si in 3..(m1 - 3) {
786 for ti in 3..(m2 - 3) {
787 let idx = si + ti * m1;
788 let expected = argvals_t[ti];
789 assert!(
790 (result.ds[(0, idx)] - expected).abs() < 0.1,
791 "∂f/∂s at ({}, {}) = {}, expected {}",
792 si,
793 ti,
794 result.ds[(0, idx)],
795 expected
796 );
797 }
798 }
799
800 for si in 3..(m1 - 3) {
802 for ti in 3..(m2 - 3) {
803 let idx = si + ti * m1;
804 let expected = argvals_s[si];
805 assert!(
806 (result.dt[(0, idx)] - expected).abs() < 0.1,
807 "∂f/∂t at ({}, {}) = {}, expected {}",
808 si,
809 ti,
810 result.dt[(0, idx)],
811 expected
812 );
813 }
814 }
815
816 for si in 3..(m1 - 3) {
818 for ti in 3..(m2 - 3) {
819 let idx = si + ti * m1;
820 assert!(
821 (result.dsdt[(0, idx)] - 1.0).abs() < 0.3,
822 "∂²f/∂s∂t at ({}, {}) = {}, expected 1",
823 si,
824 ti,
825 result.dsdt[(0, idx)]
826 );
827 }
828 }
829 }
830
831 #[test]
832 fn test_deriv_2d_invalid_input() {
833 let result = deriv_2d(&FdMatrix::zeros(0, 0), &[], &[], 0, 0);
835 assert!(result.is_none());
836
837 let mat = FdMatrix::from_column_major(vec![1.0; 4], 1, 4).unwrap();
839 let argvals = vec![0.0, 1.0];
840 let result = deriv_2d(&mat, &argvals, &[0.0, 0.5, 1.0], 2, 2);
841 assert!(result.is_none());
842 }
843
844 #[test]
847 fn test_geometric_median_2d_basic() {
848 let m1 = 5;
850 let m2 = 5;
851 let m = m1 * m2;
852 let n = 3;
853 let argvals_s: Vec<f64> = (0..m1).map(|i| i as f64 / (m1 - 1) as f64).collect();
854 let argvals_t: Vec<f64> = (0..m2).map(|i| i as f64 / (m2 - 1) as f64).collect();
855
856 let mut data = vec![0.0; n * m];
857
858 for i in 0..n {
860 for si in 0..m1 {
861 for ti in 0..m2 {
862 let idx = si + ti * m1;
863 let s = argvals_s[si];
864 let t = argvals_t[ti];
865 data[i + idx * n] = s + t;
866 }
867 }
868 }
869
870 let mat = FdMatrix::from_column_major(data, n, m).unwrap();
871 let median = geometric_median_2d(&mat, &argvals_s, &argvals_t, 100, 1e-6);
872 assert_eq!(median.len(), m);
873
874 for si in 0..m1 {
876 for ti in 0..m2 {
877 let idx = si + ti * m1;
878 let expected = argvals_s[si] + argvals_t[ti];
879 assert!(
880 (median[idx] - expected).abs() < 0.01,
881 "Median at ({}, {}) = {}, expected {}",
882 si,
883 ti,
884 median[idx],
885 expected
886 );
887 }
888 }
889 }
890
891 #[test]
892 fn test_nan_mean_no_panic() {
893 let mut data_vec = vec![1.0; 6];
894 data_vec[2] = f64::NAN;
895 let data = FdMatrix::from_column_major(data_vec, 2, 3).unwrap();
896 let m = mean_1d(&data);
897 assert_eq!(m.len(), 3);
898 }
899
900 #[test]
901 fn test_nan_center_no_panic() {
902 let mut data_vec = vec![1.0; 6];
903 data_vec[2] = f64::NAN;
904 let data = FdMatrix::from_column_major(data_vec, 2, 3).unwrap();
905 let c = center_1d(&data);
906 assert_eq!(c.nrows(), 2);
907 }
908
909 #[test]
910 fn test_nan_norm_no_panic() {
911 let mut data_vec = vec![1.0; 6];
912 data_vec[2] = f64::NAN;
913 let data = FdMatrix::from_column_major(data_vec, 2, 3).unwrap();
914 let argvals = vec![0.0, 0.5, 1.0];
915 let norms = norm_lp_1d(&data, &argvals, 2.0);
916 assert_eq!(norms.len(), 2);
917 }
918
919 #[test]
920 fn test_n1_norm() {
921 let data = FdMatrix::from_column_major(vec![0.0, 1.0, 0.0], 1, 3).unwrap();
922 let argvals = vec![0.0, 0.5, 1.0];
923 let norms = norm_lp_1d(&data, &argvals, 2.0);
924 assert_eq!(norms.len(), 1);
925 assert!(norms[0] > 0.0);
926 }
927
928 #[test]
929 fn test_n2_center() {
930 let data = FdMatrix::from_column_major(vec![1.0, 3.0, 2.0, 4.0], 2, 2).unwrap();
931 let centered = center_1d(&data);
932 assert!((centered[(0, 0)] - (-1.0)).abs() < 1e-12);
935 assert!((centered[(1, 0)] - 1.0).abs() < 1e-12);
936 }
937
938 #[test]
939 fn test_deriv_nderiv0() {
940 let data = FdMatrix::from_column_major(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3).unwrap();
942 let argvals = vec![0.0, 0.5, 1.0];
943 let result = deriv_1d(&data, &argvals, 0);
944 assert_eq!(result.shape(), data.shape());
945 for i in 0..2 {
946 for j in 0..3 {
947 assert!((result[(i, j)] - data[(i, j)]).abs() < 1e-12);
948 }
949 }
950 }
951}