#![cfg(feature = "ppl")]
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>()
}
}
#[test]
fn e_integration_smc_targets_conjugate_posterior() {
let center = 2.0;
let sigma0 = 2.0;
let model = EvolutionModel::new(GaussianPrior::new(0.0, sigma0, 1), Quadratic { center });
let mut rng = StdRng::seed_from_u64(1234);
let result = EvolutionSMC::run(
&mut rng,
&model,
EvoSmcConfig {
num_particles: 1500,
rejuvenation_steps: 5,
crossover: Some(CrossoverConfig::default()),
..Default::default()
},
);
let tau = 1.0 / (sigma0 * sigma0) + 1.0;
let analytic_mean = center / tau;
let analytic_var = 1.0 / tau;
let mean = result.weighted_mean(0);
let var = result.weighted_variance(0);
assert!(
(mean - analytic_mean).abs() < 0.2,
"SMC posterior mean {} vs analytic {}",
mean,
analytic_mean
);
assert!(
(var - analytic_var).abs() < 0.25,
"SMC posterior variance {} vs analytic {}",
var,
analytic_var
);
let total: f64 = result.particles.iter().map(|p| p.weight).sum();
assert!((total - 1.0).abs() < 1e-6, "weights must self-normalise");
}
#[test]
fn e_integration_weighted_trace_is_boltzmann_weight() {
let bounds = MultiBounds::symmetric(5.0, 3);
let model =
EvolutionModel::new(UniformBoxPrior::new(bounds), Quadratic { center: 0.0 }).with_beta(1.5);
let g = RealVector::new(vec![1.0, 0.0, -2.0]);
let f = model.fitness_value(&g);
let trace = model.to_weighted_trace(&g);
assert!((trace.total_log_weight() - 1.5 * f).abs() < 1e-9);
}
#[test]
fn e_integration_mh_stays_in_bounds() {
let bounds = MultiBounds::symmetric(3.0, 2);
let model = EvolutionModel::new(UniformBoxPrior::new(bounds), Quadratic { center: 100.0 })
.with_beta(1.0);
let mut chain = EvolutionChain::new(model);
let mut rng = StdRng::seed_from_u64(77);
let mut current = chain.init(&mut rng);
for _ in 0..5000 {
let (g, t) = chain.step(&mut rng, ¤t);
current = t;
for &x in g.genes() {
assert!((-3.0..=3.0).contains(&x), "escaped bounds: {}", x);
}
}
}
#[test]
fn e_integration_bayesian_ga_learns_and_optimises() {
let sphere = Sphere::new(4);
let bounds = MultiBounds::symmetric(5.12, 4);
let mut ga = BayesianAdaptiveGA::new(UniformBoxPrior::new(bounds), sphere, 40, 40);
let mut rng = StdRng::seed_from_u64(9);
let result = ga.run(&mut rng);
let evidence: f64 = result
.operator_posteriors
.iter()
.map(|a| a.posterior.total() - 2.0)
.sum();
assert!(evidence >= (40 * 40) as f64 - 1.0, "evidence {}", evidence);
assert!(result.improvement_rate.shape > 1.0);
assert!(result.best_fitness > -5.0, "best {}", result.best_fitness);
assert_eq!(result.fitness_history.len(), 40);
}