Bayesian Pilot and Mixer

The Bayesian pilot + mixer (bGSA auto-allocation) removes the usual tuning knobs. You supply a single budget (max_proposals); everything else (chain count, per-chain temperatures, allocation) follows online from a Beta-Bernoulli posterior.

Reference

The pilot (three-term Laplace posterior on log-T0/log-sigma/q_v with Roberts-Rosenthal 0.234 target plus improvement term) and the mixer (per-chain Beta(4/1,4) on “produced new global best”, Thompson sampling with 0.05 incumbent guard, auto n_chains = min(budget/64, sqrt(dim)) clamped 2..4, QMC starts when bounds supplied) are described in src/methods/bayesian_pilot.rs and src/methods/bayesian_mixing.rs. The same logic appears in the Python re-implementations under experiments/scripts/demo_bgsa.py.

Prerequisites

Values-only objective (no gradient required). Bounds recommended for QMC seeding.

pip install anneal

Step 1: Pilot draws (prior samples for (T0, sigma, q_v))

The pilot draws from the prior (log-normal on scales, truncated Beta-like on q_v in (1,3)). q_v = 1 recovers Boltzmann simulated annealing (BSA), q_v = 2 recovers fast simulated annealing (FSA); values between them give a continuous model-selection coordinate.

import numpy as np
from anneal import pilot_draws_qmc

draws = pilot_draws_qmc(6, seed=0)
print("Pilot draws (T0, sigma, q_v):\n", draws)

Typical output (rounded):

Pilot draws (T0, sigma, q_v): [[ 4.82 0.71 1.34] [ 7.15 1.12 2.11] …

Each row is a candidate Hyperparameter triple. In a full bGSA run the pilot executes short chains at a subset of these, observes acceptance rates and improvement sizes, then returns the maximum a posteriori (maximum a posteriori (MAP)) triple for the production phase.

Step 2: The mixer on a 5-D random problem (concrete trace)

Consider a 5-D quadratic “random problem” (shifted Rastrigin-like) whose global min is known. With a small budget we watch the per-chain Beta posteriors evolve.

The mixer starts:

  • n_chains = min(=max_proposals=/64, sqrt(5)) clamped -> 2

  • improve_alpha = [4.0, 1.0], improve_beta = [1.0, 4.0] (chain 0 receives a head-start Beta prior)

  • Every step: Thompson-sample a chain by drawing from its current Beta(alpha, beta)

  • If the chosen chain produces a new global best: alpha += 1 else beta += 1

  • Guard: only switch away from incumbent if the sampled score exceeds incumbent score + 0.05

A 6-step trace (illustrative numbers consistent with the update rules):

Step 0 (init): chains 0,1 ; alpha=[4,1], beta=[1,4] Thompson draws: chain0 ~ 0.78, chain1 ~ 0.19 -> run chain 0 No improvement yet.

Step 1: Thompson: chain0 ~ 0.81, chain1 ~ 0.22 -> chain 0 improves global best -> alpha[0] = 5, beta[0] = 1

Step 2: Thompson: chain0 ~ 0.91, chain1 ~ 0.11 -> chain 0 again (still best) -> no new global; beta[0] = 2

Step 3: Thompson: chain0 ~ 0.64, chain1 ~ 0.33 -> chain 0 -> chain 1 produces new global best -> alpha[1] = 2, beta[1] = 4

Step 4: Thompson draws now favor chain 1 (its Beta(2,4) can still beat the depleted chain 0). Chain 1 improves again -> alpha[1] = 3

Step 5: Guard prevents thrashing: even if a draw slightly favors chain 0, the +0.05 margin keeps the current incumbent (chain 1) unless a clear winner appears.

The Python surface does not expose the internal mixer directly (the full auto-orchestration lives in the experiments tree), but pilot_draws_qmc plus the exposed advanced drivers let you reproduce the same “one-budget, auto everything” behaviour by hand or via the demo scripts.

# High-level illustration (the real bGSA driver is in experiments)
print("After pilot, production run uses the maximum a posteriori (MAP) (T0, sigma, q_v)")
print("Mixer then allocates the remaining budget across chains via Thompson on the Beta posteriors.")

Step 3: Full pilot + production pattern (conceptual)

In the reproducibility scripts the pattern is:

  1. pilot_draws_qmc or the internal pilot runner -> (t_map, e_map, q_map)

  2. Short pilot chains at those hyperparameters.

  3. Production: BayesianMixingSampler around a classical or GLE inner sampler, single max_proposals budget.

  4. Final polish on the winner.

The exposed additive_independence and gle_langevin already embed pilot logic when n_pilot > 0.

Why this works (slot view + algebra)

The pilot only ever touches Cool and Neigh/Move parameters (it never changes Obj or Accept). The mixer is an orthogonal lift over the Sampler trait: it chooses which inner chain (that is which state + its own Cool/Neigh/Move/Accept tuple) receives the next proposal. Because every inner sampler obeys L1-L4, the outer mixer automatically inherits BestMonotone and the other TLA+ invariants. The Beta update is exactly the improvement term that appears in the three-term posterior of the pilot; both pieces are instances of the same “count successes, shrink toward target” reasoning.

Figures

Styblinski-Tang 2-D contours and 5-D/20-D CUTEst performance profiles that compare the Bayesian mixer against fixed classical and GLE drivers are regenerated by experiments/scripts/render_plots.py (and the full partitioned CUTEst suite in experiments/benchmarks/). See the anneal_repro package and used-by page for the exact commands and the resulting Pareto and data-profile figures.