1use 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#[derive(Debug, Clone)]
16pub struct AdvancedBootstrapConfig {
17 pub n_bootstrap: usize,
19 pub seed: Option<u64>,
21 pub bootstrap_type: BootstrapType,
23 pub parallel: bool,
25 pub confidence_level: f64,
27 pub block_length: Option<usize>,
29 pub bias_correction: bool,
31 pub acceleration_correction: bool,
33 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#[derive(Debug, Clone, PartialEq)]
55pub enum BootstrapType {
56 Basic,
58 Stratified {
60 strata: Vec<usize>,
62 },
63 Block {
65 block_type: BlockType,
67 },
68 Bayesian,
70 Wild {
72 distribution: WildDistribution,
74 },
75 Parametric {
77 distribution_params: ParametricBootstrapParams,
79 },
80 Balanced,
82}
83
84#[derive(Debug, Clone, PartialEq)]
86pub enum BlockType {
87 Moving,
89 Circular,
91 NonOverlapping,
93 Stationary {
95 expected_length: f64,
97 },
98 Tapered {
100 taper_function: TaperFunction,
102 },
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum TaperFunction {
108 Linear,
110 Cosine,
112 Exponential { decay_rate: f64 },
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub enum WildDistribution {
119 Rademacher,
121 Mammen,
123 Normal,
125 TwoPoint { prob_positive: f64 },
127}
128
129#[derive(Debug, Clone, PartialEq)]
131pub enum ParametricBootstrapParams {
132 Normal { mean: f64, std: f64 },
134 Exponential { rate: f64 },
136 Gamma { shape: f64, scale: f64 },
138 Beta { alpha: f64, beta: f64 },
140 Custom {
142 name: String,
144 params: HashMap<String, f64>,
146 },
147}
148
149#[derive(Debug, Clone)]
151pub struct AdvancedBootstrapResult<F> {
152 pub bootstrap_samples: Array1<F>,
154 pub original_statistic: F,
156 pub bootstrap_mean: F,
158 pub standard_error: F,
160 pub bias: F,
162 pub confidence_intervals: BootstrapConfidenceIntervals<F>,
164 pub method: BootstrapType,
166 pub n_successful: usize,
168 pub effective_samplesize: Option<usize>,
170 pub diagnostics: BootstrapDiagnostics<F>,
172}
173
174#[derive(Debug, Clone)]
176pub struct BootstrapConfidenceIntervals<F> {
177 pub percentile: (F, F),
179 pub basic: (F, F),
181 pub bias_corrected: Option<(F, F)>,
183 pub bias_corrected_accelerated: Option<(F, F)>,
185 pub studentized: Option<(F, F)>,
187}
188
189#[derive(Debug, Clone)]
191pub struct BootstrapDiagnostics<F> {
192 pub distribution_stats: BootstrapDistributionStats<F>,
194 pub quality_metrics: QualityMetrics<F>,
196 pub convergence_info: ConvergenceInfo<F>,
198 pub method_specific: HashMap<String, F>,
200}
201
202#[derive(Debug, Clone)]
204pub struct BootstrapDistributionStats<F> {
205 pub skewness: F,
207 pub kurtosis: F,
210 pub jarque_bera: F,
215 pub jarque_bera_p_value: F,
220 pub anderson_darling: F,
224 pub min_value: F,
226 pub max_value: F,
228}
229
230#[derive(Debug, Clone)]
232pub struct QualityMetrics<F> {
233 pub mc_standard_error: F,
235 pub coverage_probability: F,
237 pub efficiency: Option<F>,
239 pub stability: F,
241}
242
243#[derive(Debug, Clone)]
245pub struct ConvergenceInfo<F> {
246 pub converged: bool,
248 pub convergence_samplesize: Option<usize>,
250 pub mean_stability: F,
252 pub variance_stability: F,
254}
255
256pub 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 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 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 let original_statistic = statistic_fn(data)?.into();
310
311 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 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 let confidence_intervals = self.compute_confidence_intervals(
338 &bootstrap_samples,
339 original_statistic,
340 standard_error,
341 )?;
342
343 let diagnostics = self.compute_diagnostics(&bootstrap_samples, original_statistic)?;
345
346 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 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 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 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 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 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 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 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 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 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 let mut local_values = Vec::with_capacity(current_chunk_size * n);
474 if capabilities.has_avx2() && n >= 8 {
475 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 let data_f32: Vec<f32> = data
482 .iter()
483 .map(|&x| x.to_f64().expect("Operation failed") as f32)
484 .collect();
485
486 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 for &idx in &local_indices {
497 local_values.push(data[idx].to_f64().expect("Operation failed") as f32);
498 }
499 }
500
501 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 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 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 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 resample_values.clear();
552 if capabilities.has_avx2() && n >= 8 {
553 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 for &idx in &resample_indices {
571 resample_values.push(data[idx]);
572 }
573 }
574
575 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 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 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 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 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 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 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 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); 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 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; resample[pos + i] = data[idx];
745 }
746 pos += copy_length;
747 }
748
749 Ok(resample)
750 }
751
752 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 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 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 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 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; 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 while self.rng.random::<f64>() > p && block_length < n - pos {
811 block_length += 1;
812 }
813
814 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 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 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 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 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 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(); weights[j] = F::from(exp_sample).expect("Failed to convert to float");
910 weight_sum = weight_sum + weights[j];
911 }
912
913 for j in 0..n {
915 weights[j] = weights[j] / weight_sum;
916 }
917
918 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 }
925
926 bootstrap_samples[i] = statistic_fn(&resample.view())?.into();
927 }
928
929 Ok(bootstrap_samples)
930 }
931
932 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 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 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 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 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 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 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 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 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 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 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 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 if u < 1.0 - 0.0331 * x.powi(4) {
1168 return d * v;
1169 }
1170 if u.ln() < 0.5 * x * x + d * (1.0 - v + v.ln()) {
1172 return d * v;
1173 }
1174 }
1175 }
1176
1177 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 fn optimal_block_length(&self, n: usize) -> usize {
1213 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 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 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 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 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 let percentile = (
1251 sorted_samples[lower_idx],
1252 sorted_samples[upper_idx.min(n - 1)],
1253 );
1254
1255 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 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 let bias_corrected_accelerated = if self.config.acceleration_correction {
1277 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, })
1290 }
1291
1292 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 let bootstrap_mean = self.compute_mean(bootstrap_samples);
1303 bootstrap_mean - originalstatistic
1304 }
1305
1306 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(); Ok(BootstrapDiagnostics {
1319 distribution_stats,
1320 quality_metrics,
1321 convergence_info,
1322 method_specific,
1323 })
1324 }
1325
1326 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 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 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 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 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 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 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 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, stability: F::one(), })
1483 }
1484
1485 fn compute_convergence_info(&self, samples: &Array1<F>) -> StatsResult<ConvergenceInfo<F>> {
1487 let converged = samples.len() >= 100; Ok(ConvergenceInfo {
1491 converged,
1492 convergence_samplesize: if converged { Some(samples.len()) } else { None },
1493 mean_stability: F::one(), variance_stability: F::one(), })
1496 }
1497
1498 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 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#[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#[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#[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#[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#[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;