1use crate::addr;
7use crate::core::distribution::*;
8
9use crate::inference::mcmc_utils::effective_sample_size_mcmc;
10use crate::runtime::trace::{ChoiceValue, Trace};
11use rand::Rng;
12
13pub fn ks_test_distribution<R: Rng>(
18 rng: &mut R,
19 dist: &dyn Distribution<f64>,
20 reference_samples: &[f64],
21 n_samples: usize,
22 alpha: f64,
23) -> bool {
24 let mut our_samples = Vec::with_capacity(n_samples);
26 for _ in 0..n_samples {
27 our_samples.push(dist.sample(rng));
28 }
29
30 our_samples.sort_by(|a, b| a.partial_cmp(b).unwrap());
32 let mut ref_sorted = reference_samples.to_vec();
33 ref_sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
34
35 let ks_stat = ks_statistic(&our_samples, &ref_sorted);
37
38 let n1 = our_samples.len() as f64;
40 let n2 = ref_sorted.len() as f64;
41 let critical_value = (-0.5 * alpha.ln()).sqrt() * ((n1 + n2) / (n1 * n2)).sqrt();
42
43 ks_stat < critical_value
44}
45
46fn ks_statistic(sample1: &[f64], sample2: &[f64]) -> f64 {
48 let n1 = sample1.len() as f64;
49 let n2 = sample2.len() as f64;
50
51 let mut max_diff: f64 = 0.0;
52 let mut i1 = 0;
53 let mut i2 = 0;
54
55 while i1 < sample1.len() && i2 < sample2.len() {
56 let cdf1 = (i1 + 1) as f64 / n1;
57 let cdf2 = (i2 + 1) as f64 / n2;
58
59 max_diff = max_diff.max((cdf1 - cdf2).abs());
60
61 if sample1[i1] <= sample2[i2] {
62 i1 += 1;
63 } else {
64 i2 += 1;
65 }
66 }
67
68 max_diff
69}
70
71#[derive(Debug, Clone)]
73pub struct ConjugateNormalConfig {
74 pub prior_mu: f64,
76 pub prior_sigma: f64,
78 pub likelihood_sigma: f64,
80 pub observation: f64,
82 pub n_samples: usize,
84 pub n_warmup: usize,
86}
87
88pub fn test_conjugate_normal_model<R: Rng>(
93 rng: &mut R,
94 mcmc_fn: impl Fn(&mut R, usize, usize) -> Vec<(f64, Trace)>,
95 config: ConjugateNormalConfig,
96) -> ValidationResult {
97 let prior_precision = 1.0 / (config.prior_sigma * config.prior_sigma);
99 let likelihood_precision = 1.0 / (config.likelihood_sigma * config.likelihood_sigma);
100
101 let posterior_precision = prior_precision + likelihood_precision;
102 let posterior_variance = 1.0 / posterior_precision;
103 let posterior_mu = posterior_variance
104 * (prior_precision * config.prior_mu + likelihood_precision * config.observation);
105
106 let samples = mcmc_fn(rng, config.n_samples, config.n_warmup);
107 validate_against_analytical_posterior(
108 &samples,
109 &addr!("mu"),
110 posterior_mu,
111 posterior_variance,
112 config.n_samples,
113 )
114}
115
116#[derive(Debug, Clone)]
122pub struct ConjugateBetaBernoulliConfig {
123 pub prior_alpha: f64,
125 pub prior_beta: f64,
127 pub observations: Vec<bool>,
129 pub n_samples: usize,
131 pub n_warmup: usize,
133}
134
135pub fn test_conjugate_beta_bernoulli_model<R: Rng>(
145 rng: &mut R,
146 mcmc_fn: impl Fn(&mut R, usize, usize) -> Vec<(f64, Trace)>,
147 config: ConjugateBetaBernoulliConfig,
148) -> ValidationResult {
149 let successes = config.observations.iter().filter(|&&b| b).count() as f64;
150 let n = config.observations.len() as f64;
151 let post_alpha = config.prior_alpha + successes;
152 let post_beta = config.prior_beta + (n - successes);
153 let post_sum = post_alpha + post_beta;
154
155 let posterior_mu = post_alpha / post_sum;
156 let posterior_variance = (post_alpha * post_beta) / (post_sum * post_sum * (post_sum + 1.0));
157
158 let samples = mcmc_fn(rng, config.n_samples, config.n_warmup);
159 validate_against_analytical_posterior(
160 &samples,
161 &addr!("theta"),
162 posterior_mu,
163 posterior_variance,
164 config.n_samples,
165 )
166}
167
168fn validate_against_analytical_posterior(
174 samples: &[(f64, Trace)],
175 address: &crate::core::address::Address,
176 posterior_mu: f64,
177 posterior_variance: f64,
178 n_samples: usize,
179) -> ValidationResult {
180 let posterior_sigma = posterior_variance.sqrt();
181
182 let param_samples: Vec<f64> = samples
183 .iter()
184 .filter_map(|(_, trace)| trace.choices.get(address))
185 .filter_map(|choice| match choice.value {
186 ChoiceValue::F64(val) => Some(val),
187 _ => None,
188 })
189 .collect();
190
191 if param_samples.is_empty() {
192 return ValidationResult::Failed("No samples extracted".to_string());
193 }
194
195 let sample_mean = param_samples.iter().sum::<f64>() / param_samples.len() as f64;
197 let sample_var = param_samples
198 .iter()
199 .map(|&x| (x - sample_mean).powi(2))
200 .sum::<f64>()
201 / (param_samples.len() - 1) as f64;
202 let sample_sigma = sample_var.sqrt();
203
204 let ess = effective_sample_size_mcmc(¶m_samples);
206
207 let se_mean = posterior_sigma / (ess.sqrt());
209 let se_var = posterior_variance * (2.0 / ess).sqrt();
210
211 let mean_error = (sample_mean - posterior_mu).abs();
212 let var_error = (sample_var - posterior_variance).abs();
213
214 let mean_ok = mean_error < 2.0 * se_mean;
215 let var_ok = var_error < 2.0 * se_var;
216 let ess_ok = ess > n_samples as f64 * 0.1; ValidationResult::Success {
219 mean_error,
220 var_error,
221 effective_sample_size: ess,
222 mean_within_bounds: mean_ok,
223 var_within_bounds: var_ok,
224 ess_adequate: ess_ok,
225 posterior_mu,
226 posterior_sigma,
227 sample_mean,
228 sample_sigma,
229 }
230}
231
232#[derive(Debug)]
234pub enum ValidationResult {
235 Success {
236 mean_error: f64,
237 var_error: f64,
238 effective_sample_size: f64,
239 mean_within_bounds: bool,
240 var_within_bounds: bool,
241 ess_adequate: bool,
242 posterior_mu: f64,
243 posterior_sigma: f64,
244 sample_mean: f64,
245 sample_sigma: f64,
246 },
247 Failed(String),
248}
249
250impl ValidationResult {
251 pub fn is_valid(&self) -> bool {
252 match self {
253 ValidationResult::Success {
254 mean_within_bounds,
255 var_within_bounds,
256 ess_adequate,
257 ..
258 } => *mean_within_bounds && *var_within_bounds && *ess_adequate,
259 ValidationResult::Failed(_) => false,
260 }
261 }
262
263 pub fn print_summary(&self) {
264 match self {
265 ValidationResult::Success {
266 mean_error,
267 var_error,
268 effective_sample_size,
269 posterior_mu,
270 posterior_sigma,
271 sample_mean,
272 sample_sigma,
273 mean_within_bounds,
274 var_within_bounds,
275 ess_adequate,
276 } => {
277 println!("Validation Results:");
278 println!(
279 " True posterior: N({:.4}, {:.4})",
280 posterior_mu, posterior_sigma
281 );
282 println!(
283 " Sample estimates: N({:.4}, {:.4})",
284 sample_mean, sample_sigma
285 );
286 println!(
287 " Mean error: {:.6} ({})",
288 mean_error,
289 if *mean_within_bounds { "PASS" } else { "FAIL" }
290 );
291 println!(
292 " Var error: {:.6} ({})",
293 var_error,
294 if *var_within_bounds { "PASS" } else { "FAIL" }
295 );
296 println!(
297 " ESS: {:.1} ({})",
298 effective_sample_size,
299 if *ess_adequate { "PASS" } else { "FAIL" }
300 );
301 println!(
302 " Overall: {}",
303 if self.is_valid() { "PASS" } else { "FAIL" }
304 );
305 }
306 ValidationResult::Failed(msg) => {
307 println!("Validation FAILED: {}", msg);
308 }
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests_more {
315 use super::*;
316 use rand::rngs::StdRng;
317 use rand::SeedableRng;
318
319 #[test]
320 fn ks_test_edge_thresholds_and_print_summary() {
321 let mut rng = StdRng::seed_from_u64(50);
322 let normal = Normal::new(0.0, 1.0).unwrap();
323 let ref_samples: Vec<f64> = (0..200).map(|_| normal.sample(&mut rng)).collect();
324 let ok = ks_test_distribution(&mut rng, &normal, &ref_samples, 200, 0.05);
325 assert!(ok);
326
327 let res = ValidationResult::Success {
329 mean_error: 0.0,
330 var_error: 0.0,
331 effective_sample_size: 10.0,
332 mean_within_bounds: true,
333 var_within_bounds: true,
334 ess_adequate: true,
335 posterior_mu: 0.0,
336 posterior_sigma: 1.0,
337 sample_mean: 0.0,
338 sample_sigma: 1.0,
339 };
340 res.print_summary();
341 assert!(res.is_valid());
342 }
343}
344
345#[cfg(test)]
346mod validation_tests {
347 use super::*;
348 use rand::rngs::StdRng;
349 use rand::SeedableRng;
350
351 #[test]
352 fn test_normal_distribution() {
353 let mut rng = StdRng::seed_from_u64(42);
354 let normal = Normal::new(0.0, 1.0).unwrap();
355
356 let reference: Vec<f64> = (0..1000)
358 .map(|_| {
359 let u1: f64 = rng.gen();
360 let u2: f64 = rng.gen();
361 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
362 })
363 .collect();
364
365 let is_valid = ks_test_distribution(&mut rng, &normal, &reference, 1000, 0.05);
366 assert!(is_valid, "Normal distribution failed KS test");
367 }
368}