Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Boltzmann target

One distribution, two factors: the grammar supplies parsimony, the learned taste supplies direction, and β\beta is the single dial between them.

The target

πβ(x)  ∝  pgrammar(x)  exp⁡ ⁣(β⋅E[uθ(φ(x))])\pi_\beta(x) ;\propto; p_{\text{grammar}}(x);\exp!\big(\beta \cdot \E[u_\theta(\varphi(x))]\big)

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

pgrammar(x)p_{\text{grammar}}(x) 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.

exp⁡(βE[uθ])\exp(\beta \E[u_\theta]) 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.

Turn β and watch the target move. At β = 0 the target is the prior: the search browses, and simple terms dominate because that is what the prior says, not because anything penalizes size. Turn it up and the mass walks out toward the region the taste model likes, paying prior probability to get there. One dial, and the trade it makes is visible rather than tuned.

β\beta

SessionConfig::beta, default 2.0 (in engine.rs).

β\betaBehavior
→0\to 0Browse the prior. The taste model is ignored
2.02.0Shipped default
largeOptimizer 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. θ\theta is meaningless against any other scaling; see Standardization.

want_audio: false. The surrogate only ever wants φ\varphi; 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 πβ\pi_\beta. 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.