mod screen

module screen

Deciding which trials are worth relaxing, under a posterior.

The expensive step in a cluster search is the local relaxation, at roughly thirty charged evaluations against one for a proposal, and most trials are not worth it. The cheap version of that decision is a threshold on the energy after a few relaxation steps, and it is the one mechanism in this crate that is measurably worth having: at 75 points it takes the success rate from 2 seeds in 8 to 13 in 24. Its threshold is a hand-set margin.

This replaces the margin with inference. A partial relaxation supplies cheap features, the full relaxation supplies the answer, and a Bayesian linear model of the second given the first says how plausible it is that finishing the relaxation would improve on the incumbent. Spending the thirty evaluations is then a decision under a posterior rather than a comparison against a constant, which is what probabilistic numerics asks of a numerical procedure: treat the quantity you have not computed as unknown, not as absent.

Why the exploration floor is not optional

The model is trained on the trials it chose to relax, so a rule that relaxes only where it already predicts improvement never sees a counterexample and its own confidence is self-confirming. A fixed fraction of trials is relaxed regardless of the posterior, which keeps the training set from being censored by the decision rule it trains. This is the same reason a bandit keeps a floor on every arm, and crate::allocate does it there.

The approximation, stated

The conjugate Normal-Inverse-Gamma posterior gives a Student-t predictive. The tail probability here uses the Gaussian with the same variance, which is exact in the limit of many observations and understates the tails below it. That is why nothing is decided by the model until Screen::warmup observations have arrived; before then every trial is relaxed.

Functions

fn cost_asymmetric_threshold(screen_steps: usize, relax_steps: usize) -> f64

Bayes threshold from the hop’s own step counts.

Finishing the quench costs Q = R - S extra evaluations. A discarded winner costs one full hop R of opportunity. Net value of quenching is p R - (1-p) Q, which is positive exactly when p > Q / (Q + R) = (R - S) / (2 R - S).

For the measured hop (S = 25, R = 200) this is 7/15, not a knob.

Structs and Unions

struct DropModel

Remaining drop D = E_sc - E_full of a purchased quench.

Features are an intercept and the screened energy. The predicted landing is E_sc - E[D | E_sc]. A trial whose landing sits on a known floor is refused by the caller; this type only predicts the drop.

Implementations

impl DropModel

Functions

fn calibrated(&self) -> bool

Whether enough purchased pairs have arrived to trust a refuse.

fn new() -> Self

Weak prior, no exploration floor: the caller decides whether to buy.

fn observations(&self) -> usize

Observations folded in.

fn observe(&mut self, e_sc: f64, e_full: f64)

Fold in one purchased pair.

fn predict_drop(&self, e_sc: f64) -> Option<(f64, f64)>

Predictive remaining drop at e_sc.

fn predicted_full(&self, e_sc: f64) -> Option<f64>

Predicted full-quench energy.

fn warmup(&self) -> usize

Purchased pairs required before a predicted landing may refuse a quench.

This is the screen warmup the model was built with, not a search-level knob: refusing before it has seen that many pairs starves the driver.

Traits implemented

impl Default for DropModel
struct Screen

Features of a partially relaxed trial, and whether it deserves a full one.

The design vector is the caller’s. What the model needs is that it be cheap relative to a relaxation and computed the same way at fit time and at decision time.

warmup: usize

Observations required before the posterior is used at all.

exploration: f64

Fraction of trials relaxed regardless of what the model says.

threshold: f64

Posterior probability of improvement above which a trial is relaxed.

Set from the cost asymmetry rather than by taste: relaxing costs a fixed number of evaluations, and failing to relax a trial that would have improved costs the search that improvement. A low threshold spends more and misses less.

decided: usize

Decisions made, and how many said relax.

relaxed: usize

Of those, how many were relaxed.

explored: usize

Relaxations forced by the exploration floor.

Implementations

impl Screen

Functions

fn coefficients(&self) -> Option<Array1<f64>>

Posterior mean of the coefficients, or None before anything is known.

fn decide(&mut self, x: ArrayView1<f64>, best: f64, u: f64) -> bool

Whether to pay for the full relaxation of a trial with features x, given the incumbent best.

u is a uniform draw supplied by the caller, so the decision is reproducible under the caller’s seed rather than reaching for its own randomness.

fn new(d: usize, warmup: usize, exploration: f64, threshold: f64) -> Self

A screen over d features, with a weak prior.

The prior precision is a small multiple of the identity, which is a ridge: it keeps the first few updates from producing an ill-conditioned posterior without expressing an opinion about the coefficients.

fn observations(&self) -> usize

Observations folded in so far.

fn observe(&mut self, x: ArrayView1<f64>, y: f64)

Records a trial whose full relaxation is known.

fn predict(&self, x: ArrayView1<f64>) -> Option<(f64, f64)>

Predictive mean and variance at x.

The variance is the Student-t scale, which carries both the noise and the uncertainty in the coefficients; the second term is what makes a trial unlike anything seen before come out uncertain rather than confidently predicted.

fn probability_of_improvement(&self, x: ArrayView1<f64>, best: f64) -> Option<f64>

Posterior probability that a full relaxation would land below best.