Evolution as inference
This page describes the ppl feature (on by default). Without it, fugue-evo
is a standalone evolutionary-computation library with no
probabilistic-programming dependency at all.
fugue-evo's inference layer makes "evolutionary algorithms as probabilistic
programs" literal. Given a fitness f and a prior over genomes, the
Boltzmann/Gibbs posterior
pi_beta(x) ∝ p(x) · exp(beta · f(x))
is not merely a mathematical analogy — it is a fugue program, and every sampler in the layer is fugue's own inference machinery run against that program.
This is the whole idea, live: fugue-evo's real inference layer (compiled to
WASM) runs tempered SMC against a twin-peaks Boltzmann target. The yellow heat
is the exact tempered density π_β ∝ p(x)·exp(β·f(x)), recomputed each rung
as β climbs from the prior (β = 0, the blue rings) to the posterior (β = 1);
the green particles are the SMC population — reweighted, resampled when the
ESS drops (watch the ESS readout flash), rejuvenated by typed MH, and mixed
across the two modes by the crossover kernel (a product-target Metropolis
swap of x-coordinates). Push β MAX past 1 to watch inference become an
annealed optimizer, and drag the seed to replay a different history. The
log Z readout is the running evidence estimate — a model score no classic GA
can report.
Priors are programs
The GenomePrior trait replaces any notion of a built-in prior enum:
pub trait GenomePrior: Clone + Send + Sync + 'static {
type Genome: TraceGenome;
fn model(&self) -> fugue::Model<Self::Genome>;
}
The model samples the genome's canonical trace sites (gene#i, bit#i,
perm#i, …) and returns the assembled genome — the model's return value is
the decode. Anything expressible as a fugue model is a valid prior:
correlated coordinates, hierarchical scales, variable-length genomes.
Built-ins: UniformBoxPrior, GaussianPrior, BitStringPrior,
PermutationPrior (a Fisher–Yates/Lehmer-code program whose single-site
moves always decode to valid permutations), and ArithmeticGrammarPrior
(below).
There is deliberately no hand-written density code anywhere in the layer: prior mass, tempered joints, and MH acceptance ratios are all obtained by running or replaying the target program through fugue's handlers.
The target as a program
EvolutionModel::new(prior, fitness) assembles
prior.model().bind(move |g| {
let fit = fitness.evaluate(&g);
factor(beta * fit).map(move |_| g)
})
Two builders exist because MH wants a fixed-beta target (target_model())
while tempered SMC must receive the untempered factor (smc_model()):
fugue's adaptive_smc supplies beta by likelihood-tempering, applying it
exactly once.
Samplers
EvolutionChain— Metropolis–Hastings via fugue'sadaptive_single_site_mh. Typed proposals move every site kind: Gaussian / log-space walks for reals, flips for bits, prior-resample for categorical sites (permutation ranks), with reversible-jump corrections when a proposal changes the model's structure.EvolutionSMC— tempered SMC via fugue'sadaptive_smc_with_kernel: an adaptive ESS-driven beta ladder from the prior to the posterior, systematic resampling, per-particle MH rejuvenation, an optional population-coupled crossover kernel (a product-target Metropolis move that swaps an address block between two particles), and an unbiased log-evidence estimate — a genuine Bayesian model score. Genomes are recovered from bare particle traces by decode-replay.
Genetic programming as exact inference
ArithmeticGrammarPrior is a probabilistic context-free grammar over
expression trees, written as a fugue program with tree-path addresses
(node#leaf, node/0#func, node/0/1#const, …). Because an execution's
structure is encoded in its own choices:
- subtree regeneration mutation is fugue's ordinary single-site MH — a
flip of one
#leafbit births or kills the subtree below it, fresh structure drawn from the grammar, reversible-jump corrections applied automatically; - subtree crossover is the crossover kernel with a mask that grafts the subtrees under one shared node path between two particles;
- parsimony is the grammar prior itself — deeper trees pay more mass, no ad-hoc penalty needed.
The flagship example, examples/symbolic_regression_inference.rs, fits
x² + 1 by sampling the posterior over programs and compares function-set
grammars by Bayes factor. The regression test
test_symreg_recovers_known_expression pins the recovery.
The layer boundary
The TraceGenome extension trait (genome::trace_genome) is the boundary:
classic algorithms require only EvolutionaryGenome; genomes that also
implement TraceGenome can be driven by the inference layer. The canonical
trace encoding is pure data (zero stored log-probabilities); probability mass
always comes from scoring under a prior program.