Skip to main content

auracle_session/
surrogate.rs

1//! The learned taste as a fugue-evo fitness: the surrogate that lets the
2//! machine evolve thousands of candidates silently (the reference: *The two
3//! loops*).
4//!
5//! `SurrogateFitness` plugs the taste posterior's expected utility into
6//! fugue-evo's `Fitness`, so the Boltzmann target
7//! `π_β(x) ∝ p_grammar(x) · exp(β · E[u_θ(φ(x))])` becomes an ordinary
8//! `EvolutionModel` and typed-MH / SMC drivers apply unchanged. Quarantined
9//! candidates score a large negative fitness — safety layer 2: evolution
10//! learns to avoid the pathological region.
11
12use std::sync::Arc;
13
14use auracle_features::{featurize_memo, PhraseSpec, RenderMemo};
15use auracle_grammar::PatchTree;
16use auracle_taste::{Standardizer, TastePosterior};
17use fugue_evo::fitness::traits::Fitness;
18
19/// Fitness a quarantined (unrenderable/unlistenable) candidate receives.
20pub const QUARANTINE_FITNESS: f64 = -50.0;
21
22/// Expected posterior utility as a scalar fitness over patch terms.
23#[derive(Clone, Debug)]
24pub struct SurrogateFitness {
25    /// The fitted taste posterior.
26    pub posterior: Arc<TastePosterior>,
27    /// The standardizer the posterior's observations were made under.
28    pub standardizer: Arc<Standardizer>,
29    /// The audition stimulus (must match the one used for observations).
30    pub phrase: PhraseSpec,
31    /// The engine's featurization memo.
32    ///
33    /// Not an optimization detail — it is what makes the MH walk affordable.
34    /// `adaptive_single_site_mh` executes the model **twice per step**: once
35    /// to re-score the current trace, which is bit-identically the tree the
36    /// previous step accepted and therefore already featurized, and once for
37    /// the proposal. Without a memo, one render in two is a recomputation of a
38    /// number the walk already has.
39    pub memo: RenderMemo,
40}
41
42impl Fitness for SurrogateFitness {
43    type Genome = PatchTree;
44    type Value = f64;
45
46    fn evaluate(&self, genome: &PatchTree) -> f64 {
47        // `want_audio: false` — the surrogate only ever wants φ, and nothing
48        // in a refinement generation is ever played. Asking for samples here
49        // would undo the memo: a miss would convert 141 k f64s it then drops,
50        // and a hit would copy a ~565 KB buffer out of the audio tier. Twice
51        // per MH step, ~96 times per seed, that is tens of megabytes of churn
52        // for a value discarded on the next line.
53        match featurize_memo(genome, &self.phrase, &self.memo, false) {
54            Ok((cf, _)) => {
55                let phi = self.standardizer.transform(&cf.features.phi());
56                self.posterior.utility_mix(&phi).0
57            }
58            Err(_) => QUARANTINE_FITNESS,
59        }
60    }
61}