The Boltzmann target
One distribution, two factors: the grammar supplies parsimony, the learned taste supplies direction, and is the single dial between them.
The target
This is fugue-evo’s EvolutionModel with the learned utility plugged in as
fitness, so the whole thing becomes an ordinary probabilistic program and
fugue-evo’s typed MH applies unchanged.
What each factor does
is the parsimony pressure. It is the prior probability of the term under the typed PCFG, not a penalty term. Deeper terms pay more prior mass by construction, because each extra level multiplies in another Bernoulli that came out “processor” plus that node’s own parameter draws.
Ad-hoc size penalties in genetic programming need tuning, interact badly with fitness scaling, and leave the target distribution unwritten. Here the target is written down, and the parsimony term is a probability rather than a hyperparameter.
is the direction. The expectation is over the posterior, so the search climbs the model’s mean belief and is not seduced by a single confident-looking draw.
SessionConfig::beta, default 2.0 (in engine.rs).
| Behavior | |
|---|---|
| Browse the prior. The taste model is ignored | |
| Shipped default | |
| large | Optimizer mode: the search climbs toward the model’s single best guess |
One dial for conservatism, which is the practical payoff of writing the target down: there is no explore/exploit schedule to tune, no diversity term, no niching parameter. Tempered SMC, which is not built, would temper this same target.
Fitness through the surrogate
SurrogateFitness is the bridge:
impl Fitness for SurrogateFitness {
fn evaluate(&self, genome: &PatchTree) -> f64 {
match featurize_memo(genome, &self.phrase, &self.memo, false) {
Ok((cf, _)) => {
let phi = self.standardizer.transform(&cf.features.phi());
self.posterior.utility_mix(&phi).0
}
Err(_) => QUARANTINE_FITNESS,
}
}
}
Three things in nine lines:
Quarantine is a fitness, not just a filter. QUARANTINE_FITNESS = -50.0
(in surrogate.rs), so a pathological candidate contributes a large negative factor to the target
and the search learns to avoid the region rather than repeatedly sampling
it. That is
safety layer 2; hiding alone would
leave the search wasting budget somewhere it cannot see is bad.
The standardizer must be the one the observations were made under. is meaningless against any other scaling; see Standardization.
want_audio: false. The surrogate only ever wants ; nothing in a
refinement generation is played. Asking for samples would undo the memo: a miss
would convert about 223,000 f64s (the 5.05 s phrase at 44,100 Hz) that it then
drops, and a hit would copy that buffer, about 0.9 MB as f32, out of the audio
tier. Once per MH step, 40 times per seed at 40 steps, that is tens of
megabytes of churn for a value discarded on the next line.
Why the render memo matters
It is what makes the walk affordable at all.
A walk step executes the model once, for the proposal; the current state is carried scored from the step before and is not run again. A walk still revisits trees (the seed, a state it returns to, the drift walks), and a render is the one expensive thing the model does, so a revisit that is recomputed is a number the walk already had.
A render of the audition phrase costs about a ninth of a second of CPU in
wasm on a 4-core Linux machine (quiver-dsp 0.5.1; 2.0 s for the 18 trees of
make bench-render, 3.1 s on 0.4.1 the same day) and several times that on
the reference MacBook Air, so every revisit the memo answers is a whole render
the generation does not wait for.
What is not sampled from this
What ships is not a sample from . Refinement runs a short adaptive single-site MH walk warm-started from each of the best pool members and keeps the final state: local hill-climbing on that target, which is what a candidate pool needs, rather than a draw from it.
The design’s tempered SMC is not built, and there is no crossover: every child
grows from one seed (walk.rs). Refinement draws the
distinction.