From gpcam
Translates scientist's experiment descriptions into gpCAM scripts for autonomous adaptive sampling, peak-finding, and parameter optimization.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gpcam:experiment-designerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Design complete autonomous experiment scripts using gpCAM. You translate a scientist's description of their measurement into a runnable Python script.
Design complete autonomous experiment scripts using gpCAM. You translate a scientist's description of their measurement into a runnable Python script.
When a user wants to:
You are helping beamline scientists who may not know GP math or the gpCAM API. Your job is to:
Ask about:
Based on their answers, decide:
| Choice | Guidance |
|---|---|
| Optimizer class | Pick by the support of the observations: GPOptimizer for unconstrained or negative-allowed y; LogGPOptimizer if y > 0 (intensities, rates, concentrations); LogitGPOptimizer if y ∈ [0, 1] (yields, fractions, probabilities). A plain GP on positive-only or bounded data can predict invalid values — see transformed-optimizers-advanced skill. |
| Kernel | Default Matérn-3/2 ARD is good for most cases. Use periodic kernel if periodicity is known. Use Matérn-1/2 for rough/discontinuous data, Matérn-5/2 or SE for very smooth. See kernel-designer skill for custom kernels. |
| Acquisition function | 'variance' for exploration/mapping. 'expected improvement' or 'ucb' for optimization (UCB exposes a tunable exploration/exploitation tradeoff via beta). Custom callable for multi-objective or constraints. See acquisition-functions skill. |
| Prior mean | Zero (default) unless they have a physical model. See prior-mean-functions skill. |
| Noise model | Use noise_variances if noise is known and uniform. Use noise_function if noise varies. See noise-functions skill. |
| Training strategy | method='global' for first training, method='local' for re-training during the loop. Other options: "mcmc" (Bayesian — returns posterior samples over hyperparameters), "adam" (stochastic-gradient, fast, works well for high-dimensional hyperparameter vectors like deep kernels), "hgdl" (distributed local+global hybrid — needs a dask_client). |
| linalg_mode | Leave at the default (None) — gpCAM picks "Chol" automatically. For frequent posterior-covariance calls on small datasets (<5 000 points), set linalg_mode="CholInv" to store the inverse for a 3-10× speedup. For sparse / gp2Scale problems, see the gp2scale-advanced skill. |
| Number of initial points | Rule of thumb: 5-10× the input dimensionality for initial random sampling. |
| Validation | gpo.rmse(x_test, y_true) and gpo.crps(x_test, y_true) give RMSE and continuous ranked probability score on a held-out grid — call these after training to sanity-check the fit. |
Two paths: If the scientist only needs a scalar black-box optimized and has no need to inspect or customize the ask/tell loop, use the one-shot GPOptimizer.optimize() shortcut (A). Otherwise use the full template (B). The full template is required when they want custom acquisition, mid-loop re-training with specific schedules, async training, checkpointing, validation plots during the run, or integration with a live instrument.
import numpy as np
from gpcam import GPOptimizer
def f(x):
# x is shape (N, D); return (y, noise_variance) with matching shapes
y = np.sin(x[:, 0]) * np.cos(x[:, 1])
return y, np.full(len(x), 0.01)
gpo = GPOptimizer()
result = gpo.optimize(
func=f,
search_space=np.array([[0., 1.], [0., 1.]]),
max_iter=50,
)
optimize() handles initial sampling, training schedule, the ask/tell loop, and termination. Use it when the scientist has a simulator or instrument wrapper they can hand gpCAM as a Python function. For fvGP/multi-task, pass x_out=np.array([...]).
Output a complete Python script with this structure:
"""
Autonomous Experiment: [description]
Generated for gpCAM v8.4.x
Input space: [dimensions and ranges]
Output: [what is measured]
Strategy: [exploration/optimization/hybrid]
"""
import numpy as np
from gpcam import GPOptimizer
# For strictly positive observations use LogGPOptimizer; for y in [0, 1] use
# LogitGPOptimizer (drop-in replacements for GPOptimizer — see the
# transformed-optimizers-advanced skill).
# ============================================================
# 1. EXPERIMENT PARAMETERS — EDIT THESE
# ============================================================
# Define the parameter space bounds
# Each row: [min, max] for one dimension
parameter_bounds = np.array([
[0.0, 10.0], # motor_x (mm)
[0.0, 5.0], # motor_y (mm)
])
parameter_names = ["motor_x", "motor_y"]
N_INITIAL = 10 # Initial random measurements
N_ITERATIONS = 50 # Adaptive measurements
RETRAIN_EVERY = 10 # Re-train hyperparameters every N iterations
# ============================================================
# 2. YOUR MEASUREMENT FUNCTION — REPLACE THIS
# ============================================================
def measure(x):
"""
Replace this with your actual measurement.
Parameters
----------
x : np.ndarray, shape (1, D)
The point to measure. x[0, 0] is motor_x, x[0, 1] is motor_y.
Returns
-------
y : float
The measured value (scalar).
noise_variance : float or None
The estimated variance of this measurement, or None if unknown.
"""
# EXAMPLE: replace with your instrument call
y = np.sin(x[0, 0]) * np.cos(x[0, 1])
noise_variance = 0.01 # or None
return y, noise_variance
# ============================================================
# 3. KERNEL (optional customization)
# ============================================================
# The default ARD Matérn-3/2 kernel is used if kernel_function=None.
# Uncomment and modify to use a custom kernel.
# from gpcam.kernels import matern_kernel_diff1, get_anisotropic_distance_matrix
#
# def my_kernel(x1, x2, hyperparameters):
# d = get_anisotropic_distance_matrix(x1, x2, hyperparameters[1:])
# return hyperparameters[0] * matern_kernel_diff1(d, 1.0)
kernel_function = None # None = default ARD Matérn-3/2
# ============================================================
# 4. HYPERPARAMETER BOUNDS
# ============================================================
# For the default kernel: [signal_variance, length_scale_dim1, ..., length_scale_dimD]
# Rule of thumb:
# signal_variance bounds: [0.01, 10 * std(y)] (estimated after initial data)
# length_scale bounds: [0.01, 10 * range(x_dim)]
D = parameter_bounds.shape[0]
hp_bounds = np.array(
[[0.001, 100.0]] + # signal variance
[[0.01, 10.0 * (b[1] - b[0])] for b in parameter_bounds] # length scales
)
# ============================================================
# 5. ACQUISITION FUNCTION
# ============================================================
acquisition_function = "variance" # Options: "variance", "expected improvement",
# "ucb", "relative information entropy",
# or a callable
# ============================================================
# 6. RUN THE EXPERIMENT
# ============================================================
def run():
# --- Initial random sampling ---
x_init = np.random.uniform(
parameter_bounds[:, 0], parameter_bounds[:, 1],
size=(N_INITIAL, D)
)
y_init = np.zeros(N_INITIAL)
noise_init = np.zeros(N_INITIAL)
for i in range(N_INITIAL):
y_init[i], nv = measure(x_init[i:i+1])
noise_init[i] = nv if nv is not None else 0.0
# --- Initialize GP ---
gpo = GPOptimizer(
x_data=x_init,
y_data=y_init,
noise_variances=noise_init if noise_init.any() else None,
kernel_function=kernel_function,
# linalg_mode=None lets gpCAM pick Cholesky; for many posterior_covariance
# calls on a small dataset (<5 000 points) use linalg_mode="CholInv".
)
# --- Initial training ---
gpo.train(hyperparameter_bounds=hp_bounds, method="global", max_iter=200)
# --- Adaptive loop ---
for i in range(N_ITERATIONS):
# Ask: where should we measure next?
result = gpo.ask(
input_set=parameter_bounds,
acquisition_function=acquisition_function,
)
next_x = result["x"]
# Measure
new_y, new_nv = measure(next_x)
# Tell: update the GP
gpo.tell(
next_x,
np.array([new_y]),
noise_variances=np.array([new_nv]) if new_nv is not None else None,
)
# Re-train periodically
if (i + 1) % RETRAIN_EVERY == 0:
gpo.train(hyperparameter_bounds=hp_bounds, method="local", max_iter=100)
print(f"Iteration {i+1}/{N_ITERATIONS}: "
f"measured at {next_x[0]} -> {new_y:.4f}")
# --- Results ---
data = gpo.get_data()
print(f"\nDone! Collected {len(data['x data'])} points total.")
print(f"Final hyperparameters: {data['hyperparameters']}")
return gpo
if __name__ == "__main__":
gpo = run()
measure() function is a placeholder — clearly mark it and explain what to replace.This is critical and often the source of bugs:
hps[0] = signal variance, hps[1:D+1] = length scalesExample with custom noise:
# Hyperparameter layout:
# hps[0] = signal variance (kernel)
# hps[1:D+1] = length scales (kernel)
# hps[D+1] = noise amplitude (noise function)
#
# Total: D + 2 hyperparameters
def my_noise(x, hps):
return np.full(len(x), hps[D+1]**2)
hp_bounds = np.array(
[[0.001, 100.0]] + # signal variance
[[0.01, 10.0 * (b[1]-b[0])] for b in parameter_bounds] + # length scales
[[0.001, 10.0]] # noise amplitude
)
gpo.tell(x_new, y_new, append=True) adds points without overwriting; append=False replaces. Default is to replace — use append=True in a streaming instrument loop.opt_obj = gpo.train(..., asynchronous=True, method="hgdl", dask_client=client) returns immediately; later call gpo.update_hyperparameters(opt_obj) to pull current best hyperparameters, and gpo.stop_training(opt_obj) to finish. Useful when training is expensive and the loop shouldn't block.method="hgdlAsync" starts a background search for the next point; the result dict contains an opt_obj you can kill_client() once you've used the suggestion.GPOptimizer instances are picklable — pickle.dumps(gpo) before a long run lets you reload state later.gpo.gp_mutual_information(x_test) and gpo.gp_total_correlation(x_test) report information content at a candidate set.GPOptimizer(..., args={"a": 1.5, "b": 2.0}) plumbs a dict through to custom kernel/mean/noise/cost functions (they receive it as an extra argument when they declare it).For detailed API docs, kernel math, and advanced options, see the gpCAM documentation.
npx claudepluginhub lbl-camera/gpcam --plugin gpcamDesigns custom acquisition functions for gpCAM — balancing exploration vs exploitation, multi-objective targets, constrained regions, cost-aware moves, UCB, LCB, and probability-of-improvement criteria.
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.'
Generates structured experimental designs (factorial, response surface, Taguchi) to systematically discover how multiple factors affect outcomes while minimizing runs. Use for multi-factor optimization, screening, or parameter tuning.