1use crate::error::{StatsError, StatsResult};
7use scirs2_core::ndarray::{Array1, ArrayView1, ArrayView2};
8use scirs2_core::numeric::{Float, NumCast, One, Zero};
9use scirs2_core::random::Rng;
10use scirs2_core::{simd_ops::SimdUnifiedOps, validation::*};
11use statrs::statistics::Statistics;
12
13#[allow(dead_code)]
18pub fn rolling_statistics_simd<F>(
19 data: &ArrayView1<F>,
20 windowsize: usize,
21 statistics: &[RollingStatistic],
22) -> StatsResult<RollingStatsResult<F>>
23where
24 F: Float
25 + NumCast
26 + SimdUnifiedOps
27 + Zero
28 + One
29 + PartialOrd
30 + Copy
31 + Send
32 + Sync
33 + std::fmt::Display
34 + std::iter::Sum<F>,
35{
36 checkarray_finite(data, "data")?;
37 check_positive(windowsize, "windowsize")?;
38
39 if windowsize > data.len() {
40 return Err(StatsError::InvalidArgument(
41 "Window size cannot be larger than data length".to_string(),
42 ));
43 }
44
45 let n_windows = data.len() - windowsize + 1;
46 let mut results = RollingStatsResult::new(n_windows, statistics);
47
48 for i in 0..n_windows {
50 let window = data.slice(scirs2_core::ndarray::s![i..i + windowsize]);
51 compute_window_statistics(&window, statistics, &mut results, i);
52 }
53
54 Ok(results)
55}
56
57#[derive(Debug, Clone, PartialEq)]
59pub enum RollingStatistic {
60 Mean,
61 Variance,
62 Min,
63 Max,
64 Median,
65 Skewness,
66 Kurtosis,
67 Range,
68 StandardDeviation,
69 MeanAbsoluteDeviation,
70}
71
72#[derive(Debug, Clone)]
74pub struct RollingStatsResult<F> {
75 pub means: Option<Array1<F>>,
76 pub variances: Option<Array1<F>>,
77 pub mins: Option<Array1<F>>,
78 pub maxs: Option<Array1<F>>,
79 pub medians: Option<Array1<F>>,
80 pub skewness: Option<Array1<F>>,
81 pub kurtosis: Option<Array1<F>>,
82 pub ranges: Option<Array1<F>>,
83 pub std_devs: Option<Array1<F>>,
84 pub mad: Option<Array1<F>>,
85}
86
87impl<F: Zero + Clone> RollingStatsResult<F> {
88 fn new(nwindows: usize, statistics: &[RollingStatistic]) -> Self {
89 let mut result = RollingStatsResult {
90 means: None,
91 variances: None,
92 mins: None,
93 maxs: None,
94 medians: None,
95 skewness: None,
96 kurtosis: None,
97 ranges: None,
98 std_devs: None,
99 mad: None,
100 };
101
102 for stat in statistics {
103 match stat {
104 RollingStatistic::Mean => result.means = Some(Array1::zeros(nwindows)),
105 RollingStatistic::Variance => result.variances = Some(Array1::zeros(nwindows)),
106 RollingStatistic::Min => result.mins = Some(Array1::zeros(nwindows)),
107 RollingStatistic::Max => result.maxs = Some(Array1::zeros(nwindows)),
108 RollingStatistic::Median => result.medians = Some(Array1::zeros(nwindows)),
109 RollingStatistic::Skewness => result.skewness = Some(Array1::zeros(nwindows)),
110 RollingStatistic::Kurtosis => result.kurtosis = Some(Array1::zeros(nwindows)),
111 RollingStatistic::Range => result.ranges = Some(Array1::zeros(nwindows)),
112 RollingStatistic::StandardDeviation => {
113 result.std_devs = Some(Array1::zeros(nwindows))
114 }
115 RollingStatistic::MeanAbsoluteDeviation => {
116 result.mad = Some(Array1::zeros(nwindows))
117 }
118 }
119 }
120
121 result
122 }
123}
124
125#[allow(dead_code)]
126fn compute_window_statistics<F>(
127 window: &ArrayView1<F>,
128 statistics: &[RollingStatistic],
129 results: &mut RollingStatsResult<F>,
130 window_idx: usize,
131) where
132 F: Float
133 + NumCast
134 + SimdUnifiedOps
135 + Zero
136 + One
137 + PartialOrd
138 + Copy
139 + std::fmt::Display
140 + std::iter::Sum<F>,
141{
142 let windowsize = window.len();
143 let windowsize_f = F::from(windowsize).expect("Failed to convert to float");
144
145 let (sum, sum_sq, min_val, max_val) = if windowsize > 16 {
147 let sum = F::simd_sum(window);
149 let sqdata = F::simd_mul(window, window);
150 let sum_sq = F::simd_sum(&sqdata.view());
151 let min_val = F::simd_min_element(window);
152 let max_val = F::simd_max_element(window);
153 (sum, sum_sq, min_val, max_val)
154 } else {
155 let sum = window.iter().fold(F::zero(), |acc, &x| acc + x);
157 let sum_sq = window.iter().fold(F::zero(), |acc, &x| acc + x * x);
158 let min_val = window
159 .iter()
160 .fold(window[0], |acc, &x| if x < acc { x } else { acc });
161 let max_val = window
162 .iter()
163 .fold(window[0], |acc, &x| if x > acc { x } else { acc });
164 (sum, sum_sq, min_val, max_val)
165 };
166
167 let mean = sum / windowsize_f;
168 let variance = if windowsize > 1 {
169 let n_minus_1 = F::from(windowsize - 1).expect("Failed to convert to float");
170 (sum_sq - sum * sum / windowsize_f) / n_minus_1
171 } else {
172 F::zero()
173 };
174
175 for stat in statistics {
177 match stat {
178 RollingStatistic::Mean => {
179 if let Some(ref mut means) = results.means {
180 means[window_idx] = mean;
181 }
182 }
183 RollingStatistic::Variance => {
184 if let Some(ref mut variances) = results.variances {
185 variances[window_idx] = variance;
186 }
187 }
188 RollingStatistic::Min => {
189 if let Some(ref mut mins) = results.mins {
190 mins[window_idx] = min_val;
191 }
192 }
193 RollingStatistic::Max => {
194 if let Some(ref mut maxs) = results.maxs {
195 maxs[window_idx] = max_val;
196 }
197 }
198 RollingStatistic::Range => {
199 if let Some(ref mut ranges) = results.ranges {
200 ranges[window_idx] = max_val - min_val;
201 }
202 }
203 RollingStatistic::StandardDeviation => {
204 if let Some(ref mut std_devs) = results.std_devs {
205 std_devs[window_idx] = variance.sqrt();
206 }
207 }
208 RollingStatistic::Median => {
209 if let Some(ref mut medians) = results.medians {
210 let mut sorted_window = window.to_owned();
211 sorted_window
212 .as_slice_mut()
213 .expect("Operation failed")
214 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
215 medians[window_idx] = if windowsize % 2 == 1 {
216 sorted_window[windowsize / 2]
217 } else {
218 let mid1 = sorted_window[windowsize / 2 - 1];
219 let mid2 = sorted_window[windowsize / 2];
220 (mid1 + mid2) / F::from(2.0).expect("Failed to convert constant to float")
221 };
222 }
223 }
224 RollingStatistic::Skewness => {
225 if let Some(ref mut skewness) = results.skewness {
226 if variance > F::zero() {
227 let std_dev = variance.sqrt();
228 let skew_sum = if windowsize > 16 {
229 let mean_vec = Array1::from_elem(windowsize, mean);
230 let centered = F::simd_sub(window, &mean_vec.view());
231 let cubed = F::simd_mul(
232 &F::simd_mul(¢ered.view(), ¢ered.view()).view(),
233 ¢ered.view(),
234 );
235 F::simd_sum(&cubed.view())
236 } else {
237 window
238 .iter()
239 .map(|&x| {
240 let dev = x - mean;
241 dev * dev * dev
242 })
243 .fold(F::zero(), |acc, x| acc + x)
244 };
245 skewness[window_idx] = skew_sum / (windowsize_f * std_dev.powi(3));
246 } else {
247 skewness[window_idx] = F::zero();
248 }
249 }
250 }
251 RollingStatistic::Kurtosis => {
252 if let Some(ref mut kurtosis) = results.kurtosis {
253 if variance > F::zero() {
254 let kurt_sum = if windowsize > 16 {
255 let mean_vec = Array1::from_elem(windowsize, mean);
256 let centered = F::simd_sub(window, &mean_vec.view());
257 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
258 let fourth = F::simd_mul(&squared.view(), &squared.view());
259 F::simd_sum(&fourth.view())
260 } else {
261 window
262 .iter()
263 .map(|&x| {
264 let dev = x - mean;
265 let dev_sq = dev * dev;
266 dev_sq * dev_sq
267 })
268 .fold(F::zero(), |acc, x| acc + x)
269 };
270 kurtosis[window_idx] = (kurt_sum / (windowsize_f * variance * variance))
271 - F::from(3.0).expect("Failed to convert constant to float");
272 } else {
273 kurtosis[window_idx] = F::zero();
274 }
275 }
276 }
277 RollingStatistic::MeanAbsoluteDeviation => {
278 if let Some(ref mut mad) = results.mad {
279 let mad_sum = if windowsize > 16 {
280 let mean_vec = Array1::from_elem(windowsize, mean);
281 let centered = F::simd_sub(window, &mean_vec.view());
282 let abs_centered = F::simd_abs(¢ered.view());
283 F::simd_sum(&abs_centered.view())
284 } else {
285 window
286 .iter()
287 .map(|&x| (x - mean).abs())
288 .fold(F::zero(), |acc, x| acc + x)
289 };
290 mad[window_idx] = mad_sum / windowsize_f;
291 }
292 }
293 }
294 }
295}
296
297#[allow(dead_code)]
302pub fn matrix_statistics_simd<F>(
303 data: &ArrayView2<F>,
304 axis: Option<usize>,
305 operations: &[MatrixOperation],
306) -> StatsResult<MatrixStatsResult<F>>
307where
308 F: Float
309 + NumCast
310 + SimdUnifiedOps
311 + Zero
312 + One
313 + PartialOrd
314 + Copy
315 + Send
316 + Sync
317 + std::fmt::Display
318 + std::iter::Sum<F>,
319{
320 checkarray_finite(data, "data")?;
321
322 let (n_rows, n_cols) = data.dim();
323
324 if n_rows == 0 || n_cols == 0 {
325 return Err(StatsError::InvalidArgument(
326 "Data matrix cannot be empty".to_string(),
327 ));
328 }
329
330 match axis {
331 None => compute_global_matrix_stats(data, operations),
332 Some(0) => compute_column_wise_stats(data, operations),
333 Some(1) => compute_row_wise_stats(data, operations),
334 Some(_) => Err(StatsError::InvalidArgument(
335 "Axis must be None, 0, or 1 for 2D arrays".to_string(),
336 )),
337 }
338}
339
340#[derive(Debug, Clone, PartialEq)]
342pub enum MatrixOperation {
343 Mean,
344 Variance,
345 StandardDeviation,
346 Min,
347 Max,
348 Sum,
349 Product,
350 Median,
351 Quantile(f64),
352 L1Norm,
353 L2Norm,
354 FrobeniusNorm,
355}
356
357#[derive(Debug, Clone)]
359pub struct MatrixStatsResult<F> {
360 pub means: Option<Array1<F>>,
361 pub variances: Option<Array1<F>>,
362 pub std_devs: Option<Array1<F>>,
363 pub mins: Option<Array1<F>>,
364 pub maxs: Option<Array1<F>>,
365 pub sums: Option<Array1<F>>,
366 pub products: Option<Array1<F>>,
367 pub medians: Option<Array1<F>>,
368 pub quantiles: Option<Array1<F>>,
369 pub l1_norms: Option<Array1<F>>,
370 pub l2_norms: Option<Array1<F>>,
371 pub frobenius_norm: Option<F>,
372}
373
374#[allow(dead_code)]
375fn compute_column_wise_stats<F>(
376 data: &ArrayView2<F>,
377 operations: &[MatrixOperation],
378) -> StatsResult<MatrixStatsResult<F>>
379where
380 F: Float
381 + NumCast
382 + SimdUnifiedOps
383 + Zero
384 + One
385 + PartialOrd
386 + Copy
387 + Send
388 + Sync
389 + std::fmt::Display
390 + std::iter::Sum<F>,
391{
392 let (_n_rows, n_cols) = data.dim();
393 let mut results = MatrixStatsResult::new_column_wise(n_cols, operations);
394
395 for j in 0..n_cols {
397 let column = data.column(j);
398 compute_column_statistics(&column, operations, &mut results, j);
399 }
400
401 Ok(results)
402}
403
404#[allow(dead_code)]
405fn compute_row_wise_stats<F>(
406 data: &ArrayView2<F>,
407 operations: &[MatrixOperation],
408) -> StatsResult<MatrixStatsResult<F>>
409where
410 F: Float
411 + NumCast
412 + SimdUnifiedOps
413 + Zero
414 + One
415 + PartialOrd
416 + Copy
417 + Send
418 + Sync
419 + std::fmt::Display
420 + std::iter::Sum<F>,
421{
422 let (n_rows, n_cols) = data.dim();
423 let mut results = MatrixStatsResult::new_row_wise(n_rows, operations);
424
425 for i in 0..n_rows {
427 let row = data.row(i);
428 compute_row_statistics(&row, operations, &mut results, i);
429 }
430
431 Ok(results)
432}
433
434#[allow(dead_code)]
435fn compute_global_matrix_stats<F>(
436 data: &ArrayView2<F>,
437 operations: &[MatrixOperation],
438) -> StatsResult<MatrixStatsResult<F>>
439where
440 F: Float
441 + NumCast
442 + SimdUnifiedOps
443 + Zero
444 + One
445 + PartialOrd
446 + Copy
447 + Send
448 + Sync
449 + std::fmt::Display
450 + std::iter::Sum<F>,
451{
452 let mut results = MatrixStatsResult::new_global(operations);
453
454 for operation in operations {
455 match operation {
456 MatrixOperation::FrobeniusNorm => {
457 let flattened = Array1::from_iter(data.iter().cloned());
459 let squared_sum = if flattened.len() > 1000 {
460 let mut sum = F::zero();
462 let chunksize = 1000;
463 for i in (0..flattened.len()).step_by(chunksize) {
464 let end = (i + chunksize).min(flattened.len());
465 let chunk = flattened.slice(scirs2_core::ndarray::s![i..end]);
466 let squared = F::simd_mul(&chunk, &chunk);
467 sum = sum + F::simd_sum(&squared.view());
468 }
469 sum
470 } else {
471 let squared = F::simd_mul(&flattened.view(), &flattened.view());
472 F::simd_sum(&squared.view())
473 };
474 results.frobenius_norm = Some(squared_sum.sqrt());
475 }
476 _ => {
477 let flattened = Array1::from_iter(data.iter().cloned());
479 compute_vector_operation(&flattened.view(), operation, &mut results, 0);
480 }
481 }
482 }
483
484 Ok(results)
485}
486
487#[allow(dead_code)]
488fn compute_column_statistics<F>(
489 column: &ArrayView1<F>,
490 operations: &[MatrixOperation],
491 results: &mut MatrixStatsResult<F>,
492 col_idx: usize,
493) where
494 F: Float
495 + NumCast
496 + SimdUnifiedOps
497 + Zero
498 + One
499 + PartialOrd
500 + Copy
501 + std::fmt::Display
502 + std::iter::Sum<F>,
503{
504 for operation in operations {
505 compute_vector_operation(column, operation, results, col_idx);
506 }
507}
508
509#[allow(dead_code)]
510fn compute_row_statistics<F>(
511 row: &ArrayView1<F>,
512 operations: &[MatrixOperation],
513 results: &mut MatrixStatsResult<F>,
514 row_idx: usize,
515) where
516 F: Float
517 + NumCast
518 + SimdUnifiedOps
519 + Zero
520 + One
521 + PartialOrd
522 + Copy
523 + std::fmt::Display
524 + std::iter::Sum<F>,
525{
526 for operation in operations {
527 compute_vector_operation(row, operation, results, row_idx);
528 }
529}
530
531#[allow(dead_code)]
532fn compute_vector_operation<F>(
533 data: &ArrayView1<F>,
534 operation: &MatrixOperation,
535 results: &mut MatrixStatsResult<F>,
536 idx: usize,
537) where
538 F: Float
539 + NumCast
540 + SimdUnifiedOps
541 + Zero
542 + One
543 + PartialOrd
544 + Copy
545 + std::fmt::Display
546 + std::iter::Sum<F>,
547{
548 let n = data.len();
549 let n_f = F::from(n).expect("Failed to convert to float");
550 let use_simd = n > 16;
551
552 match operation {
553 MatrixOperation::Mean => {
554 if let Some(ref mut means) = results.means {
555 means[idx] = if use_simd {
556 F::simd_sum(data) / n_f
557 } else {
558 data.iter().copied().sum::<F>() / n_f
559 };
560 }
561 }
562 MatrixOperation::Sum => {
563 if let Some(ref mut sums) = results.sums {
564 sums[idx] = if use_simd {
565 F::simd_sum(data)
566 } else {
567 data.iter().copied().sum::<F>()
568 };
569 }
570 }
571 MatrixOperation::Min => {
572 if let Some(ref mut mins) = results.mins {
573 mins[idx] = if use_simd {
574 F::simd_min_element(&data.view())
575 } else {
576 data.iter().copied().fold(data[0], F::min)
577 };
578 }
579 }
580 MatrixOperation::Max => {
581 if let Some(ref mut maxs) = results.maxs {
582 maxs[idx] = if use_simd {
583 F::simd_max_element(&data.view())
584 } else {
585 data.iter().copied().fold(data[0], F::max)
586 };
587 }
588 }
589 MatrixOperation::Variance => {
590 if let Some(ref mut variances) = results.variances {
591 if use_simd {
592 let sum = F::simd_sum(data);
593 let mean = sum / n_f;
594 let mean_vec = Array1::from_elem(n, mean);
595 let centered = F::simd_sub(data, &mean_vec.view());
596 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
597 let sum_sq = F::simd_sum(&squared.view());
598 variances[idx] = if n > 1 {
599 sum_sq / F::from(n - 1).expect("Failed to convert to float")
600 } else {
601 F::zero()
602 };
603 } else {
604 let mean = data.iter().copied().sum::<F>() / n_f;
605 let sum_sq = data.iter().map(|&x| (x - mean) * (x - mean)).sum::<F>();
606 variances[idx] = if n > 1 {
607 sum_sq / F::from(n - 1).expect("Failed to convert to float")
608 } else {
609 F::zero()
610 };
611 }
612 }
613 }
614 MatrixOperation::StandardDeviation => {
615 if let Some(ref mut std_devs) = results.std_devs {
616 let variance = if use_simd {
618 let sum = F::simd_sum(data);
619 let mean = sum / n_f;
620 let mean_vec = Array1::from_elem(n, mean);
621 let centered = F::simd_sub(data, &mean_vec.view());
622 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
623 let sum_sq = F::simd_sum(&squared.view());
624 if n > 1 {
625 sum_sq / F::from(n - 1).expect("Failed to convert to float")
626 } else {
627 F::zero()
628 }
629 } else {
630 let mean = data.iter().copied().sum::<F>() / n_f;
631 let sum_sq = data.iter().map(|&x| (x - mean) * (x - mean)).sum::<F>();
632 if n > 1 {
633 sum_sq / F::from(n - 1).expect("Failed to convert to float")
634 } else {
635 F::zero()
636 }
637 };
638 std_devs[idx] = variance.sqrt();
639 }
640 }
641 MatrixOperation::L1Norm => {
642 if let Some(ref mut l1_norms) = results.l1_norms {
643 l1_norms[idx] = if use_simd {
644 let absdata = F::simd_abs(data);
645 F::simd_sum(&absdata.view())
646 } else {
647 data.iter().map(|&x| x.abs()).sum::<F>()
648 };
649 }
650 }
651 MatrixOperation::L2Norm => {
652 if let Some(ref mut l2_norms) = results.l2_norms {
653 l2_norms[idx] = if use_simd {
654 let squared = F::simd_mul(data, data);
655 F::simd_sum(&squared.view()).sqrt()
656 } else {
657 data.iter().map(|&x| x * x).sum::<F>().sqrt()
658 };
659 }
660 }
661 MatrixOperation::Product => {
662 if let Some(ref mut products) = results.products {
663 products[idx] = data.iter().copied().fold(F::one(), |acc, x| acc * x);
664 }
665 }
666 MatrixOperation::Median => {
667 if let Some(ref mut medians) = results.medians {
668 let mut sorteddata = data.to_owned();
669 sorteddata
670 .as_slice_mut()
671 .expect("Operation failed")
672 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
673 medians[idx] = if n % 2 == 1 {
674 sorteddata[n / 2]
675 } else {
676 let mid1 = sorteddata[n / 2 - 1];
677 let mid2 = sorteddata[n / 2];
678 (mid1 + mid2) / F::from(2.0).expect("Failed to convert constant to float")
679 };
680 }
681 }
682 MatrixOperation::Quantile(q) => {
683 if let Some(ref mut quantiles) = results.quantiles {
684 let mut sorteddata = data.to_owned();
685 sorteddata
686 .as_slice_mut()
687 .expect("Operation failed")
688 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
689 let pos = q * (n - 1) as f64;
690 let lower_idx = pos.floor() as usize;
691 let upper_idx = (lower_idx + 1).min(n - 1);
692 let weight = F::from(pos - lower_idx as f64).expect("Failed to convert to float");
693 let lower_val = sorteddata[lower_idx];
694 let upper_val = sorteddata[upper_idx];
695 quantiles[idx] = lower_val + weight * (upper_val - lower_val);
696 }
697 }
698 MatrixOperation::FrobeniusNorm => {
699 }
701 }
702}
703
704impl<F: Zero + Clone> MatrixStatsResult<F> {
705 fn new_column_wise(ncols: usize, operations: &[MatrixOperation]) -> Self {
706 let mut result = MatrixStatsResult {
707 means: None,
708 variances: None,
709 std_devs: None,
710 mins: None,
711 maxs: None,
712 sums: None,
713 products: None,
714 medians: None,
715 quantiles: None,
716 l1_norms: None,
717 l2_norms: None,
718 frobenius_norm: None,
719 };
720
721 for operation in operations {
722 Self::allocate_for_operation(&mut result, operation, ncols);
723 }
724
725 result
726 }
727
728 fn new_row_wise(nrows: usize, operations: &[MatrixOperation]) -> Self {
729 let mut result = MatrixStatsResult {
730 means: None,
731 variances: None,
732 std_devs: None,
733 mins: None,
734 maxs: None,
735 sums: None,
736 products: None,
737 medians: None,
738 quantiles: None,
739 l1_norms: None,
740 l2_norms: None,
741 frobenius_norm: None,
742 };
743
744 for operation in operations {
745 Self::allocate_for_operation(&mut result, operation, nrows);
746 }
747
748 result
749 }
750
751 fn new_global(operations: &[MatrixOperation]) -> Self {
752 let mut result = MatrixStatsResult {
753 means: None,
754 variances: None,
755 std_devs: None,
756 mins: None,
757 maxs: None,
758 sums: None,
759 products: None,
760 medians: None,
761 quantiles: None,
762 l1_norms: None,
763 l2_norms: None,
764 frobenius_norm: None,
765 };
766
767 for operation in operations {
768 Self::allocate_for_operation(&mut result, operation, 1);
769 }
770
771 result
772 }
773
774 fn allocate_for_operation(result: &mut Self, operation: &MatrixOperation, size: usize) {
775 match operation {
776 MatrixOperation::Mean => result.means = Some(Array1::zeros(size)),
777 MatrixOperation::Variance => result.variances = Some(Array1::zeros(size)),
778 MatrixOperation::StandardDeviation => result.std_devs = Some(Array1::zeros(size)),
779 MatrixOperation::Min => result.mins = Some(Array1::zeros(size)),
780 MatrixOperation::Max => result.maxs = Some(Array1::zeros(size)),
781 MatrixOperation::Sum => result.sums = Some(Array1::zeros(size)),
782 MatrixOperation::Product => result.products = Some(Array1::zeros(size)),
783 MatrixOperation::Median => result.medians = Some(Array1::zeros(size)),
784 MatrixOperation::Quantile(_) => result.quantiles = Some(Array1::zeros(size)),
785 MatrixOperation::L1Norm => result.l1_norms = Some(Array1::zeros(size)),
786 MatrixOperation::L2Norm => result.l2_norms = Some(Array1::zeros(size)),
787 MatrixOperation::FrobeniusNorm => {} }
789 }
790}
791
792#[allow(dead_code)]
797pub fn bootstrap_confidence_interval_simd<F>(
798 data: &ArrayView1<F>,
799 statistic_fn: BootstrapStatistic,
800 n_bootstrap: usize,
801 confidence_level: f64,
802 random_seed: Option<u64>,
803) -> StatsResult<BootstrapResult<F>>
804where
805 F: Float
806 + NumCast
807 + SimdUnifiedOps
808 + Zero
809 + One
810 + PartialOrd
811 + Copy
812 + Send
813 + Sync
814 + std::fmt::Display
815 + std::iter::Sum<F>
816 + scirs2_core::numeric::FromPrimitive,
817{
818 checkarray_finite(data, "data")?;
819 check_positive(n_bootstrap, "n_bootstrap")?;
820 check_probability(confidence_level, "confidence_level")?;
821
822 if data.is_empty() {
823 return Err(StatsError::InvalidArgument(
824 "Data array cannot be empty".to_string(),
825 ));
826 }
827
828 use scirs2_core::random::Random;
829
830 let mut rng = match random_seed {
831 Some(_seed) => Random::seed(_seed),
832 None => Random::seed(42), };
834
835 let _ndata = data.len();
836 let mut bootstrap_stats = Array1::zeros(n_bootstrap);
837
838 for i in 0..n_bootstrap {
840 let bootstrap_sample = generate_bootstrap_sample(data, &mut rng);
841 bootstrap_stats[i] = compute_bootstrap_statistic(&bootstrap_sample.view(), &statistic_fn);
842 }
843
844 bootstrap_stats
846 .as_slice_mut()
847 .expect("Operation failed")
848 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
849
850 let alpha = 1.0 - confidence_level;
852 let lower_idx = ((alpha / 2.0) * n_bootstrap as f64) as usize;
853 let upper_idx = ((1.0 - alpha / 2.0) * n_bootstrap as f64) as usize;
854
855 let lower_bound = bootstrap_stats[lower_idx.min(n_bootstrap - 1)];
856 let upper_bound = bootstrap_stats[upper_idx.min(n_bootstrap - 1)];
857
858 let original_stat = compute_bootstrap_statistic(data, &statistic_fn);
860
861 let bootstrap_mean = bootstrap_stats.mean().expect("Operation failed");
863 let bootstrap_std = bootstrap_stats
864 .var(F::from(1.0).expect("Failed to convert constant to float"))
865 .sqrt(); Ok(BootstrapResult {
868 original_statistic: original_stat,
869 bootstrap_mean,
870 bootstrap_std,
871 confidence_interval: (lower_bound, upper_bound),
872 confidence_level,
873 n_bootstrap,
874 bootstrap_statistics: bootstrap_stats,
875 })
876}
877
878#[derive(Debug, Clone, PartialEq)]
880pub enum BootstrapStatistic {
881 Mean,
882 Median,
883 StandardDeviation,
884 Variance,
885 Skewness,
886 Kurtosis,
887 Min,
888 Max,
889 Range,
890 InterquartileRange,
891 Quantile(f64),
892}
893
894#[derive(Debug, Clone)]
896pub struct BootstrapResult<F> {
897 pub original_statistic: F,
898 pub bootstrap_mean: F,
899 pub bootstrap_std: F,
900 pub confidence_interval: (F, F),
901 pub confidence_level: f64,
902 pub n_bootstrap: usize,
903 pub bootstrap_statistics: Array1<F>,
904}
905
906#[allow(dead_code)]
907fn generate_bootstrap_sample<F, R>(data: &ArrayView1<F>, rng: &mut R) -> Array1<F>
908where
909 F: Copy + Zero,
910 R: Rng,
911{
912 let n = data.len();
913 let mut sample = Array1::zeros(n);
914
915 for i in 0..n {
916 let idx = rng.random_range(0..n);
917 sample[i] = data[idx];
918 }
919
920 sample
921}
922
923#[allow(dead_code)]
924fn compute_bootstrap_statistic<F>(data: &ArrayView1<F>, statistic: &BootstrapStatistic) -> F
925where
926 F: Float
927 + NumCast
928 + SimdUnifiedOps
929 + Zero
930 + One
931 + PartialOrd
932 + Copy
933 + std::fmt::Display
934 + std::iter::Sum<F>,
935{
936 let n = data.len();
937 let n_f = F::from(n).expect("Failed to convert to float");
938 let use_simd = n > 16;
939
940 match statistic {
941 BootstrapStatistic::Mean => {
942 if use_simd {
943 F::simd_sum(data) / n_f
944 } else {
945 data.iter().copied().sum::<F>() / n_f
946 }
947 }
948 BootstrapStatistic::StandardDeviation => {
949 let variance = if use_simd {
950 let sum = F::simd_sum(data);
951 let mean = sum / n_f;
952 let mean_vec = Array1::from_elem(n, mean);
953 let centered = F::simd_sub(data, &mean_vec.view());
954 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
955 let sum_sq = F::simd_sum(&squared.view());
956 if n > 1 {
957 sum_sq / F::from(n - 1).expect("Failed to convert to float")
958 } else {
959 F::zero()
960 }
961 } else {
962 let mean = data.iter().copied().sum::<F>() / n_f;
963 let sum_sq = data.iter().map(|&x| (x - mean) * (x - mean)).sum::<F>();
964 if n > 1 {
965 sum_sq / F::from(n - 1).expect("Failed to convert to float")
966 } else {
967 F::zero()
968 }
969 };
970 variance.sqrt()
971 }
972 BootstrapStatistic::Variance => {
973 if use_simd {
974 let sum = F::simd_sum(data);
975 let mean = sum / n_f;
976 let mean_vec = Array1::from_elem(n, mean);
977 let centered = F::simd_sub(data, &mean_vec.view());
978 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
979 let sum_sq = F::simd_sum(&squared.view());
980 if n > 1 {
981 sum_sq / F::from(n - 1).expect("Failed to convert to float")
982 } else {
983 F::zero()
984 }
985 } else {
986 let mean = data.iter().copied().sum::<F>() / n_f;
987 let sum_sq = data.iter().map(|&x| (x - mean) * (x - mean)).sum::<F>();
988 if n > 1 {
989 sum_sq / F::from(n - 1).expect("Failed to convert to float")
990 } else {
991 F::zero()
992 }
993 }
994 }
995 BootstrapStatistic::Min => {
996 if use_simd {
997 F::simd_min_element(&data.view())
998 } else {
999 data.iter().copied().fold(data[0], F::min)
1000 }
1001 }
1002 BootstrapStatistic::Max => {
1003 if use_simd {
1004 F::simd_max_element(&data.view())
1005 } else {
1006 data.iter().copied().fold(data[0], F::max)
1007 }
1008 }
1009 BootstrapStatistic::Range => {
1010 let (min_val, max_val) = if use_simd {
1011 (
1012 F::simd_min_element(&data.view()),
1013 F::simd_max_element(&data.view()),
1014 )
1015 } else {
1016 let min_val = data.iter().copied().fold(data[0], F::min);
1017 let max_val = data.iter().copied().fold(data[0], F::max);
1018 (min_val, max_val)
1019 };
1020 max_val - min_val
1021 }
1022 BootstrapStatistic::Median => {
1023 let mut sorteddata = data.to_owned();
1024 sorteddata
1025 .as_slice_mut()
1026 .expect("Operation failed")
1027 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
1028 if n % 2 == 1 {
1029 sorteddata[n / 2]
1030 } else {
1031 let mid1 = sorteddata[n / 2 - 1];
1032 let mid2 = sorteddata[n / 2];
1033 (mid1 + mid2) / F::from(2.0).expect("Failed to convert constant to float")
1034 }
1035 }
1036 BootstrapStatistic::Quantile(q) => {
1037 let mut sorteddata = data.to_owned();
1038 sorteddata
1039 .as_slice_mut()
1040 .expect("Operation failed")
1041 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
1042 let pos = q * (n - 1) as f64;
1043 let lower_idx = pos.floor() as usize;
1044 let upper_idx = (lower_idx + 1).min(n - 1);
1045 let weight = F::from(pos - lower_idx as f64).expect("Failed to convert to float");
1046 let lower_val = sorteddata[lower_idx];
1047 let upper_val = sorteddata[upper_idx];
1048 lower_val + weight * (upper_val - lower_val)
1049 }
1050 BootstrapStatistic::InterquartileRange => {
1051 let mut sorteddata = data.to_owned();
1052 sorteddata
1053 .as_slice_mut()
1054 .expect("Operation failed")
1055 .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
1056 let q1_pos = 0.25 * (n - 1) as f64;
1057 let q3_pos = 0.75 * (n - 1) as f64;
1058
1059 let q1_lower = q1_pos.floor() as usize;
1060 let q1_upper = (q1_lower + 1).min(n - 1);
1061 let q1_weight = F::from(q1_pos - q1_lower as f64).expect("Failed to convert to float");
1062 let q1 =
1063 sorteddata[q1_lower] + q1_weight * (sorteddata[q1_upper] - sorteddata[q1_lower]);
1064
1065 let q3_lower = q3_pos.floor() as usize;
1066 let q3_upper = (q3_lower + 1).min(n - 1);
1067 let q3_weight = F::from(q3_pos - q3_lower as f64).expect("Failed to convert to float");
1068 let q3 =
1069 sorteddata[q3_lower] + q3_weight * (sorteddata[q3_upper] - sorteddata[q3_lower]);
1070
1071 q3 - q1
1072 }
1073 BootstrapStatistic::Skewness => {
1074 let sum = if use_simd {
1075 F::simd_sum(data)
1076 } else {
1077 data.iter().copied().sum::<F>()
1078 };
1079 let mean = sum / n_f;
1080
1081 let (variance, skew_sum) = if use_simd {
1082 let mean_vec = Array1::from_elem(n, mean);
1083 let centered = F::simd_sub(data, &mean_vec.view());
1084 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
1085 let cubed = F::simd_mul(&squared.view(), ¢ered.view());
1086 let variance = F::simd_sum(&squared.view())
1087 / F::from(n - 1).expect("Failed to convert to float");
1088 let skew_sum = F::simd_sum(&cubed.view());
1089 (variance, skew_sum)
1090 } else {
1091 let mut variance_sum = F::zero();
1092 let mut skew_sum = F::zero();
1093 for &x in data.iter() {
1094 let dev = x - mean;
1095 let dev_sq = dev * dev;
1096 variance_sum = variance_sum + dev_sq;
1097 skew_sum = skew_sum + dev_sq * dev;
1098 }
1099 let variance = variance_sum / F::from(n - 1).expect("Failed to convert to float");
1100 (variance, skew_sum)
1101 };
1102
1103 if variance > F::zero() {
1104 let std_dev = variance.sqrt();
1105 skew_sum / (n_f * std_dev.powi(3))
1106 } else {
1107 F::zero()
1108 }
1109 }
1110 BootstrapStatistic::Kurtosis => {
1111 let sum = if use_simd {
1112 F::simd_sum(data)
1113 } else {
1114 data.iter().copied().sum::<F>()
1115 };
1116 let mean = sum / n_f;
1117
1118 let (variance, kurt_sum) = if use_simd {
1119 let mean_vec = Array1::from_elem(n, mean);
1120 let centered = F::simd_sub(data, &mean_vec.view());
1121 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
1122 let fourth = F::simd_mul(&squared.view(), &squared.view());
1123 let variance = F::simd_sum(&squared.view())
1124 / F::from(n - 1).expect("Failed to convert to float");
1125 let kurt_sum = F::simd_sum(&fourth.view());
1126 (variance, kurt_sum)
1127 } else {
1128 let mut variance_sum = F::zero();
1129 let mut kurt_sum = F::zero();
1130 for &x in data.iter() {
1131 let dev = x - mean;
1132 let dev_sq = dev * dev;
1133 variance_sum = variance_sum + dev_sq;
1134 kurt_sum = kurt_sum + dev_sq * dev_sq;
1135 }
1136 let variance = variance_sum / F::from(n - 1).expect("Failed to convert to float");
1137 (variance, kurt_sum)
1138 };
1139
1140 if variance > F::zero() {
1141 (kurt_sum / (n_f * variance * variance))
1142 - F::from(3.0).expect("Failed to convert constant to float")
1143 } else {
1144 F::zero()
1145 }
1146 }
1147 }
1148}
1149
1150#[allow(dead_code)]
1155pub fn kernel_density_estimation_simd<F>(
1156 data: &ArrayView1<F>,
1157 eval_points: &ArrayView1<F>,
1158 bandwidth: Option<F>,
1159 kernel: KernelType,
1160) -> StatsResult<Array1<F>>
1161where
1162 F: Float
1163 + NumCast
1164 + SimdUnifiedOps
1165 + Zero
1166 + One
1167 + PartialOrd
1168 + Copy
1169 + Send
1170 + Sync
1171 + std::fmt::Display
1172 + std::iter::Sum<F>,
1173{
1174 checkarray_finite(data, "data")?;
1175 checkarray_finite(eval_points, "eval_points")?;
1176
1177 if data.is_empty() {
1178 return Err(StatsError::InvalidArgument(
1179 "Data array cannot be empty".to_string(),
1180 ));
1181 }
1182
1183 let ndata = data.len();
1184 let n_eval = eval_points.len();
1185
1186 let h = match bandwidth {
1188 Some(bw) => {
1189 check_positive(bw.to_f64().expect("Operation failed"), "bandwidth")?;
1190 bw
1191 }
1192 None => {
1193 let std_dev = if ndata > 16 {
1195 let sum = F::simd_sum(data);
1196 let mean = sum / F::from(ndata).expect("Failed to convert to float");
1197 let mean_vec = Array1::from_elem(ndata, mean);
1198 let centered = F::simd_sub(data, &mean_vec.view());
1199 let squared = F::simd_mul(¢ered.view(), ¢ered.view());
1200 let variance = F::simd_sum(&squared.view())
1201 / F::from(ndata - 1).expect("Failed to convert to float");
1202 variance.sqrt()
1203 } else {
1204 let mean = data.iter().copied().sum::<F>()
1205 / F::from(ndata).expect("Failed to convert to float");
1206 let variance = data.iter().map(|&x| (x - mean) * (x - mean)).sum::<F>()
1207 / F::from(ndata - 1).expect("Failed to convert to float");
1208 variance.sqrt()
1209 };
1210
1211 let scott_factor = F::from(ndata as f64)
1212 .expect("Failed to convert to float")
1213 .powf(F::from(-0.2).expect("Failed to convert constant to float"));
1214 std_dev * scott_factor
1215 }
1216 };
1217
1218 let mut density = Array1::zeros(n_eval);
1219 let ndata_f = F::from(ndata).expect("Failed to convert to float");
1220 let normalization = F::one() / (ndata_f * h);
1221
1222 {
1224 for (eval_idx, &eval_point) in eval_points.iter().enumerate() {
1225 let mut density_sum = F::zero();
1226
1227 if ndata > 16 {
1228 let eval_vec = Array1::from_elem(ndata, eval_point);
1230 let differences = F::simd_sub(data, &eval_vec.view());
1231 let h_vec = Array1::from_elem(ndata, h);
1232 let standardized = F::simd_div(&differences.view(), &h_vec.view());
1233
1234 for &z in standardized.iter() {
1235 density_sum = density_sum + kernel_function(z, &kernel);
1236 }
1237 } else {
1238 for &data_point in data.iter() {
1240 let z = (data_point - eval_point) / h;
1241 density_sum = density_sum + kernel_function(z, &kernel);
1242 }
1243 }
1244
1245 density[eval_idx] = density_sum * normalization;
1246 }
1247 }
1248
1249 Ok(density)
1250}
1251
1252#[derive(Debug, Clone, PartialEq)]
1254pub enum KernelType {
1255 Gaussian,
1256 Epanechnikov,
1257 Uniform,
1258 Triangle,
1259 Biweight,
1260 Triweight,
1261 Cosine,
1262}
1263
1264#[allow(dead_code)]
1265fn kernel_function<F>(z: F, kernel: &KernelType) -> F
1266where
1267 F: Float + NumCast + std::fmt::Display,
1268{
1269 match kernel {
1270 KernelType::Gaussian => {
1271 let sqrt_2pi =
1273 F::from(2.5066282746310005).expect("Failed to convert constant to float"); let z_squared = z * z;
1275 let exp_term =
1276 (-z_squared / F::from(2.0).expect("Failed to convert constant to float")).exp();
1277 exp_term / sqrt_2pi
1278 }
1279 KernelType::Epanechnikov => {
1280 let abs_z = z.abs();
1282 if abs_z <= F::one() {
1283 let z_squared = z * z;
1284 F::from(0.75).expect("Failed to convert constant to float") * (F::one() - z_squared)
1285 } else {
1286 F::zero()
1287 }
1288 }
1289 KernelType::Uniform => {
1290 let abs_z = z.abs();
1292 if abs_z <= F::one() {
1293 F::from(0.5).expect("Failed to convert constant to float")
1294 } else {
1295 F::zero()
1296 }
1297 }
1298 KernelType::Triangle => {
1299 let abs_z = z.abs();
1301 if abs_z <= F::one() {
1302 F::one() - abs_z
1303 } else {
1304 F::zero()
1305 }
1306 }
1307 KernelType::Biweight => {
1308 let abs_z = z.abs();
1310 if abs_z <= F::one() {
1311 let z_squared = z * z;
1312 let term = F::one() - z_squared;
1313 F::from(15.0 / 16.0).expect("Failed to convert to float") * term * term
1314 } else {
1315 F::zero()
1316 }
1317 }
1318 KernelType::Triweight => {
1319 let abs_z = z.abs();
1321 if abs_z <= F::one() {
1322 let z_squared = z * z;
1323 let term = F::one() - z_squared;
1324 F::from(35.0 / 32.0).expect("Failed to convert to float") * term * term * term
1325 } else {
1326 F::zero()
1327 }
1328 }
1329 KernelType::Cosine => {
1330 let abs_z = z.abs();
1332 if abs_z <= F::one() {
1333 let pi = F::from(std::f64::consts::PI).expect("Failed to convert to float");
1334 let pi_4 = pi / F::from(4.0).expect("Failed to convert constant to float");
1335 let pi_z_2 = pi * z / F::from(2.0).expect("Failed to convert constant to float");
1336 pi_4 * pi_z_2.cos()
1337 } else {
1338 F::zero()
1339 }
1340 }
1341 }
1342}