Rejection Sampling¶
Overview¶
integrate_rejection performs probabilistic inversion by rejection
sampling: prior samples (and their pre-computed forward-modelled data,
stored in a prior HDF5 file) are compared against observed data (stored in
a data HDF5 file), and samples inconsistent with the observation – within
a temperature-controlled tolerance – are rejected. What remains is a
posterior ensemble of nr samples per data point, written to a posterior
HDF5 file.
It can be run either from Python, via ig.integrate_rejection(), or
from the command line, via the integrate_rejection script installed
with the package.
Running from Python¶
Minimal call, using all defaults (numpy backend, automatic temperature
estimation, one posterior file per prior file):
import integrate as ig
f_post_h5 = ig.integrate_rejection('prior.h5', 'data.h5')
A more complete call, matching the options available on the command line:
f_post_h5 = ig.integrate_rejection(
f_prior_h5='prior.h5',
f_data_h5='data.h5',
f_post_h5='posterior.h5',
N_use=1_000_000, # max number of prior samples to use
id_use=[1, 2], # which /D<id> data sets to invert
nr=1000, # posterior samples retained per data point
autoT=1, # automatic temperature estimation
Ncpu=4, # CPU cores (0 = auto-detect)
parallel=True,
backend='numpy', # 'numpy' (default) or 'jax'
showInfo=1,
)
See the Parameters section of ig.integrate_rejection() (or
help(ig.integrate_rejection)) for the full, current list of keyword
arguments – including less commonly used ones such as id_prior,
use_N_best, T_N_above/T_P_acc_level, and progress_callback
for GUI integration.
Running from the command line¶
The same functionality is exposed by the integrate_rejection console
script (installed as part of the package, see [project.scripts] in
pyproject.toml):
integrate_rejection --prior prior.h5 --data data.h5 --output post.h5
Common options:
integrate_rejection --prior prior.h5 --data data.h5 \
--samples 1000000 \
--nr 1000 \
--auto-temp \
--cpus 4 \
--backend numpy \
--verbose
Option |
Short |
Description |
|---|---|---|
|
|
Path to prior HDF5 file (required) |
|
|
Path to observed-data HDF5 file (required) |
|
|
Output posterior HDF5 path (auto-generated if omitted) |
|
|
Max number of prior samples to use |
|
|
Enable automatic temperature estimation |
|
Base temperature when |
|
|
Posterior samples retained per data point (default 400 on the CLI) |
|
|
|
Number of CPU cores (0 = auto-detect) |
|
Disable parallel processing |
|
|
Number of chunks for parallel processing (0 = auto) |
|
|
Comma-separated list of data IDs to invert |
|
|
Restrict to the N best-fitting samples (0 = disabled) |
|
|
|
|
|
|
Print configuration and full tracebacks on error |
Run integrate_rejection --help for the authoritative, up-to-date list.
Backends: numpy (default) vs. jax (experimental)¶
Choosing the default backend: REJECTION_BACKEND¶
Passing backend= explicitly (Python) or --backend (CLI) always
wins. If it is not given, the backend is taken from the
REJECTION_BACKEND environment variable, and only falls back to
'numpy' if that is unset too. This is useful to switch the default
backend for a whole script, notebook, or shell session without touching
every integrate_rejection() call:
import os
os.environ["REJECTION_BACKEND"] = "jax" # or "numpy"
import integrate as ig
ig.integrate_rejection('prior.h5', 'data.h5') # uses 'jax'
or from the shell:
REJECTION_BACKEND=jax integrate_rejection --prior prior.h5 --data data.h5
numpy backend¶
The default. Likelihood evaluation and resampling run on the CPU via
NumPy, parallelised across processes with multiprocessing (see
--cpus/Ncpu and --chunks/Nchunks). This is the
well-tested, recommended path for routine use and does not require any
GPU or extra dependency.
jax backend (experimental)¶
Selected with backend='jax' (Python) or --backend jax (CLI).
Implemented in integrate.integrate_rejection_jax. Likelihood
evaluation and post-processing (temperature estimation, weighted
sampling, evidence, CHI2) run on-device (GPU, if available), and only the
small final result per batch is transferred back to the host – this
avoids a PCIe bottleneck that otherwise makes a naive GPU path much slower
than the CPU path. Data points are processed in batches; tune the batch
size with Nbatch=<int> (default 64), passed through **kwargs.
This backend requires JAX to be installed separately, e.g.:
pip install jax # CPU only
pip install jax[cuda12] # GPU (CUDA 12)
It is marked experimental: it is newer and less exercised than the
numpy backend, and its main advantage only materialises on GPU
hardware and/or very large N.
First-run compile time. JAX JIT-compiles its likelihood kernel the
first time it sees a given input shape (roughly, the number of prior
samples and features). On GPU this compilation – in particular for the
large reduction fusions involved (sort/cumsum/searchsorted over
N ~ 1e6 samples) – can take tens of seconds, and XLA will print a
slow_operation_alarm warning if a single fusion takes unusually long
to compile. A per-shape compiled-kernel cache is already wired up
automatically (~/.cache/jax_xla_gpu, configured in
integrate_rejection_jax.py via
jax.config.update("jax_compilation_cache_dir", ...)), so repeated
runs with the same shape reuse the compiled kernel and skip this cost.
For a new shape, or to speed up the first compile itself, set the
following before importing jax (i.e. before import integrate):
import os
os.environ["XLA_FLAGS"] = (
"--xla_gpu_autotune_level=1 " # skip exhaustive GEMM/conv autotuning
"--xla_backend_optimization_level=1" # lower LLVM codegen effort for GPU kernels
)
xla_gpu_autotune_level=1 reduces time spent benchmarking GEMM/
convolution algorithm choices; xla_backend_optimization_level=1
reduces the optimization effort XLA’s LLVM-based codegen spends per
kernel, which is what actually dominates compile time for the reduction
fusions used here. Both must be set as a single XLA_FLAGS string
before JAX is imported anywhere in the process to take effect.
If compile time is still a bottleneck after this, consider keeping N
(and other shape-determining parameters) constant across runs so the
per-shape cache is reused, rather than lowering optimization further.