Skip to main content

scirs2_stats/
simd_enhanced_v5.rs

1//! Advanced-advanced SIMD optimizations for statistical operations (v5)
2//!
3//! This module provides state-of-the-art SIMD optimizations for advanced statistical
4//! operations, building upon v4 with additional functionality and improved algorithms.
5
6use 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/// SIMD-optimized rolling statistics with configurable functions
14///
15/// Computes rolling statistics (mean, variance, min, max, custom functions) efficiently
16/// using SIMD operations and optimized sliding window algorithms.
17#[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    // Sequential processing for all datasets (parallel version needs different implementation)
49    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/// Types of rolling statistics that can be computed
58#[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/// Results container for rolling statistics
73#[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    // Compute basic statistics that might be needed for derived ones
146    let (sum, sum_sq, min_val, max_val) = if windowsize > 16 {
147        // SIMD path
148        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        // Scalar fallback
156        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    // Store requested statistics
176    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(&centered.view(), &centered.view()).view(),
233                                &centered.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(&centered.view(), &centered.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(&centered.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/// SIMD-optimized matrix-wise statistical operations
298///
299/// Computes statistics along specified axes of 2D matrices using SIMD operations
300/// and parallel processing for optimal performance.
301#[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/// Types of matrix operations that can be computed
341#[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/// Results container for matrix statistics
358#[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    // Process columns sequentially (parallel version would need different data structure)
396    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    // Process rows sequentially (parallel version would need different data structure)
426    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                // Flatten to 1D for SIMD operations since simd_mul expects 1D arrays
458                let flattened = Array1::from_iter(data.iter().cloned());
459                let squared_sum = if flattened.len() > 1000 {
460                    // Use sequential chunked processing for large matrices
461                    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                // For other operations, flatten and compute
478                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(&centered.view(), &centered.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                // Reuse variance computation logic
617                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(&centered.view(), &centered.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            // This should only be called for global operations
700        }
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 => {} // Will be handled separately
788        }
789    }
790}
791
792/// SIMD-optimized bootstrap sampling with confidence intervals
793///
794/// Performs bootstrap resampling with SIMD-accelerated statistic computation
795/// and efficient confidence interval estimation.
796#[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), // Use default _seed
833    };
834
835    let _ndata = data.len();
836    let mut bootstrap_stats = Array1::zeros(n_bootstrap);
837
838    // Bootstrap sampling (sequential for thread safety)
839    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    // Sort _bootstrap statistics for confidence interval computation
845    bootstrap_stats
846        .as_slice_mut()
847        .expect("Operation failed")
848        .sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
849
850    // Compute confidence interval
851    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    // Compute original statistic
859    let original_stat = compute_bootstrap_statistic(data, &statistic_fn);
860
861    // Compute _bootstrap statistics
862    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(); // ddof=1
866
867    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/// Types of statistics that can be bootstrapped
879#[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/// Results from bootstrap confidence interval estimation
895#[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(&centered.view(), &centered.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(&centered.view(), &centered.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(&centered.view(), &centered.view());
1085                let cubed = F::simd_mul(&squared.view(), &centered.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(&centered.view(), &centered.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/// SIMD-optimized kernel density estimation
1151///
1152/// Computes kernel density estimates using SIMD-accelerated operations
1153/// for improved performance on large datasets.
1154#[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    // Compute bandwidth using Scott's rule if not provided
1187    let h = match bandwidth {
1188        Some(bw) => {
1189            check_positive(bw.to_f64().expect("Operation failed"), "bandwidth")?;
1190            bw
1191        }
1192        None => {
1193            // Scott's rule: h = n^(-1/5) * std(data)
1194            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(&centered.view(), &centered.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    // Sequential processing for all datasets (parallel version has data race issues)
1223    {
1224        for (eval_idx, &eval_point) in eval_points.iter().enumerate() {
1225            let mut density_sum = F::zero();
1226
1227            if ndata > 16 {
1228                // SIMD path
1229                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                // Scalar fallback
1239                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/// Types of kernel functions for density estimation
1253#[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            // (1/√(2π)) * exp(-z²/2)
1272            let sqrt_2pi =
1273                F::from(2.5066282746310005).expect("Failed to convert constant to float"); // √(2π)
1274            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            // (3/4) * (1 - z²) for |z| ≤ 1, 0 otherwise
1281            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            // 1/2 for |z| ≤ 1, 0 otherwise
1291            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            // (1 - |z|) for |z| ≤ 1, 0 otherwise
1300            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            // (15/16) * (1 - z²)² for |z| ≤ 1, 0 otherwise
1309            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            // (35/32) * (1 - z²)³ for |z| ≤ 1, 0 otherwise
1320            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            // (π/4) * cos(πz/2) for |z| ≤ 1, 0 otherwise
1331            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}