use fugue_evo::prelude::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
#[derive(Clone)]
struct Quadratic {
center: f64,
}
impl Fitness for Quadratic {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &RealVector) -> f64 {
-0.5 * genome
.genes()
.iter()
.map(|x| (x - self.center).powi(2))
.sum::<f64>()
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Evolution as Bayesian Inference ===\n");
let mut rng = StdRng::seed_from_u64(20260710);
const DIM: usize = 2;
let center = 3.0;
let sigma0 = 2.0;
let fitness = Quadratic { center };
let bounds = MultiBounds::symmetric(30.0, DIM);
let smc = EvolutionarySMC::new(fitness.clone(), bounds.clone(), 3000)
.with_prior(Prior::Gaussian {
mean: 0.0,
std: sigma0,
})
.with_beta_schedule(EvolutionarySMC::<RealVector, Quadratic>::linear_beta_schedule(16))
.with_mcmc_steps(6)
.with_mutation(0.9, 0.6)
.with_crossover(true);
println!(
"Running tempered SMC: prior N(0, {:.1}²) ⇒ Boltzmann posterior over a",
sigma0
);
println!(
"quadratic fitness peaked at {}, along a β ladder 0 → 1.\n",
center
);
let particles = smc.run(&mut rng);
let est_mean = EvolutionarySMC::<RealVector, Quadratic>::weighted_mean(&particles);
let est_var = EvolutionarySMC::<RealVector, Quadratic>::weighted_variance(&particles);
let ess = EvolutionarySMC::<RealVector, Quadratic>::effective_sample_size(&particles);
let tau0 = 1.0 / (sigma0 * sigma0);
let tau = tau0 + 1.0;
let analytic_mean = center / tau;
let analytic_var = 1.0 / tau;
println!(
" Effective sample size at β=1 : {:.1} / {}",
ess,
particles.len()
);
println!(
" Posterior mean (SMC) : [{:.3}, {:.3}]",
est_mean[0], est_mean[1]
);
println!(
" Posterior mean (analytic): [{:.3}, {:.3}]",
analytic_mean, analytic_mean
);
println!(
" Posterior variance (SMC) : [{:.3}, {:.3}]",
est_var[0], est_var[1]
);
println!(
" Posterior variance (analytic): [{:.3}, {:.3}]",
analytic_var, analytic_var
);
if let Some(best) = EvolutionarySMC::<RealVector, Quadratic>::best_particle(&particles) {
println!(
" MAP-ish best particle : {:?} (f = {:.4})\n",
best.genome.genes(),
best.fitness
);
}
let model = EvolutionModel::new(fitness.clone(), bounds.clone()).with_beta(1.0);
let probe = RealVector::new(vec![center, center]); let wt = model.to_weighted_trace(&probe);
println!("Fitness as likelihood (β = 1):");
println!(
" f(peak) = {:.4}",
model.fitness_value(&probe)
);
println!(
" trace.total_log_weight = {:.4} (== β·f, in log_factors: {:.4})\n",
wt.total_log_weight(),
wt.log_factors
);
println!("Running Bayesian adaptive GA (Thompson sampling over step sizes)...\n");
let sphere = Sphere::new(5);
let ga_bounds = MultiBounds::symmetric(5.12, 5);
let mut ga = BayesianAdaptiveGA::new(sphere, ga_bounds, 60, 120)
.with_step_sizes(vec![0.02, 0.1, 0.5, 2.0]);
let result = ga.run(&mut rng);
println!(" Best fitness found : {:.5}", result.best_fitness);
println!(" Learned operator posteriors (Beta success probability):");
for arm in &result.operator_posteriors {
println!(
" σ = {:<4} E[θ] = {:.3} (α={:.0}, β={:.0}, selected {} gens)",
arm.sigma,
arm.posterior.mean(),
arm.posterior.alpha,
arm.posterior.beta,
arm.times_selected
);
}
println!(
" Improvement-rate posterior : Gamma(shape={:.1}, rate={:.1}), E[λ] = {:.2} improving/gen\n",
result.improvement_rate.shape,
result.improvement_rate.rate,
result.improvement_rate.mean()
);
println!("Done. Evolution ran as genuine posterior inference through the Fugue layer.");
Ok(())
}