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 -> 2improve_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:
pilot_draws_qmcor the internal pilot runner -> (t_map,e_map,q_map)Short pilot chains at those hyperparameters.
Production:
BayesianMixingSampleraround a classical or GLE inner sampler, singlemax_proposalsbudget.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.