mixle.task.emulate module

emulate – a cheap forward surrogate for an expensive simulator, placed by acquisition under budget.

M1 (belief-driven interaction) and M3 (inversion-surrogate pair generation) both need to call a forward map many times; when that map is a real simulator (a PDE solve, a Monte-Carlo physics run, …) that cost dominates. emulate() fits a GP surrogate of simulator over bounds from a budget-limited number of true calls, places those calls where they most reduce the surrogate’s own predictive variance (active learning, not random sampling), and returns an Emulator whose .predict gives a mean and a calibrated standard deviation. Callers use the standard deviation directly: Emulator.escalate_mask() flags inputs where the surrogate is not to be trusted, so a caller (M1’s planner, M3’s pair generator) can fall back to the true simulator exactly there instead of guessing a fixed re-run schedule.

Discovery, and why nothing here is a new GP. mixle.models.gaussian_process.GaussianProcessRegressor (the exact torch GP; mixle.doe already fits it as its default surrogate via mixle.doe.bayesopt._fit_surrogate) is the only regression model this module touches – mixle.models.sparse_gaussian_process.SparseGaussianProcessRegressor (FITC) is for the n too large for an exact GP to invert, which is not this card’s regime (the whole point of a budgeted emulator is that n stays small: a handful to a few hundred simulator calls). The placement logic is not new either:

  • Single fidelity reuses mixle.doe.active.active_learning_design() verbatim – ALC (Active Learning Cohn / IMSE), the integrated posterior-variance-reduction criterion, sequentially adds the candidate that most shrinks the surrogate’s error everywhere over bounds, which is exactly “cheap forward surrogate with uncertainty, placed by acquisition” for the single-fidelity case.

  • Multi-fidelity reuses the GP-over-augmented-fidelity-coordinate construction from mixle.doe.multifidelity.multi_fidelity_minimize() (fit one GP on [x, s]; pick the fidelity that buys the most target-variance reduction per unit cost) but swaps its why pick this x half: BOCA picks x by Expected Improvement (it is chasing an optimum), this module picks x by mixle.doe.active.alc_scores() at the target fidelity (it is chasing surrogate accuracy everywhere) – active learning’s ALC criterion transplanted onto multi-fidelity’s cost-aware fidelity choice, not a new algorithm.

Why not ``mixle.task.acquire``, despite the roadmap listing it as a dependency. acquire()’s dispatch (mixle.task.acquire._proba_batch()) is built for models that emit a row-stochastic (n, k) categorical prediction over an already-materialized discrete pool – text classifiers, ensembles of them. A simulator here is a continuous, generally scalar-valued regression map over a bounded continuous domain, not a finite pool of discrete items with class probabilities; forcing it through acquire’s predict_proba contract would mean discretizing the domain and inventing fake class probabilities from a GP mean/std, which throws away exactly the calibrated uncertainty this module exists to keep. The dependency is real in spirit, not in import: A5 established the “acquisition places the next expensive call, active learning shape, not a hardcoded type” pattern in mixle.task; this module is the continuous-domain sibling of it, reusing mixle.doe’s acquisition machinery the same way A5 reuses mixle.epistemic.portfolio.

class Emulator(gp, x_train, y_train, bounds, target_fidelity, receipt)[source]

Bases: object

A fitted forward surrogate: .predict, .escalate_mask, .receipt. Built by emulate().

Parameters:
  • gp (Any)

  • x_train (np.ndarray)

  • y_train (np.ndarray)

  • bounds (np.ndarray)

  • target_fidelity (float | None)

  • receipt (EmulatorReceipt)

predict(x)[source]

Return (mean, std) of the surrogate’s posterior at x (always at the target fidelity).

Parameters:

x (Any)

Return type:

tuple[ndarray, ndarray]

escalate_mask(x, tol)[source]

Return a boolean mask: True where the surrogate’s std at x exceeds tol (escalate).

Parameters:
Return type:

ndarray

class EmulatorReceipt(held_out_rmse, coverage, nominal_coverage, n_holdout, n_train, cost_spent, fidelities)[source]

Bases: object

A measured, not asserted, report of an Emulator’s own quality.

held_out_rmse and coverage are computed against true-simulator calls that were not used to fit the surrogate (n_holdout of them, carved out of budget before training starts). coverage is the empirical fraction of holdout points whose true value falls within the emulator’s own mean +/- 1 std; nominal_coverage is what that fraction should be if the error bars are calibrated (~0.6827 for a Gaussian posterior). cost_spent is the total simulator cost actually used (holdout + training; each single-fidelity call costs 1, each multi-fidelity call costs its fidelity’s entry in costs).

Parameters:
emulate(simulator, bounds, *, budget, fidelities=None, costs=None, seed=None, n_init=None, n_candidates=256, n_reference=128, holdout_frac=0.2, method='alc', fit_kwargs=None)[source]

Fit a budget-limited GP surrogate of simulator over bounds, placing calls by acquisition.

simulator(x) (single fidelity) or simulator(x, s) (fidelities given, s one of them) returns the true response at x; budget is the total simulator cost available (single fidelity: 1 unit per call; multi-fidelity: costs per fidelity, default the fidelity value itself, mirroring mixle.doe.multifidelity.multi_fidelity_minimize()). A holdout_frac slice of the budget is spent up front on Latin-hypercube points evaluated at the target (highest) fidelity and held out of training, purely to compute EmulatorReceipt; the remainder trains the surrogate: single fidelity via mixle.doe.active.active_learning_design() (method "alc" or "alm"; "random" places a plain Latin-hypercube design instead, for comparison), multi-fidelity via ALC-at-target-fidelity point choice plus BOCA-style cost-aware fidelity choice (see the module docstring). Returns a fitted Emulator.

Parameters:
Return type:

Emulator