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.