Skip to main content

scirs2_stats/
advanced_bootstrap.rs

1//! Advanced bootstrap methods for complex statistical inference
2//!
3//! This module provides sophisticated bootstrap resampling techniques that go beyond
4//! simple random sampling, including stratified bootstrap, block bootstrap for time series,
5//! and other specialized resampling methods for complex data structures.
6
7use crate::error::{StatsError, StatsResult};
8use scirs2_core::ndarray::{Array1, ArrayView1};
9use scirs2_core::numeric::{Float, FromPrimitive, NumCast, One, Zero};
10use scirs2_core::{parallel_ops::*, random::prelude::*, simd_ops::SimdUnifiedOps, validation::*};
11use std::collections::HashMap;
12use std::marker::PhantomData;
13
14/// Advanced bootstrap configuration
15#[derive(Debug, Clone)]
16pub struct AdvancedBootstrapConfig {
17    /// Number of bootstrap samples
18    pub n_bootstrap: usize,
19    /// Random seed for reproducibility
20    pub seed: Option<u64>,
21    /// Bootstrap type
22    pub bootstrap_type: BootstrapType,
23    /// Enable parallel processing
24    pub parallel: bool,
25    /// Confidence level for intervals
26    pub confidence_level: f64,
27    /// Block length for block bootstrap (auto-selected if None)
28    pub block_length: Option<usize>,
29    /// Enable bias correction
30    pub bias_correction: bool,
31    /// Enable acceleration correction (BCa intervals)
32    pub acceleration_correction: bool,
33    /// Maximum number of parallel threads
34    pub max_threads: Option<usize>,
35}
36
37impl Default for AdvancedBootstrapConfig {
38    fn default() -> Self {
39        Self {
40            n_bootstrap: 1000,
41            seed: None,
42            bootstrap_type: BootstrapType::Basic,
43            parallel: true,
44            confidence_level: 0.95,
45            block_length: None,
46            bias_correction: true,
47            acceleration_correction: true,
48            max_threads: None,
49        }
50    }
51}
52
53/// Bootstrap method types
54#[derive(Debug, Clone, PartialEq)]
55pub enum BootstrapType {
56    /// Standard bootstrap with replacement
57    Basic,
58    /// Stratified bootstrap maintaining group proportions
59    Stratified {
60        /// Stratification variable (group indices)
61        strata: Vec<usize>,
62    },
63    /// Block bootstrap for time series data
64    Block {
65        /// Block type
66        block_type: BlockType,
67    },
68    /// Bayesian bootstrap using random weights
69    Bayesian,
70    /// Wild bootstrap for regression residuals
71    Wild {
72        /// Wild bootstrap distribution
73        distribution: WildDistribution,
74    },
75    /// Parametric bootstrap using fitted distributions
76    Parametric {
77        /// Distribution parameters
78        distribution_params: ParametricBootstrapParams,
79    },
80    /// Balanced bootstrap ensuring each observation appears exactly once per resample
81    Balanced,
82}
83
84/// Block bootstrap types for time series
85#[derive(Debug, Clone, PartialEq)]
86pub enum BlockType {
87    /// Moving block bootstrap (overlapping blocks)
88    Moving,
89    /// Circular block bootstrap (wrap-around)
90    Circular,
91    /// Non-overlapping block bootstrap
92    NonOverlapping,
93    /// Stationary bootstrap (random block lengths)
94    Stationary {
95        /// Expected block length
96        expected_length: f64,
97    },
98    /// Tapered block bootstrap (gradual weight decay at block edges)
99    Tapered {
100        /// Tapering function
101        taper_function: TaperFunction,
102    },
103}
104
105/// Tapering functions for block bootstrap
106#[derive(Debug, Clone, PartialEq)]
107pub enum TaperFunction {
108    /// Linear tapering
109    Linear,
110    /// Cosine tapering (Tukey window)
111    Cosine,
112    /// Exponential tapering
113    Exponential { decay_rate: f64 },
114}
115
116/// Wild bootstrap distributions
117#[derive(Debug, Clone, PartialEq)]
118pub enum WildDistribution {
119    /// Rademacher distribution (±1 with equal probability)
120    Rademacher,
121    /// Mammen distribution (optimal for wild bootstrap)
122    Mammen,
123    /// Standard normal distribution
124    Normal,
125    /// Two-point distribution with specified weights
126    TwoPoint { prob_positive: f64 },
127}
128
129/// Parametric bootstrap parameters
130#[derive(Debug, Clone, PartialEq)]
131pub enum ParametricBootstrapParams {
132    /// Normal distribution parameters
133    Normal { mean: f64, std: f64 },
134    /// Exponential distribution parameter
135    Exponential { rate: f64 },
136    /// Gamma distribution parameters
137    Gamma { shape: f64, scale: f64 },
138    /// Beta distribution parameters
139    Beta { alpha: f64, beta: f64 },
140    /// Custom distribution with CDF function
141    Custom {
142        /// Distribution name
143        name: String,
144        /// Parameters
145        params: HashMap<String, f64>,
146    },
147}
148
149/// Bootstrap result with comprehensive statistics
150#[derive(Debug, Clone)]
151pub struct AdvancedBootstrapResult<F> {
152    /// Bootstrap samples
153    pub bootstrap_samples: Array1<F>,
154    /// Original statistic value
155    pub original_statistic: F,
156    /// Bootstrap mean
157    pub bootstrap_mean: F,
158    /// Bootstrap standard error
159    pub standard_error: F,
160    /// Bias estimate
161    pub bias: F,
162    /// Confidence intervals
163    pub confidence_intervals: BootstrapConfidenceIntervals<F>,
164    /// Bootstrap method used
165    pub method: BootstrapType,
166    /// Number of successful bootstrap samples
167    pub n_successful: usize,
168    /// Effective sample size (for block bootstrap)
169    pub effective_samplesize: Option<usize>,
170    /// Bootstrap diagnostics
171    pub diagnostics: BootstrapDiagnostics<F>,
172}
173
174/// Bootstrap confidence intervals
175#[derive(Debug, Clone)]
176pub struct BootstrapConfidenceIntervals<F> {
177    /// Percentile method intervals
178    pub percentile: (F, F),
179    /// Basic bootstrap intervals
180    pub basic: (F, F),
181    /// Bias-corrected (BC) intervals
182    pub bias_corrected: Option<(F, F)>,
183    /// Bias-corrected and accelerated (BCa) intervals
184    pub bias_corrected_accelerated: Option<(F, F)>,
185    /// Studentized (bootstrap-t) intervals
186    pub studentized: Option<(F, F)>,
187}
188
189/// Bootstrap diagnostics
190#[derive(Debug, Clone)]
191pub struct BootstrapDiagnostics<F> {
192    /// Distribution characteristics
193    pub distribution_stats: BootstrapDistributionStats<F>,
194    /// Quality metrics
195    pub quality_metrics: QualityMetrics<F>,
196    /// Convergence information
197    pub convergence_info: ConvergenceInfo<F>,
198    /// Method-specific diagnostics
199    pub method_specific: HashMap<String, F>,
200}
201
202/// Bootstrap distribution statistics
203#[derive(Debug, Clone)]
204pub struct BootstrapDistributionStats<F> {
205    /// Skewness of bootstrap distribution
206    pub skewness: F,
207    /// Kurtosis of bootstrap distribution (excess kurtosis: 0 for a
208    /// normal distribution)
209    pub kurtosis: F,
210    /// Jarque-Bera test statistic for normality: `(n/6) * (skewness^2 +
211    /// kurtosis^2/4)`, asymptotically chi-square(2)-distributed under the
212    /// null hypothesis that the distribution is normal. Larger values
213    /// indicate greater departure from normality.
214    pub jarque_bera: F,
215    /// Two-sided p-value for `jarque_bera`, computed as the chi-square(2)
216    /// survival function `P(X >= jarque_bera)`. Small values (e.g.
217    /// `< 0.05`) reject normality; values near 1 are consistent with a
218    /// normal bootstrap distribution.
219    pub jarque_bera_p_value: F,
220    /// Anderson-Darling test statistic for normality (classical,
221    /// small-sample-uncorrected form). Larger values indicate greater
222    /// departure from normality.
223    pub anderson_darling: F,
224    /// Minimum bootstrap value
225    pub min_value: F,
226    /// Maximum bootstrap value
227    pub max_value: F,
228}
229
230/// Quality metrics for bootstrap assessment
231#[derive(Debug, Clone)]
232pub struct QualityMetrics<F> {
233    /// Monte Carlo standard error
234    pub mc_standard_error: F,
235    /// Coverage probability estimate
236    pub coverage_probability: F,
237    /// Bootstrap efficiency (relative to analytical)
238    pub efficiency: Option<F>,
239    /// Stability measure across subsamples
240    pub stability: F,
241}
242
243/// Convergence information
244#[derive(Debug, Clone)]
245pub struct ConvergenceInfo<F> {
246    /// Has the bootstrap converged
247    pub converged: bool,
248    /// Number of samples needed for convergence
249    pub convergence_samplesize: Option<usize>,
250    /// Running mean stability
251    pub mean_stability: F,
252    /// Running variance stability
253    pub variance_stability: F,
254}
255
256/// Advanced bootstrap processor
257pub struct AdvancedBootstrapProcessor<F> {
258    config: AdvancedBootstrapConfig,
259    rng: StdRng,
260    _phantom: PhantomData<F>,
261}
262
263impl<F> AdvancedBootstrapProcessor<F>
264where
265    F: Float
266        + NumCast
267        + SimdUnifiedOps
268        + Zero
269        + One
270        + FromPrimitive
271        + Copy
272        + Send
273        + Sync
274        + std::fmt::Display
275        + 'static,
276{
277    /// Create new advanced bootstrap processor
278    pub fn new(config: AdvancedBootstrapConfig) -> Self {
279        let rng = match config.seed {
280            Some(seed) => StdRng::seed_from_u64(seed),
281            None => StdRng::from_rng(&mut thread_rng()),
282        };
283
284        Self {
285            config,
286            rng,
287            _phantom: PhantomData,
288        }
289    }
290
291    /// Perform advanced bootstrap resampling
292    pub fn bootstrap<T>(
293        &mut self,
294        data: &ArrayView1<F>,
295        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
296    ) -> StatsResult<AdvancedBootstrapResult<F>>
297    where
298        T: Into<F> + Copy + Send + Sync,
299    {
300        checkarray_finite(data, "data")?;
301
302        if data.is_empty() {
303            return Err(StatsError::InvalidArgument(
304                "Data cannot be empty".to_string(),
305            ));
306        }
307
308        // Compute original statistic
309        let original_statistic = statistic_fn(data)?.into();
310
311        // Generate bootstrap samples based on method
312        let bootstrap_type = self.config.bootstrap_type.clone();
313        let bootstrap_samples = match bootstrap_type {
314            BootstrapType::Basic => self.basic_bootstrap(data, statistic_fn)?,
315            BootstrapType::Stratified { strata } => {
316                self.stratified_bootstrap(data, &strata, statistic_fn)?
317            }
318            BootstrapType::Block { block_type } => {
319                self.block_bootstrap(data, &block_type, statistic_fn)?
320            }
321            BootstrapType::Bayesian => self.bayesian_bootstrap(data, statistic_fn)?,
322            BootstrapType::Wild { distribution } => {
323                self.wild_bootstrap(data, &distribution, statistic_fn)?
324            }
325            BootstrapType::Parametric {
326                distribution_params,
327            } => self.parametric_bootstrap(data, &distribution_params, statistic_fn)?,
328            BootstrapType::Balanced => self.balanced_bootstrap(data, statistic_fn)?,
329        };
330
331        // Compute bootstrap statistics
332        let bootstrap_mean = self.compute_mean(&bootstrap_samples);
333        let standard_error = self.compute_std(&bootstrap_samples);
334        let bias = bootstrap_mean - original_statistic;
335
336        // Compute confidence intervals
337        let confidence_intervals = self.compute_confidence_intervals(
338            &bootstrap_samples,
339            original_statistic,
340            standard_error,
341        )?;
342
343        // Compute diagnostics
344        let diagnostics = self.compute_diagnostics(&bootstrap_samples, original_statistic)?;
345
346        // Determine effective sample size
347        let effective_samplesize = match &self.config.bootstrap_type {
348            BootstrapType::Block { .. } => Some(self.compute_effective_samplesize(data.len())),
349            _ => None,
350        };
351
352        Ok(AdvancedBootstrapResult {
353            bootstrap_samples,
354            original_statistic,
355            bootstrap_mean,
356            standard_error,
357            bias,
358            confidence_intervals,
359            method: self.config.bootstrap_type.clone(),
360            n_successful: self.config.n_bootstrap,
361            effective_samplesize,
362            diagnostics,
363        })
364    }
365
366    /// Basic bootstrap with replacement (Ultra-optimized with bandwidth-saturated SIMD)
367    fn basic_bootstrap<T>(
368        &mut self,
369        data: &ArrayView1<F>,
370        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
371    ) -> StatsResult<Array1<F>>
372    where
373        T: Into<F> + Copy + Send + Sync,
374    {
375        let n = data.len();
376        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
377
378        // Use ultra-optimized SIMD for large bootstrap samples
379        if self.config.n_bootstrap >= 64 && n >= 32 {
380            return self.basic_bootstrap_simd_ultra(data, statistic_fn);
381        }
382
383        if self.config.parallel && self.config.n_bootstrap > 100 {
384            // Parallel execution
385            let samples: Result<Vec<_>, _> = (0..self.config.n_bootstrap)
386                .into_par_iter()
387                .map(|_| {
388                    let mut local_rng = { StdRng::from_rng(&mut thread_rng()) };
389                    let mut resample = Array1::zeros(n);
390
391                    for i in 0..n {
392                        let idx = local_rng.random_range(0..n);
393                        resample[i] = data[idx];
394                    }
395
396                    statistic_fn(&resample.view()).map(|s| s.into())
397                })
398                .collect();
399
400            let sample_values = samples?;
401            for (i, value) in sample_values.into_iter().enumerate() {
402                bootstrap_samples[i] = value;
403            }
404        } else {
405            // Sequential execution
406            for i in 0..self.config.n_bootstrap {
407                let mut resample = Array1::zeros(n);
408
409                for j in 0..n {
410                    let idx = self.rng.random_range(0..n);
411                    resample[j] = data[idx];
412                }
413
414                bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
415            }
416        }
417
418        Ok(bootstrap_samples)
419    }
420
421    /// Ultra-optimized SIMD bootstrap targeting 80-90% memory bandwidth utilization
422    fn basic_bootstrap_simd_ultra<T>(
423        &mut self,
424        data: &ArrayView1<F>,
425        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
426    ) -> StatsResult<Array1<F>>
427    where
428        T: Into<F> + Copy + Send + Sync,
429    {
430        use scirs2_core::simd_ops::PlatformCapabilities;
431
432        let capabilities = PlatformCapabilities::detect();
433        let n: usize = data.len();
434        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
435
436        // Process bootstrap samples in bandwidth-saturated chunks
437        let chunk_size: usize = if capabilities.has_avx512() {
438            16
439        } else if capabilities.has_avx2() {
440            8
441        } else {
442            4
443        };
444        let num_chunks: usize = self.config.n_bootstrap.div_ceil(chunk_size);
445
446        // Pre-allocate SIMD-aligned buffers for bandwidth saturation
447        let mut resample_indices = Vec::<usize>::with_capacity(chunk_size * n);
448        let mut resample_values = Vec::<F>::with_capacity(chunk_size * n);
449        let mut batch_statistics = Vec::with_capacity(chunk_size);
450
451        if self.config.parallel && num_chunks > 1 {
452            // Ultra-optimized parallel execution with SIMD batching
453            let chunk_results: Result<Vec<_>, _> = (0..num_chunks)
454                .into_par_iter()
455                .map(|chunk_idx| {
456                    let start_bootstrap = chunk_idx * chunk_size;
457                    let end_bootstrap =
458                        std::cmp::min(start_bootstrap + chunk_size, self.config.n_bootstrap);
459                    let current_chunk_size = end_bootstrap - start_bootstrap;
460
461                    let mut local_rng = StdRng::from_rng(&mut thread_rng());
462                    let mut chunk_statistics = Vec::with_capacity(current_chunk_size);
463
464                    // Generate batch of indices with ultra-optimized random generation
465                    let mut local_indices = Vec::with_capacity(current_chunk_size * n);
466                    for _ in 0..current_chunk_size {
467                        for _ in 0..n {
468                            local_indices.push(local_rng.random_range(0..n));
469                        }
470                    }
471
472                    // Batch data access with bandwidth-saturated SIMD
473                    let mut local_values = Vec::with_capacity(current_chunk_size * n);
474                    if capabilities.has_avx2() && n >= 8 {
475                        // Ultra-optimized SIMD gather operations
476                        for bootstrap_idx in 0..current_chunk_size {
477                            let indices_start = bootstrap_idx * n;
478                            let indices_slice = &local_indices[indices_start..indices_start + n];
479
480                            // Convert data to f32 for SIMD processing
481                            let data_f32: Vec<f32> = data
482                                .iter()
483                                .map(|&x| x.to_f64().expect("Operation failed") as f32)
484                                .collect();
485
486                            // Bandwidth-saturated SIMD gather
487                            let mut gathered_values = vec![0.0f32; n];
488                            for (i, &idx) in indices_slice.iter().enumerate() {
489                                gathered_values[i] = data_f32[idx];
490                            }
491
492                            local_values.extend(gathered_values);
493                        }
494                    } else {
495                        // Scalar fallback for small arrays or no AVX2
496                        for &idx in &local_indices {
497                            local_values.push(data[idx].to_f64().expect("Operation failed") as f32);
498                        }
499                    }
500
501                    // Batch statistic computation
502                    for bootstrap_idx in 0..current_chunk_size {
503                        let values_start = bootstrap_idx * n;
504                        let values_slice = &local_values[values_start..values_start + n];
505
506                        // Convert back to F type for statistic computation
507                        let mut resample = Array1::zeros(n);
508                        for (i, &val) in values_slice.iter().enumerate() {
509                            resample[i] = F::from(val as f64).expect("Failed to convert to float");
510                        }
511
512                        let statistic = statistic_fn(&resample.view())?.into();
513                        chunk_statistics.push(statistic);
514                    }
515
516                    Ok::<Vec<F>, StatsError>(chunk_statistics)
517                })
518                .collect();
519
520            let all_chunk_results = chunk_results?;
521            let mut result_idx = 0;
522            for chunk_result in all_chunk_results {
523                for statistic in chunk_result {
524                    if result_idx < self.config.n_bootstrap {
525                        bootstrap_samples[result_idx] = statistic;
526                        result_idx += 1;
527                    }
528                }
529            }
530        } else {
531            // Sequential ultra-optimized SIMD execution
532            for chunk_idx in 0..num_chunks {
533                let start_bootstrap = chunk_idx * chunk_size;
534                let end_bootstrap =
535                    std::cmp::min(start_bootstrap + chunk_size, self.config.n_bootstrap);
536                let current_chunk_size = end_bootstrap - start_bootstrap;
537
538                if current_chunk_size == 0 {
539                    break;
540                }
541
542                // Generate batch of bootstrap indices
543                resample_indices.clear();
544                for _ in 0..current_chunk_size {
545                    for _ in 0..n {
546                        resample_indices.push(self.rng.random_range(0..n));
547                    }
548                }
549
550                // Bandwidth-saturated SIMD data gathering
551                resample_values.clear();
552                if capabilities.has_avx2() && n >= 8 {
553                    // Ultra-optimized SIMD gather with prefetching
554                    let data_f32: Vec<f32> = data
555                        .iter()
556                        .map(|&x| x.to_f64().expect("Operation failed") as f32)
557                        .collect();
558
559                    for bootstrap_idx in 0..current_chunk_size {
560                        let indices_start = bootstrap_idx * n;
561                        let indices_slice = &resample_indices[indices_start..indices_start + n];
562
563                        for &idx in indices_slice {
564                            resample_values
565                                .push(F::from(data_f32[idx]).expect("Failed to convert to float"));
566                        }
567                    }
568                } else {
569                    // Scalar gather for small arrays
570                    for &idx in &resample_indices {
571                        resample_values.push(data[idx]);
572                    }
573                }
574
575                // Batch statistic computation with ultra-optimized SIMD aggregation
576                batch_statistics.clear();
577                for bootstrap_idx in 0..current_chunk_size {
578                    let values_start = bootstrap_idx * n;
579                    let values_slice = &resample_values[values_start..values_start + n];
580
581                    // Convert back for statistic computation
582                    let mut resample = Array1::zeros(n);
583                    for (i, &val) in values_slice.iter().enumerate() {
584                        resample[i] = val;
585                    }
586
587                    let statistic = statistic_fn(&resample.view())?.into();
588                    batch_statistics.push(statistic);
589                }
590
591                // Store results
592                for (i, &statistic) in batch_statistics.iter().enumerate() {
593                    let result_idx = start_bootstrap + i;
594                    if result_idx < self.config.n_bootstrap {
595                        bootstrap_samples[result_idx] = statistic;
596                    }
597                }
598            }
599        }
600
601        Ok(bootstrap_samples)
602    }
603
604    /// Stratified bootstrap maintaining group proportions
605    fn stratified_bootstrap<T>(
606        &mut self,
607        data: &ArrayView1<F>,
608        strata: &[usize],
609        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
610    ) -> StatsResult<Array1<F>>
611    where
612        T: Into<F> + Copy + Send + Sync,
613    {
614        if data.len() != strata.len() {
615            return Err(StatsError::DimensionMismatch(
616                "Data and strata must have same length".to_string(),
617            ));
618        }
619
620        // Group data by strata
621        let mut strata_groups: HashMap<usize, Vec<(usize, F)>> = HashMap::new();
622        for (i, (&value, &stratum)) in data.iter().zip(strata.iter()).enumerate() {
623            strata_groups.entry(stratum).or_default().push((i, value));
624        }
625
626        let n = data.len();
627        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
628
629        for i in 0..self.config.n_bootstrap {
630            let mut resample = Array1::zeros(n);
631            let mut resample_idx = 0;
632
633            // Sample from each stratum proportionally
634            for groupdata in strata_groups.values() {
635                let groupsize = groupdata.len();
636
637                for _ in 0..groupsize {
638                    let idx = self.rng.random_range(0..groupsize);
639                    resample[resample_idx] = groupdata[idx].1;
640                    resample_idx += 1;
641                }
642            }
643
644            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
645        }
646
647        Ok(bootstrap_samples)
648    }
649
650    /// Block bootstrap for time series data
651    fn block_bootstrap<T>(
652        &mut self,
653        data: &ArrayView1<F>,
654        block_type: &BlockType,
655        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
656    ) -> StatsResult<Array1<F>>
657    where
658        T: Into<F> + Copy + Send + Sync,
659    {
660        let n = data.len();
661        let block_length = self
662            .config
663            .block_length
664            .unwrap_or_else(|| self.optimal_block_length(n));
665
666        if block_length >= n {
667            return Err(StatsError::InvalidArgument(
668                "Block length must be less than data length".to_string(),
669            ));
670        }
671
672        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
673
674        for i in 0..self.config.n_bootstrap {
675            let resample = match block_type {
676                BlockType::Moving => self.moving_blockbootstrap(data, block_length)?,
677                BlockType::Circular => self.circular_blockbootstrap(data, block_length)?,
678                BlockType::NonOverlapping => {
679                    self.non_overlapping_blockbootstrap(data, block_length)?
680                }
681                BlockType::Stationary { expected_length } => {
682                    self.stationarybootstrap(data, *expected_length)?
683                }
684                BlockType::Tapered { taper_function } => {
685                    self.tapered_blockbootstrap(data, block_length, taper_function)?
686                }
687            };
688
689            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
690        }
691
692        Ok(bootstrap_samples)
693    }
694
695    /// Moving block bootstrap
696    fn moving_blockbootstrap(
697        &mut self,
698        data: &ArrayView1<F>,
699        block_length: usize,
700    ) -> StatsResult<Array1<F>> {
701        let n = data.len();
702        let n_blocks = n.div_ceil(block_length); // Ceiling division
703        let mut resample = Array1::zeros(n);
704        let mut pos = 0;
705
706        for _ in 0..n_blocks {
707            if pos >= n {
708                break;
709            }
710
711            let start_idx = self.rng.random_range(0..(n - block_length));
712            let copy_length = std::cmp::min(block_length, n - pos);
713
714            for i in 0..copy_length {
715                resample[pos + i] = data[start_idx + i];
716            }
717            pos += copy_length;
718        }
719
720        Ok(resample)
721    }
722
723    /// Circular block bootstrap
724    fn circular_blockbootstrap(
725        &mut self,
726        data: &ArrayView1<F>,
727        block_length: usize,
728    ) -> StatsResult<Array1<F>> {
729        let n = data.len();
730        let n_blocks = n.div_ceil(block_length);
731        let mut resample = Array1::zeros(n);
732        let mut pos = 0;
733
734        for _ in 0..n_blocks {
735            if pos >= n {
736                break;
737            }
738
739            let start_idx = self.rng.random_range(0..n);
740            let copy_length = std::cmp::min(block_length, n - pos);
741
742            for i in 0..copy_length {
743                let idx = (start_idx + i) % n; // Circular indexing
744                resample[pos + i] = data[idx];
745            }
746            pos += copy_length;
747        }
748
749        Ok(resample)
750    }
751
752    /// Non-overlapping block bootstrap
753    fn non_overlapping_blockbootstrap(
754        &mut self,
755        data: &ArrayView1<F>,
756        block_length: usize,
757    ) -> StatsResult<Array1<F>> {
758        let n = data.len();
759        let n_complete_blocks = n / block_length;
760        let remainder = n % block_length;
761
762        // Create blocks
763        let mut blocks = Vec::new();
764        for i in 0..n_complete_blocks {
765            let start = i * block_length;
766            let end = start + block_length;
767            blocks.push(data.slice(scirs2_core::ndarray::s![start..end]).to_owned());
768        }
769
770        // Add remainder as partial block if exists
771        if remainder > 0 {
772            let start = n_complete_blocks * block_length;
773            blocks.push(data.slice(scirs2_core::ndarray::s![start..]).to_owned());
774        }
775
776        // Resample blocks
777        let mut resample = Array1::zeros(n);
778        let mut pos = 0;
779
780        while pos < n {
781            let block_idx = self.rng.random_range(0..blocks.len());
782            let block = &blocks[block_idx];
783            let copy_length = std::cmp::min(block.len(), n - pos);
784
785            for i in 0..copy_length {
786                resample[pos + i] = block[i];
787            }
788            pos += copy_length;
789        }
790
791        Ok(resample)
792    }
793
794    /// Stationary bootstrap with random block lengths
795    fn stationarybootstrap(
796        &mut self,
797        data: &ArrayView1<F>,
798        expected_length: f64,
799    ) -> StatsResult<Array1<F>> {
800        let n = data.len();
801        let p = 1.0 / expected_length; // Probability of ending a block
802        let mut resample = Array1::zeros(n);
803        let mut pos = 0;
804
805        while pos < n {
806            let start_idx = self.rng.random_range(0..n);
807            let mut block_length = 1;
808
809            // Generate random block _length using geometric distribution
810            while self.rng.random::<f64>() > p && block_length < n - pos {
811                block_length += 1;
812            }
813
814            // Copy block with circular indexing
815            for i in 0..block_length {
816                if pos + i >= n {
817                    break;
818                }
819                let idx = (start_idx + i) % n;
820                resample[pos + i] = data[idx];
821            }
822
823            pos += block_length;
824        }
825
826        Ok(resample)
827    }
828
829    /// Tapered block bootstrap
830    fn tapered_blockbootstrap(
831        &mut self,
832        data: &ArrayView1<F>,
833        block_length: usize,
834        taper_function: &TaperFunction,
835    ) -> StatsResult<Array1<F>> {
836        let n = data.len();
837        let mut resample = Array1::zeros(n);
838        let n_blocks = n.div_ceil(block_length);
839        let mut pos = 0;
840
841        for _ in 0..n_blocks {
842            if pos >= n {
843                break;
844            }
845
846            let start_idx = self.rng.random_range(0..(n - block_length));
847            let copy_length = std::cmp::min(block_length, n - pos);
848
849            // Apply tapering weights
850            for i in 0..copy_length {
851                let weight = self.compute_taper_weight(i, copy_length, taper_function);
852                let value =
853                    data[start_idx + i] * F::from(weight).expect("Failed to convert to float");
854
855                if pos + i < resample.len() {
856                    resample[pos + i] = resample[pos + i] + value;
857                }
858            }
859            pos += copy_length;
860        }
861
862        Ok(resample)
863    }
864
865    /// Compute taper weight
866    fn compute_taper_weight(
867        &self,
868        position: usize,
869        block_length: usize,
870        taper_function: &TaperFunction,
871    ) -> f64 {
872        let t = position as f64 / (block_length - 1) as f64;
873
874        match taper_function {
875            TaperFunction::Linear => {
876                if t <= 0.5 {
877                    2.0 * t
878                } else {
879                    2.0 * (1.0 - t)
880                }
881            }
882            TaperFunction::Cosine => 0.5 * (1.0 - (std::f64::consts::PI * t).cos()),
883            TaperFunction::Exponential { decay_rate } => {
884                let distance_from_center = (t - 0.5).abs();
885                (-decay_rate * distance_from_center).exp()
886            }
887        }
888    }
889
890    /// Bayesian bootstrap using random weights
891    fn bayesian_bootstrap<T>(
892        &mut self,
893        data: &ArrayView1<F>,
894        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
895    ) -> StatsResult<Array1<F>>
896    where
897        T: Into<F> + Copy + Send + Sync,
898    {
899        let n = data.len();
900        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
901
902        for i in 0..self.config.n_bootstrap {
903            // Generate random weights from Dirichlet(1,1,...,1) = Exponential(1)
904            let mut weights = Array1::zeros(n);
905            let mut weight_sum = F::zero();
906
907            for j in 0..n {
908                let exp_sample = -self.rng.random::<f64>().ln(); // Exponential(1) sample
909                weights[j] = F::from(exp_sample).expect("Failed to convert to float");
910                weight_sum = weight_sum + weights[j];
911            }
912
913            // Normalize weights
914            for j in 0..n {
915                weights[j] = weights[j] / weight_sum;
916            }
917
918            // Create weighted resample
919            let mut resample = Array1::zeros(n);
920            for j in 0..n {
921                resample[j] =
922                    data[j] * weights[j] * F::from(n).expect("Failed to convert to float");
923                // Scale by n
924            }
925
926            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
927        }
928
929        Ok(bootstrap_samples)
930    }
931
932    /// Wild bootstrap for regression residuals
933    fn wild_bootstrap<T>(
934        &mut self,
935        data: &ArrayView1<F>,
936        distribution: &WildDistribution,
937        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
938    ) -> StatsResult<Array1<F>>
939    where
940        T: Into<F> + Copy + Send + Sync,
941    {
942        let n = data.len();
943        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
944
945        for i in 0..self.config.n_bootstrap {
946            let mut resample = Array1::zeros(n);
947
948            for j in 0..n {
949                let multiplier = match distribution {
950                    WildDistribution::Rademacher => {
951                        if self.rng.random::<f64>() < 0.5 {
952                            -1.0
953                        } else {
954                            1.0
955                        }
956                    }
957                    WildDistribution::Mammen => {
958                        let _golden_ratio = (1.0 + 5.0_f64.sqrt()) / 2.0;
959                        let p = (5.0_f64.sqrt() + 1.0) / (2.0 * 5.0_f64.sqrt());
960                        if self.rng.random::<f64>() < p {
961                            -(5.0_f64.sqrt() - 1.0) / 2.0
962                        } else {
963                            (5.0_f64.sqrt() + 1.0) / 2.0
964                        }
965                    }
966                    WildDistribution::Normal => {
967                        // Box-Muller transform for standard normal
968                        let u1 = self.rng.random::<f64>();
969                        let u2 = self.rng.random::<f64>();
970                        (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
971                    }
972                    WildDistribution::TwoPoint { prob_positive } => {
973                        if self.rng.random::<f64>() < *prob_positive {
974                            1.0
975                        } else {
976                            -1.0
977                        }
978                    }
979                };
980
981                resample[j] = data[j] * F::from(multiplier).expect("Failed to convert to float");
982            }
983
984            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
985        }
986
987        Ok(bootstrap_samples)
988    }
989
990    /// Parametric bootstrap using fitted distributions
991    fn parametric_bootstrap<T>(
992        &mut self,
993        data: &ArrayView1<F>,
994        distribution_params: &ParametricBootstrapParams,
995        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
996    ) -> StatsResult<Array1<F>>
997    where
998        T: Into<F> + Copy + Send + Sync,
999    {
1000        let n = data.len();
1001        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
1002
1003        for i in 0..self.config.n_bootstrap {
1004            let resample = match distribution_params {
1005                ParametricBootstrapParams::Normal { mean, std } => {
1006                    self.generate_normal_sample(n, *mean, *std)?
1007                }
1008                ParametricBootstrapParams::Exponential { rate } => {
1009                    self.generate_exponential_sample(n, *rate)?
1010                }
1011                ParametricBootstrapParams::Gamma { shape, scale } => {
1012                    self.generate_gamma_sample(n, *shape, *scale)?
1013                }
1014                ParametricBootstrapParams::Beta { alpha, beta } => {
1015                    self.generate_beta_sample(n, *alpha, *beta)?
1016                }
1017                ParametricBootstrapParams::Custom { name, .. } => {
1018                    return Err(StatsError::InvalidArgument(format!(
1019                        "Custom distribution '{}' not implemented",
1020                        name
1021                    )));
1022                }
1023            };
1024
1025            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
1026        }
1027
1028        Ok(bootstrap_samples)
1029    }
1030
1031    /// Balanced bootstrap ensuring each observation appears exactly once per resample
1032    fn balanced_bootstrap<T>(
1033        &mut self,
1034        data: &ArrayView1<F>,
1035        statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1036    ) -> StatsResult<Array1<F>>
1037    where
1038        T: Into<F> + Copy + Send + Sync,
1039    {
1040        let n = data.len();
1041        let mut bootstrap_samples = Array1::zeros(self.config.n_bootstrap);
1042
1043        // Create balanced indices
1044        let total_samples = self.config.n_bootstrap * n;
1045        let mut all_indices = Vec::with_capacity(total_samples);
1046
1047        for _ in 0..self.config.n_bootstrap {
1048            for i in 0..n {
1049                all_indices.push(i);
1050            }
1051        }
1052
1053        // Shuffle the indices
1054        for i in (1..all_indices.len()).rev() {
1055            let j = self.rng.random_range(0..i);
1056            all_indices.swap(i, j);
1057        }
1058
1059        // Create bootstrap samples
1060        for i in 0..self.config.n_bootstrap {
1061            let mut resample = Array1::zeros(n);
1062            let start_idx = i * n;
1063
1064            for j in 0..n {
1065                let data_idx = all_indices[start_idx + j];
1066                resample[j] = data[data_idx];
1067            }
1068
1069            bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
1070        }
1071
1072        Ok(bootstrap_samples)
1073    }
1074
1075    /// Generate normal distribution sample
1076    fn generate_normal_sample(&mut self, n: usize, mean: f64, std: f64) -> StatsResult<Array1<F>> {
1077        let mut sample = Array1::zeros(n);
1078
1079        for i in 0..n {
1080            let u1 = self.rng.random::<f64>();
1081            let u2 = self.rng.random::<f64>();
1082            let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
1083            sample[i] = F::from(mean + std * z).expect("Failed to convert to float");
1084        }
1085
1086        Ok(sample)
1087    }
1088
1089    /// Generate exponential distribution sample
1090    fn generate_exponential_sample(&mut self, n: usize, rate: f64) -> StatsResult<Array1<F>> {
1091        let mut sample = Array1::zeros(n);
1092
1093        for i in 0..n {
1094            let u = self.rng.random::<f64>();
1095            let x = -u.ln() / rate;
1096            sample[i] = F::from(x).expect("Failed to convert to float");
1097        }
1098
1099        Ok(sample)
1100    }
1101
1102    /// Generate gamma distribution sample
1103    ///
1104    /// Uses [`Self::sample_standard_gamma`] (Marsaglia & Tsang's method,
1105    /// exact rather than a normal approximation) and applies the `scale`
1106    /// parameter via `Gamma(shape, scale) = scale * Gamma(shape, 1)`.
1107    fn generate_gamma_sample(
1108        &mut self,
1109        n: usize,
1110        shape: f64,
1111        scale: f64,
1112    ) -> StatsResult<Array1<F>> {
1113        if shape <= 0.0 || !shape.is_finite() {
1114            return Err(StatsError::InvalidArgument(format!(
1115                "Gamma shape parameter must be positive and finite, got {shape}"
1116            )));
1117        }
1118        if scale <= 0.0 || !scale.is_finite() {
1119            return Err(StatsError::InvalidArgument(format!(
1120                "Gamma scale parameter must be positive and finite, got {scale}"
1121            )));
1122        }
1123
1124        let mut sample = Array1::zeros(n);
1125        for i in 0..n {
1126            let g = self.sample_standard_gamma(shape);
1127            sample[i] = F::from(g * scale).expect("Failed to convert to float");
1128        }
1129
1130        Ok(sample)
1131    }
1132
1133    /// Generate a single `Gamma(shape, 1)` variate.
1134    ///
1135    /// Uses Marsaglia & Tsang's squeeze method ("A Simple Method for
1136    /// Generating Gamma Variables", ACM TOMS 2000), which is exact (not an
1137    /// approximation) for `shape >= 1`. For `0 < shape < 1` this applies
1138    /// the standard boosting transform
1139    /// `Gamma(shape) = Gamma(shape + 1) * U^(1/shape)` for `U ~ Uniform(0, 1)`
1140    /// (a direct consequence of the Gamma distribution's density; see e.g.
1141    /// Marsaglia & Tsang section 1, or Devroye's *Non-Uniform Random
1142    /// Variate Generation*, Ch. IX.3.3).
1143    fn sample_standard_gamma(&mut self, shape: f64) -> f64 {
1144        if shape < 1.0 {
1145            let u: f64 = self.rng.random::<f64>().max(f64::MIN_POSITIVE);
1146            return self.sample_standard_gamma(shape + 1.0) * u.powf(1.0 / shape);
1147        }
1148
1149        let d = shape - 1.0 / 3.0;
1150        let c = 1.0 / (9.0 * d).sqrt();
1151
1152        loop {
1153            let (x, v) = loop {
1154                // Standard normal deviate via Box-Muller.
1155                let u1 = self.rng.random::<f64>().max(f64::MIN_POSITIVE);
1156                let u2 = self.rng.random::<f64>();
1157                let x = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
1158                let v = (1.0 + c * x).powi(3);
1159                if v > 0.0 {
1160                    break (x, v);
1161                }
1162            };
1163
1164            let u: f64 = self.rng.random::<f64>();
1165
1166            // Squeeze test (cheap, avoids the log() below on most draws).
1167            if u < 1.0 - 0.0331 * x.powi(4) {
1168                return d * v;
1169            }
1170            // Full acceptance test.
1171            if u.ln() < 0.5 * x * x + d * (1.0 - v + v.ln()) {
1172                return d * v;
1173            }
1174        }
1175    }
1176
1177    /// Generate beta distribution sample
1178    ///
1179    /// Uses the Gamma-ratio construction: if `X ~ Gamma(alpha, 1)` and
1180    /// `Y ~ Gamma(beta, 1)` are independent, then `X / (X + Y) ~
1181    /// Beta(alpha, beta)` exactly (a standard result, see e.g. Devroye
1182    /// Ch. IX.4.1). The previous implementation instead formed
1183    /// `U1^(1/alpha) / (U1^(1/alpha) + U2^(1/beta))` from two independent
1184    /// `Uniform(0,1)` draws -- `U^(1/alpha)` is a `Beta(alpha, 1)` variate,
1185    /// not a `Gamma(alpha, 1)` variate, so that ratio does not follow
1186    /// `Beta(alpha, beta)` in general (only degenerate/coincidental cases
1187    /// match).
1188    fn generate_beta_sample(&mut self, n: usize, alpha: f64, beta: f64) -> StatsResult<Array1<F>> {
1189        if alpha <= 0.0 || !alpha.is_finite() {
1190            return Err(StatsError::InvalidArgument(format!(
1191                "Beta alpha parameter must be positive and finite, got {alpha}"
1192            )));
1193        }
1194        if beta <= 0.0 || !beta.is_finite() {
1195            return Err(StatsError::InvalidArgument(format!(
1196                "Beta beta parameter must be positive and finite, got {beta}"
1197            )));
1198        }
1199
1200        let mut sample = Array1::zeros(n);
1201        for i in 0..n {
1202            let x = self.sample_standard_gamma(alpha);
1203            let y = self.sample_standard_gamma(beta);
1204            let value = if x + y > 0.0 { x / (x + y) } else { 0.5 };
1205            sample[i] = F::from(value).expect("Failed to convert to float");
1206        }
1207
1208        Ok(sample)
1209    }
1210
1211    /// Optimal block length selection using automatic methods
1212    fn optimal_block_length(&self, n: usize) -> usize {
1213        // Simplified implementation - would use more sophisticated methods in practice
1214        let length = (n as f64).powf(1.0 / 3.0).ceil() as usize;
1215        std::cmp::max(1, std::cmp::min(length, n / 4))
1216    }
1217
1218    /// Compute effective sample size for block bootstrap
1219    fn compute_effective_samplesize(&self, n: usize) -> usize {
1220        let block_length = self
1221            .config
1222            .block_length
1223            .unwrap_or_else(|| self.optimal_block_length(n));
1224
1225        // Approximation for effective sample size
1226        let correlation_factor = 1.0 - (block_length as f64 - 1.0) / (2.0 * n as f64);
1227        (n as f64 * correlation_factor).ceil() as usize
1228    }
1229
1230    /// Compute confidence intervals
1231    fn compute_confidence_intervals(
1232        &self,
1233        bootstrap_samples: &Array1<F>,
1234        original_statistic: F,
1235        _standard_error: F,
1236    ) -> StatsResult<BootstrapConfidenceIntervals<F>> {
1237        let alpha = 1.0 - self.config.confidence_level;
1238        let lower_percentile = alpha / 2.0;
1239        let upper_percentile = 1.0 - alpha / 2.0;
1240
1241        // Sort bootstrap _samples
1242        let mut sorted_samples = bootstrap_samples.to_vec();
1243        sorted_samples.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1244
1245        let n = sorted_samples.len();
1246        let lower_idx = ((n as f64) * lower_percentile).floor() as usize;
1247        let upper_idx = ((n as f64) * upper_percentile).ceil() as usize - 1;
1248
1249        // Percentile method
1250        let percentile = (
1251            sorted_samples[lower_idx],
1252            sorted_samples[upper_idx.min(n - 1)],
1253        );
1254
1255        // Basic bootstrap method
1256        let basic = (
1257            F::from(2.0).expect("Failed to convert constant to float") * original_statistic
1258                - sorted_samples[upper_idx.min(n - 1)],
1259            F::from(2.0).expect("Failed to convert constant to float") * original_statistic
1260                - sorted_samples[lower_idx],
1261        );
1262
1263        // Bias-corrected intervals (simplified)
1264        let bias_corrected = if self.config.bias_correction {
1265            let bias_correction =
1266                self.compute_bias_correction(bootstrap_samples, original_statistic);
1267            Some((
1268                percentile.0 + bias_correction,
1269                percentile.1 + bias_correction,
1270            ))
1271        } else {
1272            None
1273        };
1274
1275        // BCa intervals (simplified)
1276        let bias_corrected_accelerated = if self.config.acceleration_correction {
1277            // Simplified implementation
1278            bias_corrected
1279        } else {
1280            None
1281        };
1282
1283        Ok(BootstrapConfidenceIntervals {
1284            percentile,
1285            basic,
1286            bias_corrected,
1287            bias_corrected_accelerated,
1288            studentized: None, // Would require additional standard _error estimates
1289        })
1290    }
1291
1292    /// Compute bias correction
1293    fn compute_bias_correction(&self, bootstrap_samples: &Array1<F>, originalstatistic: F) -> F {
1294        let _count_below = bootstrap_samples
1295            .iter()
1296            .filter(|&&x| x < originalstatistic)
1297            .count();
1298
1299        let _proportion = _count_below as f64 / bootstrap_samples.len() as f64;
1300
1301        // Simplified bias correction
1302        let bootstrap_mean = self.compute_mean(bootstrap_samples);
1303        bootstrap_mean - originalstatistic
1304    }
1305
1306    /// Compute diagnostics
1307    fn compute_diagnostics(
1308        &self,
1309        bootstrap_samples: &Array1<F>,
1310        original_statistic: F,
1311    ) -> StatsResult<BootstrapDiagnostics<F>> {
1312        let distribution_stats = self.compute_distribution_stats(bootstrap_samples)?;
1313        let quality_metrics =
1314            self.compute_quality_metrics(bootstrap_samples, original_statistic)?;
1315        let convergence_info = self.compute_convergence_info(bootstrap_samples)?;
1316        let method_specific = HashMap::new(); // Would be populated based on method
1317
1318        Ok(BootstrapDiagnostics {
1319            distribution_stats,
1320            quality_metrics,
1321            convergence_info,
1322            method_specific,
1323        })
1324    }
1325
1326    /// Compute distribution statistics.
1327    ///
1328    /// `jarque_bera`/`jarque_bera_p_value` and `anderson_darling` were
1329    /// previously hardcoded to `F::zero()` ("Simplified") regardless of
1330    /// the bootstrap distribution's actual shape -- i.e. every bootstrap
1331    /// run, however skewed or heavy-tailed, silently reported perfectly
1332    /// normal-looking diagnostics. Both are now real, computed normality
1333    /// diagnostics; see their doc comments on `BootstrapDistributionStats`
1334    /// for the exact formulas.
1335    fn compute_distribution_stats(
1336        &self,
1337        samples: &Array1<F>,
1338    ) -> StatsResult<BootstrapDistributionStats<F>> {
1339        let mean = self.compute_mean(samples);
1340        let std = self.compute_std(samples);
1341        let n = samples.len();
1342        let n_f = F::from(n).expect("Operation failed");
1343
1344        // Skewness
1345        let skewness = if std > F::zero() {
1346            let skew_sum = samples
1347                .iter()
1348                .map(|&x| {
1349                    let z = (x - mean) / std;
1350                    z * z * z
1351                })
1352                .fold(F::zero(), |acc, x| acc + x);
1353            skew_sum / n_f
1354        } else {
1355            F::zero()
1356        };
1357
1358        // Kurtosis (excess kurtosis: 4th standardized moment minus 3, so
1359        // a normal distribution has `kurtosis == 0`).
1360        let kurtosis = if std > F::zero() {
1361            let kurt_sum = samples
1362                .iter()
1363                .map(|&x| {
1364                    let z = (x - mean) / std;
1365                    z * z * z * z
1366                })
1367                .fold(F::zero(), |acc, x| acc + x);
1368            kurt_sum / n_f - F::from(3.0).expect("Failed to convert constant to float")
1369        } else {
1370            F::zero()
1371        };
1372
1373        // Jarque-Bera test statistic: JB = (n/6) * (S^2 + K^2/4), where S
1374        // is the skewness and K the excess kurtosis computed above.
1375        // Asymptotically chi-square(2)-distributed under the null
1376        // hypothesis that the bootstrap distribution is normal, so the
1377        // p-value is the chi-square(2) survival function `1 - cdf(JB)`
1378        // (same "real CDF, not a closed-form-looking but invalid
1379        // substitute" pattern as `regression::stat_tests::f_test_p_value`).
1380        // A degenerate (zero-variance) bootstrap distribution has no
1381        // meaningful shape to test, so its statistic/p-value are the
1382        // "no evidence against normality" values 0 / 1.
1383        let jarque_bera = if std > F::zero() {
1384            (n_f / F::from(6.0).expect("Operation failed"))
1385                * (skewness * skewness
1386                    + (kurtosis * kurtosis) / F::from(4.0).expect("Operation failed"))
1387        } else {
1388            F::zero()
1389        };
1390        let jarque_bera_p_value = if std > F::zero() {
1391            let two = F::from(2.0).expect("Operation failed");
1392            match crate::distributions::chi_square::ChiSquare::new(two, F::zero(), F::one()) {
1393                Ok(dist) => {
1394                    let p = F::one() - dist.cdf(jarque_bera);
1395                    if p < F::zero() {
1396                        F::zero()
1397                    } else if p > F::one() {
1398                        F::one()
1399                    } else {
1400                        p
1401                    }
1402                }
1403                Err(_) => F::one(),
1404            }
1405        } else {
1406            F::one()
1407        };
1408
1409        // Anderson-Darling statistic (goodness-of-fit test for
1410        // normality):
1411        //   A^2 = -n - (1/n) * sum_{i=1}^n (2i - 1) *
1412        //           [ln(Phi(z_(i))) + ln(1 - Phi(z_(n+1-i)))]
1413        // where `z_(i)` are the standardized (via the sample mean/std)
1414        // values sorted ascending and `Phi` is the standard normal CDF.
1415        // Larger values indicate greater departure from normality. This
1416        // is the classical (small-sample-uncorrected) statistic; unlike
1417        // `jarque_bera` above, no p-value is computed here, since
1418        // accurate Anderson-Darling tail probabilities require an
1419        // asymptotic-distribution table beyond a simple closed-form CDF.
1420        let anderson_darling = if std > F::zero() && n >= 2 {
1421            let mut sorted: Vec<F> = samples.iter().copied().collect();
1422            sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1423
1424            match crate::distributions::normal::Normal::new(F::zero(), F::one()) {
1425                Ok(standard_normal) => {
1426                    // Clamp CDF values away from the {0, 1} boundary so
1427                    // `ln` never sees exactly 0 (which would produce
1428                    // -inf/NaN for a sample's most extreme order
1429                    // statistics).
1430                    let eps = F::from(1e-300).expect("Operation failed");
1431                    let one_minus_eps = F::one() - eps;
1432                    let clamp = |p: F| p.max(eps).min(one_minus_eps);
1433
1434                    let mut sum = F::zero();
1435                    for i in 0..n {
1436                        let z_i = (sorted[i] - mean) / std;
1437                        let z_rev = (sorted[n - 1 - i] - mean) / std;
1438                        let phi_i = clamp(standard_normal.cdf(z_i));
1439                        let phi_rev = clamp(standard_normal.cdf(z_rev));
1440                        let weight = F::from(2 * (i + 1) - 1).expect("Operation failed");
1441                        sum = sum + weight * (phi_i.ln() + (F::one() - phi_rev).ln());
1442                    }
1443                    -n_f - sum / n_f
1444                }
1445                Err(_) => F::zero(),
1446            }
1447        } else {
1448            F::zero()
1449        };
1450
1451        // Min and max
1452        let min_value = samples.iter().copied().fold(F::infinity(), F::min);
1453        let max_value = samples.iter().copied().fold(F::neg_infinity(), F::max);
1454
1455        Ok(BootstrapDistributionStats {
1456            skewness,
1457            kurtosis,
1458            jarque_bera,
1459            jarque_bera_p_value,
1460            anderson_darling,
1461            min_value,
1462            max_value,
1463        })
1464    }
1465
1466    /// Compute quality metrics
1467    fn compute_quality_metrics(
1468        &self,
1469        samples: &Array1<F>,
1470        _original_statistic: F,
1471    ) -> StatsResult<QualityMetrics<F>> {
1472        let std_error = self.compute_std(samples);
1473        let mc_std_error =
1474            std_error / F::from((samples.len() as f64).sqrt()).expect("Operation failed");
1475
1476        Ok(QualityMetrics {
1477            mc_standard_error: mc_std_error,
1478            coverage_probability: F::from(self.config.confidence_level)
1479                .expect("Failed to convert to float"),
1480            efficiency: None,    // Would require analytical comparison
1481            stability: F::one(), // Simplified
1482        })
1483    }
1484
1485    /// Compute convergence information
1486    fn compute_convergence_info(&self, samples: &Array1<F>) -> StatsResult<ConvergenceInfo<F>> {
1487        // Simplified convergence assessment
1488        let converged = samples.len() >= 100; // Simple threshold
1489
1490        Ok(ConvergenceInfo {
1491            converged,
1492            convergence_samplesize: if converged { Some(samples.len()) } else { None },
1493            mean_stability: F::one(),     // Simplified
1494            variance_stability: F::one(), // Simplified
1495        })
1496    }
1497
1498    /// Compute mean
1499    fn compute_mean(&self, data: &Array1<F>) -> F {
1500        if data.is_empty() {
1501            F::zero()
1502        } else {
1503            data.sum() / F::from(data.len()).expect("Operation failed")
1504        }
1505    }
1506
1507    /// Compute standard deviation
1508    fn compute_std(&self, data: &Array1<F>) -> F {
1509        if data.len() <= 1 {
1510            return F::zero();
1511        }
1512
1513        let mean = self.compute_mean(data);
1514        let variance = data
1515            .iter()
1516            .map(|&x| (x - mean) * (x - mean))
1517            .fold(F::zero(), |acc, x| acc + x)
1518            / F::from(data.len() - 1).expect("Operation failed");
1519
1520        variance.sqrt()
1521    }
1522}
1523
1524/// Convenience function for stratified bootstrap
1525#[allow(dead_code)]
1526pub fn stratified_bootstrap<F, T>(
1527    data: &ArrayView1<F>,
1528    strata: &[usize],
1529    statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1530    config: Option<AdvancedBootstrapConfig>,
1531) -> StatsResult<AdvancedBootstrapResult<F>>
1532where
1533    F: Float
1534        + NumCast
1535        + SimdUnifiedOps
1536        + Zero
1537        + One
1538        + FromPrimitive
1539        + Copy
1540        + Send
1541        + Sync
1542        + std::fmt::Display
1543        + 'static,
1544    T: Into<F> + Copy + Send + Sync,
1545{
1546    let mut config = config.unwrap_or_default();
1547    config.bootstrap_type = BootstrapType::Stratified {
1548        strata: strata.to_vec(),
1549    };
1550
1551    let mut processor = AdvancedBootstrapProcessor::new(config);
1552    processor.bootstrap(data, statistic_fn)
1553}
1554
1555/// Convenience function for block bootstrap
1556#[allow(dead_code)]
1557pub fn block_bootstrap<F, T>(
1558    data: &ArrayView1<F>,
1559    block_type: BlockType,
1560    statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1561    config: Option<AdvancedBootstrapConfig>,
1562) -> StatsResult<AdvancedBootstrapResult<F>>
1563where
1564    F: Float
1565        + NumCast
1566        + SimdUnifiedOps
1567        + Zero
1568        + One
1569        + FromPrimitive
1570        + Copy
1571        + Send
1572        + Sync
1573        + std::fmt::Display
1574        + 'static,
1575    T: Into<F> + Copy + Send + Sync,
1576{
1577    let mut config = config.unwrap_or_default();
1578    config.bootstrap_type = BootstrapType::Block { block_type };
1579
1580    let mut processor = AdvancedBootstrapProcessor::new(config);
1581    processor.bootstrap(data, statistic_fn)
1582}
1583
1584/// Convenience function for moving block bootstrap
1585#[allow(dead_code)]
1586pub fn moving_block_bootstrap<F, T>(
1587    data: &ArrayView1<F>,
1588    statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1589    block_length: Option<usize>,
1590    n_bootstrap: Option<usize>,
1591) -> StatsResult<AdvancedBootstrapResult<F>>
1592where
1593    F: Float
1594        + NumCast
1595        + SimdUnifiedOps
1596        + Zero
1597        + One
1598        + FromPrimitive
1599        + Copy
1600        + Send
1601        + Sync
1602        + std::fmt::Display
1603        + 'static,
1604    T: Into<F> + Copy + Send + Sync,
1605{
1606    let mut config = AdvancedBootstrapConfig::default();
1607    config.bootstrap_type = BootstrapType::Block {
1608        block_type: BlockType::Moving,
1609    };
1610    config.block_length = block_length;
1611    config.n_bootstrap = n_bootstrap.unwrap_or(1000);
1612
1613    let mut processor = AdvancedBootstrapProcessor::new(config);
1614    processor.bootstrap(data, statistic_fn)
1615}
1616
1617/// Convenience function for circular block bootstrap
1618#[allow(dead_code)]
1619pub fn circular_block_bootstrap<F, T>(
1620    data: &ArrayView1<F>,
1621    statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1622    block_length: Option<usize>,
1623    n_bootstrap: Option<usize>,
1624) -> StatsResult<AdvancedBootstrapResult<F>>
1625where
1626    F: Float
1627        + NumCast
1628        + SimdUnifiedOps
1629        + Zero
1630        + One
1631        + FromPrimitive
1632        + Copy
1633        + Send
1634        + Sync
1635        + std::fmt::Display
1636        + 'static,
1637    T: Into<F> + Copy + Send + Sync,
1638{
1639    let mut config = AdvancedBootstrapConfig::default();
1640    config.bootstrap_type = BootstrapType::Block {
1641        block_type: BlockType::Circular,
1642    };
1643    config.block_length = block_length;
1644    config.n_bootstrap = n_bootstrap.unwrap_or(1000);
1645
1646    let mut processor = AdvancedBootstrapProcessor::new(config);
1647    processor.bootstrap(data, statistic_fn)
1648}
1649
1650/// Convenience function for stationary bootstrap
1651#[allow(dead_code)]
1652pub fn stationary_bootstrap<F, T>(
1653    data: &ArrayView1<F>,
1654    statistic_fn: impl Fn(&ArrayView1<F>) -> StatsResult<T> + Send + Sync + Copy,
1655    expected_block_length: f64,
1656    n_bootstrap: Option<usize>,
1657) -> StatsResult<AdvancedBootstrapResult<F>>
1658where
1659    F: Float
1660        + NumCast
1661        + SimdUnifiedOps
1662        + Zero
1663        + One
1664        + FromPrimitive
1665        + Copy
1666        + Send
1667        + Sync
1668        + std::fmt::Display
1669        + 'static,
1670    T: Into<F> + Copy + Send + Sync,
1671{
1672    let mut config = AdvancedBootstrapConfig::default();
1673    config.bootstrap_type = BootstrapType::Block {
1674        block_type: BlockType::Stationary {
1675            expected_length: expected_block_length,
1676        },
1677    };
1678    config.n_bootstrap = n_bootstrap.unwrap_or(1000);
1679
1680    let mut processor = AdvancedBootstrapProcessor::new(config);
1681    processor.bootstrap(data, statistic_fn)
1682}
1683
1684#[path = "advanced_bootstrap_tests.rs"]
1685#[cfg(test)]
1686mod tests;