use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum BootstrapStatistic {
#[default]
Mean,
Median,
Std,
Var,
Percentile,
Min,
Max,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BootstrapConfig {
#[serde(default = "default_iterations")]
pub iterations: usize,
#[serde(default = "default_confidence_levels")]
pub confidence_levels: Vec<f64>,
pub seed: Option<u64>,
#[serde(default)]
pub data: Vec<f64>,
#[serde(default)]
pub statistic: BootstrapStatistic,
#[serde(default = "default_percentile")]
pub percentile_value: f64,
}
const fn default_iterations() -> usize {
10000
}
fn default_confidence_levels() -> Vec<f64> {
vec![0.90, 0.95, 0.99]
}
const fn default_percentile() -> f64 {
50.0
}
impl Default for BootstrapConfig {
fn default() -> Self {
Self {
iterations: 10000,
confidence_levels: vec![0.90, 0.95, 0.99],
seed: None,
data: Vec::new(),
statistic: BootstrapStatistic::Mean,
percentile_value: 50.0,
}
}
}
impl BootstrapConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_data(mut self, data: Vec<f64>) -> Self {
self.data = data;
self
}
#[must_use]
pub const fn with_iterations(mut self, iterations: usize) -> Self {
self.iterations = iterations;
self
}
#[must_use]
pub fn with_confidence_levels(mut self, levels: Vec<f64>) -> Self {
self.confidence_levels = levels;
self
}
#[must_use]
pub const fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
#[must_use]
pub const fn with_statistic(mut self, stat: BootstrapStatistic) -> Self {
self.statistic = stat;
self
}
#[must_use]
pub const fn with_percentile(mut self, percentile: f64) -> Self {
self.statistic = BootstrapStatistic::Percentile;
self.percentile_value = percentile;
self
}
pub fn validate(&self) -> Result<(), String> {
if self.data.is_empty() {
return Err("Data cannot be empty".to_string());
}
if self.data.len() < 2 {
return Err("Data must have at least 2 observations".to_string());
}
if self.iterations == 0 {
return Err("Iterations must be positive".to_string());
}
if self.confidence_levels.is_empty() {
return Err("At least one confidence level required".to_string());
}
for level in &self.confidence_levels {
if *level <= 0.0 || *level >= 1.0 {
return Err(format!("Confidence level {level} must be between 0 and 1"));
}
}
if self.statistic == BootstrapStatistic::Percentile
&& (self.percentile_value <= 0.0 || self.percentile_value >= 100.0)
{
return Err(format!(
"Percentile {} must be between 0 and 100",
self.percentile_value
));
}
Ok(())
}
}
#[cfg(test)]
mod config_tests {
use super::*;
#[test]
fn test_config_validation() {
let config = BootstrapConfig::new()
.with_data(vec![1.0, 2.0, 3.0, 4.0, 5.0])
.with_iterations(1000);
assert!(config.validate().is_ok());
}
#[test]
fn test_empty_data_rejected() {
let config = BootstrapConfig::new();
assert!(config.validate().is_err());
}
#[test]
fn test_single_observation_rejected() {
let config = BootstrapConfig::new().with_data(vec![1.0]);
assert!(config.validate().is_err());
}
#[test]
fn test_invalid_confidence_rejected() {
let config = BootstrapConfig::new()
.with_data(vec![1.0, 2.0, 3.0])
.with_confidence_levels(vec![1.5]);
assert!(config.validate().is_err());
}
#[test]
fn test_invalid_percentile_rejected() {
let config = BootstrapConfig::new()
.with_data(vec![1.0, 2.0, 3.0])
.with_percentile(150.0);
assert!(config.validate().is_err());
}
}