Skip to main content

Acquisition

Enum Acquisition 

Source
pub enum Acquisition {
    Random,
    Thompson,
    Bald,
}
Expand description

Which rule picks the next duel.

Selectable because the choice is an empirical claim, and learn_synthetic --compare measures it. Both alternatives are kept so that comparison stays runnable — a rule chosen on evidence should stay re-checkable, and a rule rejected on evidence doubly so.

§The measurement, and what it is a measurement of

cargo run -p auracle-session --example learn_synthetic --release -- --compare 20, on the synthetic user: 20 seeds, 72 duels, refit every 12. Common random numbers — pool fill, the user’s coin flip at duel t, MCMC seed at round r, and refinement seeds are all shared across arms, so only the acquisition draw differs. Both regimes are graded on one fixed held-out exam under a single reference scale, so arms that built different pools are still answering the same questions. ± is two standard errors of the paired difference.

§Static pool (i.i.d. prior draws, refine_steps: 0)

cos θ* ↑rank r ↑excess nats ↓
random0.4600.7310.211
thompson0.4160.6280.254
bald0.4840.7620.199
bald − thompson+0.068 ± 0.062+0.134 ± 0.044−0.055 ± 0.014
bald − random+0.025 ± 0.058+0.031 ± 0.046−0.012 ± 0.013

Dueling Thompson sampling is the one clear loser, at t = 2.2 / 6.1 / −8.0. It is a best-arm rule: it converges on identifying the top patch, which is not what a duel is for here. BALD and uniform pairing are inside two standard errors of each other on every metric.

A static i.i.d. pool is also a weak regime to conclude from on its own: prior draws are spread over feature space by construction, which is exactly where uniform pairs already achieve near-optimal ‖φ_a − φ_b‖ coverage and an information-seeking rule has no redundancy to prune. The concern was that the shipped pool is not that pool — refinement injects children near the current best and insert_candidate evicts the worst — so --compare runs an evolving regime too, with real refinement between rounds (the Regime type in learn_synthetic.rs documents the design).

§Evolving pool (refine_steps: 12, refinement between rounds)

cos θ* ↑rank r ↑excess nats ↓
random0.4790.6940.232
thompson0.4590.5830.276
bald0.4650.7070.232
bald − thompson+0.006 ± 0.068+0.124 ± 0.066−0.044 ± 0.017
bald − random−0.015 ± 0.055+0.013 ± 0.048−0.000 ± 0.014

Same answer: Thompson loses, BALD and uniform pairing tie on every metric.

The run’s manipulation check is itself a finding. Final pool spread (mean pairwise ‖Δφ‖, reference scale) was 7.7–7.9 evolving vs 7.2 static: six generations over a 72-duel session did not concentrate the pool at all — frontier-biased injection plus worst-eviction widened it slightly, because mutation pushes children into feature-space extremes faster than eviction trims them. So the concentrated regime BALD was hypothesized to win never arises at session horizon, and the tie is not an artifact of a spread pool that only the static setup guaranteed — the product’s own dynamics keep the pool spread.

§Why Random is the default

Measured in both the regime the product starts in and the regime it evolves into, uniform pairing is indistinguishable from BALD — and a rule with four tuning constants that ties a rule with none should not ship on a tie. Two supporting justifications survived checking, one did not: the info_gain BALD reports had zero consumers in the frontend, and BALD’s repeat avoidance, while real, is barely needed over a 48-candidate pool that uniform pairing already samples without repeating (measured in duels_spread_over_candidates_not_just_pairs). Random also makes every duel an unbiased calibration sample rather than one in ten — a virtue that holds regardless of which rule learns θ faster.

One earlier justification was retracted for a bad reason, and the record should say so. The “pool grows and concentrates” argument was dismissed on the grounds that insert_candidate caps the pool — but a capped size is not an unchanging spread, and evicting the worst member could in principle concentrate a pool. Dismissing the concentration argument because it was unmeasured, while treating a measurement from the other regime as decisive, had the burden of proof backwards. The evolving run above is that measurement; it happens to show the concentration never materializes, but the default rests on the measured tie, not on the dismissal.

§What Bald is still for

It is not dead code and it is not a fallback. It decisively beats the best-arm rule, so it is the right thing to reach for if acquisition ever needs to do something uniform pairing cannot: bias duels toward patches the user will enjoy auditioning (SessionConfig::duel_utility_weight), bound how often one patch reappears (SessionConfig::duel_exposure_penalty), or report why a question was asked. Those levers exist and are measured; none of them is currently worth the tie.

§A correction worth recording

An earlier version of this rule scored its enjoyment term on unnormalized utility and used an absolute softmax temperature of 0.05 nats. Both are scale bets, and both lost: the enjoyment term grew without bound as the posterior sharpened, and exp(ΔJ/T) ran to e¹⁰, so the “softmax” was an argmax. That version was measurably worse than random, and it is the version an independent replication measured. It is also what produced the duel repetition seen in the running app — the same defect, observed from two directions. Fixed, BALD ties random; the numbers above are the fixed rule.

Variants§

§

Random

Uniformly random pairs. The default — see the type doc.

§

Thompson

Dueling Thompson sampling: two posterior draws, duel their champions. Best-arm identification — converges on the top patch, not on θ.

§

Bald

Expected information gain about θ, plus an enjoyment term and a repeat penalty, sampled from a softmax. Beats Acquisition::Thompson decisively and ties Acquisition::Random; see the type doc.

Trait Implementations§

Source§

impl Clone for Acquisition

Source§

fn clone(&self) -> Acquisition

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Acquisition

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Acquisition

Source§

fn default() -> Acquisition

Returns the “default value” for a type. Read more
Source§

impl PartialEq for Acquisition

Source§

fn eq(&self, other: &Acquisition) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Acquisition

Source§

impl Eq for Acquisition

Source§

impl StructuralPartialEq for Acquisition

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,