From gpcam
Designs custom acquisition functions for gpCAM — balancing exploration vs exploitation, multi-objective targets, constrained regions, cost-aware moves, UCB, LCB, and probability-of-improvement criteria.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gpcam:acquisition-functionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Design custom acquisition functions that control where gpCAM measures next.
Design custom acquisition functions that control where gpCAM measures next.
When a user needs acquisition behavior beyond the built-in options:
def my_acquisition(x, gp_optimizer):
"""
Parameters
----------
x : np.ndarray, shape (V, D)
Candidate points to evaluate.
gp_optimizer : GPOptimizer
The GP model. Use its posterior_mean() and posterior_covariance() methods.
Returns
-------
scores : np.ndarray, shape (V,)
Score for each candidate. HIGHER = more desirable (function is MAXIMIZED).
"""
Key rule: The acquisition function is maximized. Return higher values for points you want to measure.
Pass these as strings to gpo.ask(acquisition_function=...):
| Name | String key | Best for |
|---|---|---|
| Variance | "variance" | Pure exploration / mapping |
| Expected Improvement | "expected improvement" | Optimization (find max) |
| Probability of Improvement | "probability of improvement" | Optimization, risk-averse |
| Upper Confidence Bound | "ucb" | Maximization with tunable exploration/exploitation |
| Lower Confidence Bound | "lcb" | Minimization with tunable exploration/exploitation |
| Predicted Maximum | "maximum" | Pure exploitation — mean only, no uncertainty |
| Predicted Minimum | "minimum" | Pure exploitation for minimization |
| Gradient | "gradient" | Seek steepest regions of the posterior mean |
| Target Probability | "target probability" | Find points with output near a target value |
| Relative Information Entropy | "relative information entropy" | Information-theoretic exploration |
| RIE Set | "relative information entropy set" | Batch acquisition |
| Total Correlation | "total correlation" | Batch acquisition |
To sanity-check any built-in or custom acquisition on a grid of candidates without calling ask():
scores = gpo.evaluate_acquisition_function(x_grid, acquisition_function="ucb")
Available as the built-in string "ucb" — pass directly to gpo.ask(acquisition_function="ucb"). Write the callable form below only when you need to tune beta or otherwise customize the score:
def ucb(x, gpo):
"""
beta controls exploration/exploitation tradeoff:
beta=0: pure exploitation (just go to predicted max)
beta=1: mild exploration
beta=3: strong exploration (~95% confidence)
"""
beta = 2.0 # TUNE THIS
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
return mean + beta * np.sqrt(var)
gpCAM maximizes acquisition, so flip the sign for minimization:
def lcb(x, gpo):
"""Find the minimum of the function."""
beta = 2.0
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
return -(mean - beta * np.sqrt(var)) # note the negation
from scipy.stats import norm
def expected_improvement_minimize(x, gpo):
"""Expected improvement for finding the MINIMUM."""
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
std = np.sqrt(np.maximum(var, 1e-10))
y_best = np.min(gpo.y_data) # current best (minimum)
z = (y_best - mean) / std
ei = std * (z * norm.cdf(z) + norm.pdf(z))
return ei
from scipy.stats import norm
def probability_of_improvement(x, gpo):
"""Probability that measurement improves on current best."""
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
std = np.sqrt(np.maximum(var, 1e-10))
y_best = np.max(gpo.y_data)
z = (mean - y_best) / std
return norm.cdf(z)
def constrained_variance(x, gpo):
"""Explore but avoid a circular forbidden zone."""
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
# Forbidden zone: circle at (5, 5) with radius 1
center = np.array([5.0, 5.0])
dist = np.linalg.norm(x - center, axis=1)
penalty = np.where(dist < 1.0, -1e6, 0.0)
return var + penalty
def multi_objective(x, gpo):
"""Balance finding the max with reducing uncertainty."""
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
w_exploit = 0.7 # weight on exploitation
w_explore = 0.3 # weight on exploration
# Normalize each component to [0, 1]
mean_norm = (mean - mean.min()) / (mean.max() - mean.min() + 1e-10)
var_norm = (var - var.min()) / (var.max() - var.min() + 1e-10)
return w_exploit * mean_norm + w_explore * var_norm
Useful when searching for where a signal crosses a threshold:
def threshold_finder(x, gpo):
"""Find the boundary where f(x) = threshold."""
threshold = 0.5 # EDIT THIS
mean = gpo.posterior_mean(x)["m(x)"]
var = gpo.posterior_covariance(x, variance_only=True)["v(x)"]
std = np.sqrt(np.maximum(var, 1e-10))
# Score is high near the threshold AND where uncertainty is high
distance_to_threshold = np.abs(mean - threshold)
return std / (distance_to_threshold + 0.01)
# Built-in string or a callable are both accepted:
result = gpo.ask(
input_set=parameter_bounds,
acquisition_function=ucb, # or "ucb", "expected improvement", ...
)
ask() options| Argument | Meaning |
|---|---|
n=N | Request N points at once (batch). For vectorized single-task, use a batch-aware acquisition like "relative information entropy set" or "total correlation". |
vectorized=True (default) | The acquisition function is called once with all candidate points, shape (V, D) — required for custom callables written against the contract above. |
vectorized=False | Candidates are evaluated one at a time (list of 1-D arrays). Used for non-vectorizable acquisition or non-Euclidean inputs. |
method="global"|"local"|"hgdl"|"hgdlAsync" | Inner optimizer that searches for the argmax of the acquisition over input_set. hgdl requires dask_client=; hgdlAsync starts a background search and returns an opt_obj you can poll or kill_client(). |
dask_client=client | Distribute the inner optimization across Dask workers. |
batch_size=B | When candidates are a list, evaluate them in chunks of B on the cluster. |
max_iter, pop_size, info=True | Inner optimizer controls. |
input_set can be continuous bounds (np.array([[lo,hi], ...])), a list of candidate points (discrete finite set), or a list of arbitrary objects (non-Euclidean — strings, graphs — provided your kernel handles them).
Acquisition functions don't add hyperparameters — they read the GP state via gpo.posterior_mean() and gpo.posterior_covariance(). However:
gpo.y_data directly (e.g., for y_best), make sure it's up to date after tell()train() firstvariance_only=True: faster, returns just diagonal variances (usually what you want)variance_only=False but this is O(V²) memorynp.maximum(var, 1e-10) before taking sqrt.npx claudepluginhub lbl-camera/gpcam --plugin gpcamTranslates scientist's experiment descriptions into gpCAM scripts for autonomous adaptive sampling, peak-finding, and parameter optimization.
Optimizes multi-objective problems using pymoo (NSGA-II/III, MOEA/D) with Pareto-front computation, constraint handling, and standard benchmarks (ZDT, DTLZ).
Designs experiments, ranks parameter influence, and selects optimization strategies for simulation calibration. Activates on queries like 'which parameters matter most' or 'how do I calibrate my model.'