Emulator backends¶
All emulators compress the output with PCA and fit one Gaussian process per component. They share the BaseEmulator API (fit, predict, save, load), so changing backend is a one-line change.
Backend (--backend) |
Class | Install | Use when |
|---|---|---|---|
sklearn (default) |
GaussianProcessPCA |
base install | small datasets, no JAX |
jax |
GaussianProcessPCAJAX |
uv sync --extra jax |
large N, GPU, gradients, HMC |
vae |
GaussianProcessVAEJAX |
uv sync --extra jax |
nonlinear output compression |
from class_e.stats.jax_gp import GaussianProcessPCAJAX, JAXGPConfig
emulator = GaussianProcessPCAJAX(JAXGPConfig(n_components=12, steps=200))
emulator.fit(X_train, Y_train) # X: (N, D) parameters, Y: (N, K) spectra
Y_pred = emulator.predict(X_test)
Persist emulators as bundles (class-e train --out model.cemu), not pickles.
Configuration (JAX backend)¶
JAXGPConfig(
n_components=12, # PCA components
kernel="rbf", # 'rbf', 'matern32', 'matern52', ...
ard=True, # one length scale per input
standardize_X=True,
standardization_mode="prior",
log_space=False, # emulate log(Y) (positive observables)
optimize_hyperparams=True,
steps=200,
)
GPU training¶
The JAX backends train on an NVIDIA GPU with the jax-cuda extra (Linux, CUDA 12):
uv sync --extra jax-cuda # or: pip install "class-e[jax-cuda]"
XLA_PYTHON_CLIENT_PREALLOCATE=false class-e train cfg.yaml
XLA_PYTHON_CLIENT_PREALLOCATE=false makes JAX allocate GPU memory as needed instead of
75% of it at start-up, so the memory in use is the memory the fit needs. Training holds
about six N × N float64 matrices: about 13 GB at N = 16384, whatever the number of inputs.
On the CPU, jaxlib factorizes the kernel matrix with scipy's BLAS/LAPACK, so the BLAS
thread count matters. Cluster profiles often set OPENBLAS_NUM_THREADS=1 (against an
OpenBLAS crash under concurrent XLA calls while sampling), which serializes training;
class-e train warns when it trains a JAX backend on more than 1000 points that way.
Unset it, or set it to the cores of the job, for training. Training TT on N = 4095 points
(9 inputs) takes:
| Setup | Time |
|---|---|
64 cores, OPENBLAS_NUM_THREADS=1 |
34 min |
| 64 cores, unthrottled | 18 min |
| 1 × A100 | 82 s |
The three agree to 1.5e-5 σ_CV.
Input standardization¶
Gaussian processes are sensitive to input scales, and a poor choice biases inference. Three modes are available (all backends):
| Mode | Center and scale | Use when |
|---|---|---|
"training" (default) |
mean and standard deviation of the training inputs | training and inference regions coincide |
"prior" (recommended) |
from the prior bounds, see below | uniform designs (LHC, Sobol) and inference anywhere in the prior |
"manual" |
standardization_center / standardization_scale |
inference around a known fiducial point |
Prior mode maps each parameter with
so the prior box spans \(\pm 3\) in \(x\).
Training mode can bias results
With a uniform design but inference away from the training mean, the standardization offset is systematic and does not shrink with more training data.
config = JAXGPConfig(standardize_X=True, standardization_mode="prior")
emulator = GaussianProcessPCAJAX(config)
emulator.set_bounds(bounds) # [(min, max), ...] per parameter; required for prior mode
emulator.fit(X_train, Y_train)
diag = emulator.diagnose_standardization(fiducial, param_names)
# relative_offset < 0.3 for every parameter: fine; > 0.5: bias risk
Gradients and sampling¶
The JAX and VAE backends expose differentiable predictions:
import jax, jax.numpy as jnp
y = emulator.predict_jax(jnp.asarray(theta)) # (K,) or (n, K)
grad = jax.grad(lambda p: jnp.sum(emulator.predict_jax(p)))(jnp.asarray(theta))
GaussianProcessPCAJAX.log_likelihood_jax(params, data, sigma) returns a differentiable Gaussian log-likelihood, ready for HMC/NUTS samplers such as numpyro. The first call includes JIT compilation time.
Input transforms and segments¶
InputTransformtrains on expressions of the parameters (for examples = w0 + wa) while the interface stays in the physical parameters.SegmentedEmulatorandclass-e stitchjoin emulators covering different \(\ell\) ranges, blending over an overlap window.DistanceEmulatorgives \(H(z)\) and all distances from one emulator of the expansion history, andRatioEmulatorthe recombination scalars (\(r_d\), \(\theta_*\), ...): see Distances and expansion history.
See the Emulators API.