Contextual bandit agents (GP-UCB and Thompson sampling) with GP surrogates, evolutionary acquisition optimisation, and Langevin/NUTS posterior sampling.
Project description
banditry
Contextual bandit agents for black-box optimisation over mixed design spaces.
Documentation: vahanarsenian.github.io/banditry
banditry isolates the bandit routines from
MetaFrieren into a
standalone, reusable library. Its design-space interface and the MACE
acquisition strategy are partially inspired by the
HEBO repository from
Huawei Noah's Ark Lab.
What it does
banditry runs a suggest → evaluate → observe loop against an expensive
black-box objective (it minimises the observed values) and provides the
pieces of that loop as composable modules:
- Agents (
banditry.agents) — the decision-making policies:- GP-UCB / OFU agent (
OFUGPAgent): optimism-in-the-face-of-uncertainty with either Bayesian or frequentist (Chowdhury–Gopalan) confidence widths, optimising the MACE multi-objective acquisition ensemble (mean, sigma, LCB). - Thompson-sampling agent (
TSAgent): a neural value function whose posterior is sampled by an MCMC oracle, with optional Feel-Good Thompson sampling reweighting.
- GP-UCB / OFU agent (
- Surrogates (
banditry.surrogates) — exact Gaussian processes and sparse variational GPs (gpytorch), plus the neuralValueFunctionused by TS. - Optimisation oracles (
banditry.optimisation_oracles) — evolutionary acquisition optimisers wrapping pymoo (NSGA-II / NSGA-III / U-NSGA-III) and an SGLD optimiser (stochastic gradient Langevin dynamics). - Optimisation subroutines (
banditry.optimisation_subroutines) — acquisition objectives (mean, sigma, LCB, MACE, Thompson objective) and the pymoo problem wrapper that handles contexts. - Sampling oracles (
banditry.sampling_oracles) — posterior samplers for the TS agent: Langevin dynamics (SGLD with a Welling–Teh step-size schedule) and NUTS (via pyro). - Variable domains (
banditry.variable_domains) — mixed design spaces with numeric, integer, boolean, and categorical parameters, plus the transforms (scaling, one-hot/embedding) used to feed them to the models.
Contexts are supported throughout: any subset of parameters can be pinned to observed environment values each round, turning the loop into a contextual bandit.
Best-so-far trace of plain vs Feel-Good Thompson sampling (Langevin posterior
sampling) on the built-in gaussian_valley benchmark — reproduce with
examples/04_feel_good_ts.py.
Installation
Requires Python 3.10+.
From PyPI:
pip install banditry
The NUTS sampler (used by TSConfig(sampler="nuts")) needs pyro, which is an
optional extra:
pip install "banditry[nuts]"
From source, for development:
git clone https://github.com/VahanArsenian/banditry.git
cd banditry
pip install -e ".[nuts]"
Conda users can pip install banditry inside any conda environment; the core
dependencies (numpy, pandas, torch, gpytorch, pymoo, rich) are resolved by pip.
Usage
Quickstart
import numpy as np
from banditry import DesignSpace, OFUGPConfig, build_agent
# Objective: minimise a 2-D function over a box.
def objective(df):
x0 = df["x0"].to_numpy(dtype=float)
x1 = df["x1"].to_numpy(dtype=float)
return (x0 - 0.3) ** 2 + (x1 + 0.2) ** 2
space = DesignSpace.parse([
{"name": "x0", "type": "num", "lb": -1, "ub": 1},
{"name": "x1", "type": "num", "lb": -1, "ub": 1},
])
config = OFUGPConfig(rand_sample=4, surrogate="gp", noise_std_proxy=1.0)
agent = build_agent(config, space)
for _ in range(20):
rec = agent.suggest(1) # DataFrame of suggestions
y = np.asarray(objective(rec), dtype=float).reshape(-1)
agent.observe(rec, y) # agents minimise y
best_row = agent.get_best_id() # index of the best observation
The agent starts with rand_sample quasi-random (Sobol) warmup suggestions,
then switches to model-based suggestions.
Mixed design spaces
All four parameter types can be combined freely:
space = DesignSpace.parse([
{"name": "learning_rate", "type": "num", "lb": 1e-4, "ub": 1e-1},
{"name": "num_layers", "type": "int", "lb": 1, "ub": 8},
{"name": "use_dropout", "type": "bool"},
{"name": "optimiser", "type": "cat", "categories": ["adam", "sgd", "rmsprop"]},
])
Suggestions come back as a pandas DataFrame with one column per parameter, in the original (untransformed) domain.
Contextual bandits
Pass the observed context each round via the second argument of suggest; the
pinned parameters are fixed while the rest are optimised conditionally:
context = {"x1": 0.7} # observed environment state this round
rec = agent.suggest(1, context) # x1 is pinned to 0.7 in the suggestion
y = np.asarray(objective(rec), dtype=float).reshape(-1)
agent.observe(rec, y)
best_for_context = agent.get_best_id(fix_input=context)
Thompson-sampling agents
from banditry import TSConfig, build_agent
# Langevin posterior sampling (no extra dependencies)
agent = build_agent(TSConfig(rand_sample=4, sampler="langevin"), space)
# NUTS posterior sampling (requires banditry[nuts])
agent = build_agent(TSConfig(rand_sample=4, sampler="nuts"), space)
# Feel-Good Thompson sampling
agent = build_agent(
TSConfig(sampler="langevin", feel_good=True, fg_lambda=1.0, fg_bound=1.0),
space,
)
Sampler behaviour is controlled through sampler_config; the defaults live in
banditry.agents.factory.DEFAULT_LANGEVIN_CONFIG and DEFAULT_NUTS_CONFIG and
can be copied and overridden:
from banditry.agents.factory import DEFAULT_LANGEVIN_CONFIG
config = TSConfig(
sampler="langevin",
sampler_config={**DEFAULT_LANGEVIN_CONFIG, "num_epochs": 256, "temperature": 0.1},
)
Agent configuration reference
OFUGPConfig (GP-UCB agents):
| Field | Default | Meaning |
|---|---|---|
rand_sample |
4 |
Number of Sobol warmup suggestions |
surrogate |
"svgp" |
"gp" (exact) or "svgp" (sparse variational) |
frequentist |
False |
Chowdhury–Gopalan frequentist confidence widths instead of Bayesian |
rkhs_norm |
None |
RKHS norm bound B (frequentist widths) |
noise_std_proxy |
None |
Required. Sub-Gaussian / GP noise scale R used by the confidence widths |
model_config_overrides |
{} |
Passed through to the surrogate |
TSConfig (Thompson-sampling agents):
| Field | Default | Meaning |
|---|---|---|
rand_sample |
4 |
Number of Sobol warmup suggestions |
sampler |
"nuts" |
"langevin" or "nuts" |
feel_good |
False |
Enable Feel-Good Thompson sampling |
fg_lambda, fg_bound |
1.0 |
Feel-Good reweighting strength and cap |
model_config |
{} |
Value-function network configuration |
sampler_config |
None |
Sampler overrides (None → per-sampler defaults) |
should_warm_start |
True |
Warm-start MCMC from the previous round |
latent_dimension |
None |
Latent dimension of the sampled parameter vector |
Benchmark runner
Installing the package also installs a banditry-bench CLI that exercises
every agent on synthetic benchmarks:
banditry-bench --agent ofugp-gp --benchmark branin --n-iter 30 --seed 42
banditry-bench --agent ts-langevin --benchmark gaussian_valley --n-iter 30 --seed 42
banditry-bench --agent ofugp-svgp --benchmark contextual_branin --n-iter 30 --seed 42
Agents: ofugp-gp, ofugp-svgp, ts-langevin, ts-nuts. Benchmarks:
branin, contextual_branin, gaussian_valley, contextual_gaussian_valley.
Examples
Runnable, seeded example scripts live in examples/:
01_quickstart_branin.py— GP-UCB on Branin02_mixed_design_space.py— num + int + bool + cat parameters03_contextual_bandit.py— pinning observed context viafix_input04_feel_good_ts.py— TS vs Feel-Good TS comparison plot
Origins & acknowledgements
- Partially inspired by HEBO (Huawei Noah's Ark Lab) — in particular the design-space interface and the MACE acquisition ensemble.
- Isolates the bandit routines from MetaFrieren into a standalone package.
Please cite
If you use banditry in your research, please cite:
@software{banditry,
author = {Arsenyan, Vahan},
title = {banditry: contextual bandit agents with Gaussian-process surrogates},
year = {2026},
url = {https://github.com/VahanArsenian/banditry},
version = {0.3.0}
}
License
MIT. Portions derive from HEBO (Huawei Noah's Ark Lab), also MIT-licensed — see NOTICE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file banditry-0.3.0.tar.gz.
File metadata
- Download URL: banditry-0.3.0.tar.gz
- Upload date:
- Size: 63.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a6349cff302fea7547736054e7e7105f1a24f90e01dc3c806cc463b572f5f40e
|
|
| MD5 |
c28ce28feda5caffcb4776ed32bef988
|
|
| BLAKE2b-256 |
bb4b667322187399760274260aae52ccde230f754c7914931c4b4764b47e35a9
|
Provenance
The following attestation bundles were made for banditry-0.3.0.tar.gz:
Publisher:
release.yml on VahanArsenian/banditry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
banditry-0.3.0.tar.gz -
Subject digest:
a6349cff302fea7547736054e7e7105f1a24f90e01dc3c806cc463b572f5f40e - Sigstore transparency entry: 2218243634
- Sigstore integration time:
-
Permalink:
VahanArsenian/banditry@f0985ce4c216f7b613dda2a687c411557da433d1 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/VahanArsenian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f0985ce4c216f7b613dda2a687c411557da433d1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file banditry-0.3.0-py3-none-any.whl.
File metadata
- Download URL: banditry-0.3.0-py3-none-any.whl
- Upload date:
- Size: 72.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fbca46cb45da92caad56b22ac76004070c2063e608b259a1b2461da9811777ee
|
|
| MD5 |
80039226fe680afc871e17e18a4dc0bb
|
|
| BLAKE2b-256 |
aad3f462e7086d6cf225e8c54fbd6be8d7fe93fe89ec277b51718b7b92a742a7
|
Provenance
The following attestation bundles were made for banditry-0.3.0-py3-none-any.whl:
Publisher:
release.yml on VahanArsenian/banditry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
banditry-0.3.0-py3-none-any.whl -
Subject digest:
fbca46cb45da92caad56b22ac76004070c2063e608b259a1b2461da9811777ee - Sigstore transparency entry: 2218243651
- Sigstore integration time:
-
Permalink:
VahanArsenian/banditry@f0985ce4c216f7b613dda2a687c411557da433d1 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/VahanArsenian
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f0985ce4c216f7b613dda2a687c411557da433d1 -
Trigger Event:
push
-
Statement type: