Quickstart¶
A minimal, pure-Python example – no Julia, no archived data, runs in well under a
minute on a laptop. It sequentially designs experiments for a synthetic 2-D function
using BitsForGaps, the public entry point (a thin, friendlier-named wrapper around
the adaptiveEntropy sequential-design engine; see API reference).
Note
This page shows the code as a reference, without running it during the docs build
(HMC sampling is too slow for a docs build and doesn’t belong in CI for a docs job).
A runnable version of exactly this example lives in
examples/synthetic/run_example.py –
no Julia, runs in well under a minute:
python examples/synthetic/run_example.py
See examples/synthetic/README.md for setup. (tests/integration/test_nd_synthetic.py
and test_end_to_end.py exercise the same pipeline too, if you want to see it under
test rather than as a standalone script.)
## The pieces
```python
import numpy as np
from bits_for_gaps import AnisotropicSE, BitsForGaps
# The "black box" you're designing experiments for. BitsForGaps calls it as
# black_box(*xStar) -- xStar's components in the same order as `bounds` below --
# and expects an iterable of length 1 (the observed y) back.
def black_box(x1, x2):
return [np.sin(3.0 * x1) + np.cos(3.0 * x2)]
BOUNDS = [(0.0, 1.0), (0.0, 1.0)] # one (low, high) tuple per input dimension
Seed an initial design¶
Any space-filling design works; bits_for_gaps.design provides a seeded
Latin-hypercube helper:
from bits_for_gaps import latin_hypercube_design
X_init, _ = latin_hypercube_design(BOUNDS, n_train=10, n_test=0, seed=0)
y_init = np.array([black_box(x1, x2)[0] for x1, x2 in X_init])
Build the model and run¶
bfg = BitsForGaps(
black_box=black_box,
bounds=BOUNDS,
kernel=AnisotropicSE(), # the paper's 2-D prior config (see below)
likelihood_variance=0.05,
)
# HMC/acquisition tuning knobs (defaults are set for the paper's problem size;
# a synthetic 2-D function like this one is comfortable with far fewer samples).
bfg.noSamples = 500
bfg.noBurnIn = 200
bfg.noChains = 2
history = bfg.run(X_init, y_init) # runs bfg.noIters=1 sequential-design iteration
run returns a RunHistory: one
IterationRecord per iteration, holding the fitted GP,
the HMC trace, R-hat/ESS convergence diagnostics, and the newly selected point:
record = history.last
print(record.xStar) # the next point BitsForGaps chose to evaluate
print(record.max_entropy) # the predictive entropy achieved there
print(record.rhat) # R-hat per kernel hyperparameter -- check these are ~1
To run more than one sequential-design iteration, pass iters=N to BitsForGaps(...)
(or set bfg.noIters = N) and call bfg.run(X_init, y_init) once – each iteration’s
selected point is folded back into the design automatically.
About that kernel¶
AnisotropicSE() with no arguments reproduces the paper’s exact 2-D VLE kernel
configuration – per-dimension lognormal/gamma priors on the lengthscales, a
lognormal prior on the output scale (equivalently, AnisotropicSE.paper_2d()). For
your own problem, specify priors explicitly, one per input dimension:
import tensorflow as tf
import tensorflow_probability as tfp
kernel = AnisotropicSE(
variance_prior=tfp.distributions.LogNormal(loc=tf.math.log(1.0), scale=1.0),
lengthscale_priors=[
tfp.distributions.LogNormal(loc=tf.math.log(0.3), scale=0.5),
tfp.distributions.Gamma(concentration=2.0, rate=2.0),
tfp.distributions.LogNormal(loc=tf.math.log(0.3), scale=0.5),
], # one entry per input dimension -- this example is 3-D
)
Each lengthscale (and the output scale) is its own gpflow.Parameter, so different
input dimensions can carry entirely different prior families, not just different
prior parameters – see Theory and the AnisotropicSE API reference for why
that matters. bounds (and therefore the kernel’s lengthscale_priors) can have any
number of entries – the whole pipeline is dimension-general, proven in
tests/integration/test_nd_synthetic.py at 1-D and 3-D.