Tutorial
This tutorial walks through a complete optimisation run: wrapping a blackbox function in an Objective, configuring the search, executing the run, and reading the reproducibility record it leaves behind.
Setup
git clone https://github.com/AlxndrSchroeder/actgpr.git
cd actgpr
poetry install
Step 1 — wrap your blackbox function
actgpr minimises a scalar blackbox function: something that takes one
float and returns one float. In practice that might launch a simulation or
trigger an experiment — here we use an analytic stand-in so the tutorial
runs instantly:
from actgpr import ObjectiveFn
def my_blackbox(x: float) -> float:
"""Stand-in for a simulation or experiment."""
return (x - 1) ** 2
objective = ObjectiveFn(my_blackbox)
ObjectiveFn turns any Callable[[float], float] into an Objective.
Its evaluate method accepts one or more input points and returns a tuple
of outputs:
objective.evaluate(0.0) # (1.0,)
objective.evaluate(0.0, 3.0) # (1.0, 4.0)
Errors raised inside your function propagate unchanged, so you can handle them by their original type.
Step 2 — configure the run
Three decisions matter most:
search_boundsThe closed interval
[lo, hi]in which the algorithm searches for the minimum. The blackbox is never evaluated outside it.max_iterationsThe budget cap: the maximum number of active optimisation iterations (GPR fit cycles).
ei_thresholdThe convergence threshold: the run stops early once the best achievable Expected Improvement falls below this value — meaning the surrogate sees nothing left to gain.
You also choose a fit mode:
OptimisationRun.with_training(...)— the GP hyperparameters (lengthscale, outputscale, noise) are re-tuned at every iteration using Adam (GPyTorch’storch.optim.Adamintegration), a gradient-descent variant that maximises the marginal log likelihood — how plausible the observed training data is under the GP. Use this when you do not know good hyperparameters — the usual case.OptimisationRun.without_training(...)— hyperparameters stay fixed at the values you pass. Use this for controlled comparisons or when good values are already known.
from actgpr import GPyTorchSurrogate, OptimisationRun
run = OptimisationRun.with_training(
objective=objective,
surrogate=GPyTorchSurrogate(),
search_bounds=(-3.0, 5.0), # interval in which the minimum is searched
initial_train_x=[-3.0, 5.0], # points where we start looking for the minimum
max_iterations=20,
ei_threshold=0.001,
store_snapshots=True, # keep per-iteration state for plotting
run_dir="results", # write the MRR record
)
Step 3 — execute and interpret
result = run.run()
print(result["best_x"]) # ≈ 1.0 — input point with the lowest output
print(result["best_y"]) # ≈ 0.0 — the lowest output found
print(result["n_iterations"]) # iterations actually executed
print(result["stop_reason"]) # "ei_threshold" or "max_iterations"
result["train_x"] and result["train_y"] hold every input point the
run evaluated and the corresponding Objective outputs — the initial points
first, then one point per iteration.
Step 4 — browse the iterations
Because the run was created with store_snapshots=True, you can step
through the surrogate’s view of the problem iteration by iteration:
run.plot_iterations()
An interactive matplotlib window opens with the GP prediction (mean, 95 % confidence band, training data) on top, the EI landscape below, and a slider to scrub through iterations.
EI typically shrinks by orders of magnitude as a run converges — on a
linear axis, later iterations can look like a flat line at zero with no
visible structure. Pass log_scale=True to keep that shrinkage visible,
with ei_threshold drawn as a reference line:
run.plot_iterations(log_scale=True)
If the run stopped because max_ei fell below ei_threshold, the
slider’s final frame is the fit that triggered that convergence — titled
“(converged — not evaluated)” since its candidate point was scored but
never actually evaluated, so it carries no pred_error/improvement.
Step 5 — the reproducibility record (MRR)
Because run_dir was given, the run created a timestamped folder under
results/ holding the five MRR artifacts:
config.json— every parameter used, written at the start of the runmanifest.json— a SHA-256 checksum of the inputsmeta.json— environment: package name, version, and repository, git commit, Python/library versions, platform, timestamps, and output summaryrun.log— a human-readable, per-iteration audit trailresults.h5— self-describing HDF5: configuration is stored as attributes alongside the data, so the file can be understood on its own
Revisiting a saved run
plot_run_history builds the validation-metrics plot directly from a
run directory — no OptimisationRun object needed, so a past run can be
revisited at any later time:
from pathlib import Path
from actgpr.plotting import plot_run_history
run_dir = sorted(Path("results").iterdir())[-1] # newest run
plot_run_history(run_dir)
This plots prediction_error and improvement against iteration:
prediction_error shrinking towards zero shows the surrogate learning
the blackbox; improvement flattening shows the optimisation converging.
For a custom analysis, read the same series directly:
import h5py
with h5py.File(run_dir / "results.h5") as f:
iteration = f["history/iteration"][:]
prediction_error = f["history/prediction_error"][:]
improvement = f["history/improvement"][:]
Parameter reference
with_training and without_training share the same core parameters
and differ only in how the GP hyperparameters are handled.
with_training only
Parameter |
Meaning |
|---|---|
|
Number of Adam optimisation steps run per surrogate fit, tuning lengthscale, outputscale, and noise to maximise the marginal log likelihood. |
without_training only
Parameter |
Meaning |
|---|---|
|
Fixed RBF kernel lengthscale — never tuned. |
|
Fixed kernel signal variance — never tuned. |
Where to go next
The API reference documents every class and function.
The README’s vocabulary section defines every term used in this package.