AFL.double_agent.BoTorchMixin module#
BoTorch machinery shared by the GP regressors, classifiers and acquisition ops.
Everything BoTorch specific lives on BoTorchMixin: the lazy import of the
optional dependency, the tensor helpers that move data between xarray and
torch, GP construction, and the bound helpers that read a
PipelineOp’s own attributes so an op can call
self.candidates(dataset) instead of threading the same variable and dimension
names through every call. ScoreFunctionAcquisition is the one module-level
object: a torch.nn.Module adapter for user score functions.
- class AFL.double_agent.BoTorchMixin.BoTorchMixin#
Bases:
objectAll BoTorch machinery an op needs, as methods on one class.
Mix this into a
PipelineOpsubclass (list it before the op base class) and call_init_botorch()at the end of__init__.The class has two layers. Stateless helpers (static/class methods such as
optimization_bounds_to_tensor(),fit_single_task_gp(),evaluate_acquisition_on_grid()) take explicit arguments and can be called on the class itself, e.g.BoTorchMixin.fit_single_task_gp(train_x, train_y). Bound helpers (candidates(),bounds_tensor(),load_model(),optimize_acqf(), …) read the following attributes, which the op sets itself:grid_variable,grid_dimCandidate grid and the dimension indexing its points; the last remaining dimension (or
component_dimif the op defines one) indexes components.feature_input_variable,predictor_input_variable,sample_dim,objective_directionOnly needed by
training_data()(regressors).bounds,is_simplex,model_variable,model_uri,num_restarts,raw_samplesSet by
_init_botorch().
The mixin does not define
__init__so it composes with anyPipelineOpconstructor signature.- BOUNDS_OUTPUT = 'bounds'#
dimension and coordinate names used for the
boundsoutput
- static best_grid_point(values: Tensor | ndarray, candidate_x: Tensor) tuple[Tensor, float]#
Fallback when optimization fails: the grid point with the largest (finite) value.
Returns
(x, value)withxshaped(1, d).
- property botorch: dict#
The BoTorch symbols, imported on first use so
import AFLworks without BoTorch.
- bounds_tensor(dataset: Dataset, write_output: bool = True) Tensor#
self.boundsas a(2, d)tensor aligned with the grid’s components.When
write_outputis true and the grid has component names, the bounds are also written to theboundsoutput so downstream ops can read them.
- classmethod build_builtin_acquisition(acquisition_kind: str, model, best_f: float)#
Instantiate one of the built-in BoTorch acquisition functions.
acquisition_kindis"logei"(analytic LogEI) or"qlogei"(Monte Carlo qLogEI for joint batches).
- classmethod build_single_task_gp(train_x: Tensor, train_y: Tensor, standardize: bool = True)#
Construct an unfitted SingleTaskGP. Used to rebuild saved models.
- candidates(dataset: Dataset) Tensor#
The candidate grid
grid_variableas an(n, d)tensor.
- component_names(dataset: Dataset) ndarray | None#
Component coordinate values of the grid, or
Nonewhen the grid has no component coords.
- constraints(n_dim: int) list[tuple[Tensor, Tensor, float]] | None#
Simplex inequality constraints when
is_simplexis set, otherwiseNone.
- classmethod dataset_to_botorch_candidates(dataset: Dataset, grid_variable: str, grid_dim: str) Tensor#
The candidate grid as an
(n, d)double tensor.
- classmethod dataset_to_botorch_training_data(dataset: Dataset, feature_input_variable: str, predictor_input_variable: str, sample_dim: str, objective_direction: Literal['maximize', 'minimize'] = 'minimize') tuple[Tensor, Tensor]#
(train_x, train_y)double tensors;train_yis negated when minimizing.
- static evaluate_acquisition_on_grid(acq_function, candidate_x: Tensor, q: int = 1) Tensor#
Evaluate an acquisition function at every row of
candidate_x(n, d).Points are evaluated individually when
q <= 1. For joint acquisitions withq > 1the grid is scanned with a sliding window ofqconsecutive points; windows that do not fit are padded with-inf.
- classmethod fit_single_task_gp(train_x: Tensor, train_y: Tensor, standardize: bool = True)#
Build and fit a SingleTaskGP by maximizing the exact marginal log likelihood.
- static get_observed_best_f(train_y: Tensor, objective_direction: Literal['maximize', 'minimize'] = 'maximize') float#
Best observed target in original objective units (
train_yis in maximization space).
- grid_component_dim(dataset: Dataset) str#
The component dimension of the grid (
self.component_dimif the op defines one).
- static import_botorch()#
Import the BoTorch symbols the ops use, with a single helpful error message.
- load_model(dataset: Dataset)#
Load the model whose ref an upstream op wrote to
model_variable.
- static make_simplex_constraints(n_dim: int) list[tuple[Tensor, Tensor, float]]#
Return BoTorch-compatible simplex constraints.
BoTorch’s optimize_acqf accepts linear inequality constraints of the form
\[\sum_i a_i x_i \ge b.\]A simplex equality \(\sum_i x_i = 1\) is therefore encoded as the pair
\[\sum_i x_i \ge 1, \qquad -\sum_i x_i \ge -1,\]which together are exactly equivalent to the equality constraint.
- static optimization_bounds_to_tensor(bounds: Mapping[str, Mapping[str, float] | Sequence[float]] | Sequence[Sequence[float]], component_names: Sequence[Any] | None = None) Tensor#
Convert explicit constructor bounds into a
(2, d)BoTorch bounds tensor.- Parameters:
bounds – Either a mapping from component name to
{"min": lower, "max": upper}dictionaries, a mapping from component name to(lower, upper)pairs, or an ordered sequence of(lower, upper)pairs.component_names – Optional ordered component names used to align mapping-based bounds with model inputs.
- static optimization_bounds_to_xarray(bounds: Tensor, component_names: Sequence[Any], variable_name: str) DataArray#
Represent optimization bounds as an xarray DataArray.
- optimize_acqf(acq_function, bounds: Tensor, q: int = 1, *, constraints: Sequence[tuple[Tensor, Tensor, float]] | None = None, with_grad: bool = True, batch_initial_conditions: Tensor | None = None) tuple[Tensor, Tensor]#
Maximize
acq_functionoverboundswith BoTorch’soptimize_acqf.Uses
self.num_restarts/self.raw_samples.with_grad=Falsemakes the L-BFGS-B optimizer use finite-difference gradients, which is required for score functions that are not differentiable by autograd. Returns detached(candidates, values).
- static posterior_to_xarray(posterior, grid_index: DataArray, output_prefix: str, objective_direction: Literal['maximize', 'minimize'] = 'maximize') Dataset#
{prefix}_mean/{prefix}_varianceover the grid, mean in original objective units.
- save_model(model, args: dict | None = None, builder=None) str | None#
Save
modeltomodel_uriand write the ref to{prefix}_model; no-op withoutmodel_uri.
- training_data(dataset: Dataset) tuple[Tensor, Tensor]#
(train_x, train_y)for this op’s feature/predictor variables, in maximization space.
- AFL.double_agent.BoTorchMixin.Bounds#
{component: {"min": lo, "max": hi}},{component: (lo, hi)}or an ordered sequence of(lo, hi)pairs.- Type:
Optimization bounds for BoTorch ops
alias of
Mapping[str,Mapping[str,float] |Sequence[float]] |Sequence[Sequence[float]]
- class AFL.double_agent.BoTorchMixin.ScoreFunctionAcquisition(function, kwargs: Mapping[str, Any] | None = None)#
Bases:
ModuleAdapt a user score function to BoTorch’s acquisition interface.
The wrapped function scores one candidate set: it receives
Xwith shape(q, d)(qjointly proposed design points, no batch dimension) and returns a single scalar, higher being better. This module adds BoTorch’s batch handling: a(b, q, d)input is scored one candidate set at a time and returned as(b,).Xis atorch.Tensorso that torch-based functions are optimized with exact gradients. Functions that work on NumPy arrays also work: if the function cannot handle a tensor that requires gradients, or returns something without autograd history, it is called on a detached tensor (which NumPy accepts as an array) anddifferentiableis set toFalseso that the optimizer switches to finite differences.kwargsare the staticargsof the acquisition spec, with anyVariableplaceholders already resolved; the function is called asfunction(X, **kwargs).- forward(X: Tensor) Tensor#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- score(X: Tensor) Tensor#
Score one
(q, d)candidate set, returning a scalar tensor.