use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
pub struct StationaryBootstrap {
expected_block_length: f64,
rng: ChaCha8Rng,
}
impl StationaryBootstrap {
pub fn new(expected_block_length: f64, seed: Option<u64>) -> Self {
let rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => ChaCha8Rng::from_entropy(),
};
Self {
expected_block_length: expected_block_length.max(1.0),
rng,
}
}
pub fn sample(&mut self, data: &[f64], length: usize) -> Vec<f64> {
if data.is_empty() || length == 0 {
return vec![];
}
let n = data.len();
let q = 1.0 / self.expected_block_length;
let mut result = Vec::with_capacity(length);
let mut current_idx = self.rng.gen_range(0..n);
for _ in 0..length {
result.push(data[current_idx]);
if self.rng.gen::<f64>() < q {
current_idx = self.rng.gen_range(0..n);
} else {
current_idx = (current_idx + 1) % n;
}
}
result
}
pub fn samples(&mut self, data: &[f64], length: usize, n_samples: usize) -> Vec<Vec<f64>> {
(0..n_samples).map(|_| self.sample(data, length)).collect()
}
}
pub struct CircularBlockBootstrap {
block_length: usize,
rng: ChaCha8Rng,
}
impl CircularBlockBootstrap {
pub fn new(block_length: usize, seed: Option<u64>) -> Self {
let rng = match seed {
Some(s) => ChaCha8Rng::seed_from_u64(s),
None => ChaCha8Rng::from_entropy(),
};
Self {
block_length: block_length.max(1),
rng,
}
}
pub fn sample(&mut self, data: &[f64], length: usize) -> Vec<f64> {
if data.is_empty() || length == 0 {
return vec![];
}
let n = data.len();
let mut result = Vec::with_capacity(length);
while result.len() < length {
let start = self.rng.gen_range(0..n);
for offset in 0..self.block_length {
if result.len() >= length {
break;
}
let idx = (start + offset) % n;
result.push(data[idx]);
}
}
result
}
pub fn samples(&mut self, data: &[f64], length: usize, n_samples: usize) -> Vec<Vec<f64>> {
(0..n_samples).map(|_| self.sample(data, length)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stationary_bootstrap_length() {
let data: Vec<f64> = (0..100).map(|x| x as f64).collect();
let mut bootstrap = StationaryBootstrap::new(5.0, Some(42));
let sample = bootstrap.sample(&data, 50);
assert_eq!(sample.len(), 50);
}
#[test]
fn test_stationary_bootstrap_reproducibility() {
let data: Vec<f64> = (0..100).map(|x| x as f64).collect();
let sample1 = StationaryBootstrap::new(5.0, Some(42)).sample(&data, 50);
let sample2 = StationaryBootstrap::new(5.0, Some(42)).sample(&data, 50);
assert_eq!(sample1, sample2);
}
#[test]
fn test_stationary_bootstrap_empty_data() {
let data: Vec<f64> = vec![];
let mut bootstrap = StationaryBootstrap::new(5.0, Some(42));
let sample = bootstrap.sample(&data, 10);
assert!(sample.is_empty());
}
#[test]
fn test_circular_block_bootstrap_length() {
let data: Vec<f64> = (0..100).map(|x| x as f64).collect();
let mut bootstrap = CircularBlockBootstrap::new(10, Some(42));
let sample = bootstrap.sample(&data, 50);
assert_eq!(sample.len(), 50);
}
#[test]
fn test_circular_block_bootstrap_multiple_samples() {
let data: Vec<f64> = (0..50).map(|x| x as f64).collect();
let mut bootstrap = CircularBlockBootstrap::new(5, Some(42));
let samples = bootstrap.samples(&data, 30, 100);
assert_eq!(samples.len(), 100);
for sample in &samples {
assert_eq!(sample.len(), 30);
}
}
}