mollendorff_forge/bootstrap/
config.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum BootstrapStatistic {
11 #[default]
13 Mean,
14 Median,
16 Std,
18 Var,
20 Percentile,
22 Min,
24 Max,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct BootstrapConfig {
31 #[serde(default = "default_iterations")]
33 pub iterations: usize,
34 #[serde(default = "default_confidence_levels")]
36 pub confidence_levels: Vec<f64>,
37 pub seed: Option<u64>,
39 #[serde(default)]
41 pub data: Vec<f64>,
42 #[serde(default)]
44 pub statistic: BootstrapStatistic,
45 #[serde(default = "default_percentile")]
47 pub percentile_value: f64,
48}
49
50const fn default_iterations() -> usize {
51 10000
52}
53
54fn default_confidence_levels() -> Vec<f64> {
55 vec![0.90, 0.95, 0.99]
56}
57
58const fn default_percentile() -> f64 {
59 50.0
60}
61
62impl Default for BootstrapConfig {
63 fn default() -> Self {
64 Self {
65 iterations: 10000,
66 confidence_levels: vec![0.90, 0.95, 0.99],
67 seed: None,
68 data: Vec::new(),
69 statistic: BootstrapStatistic::Mean,
70 percentile_value: 50.0,
71 }
72 }
73}
74
75impl BootstrapConfig {
76 #[must_use]
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 #[must_use]
84 pub fn with_data(mut self, data: Vec<f64>) -> Self {
85 self.data = data;
86 self
87 }
88
89 #[must_use]
91 pub const fn with_iterations(mut self, iterations: usize) -> Self {
92 self.iterations = iterations;
93 self
94 }
95
96 #[must_use]
98 pub fn with_confidence_levels(mut self, levels: Vec<f64>) -> Self {
99 self.confidence_levels = levels;
100 self
101 }
102
103 #[must_use]
105 pub const fn with_seed(mut self, seed: u64) -> Self {
106 self.seed = Some(seed);
107 self
108 }
109
110 #[must_use]
112 pub const fn with_statistic(mut self, stat: BootstrapStatistic) -> Self {
113 self.statistic = stat;
114 self
115 }
116
117 #[must_use]
119 pub const fn with_percentile(mut self, percentile: f64) -> Self {
120 self.statistic = BootstrapStatistic::Percentile;
121 self.percentile_value = percentile;
122 self
123 }
124
125 pub fn validate(&self) -> Result<(), String> {
133 if self.data.is_empty() {
134 return Err("Data cannot be empty".to_string());
135 }
136
137 if self.data.len() < 2 {
138 return Err("Data must have at least 2 observations".to_string());
139 }
140
141 if self.iterations == 0 {
142 return Err("Iterations must be positive".to_string());
143 }
144
145 if self.confidence_levels.is_empty() {
146 return Err("At least one confidence level required".to_string());
147 }
148
149 for level in &self.confidence_levels {
150 if *level <= 0.0 || *level >= 1.0 {
151 return Err(format!("Confidence level {level} must be between 0 and 1"));
152 }
153 }
154
155 if self.statistic == BootstrapStatistic::Percentile
156 && (self.percentile_value <= 0.0 || self.percentile_value >= 100.0)
157 {
158 return Err(format!(
159 "Percentile {} must be between 0 and 100",
160 self.percentile_value
161 ));
162 }
163
164 Ok(())
165 }
166}
167
168#[cfg(test)]
169mod config_tests {
170 use super::*;
171
172 #[test]
173 fn test_config_validation() {
174 let config = BootstrapConfig::new()
175 .with_data(vec![1.0, 2.0, 3.0, 4.0, 5.0])
176 .with_iterations(1000);
177
178 assert!(config.validate().is_ok());
179 }
180
181 #[test]
182 fn test_empty_data_rejected() {
183 let config = BootstrapConfig::new();
184 assert!(config.validate().is_err());
185 }
186
187 #[test]
188 fn test_single_observation_rejected() {
189 let config = BootstrapConfig::new().with_data(vec![1.0]);
190 assert!(config.validate().is_err());
191 }
192
193 #[test]
194 fn test_invalid_confidence_rejected() {
195 let config = BootstrapConfig::new()
196 .with_data(vec![1.0, 2.0, 3.0])
197 .with_confidence_levels(vec![1.5]); assert!(config.validate().is_err());
200 }
201
202 #[test]
203 fn test_invalid_percentile_rejected() {
204 let config = BootstrapConfig::new()
205 .with_data(vec![1.0, 2.0, 3.0])
206 .with_percentile(150.0); assert!(config.validate().is_err());
209 }
210}