use fugue::inference::abc::{abc_smc_weighted, ABCSMCConfig, EuclideanDistance};
use fugue::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
fn model() -> Model<f64> {
sample(addr!("mu"), Normal::new(0.0, 1.0).unwrap())
.bind(|mu| sample(addr!("y_sim"), Normal::new(mu, 0.5).unwrap()).map(move |_| mu))
}
const POSTERIOR_MEAN: f64 = 1.2;
const POSTERIOR_SD: f64 = 0.4472135954999579;
fn main() {
println!("=== Approximate Bayesian Computation (ABC) Inference ===\n");
let observed: Vec<f64> = vec![1.5];
let mut rng = StdRng::seed_from_u64(7);
let config = ABCSMCConfig {
initial_tolerance: 2.0,
tolerance_schedule: vec![1.0, 0.5, 0.25, 0.1],
particles_per_round: 500,
};
let result = abc_smc_weighted(
&mut rng,
model,
|trace| vec![trace.get_f64(&addr!("y_sim")).unwrap()],
&observed,
&EuclideanDistance,
config,
200_000,
)
.expect("ABC-SMC should complete with this many particles/attempts");
println!("Final tolerance: {}", result.final_tolerance);
println!("Particles: {}", result.particles.len());
let posterior_mean = result
.weighted_mean(&addr!("mu"))
.expect("mu is present in every particle's trace");
let posterior_var: f64 = {
let mut num = 0.0;
let mut den = 0.0;
for p in &result.particles {
let mu = p.trace.get_f64(&addr!("mu")).unwrap();
num += p.weight * (mu - posterior_mean).powi(2);
den += p.weight;
}
num / den
};
println!("Posterior mean(mu) ~= {posterior_mean:.4} (target: {POSTERIOR_MEAN})");
println!(
"Posterior sd(mu) ~= {:.4} (target: {POSTERIOR_SD:.4})",
posterior_var.sqrt()
);
assert!(
(posterior_mean - POSTERIOR_MEAN).abs() < 0.3,
"ABC posterior mean {posterior_mean} too far from target {POSTERIOR_MEAN}"
);
assert!(
posterior_var.sqrt() < POSTERIOR_SD * 2.0,
"ABC posterior sd {} implausibly larger than target {POSTERIOR_SD}",
posterior_var.sqrt()
);
println!("\nABC-SMC approximated the target posterior within tolerance.");
}