Expensive black boxes: the surrogate backend (solver="surrogate")#
Black-box global search with DIRECT (solver="direct") covers the case where your objective is opaque but cheap — DIRECT will happily spend thousands of evaluations mapping the box. This page is the other regime: one evaluation costs minutes or hours, you can afford tens of them, and it is worth spending real computation deciding where each one goes.
The method is the classical one. Fit a cheap response surface to the points evaluated so far, maximize an acquisition function over that surface to choose the next point, evaluate there, refit, repeat. Jones et al. [1998] is the founding paper for the Gaussian-process form; Gutmann [2001] and Regis and Shoemaker [2005] for the radial-basis form used here by default.
Warning
Like DIRECT, this returns no certificate for your problem. bound and gap
are None, gap_certified is False, the status is never "optimal". A
surrogate is a model of the objective, not a bound on it — an interpolant says
nothing rigorous about the function between its data points.
Two families, one interface#
surrogate="rbf" (default) fits
with a cubic, thin-plate or linear kernel and a linear tail. RBF is the default
rather than a Gaussian process for three reasons, in order of weight: integer
variables work natively (discopt is a MINLP solver, and a black-box method that
cannot take integers is half a feature); fitting is one symmetric linear solve,
so there is no likelihood to maximize and no silent “the fit did not converge”;
and it degrades better with dimension, since kriging must estimate one length
scale per dimension from m points.
surrogate="kriging" is the Jones et al. [1998] route — a DACE model
[Sacks et al., 1989] plus expected improvement — with a nugget added so a
noisy objective is not forced through its own measurement error. Use it for
smooth, low-dimensional, genuinely expensive problems.
import sys; sys.path.insert(0, "../../python/tests")
from support import direct_testfuncs as tfs
tf = tfs.get("branin") # opaque dm.custom body, true optimum 0.397887
model, _ = tfs.build_model(tf)
r = model.solve(solver="surrogate", max_evals=30, time_limit=900)
print(f"status={r.status} objective={r.objective:.6f}")
print(f"bound={r.bound} gap={r.gap} gap_certified={r.gap_certified}")
status=feasible objective=0.613531
bound=None gap=None gap_certified=False
Thirty evaluations is not convergence — the backend’s own test suite reports branin reaching 1% relative accuracy at 16-42 evaluations over seeds 0-7. The point of the number above is the second line.
The cost model — read this before choosing this backend#
Nearly all the wall clock is the acquisition solve, not your objective. On
branin with a free objective and max_evals=30, instrumented per evaluation:
evals 1-6 (initial design): 0.29 s total
evals 7-30 (each): ~19.0 s <- almost exactly acquisition_time_limit
total: 456.3 s
That is the intended trade when a single evaluation dwarfs 20 s of solver time.
It is the wrong trade when it does not: on a cheap objective solver="direct"
is far faster and gets a better answer.
Important
Do not shorten acquisition_time_limit to make it feel faster. With the default
cubic kernel the acquisition never certifies, so the budget looks wasted — it
is not. It is buying primal solution quality.
Relative error at max_evals=30, measured under the pre-#1036 initial-design
rule (15 points rather than 6 — the conclusion is about
acquisition_time_limit, which that change did not touch, but the absolute
numbers have not been re-taken):
function |
|
|
|---|---|---|
branin |
0.2156 |
0.2029 |
six_hump_camel |
0.0164 |
0.8063 |
hartman_3 |
0.0098 |
0.0103 |
branin on its own says the short budget is free money. six_hump_camel is 49× worse for it. This table exists because that branin-only reading was briefly acted on during development and was wrong — a single-instance tuning of exactly the kind this project forbids.
The acquisition subproblem is certified — and that is not a certificate#
The acquisition is an ordinary algebraic model: a sum of \(\lambda_i\varphi(q_i)\) over squared distances, plus one reversed convex quadratic per design point. So discopt’s own spatial branch-and-bound can solve it to proven global optimality — the thing Jones et al. [1998] built a bespoke branch-and-bound for, and that modern Bayesian-optimization libraries approximate with multistart gradient ascent.
It certifies where to sample next. It says nothing about the answer.
from discopt._relax.primal_heuristics import _generate_starts
from discopt.solvers.surrogate import RBFSurrogate, _SurrogateSearch, build_cors_model
import numpy as np
rng = np.random.default_rng(0)
X = _generate_starts(tf.lb, tf.ub, 8, rng)
y = np.array([float(tf.np_body(x)) for x in X])
search = _SurrogateSearch(tf.lb, tf.ub)
rbf = RBFSurrogate(kernel="linear").fit(search.normalize(X), y)
sub = build_cors_model(model, tf.lb, tf.ub, rbf, delta=0.1).solve(time_limit=20.0)
print(f"acquisition subproblem: status={sub.status} certified={sub.gap_certified}")
acquisition subproblem: status=optimal certified=True
Whether it certifies is decided by the kernel. Measured on branin with a real fitted surrogate, relative gap of the acquisition subproblem at a 20 s budget:
kernel |
m=8 |
m=20 |
|---|---|---|
|
4e-05 (certified) |
6.9e-09 (certified) |
|
0.2 (not certified) |
23.9 (not certified) |
The mechanism is that cubic-RBF coefficients grow — max|λ| runs 10 → 164 across
that range — faster than a McCormick relaxation can bound. rbf_kernel stays
"cubic" by default because it gives better optimization quality; switch to
"linear" when the certificate on the subproblem is what you want:
kernel |
wall (30 evals) |
acquisitions certified |
objective |
|---|---|---|---|
|
302.5 s |
0 / 15 |
0.6135 |
|
249.0 s |
6 / 15 |
1.1298 |
Note the trade in the last column: certifying more often did not produce a better answer here.
Note
Certified expected improvement does not work, and the attempt is documented because it was the original motivation for this backend. Built properly — the division lifted away via \(EI(x) = \max_u [d(x)\Phi(u) + s(x)\phi(u)]\) and the dense \(r^\top R^{-1} r\) whitened to \(\sum v_i^2\) — branch-and-bound finds the true acquisition maximum to five significant figures, but the dual bound never closes: on branin it runs 3.82 / 23.1 / 597 / 4871 against optima 0.60 / 0.26 / 0.42 / 1.51 at m = 8/12/20/30. For kriging, discopt is an excellent primal acquisition optimizer and not a certifying one.
Sample efficiency, honestly#
Evaluations to 1e-2 relative accuracy, from the backend’s test suite:
function |
surrogate |
DIRECT |
|
|---|---|---|---|
six_hump_camel |
23 |
137 |
6.0× |
branin |
36 |
69 |
1.9× |
hartman_3 |
50 |
79 |
1.6× |
ackley_2 |
44.5 (2/3 seeds) |
67 |
1.5× |
goldstein_price |
never, 0/3 seeds |
75 |
a loss |
Median of seeds 0-2 at a 60-evaluation budget; DIRECT is deterministic. Three
seeds is a slice, not a verdict — the 8-function, 12-seed panel behind the
default initial-design size lives in the repository’s docs/dev/. The
advantage is real but it is roughly 2×, not an order of magnitude, and
goldstein_price is an outright loss caused by objective dynamic range (3 → ~10⁶);
RBFOpt’s monotone objective transformation [Costa and Nannicini, 2018] is the
known remedy and is not implemented here. DIRECT is a stronger baseline than the
surrogate literature’s framing suggests.
Choosing#
your situation |
use |
|---|---|
The model is algebraic |
the default solver — you get a certificate |
Opaque body that traces through |
the default solver — still certified, see Reduced-Space Global Optimization of Hidden-Function Models |
Opaque body, evaluation is cheap (ms–s) |
|
Opaque body, evaluation is expensive (minutes+) |
|
Experiments taking hours or days, human in the loop |
a sequential design campaign, not a blocking solve |
You need a proof |
no derivative-free method will give you one |
Where this sits#
Closer to current practice than DIRECT, but still not a tuned BoTorch/Ax stack on a problem that suits one. What this backend offers instead: no extra dependency (numpy and scipy only), deterministic behaviour, native integer support, and one modeling layer shared with the certified solver.
Two extensions are designed for but deliberately not built: trust-region restriction of the acquisition domain [Eriksson et al., 2019], the standard answer to Bayesian optimization’s collapse in higher dimensions; and batch proposals, so several expensive evaluations can run in parallel. For constrained black-box work at scale, MADS-family solvers [Audet and Dennis, 2006] remain the mature choice; Rios and Sahinidis [2013] is the standard comparison.
Options reference#
option |
default |
meaning |
|---|---|---|
|
|
evaluation budget — the cost model |
|
|
|
|
|
|
|
auto |
initial design size; default |
|
|
|
|
|
per-acquisition budget — see the cost model above |
|
|
kriging noise floor |
|
|
two runs with the same seed are identical |
|
|
progress hook |
Full citations are on the References page.