blackjax#
This module defines a convenient interface to call
Blackjax
to do inference. You could of course just call
pangolin.jax_backend.ancestor_log_prob to get a plain jax function and
then call Blackjax yourself. But this module abstracts away all the
details.
The module has three layers:
Inference drivers (
run_nuts,run_hmc,run_pathfinder,run_meanfield_vi,run_fullrank_vi,run_smc,run_rwm): functions with the uniform signaturerun_inf(log_prob, key, initial_state, num_samples, **options), taking an unconstrained log-density and returning flat samples with leading axisnum_samples. These are public: write your own driver with this signature and wrap it withblackjax_calculate.Engines (
nuts,hmc,pathfinder,meanfield_vi,fullrank_vi,smc,rwm):pangolin.calculate.Calculateobjects pre-bound to a driver viablackjax_calculate. This is the recommended user interface.Defaults (
sample,E,var,std,sample_arviz): methods of the default engine (NUTS, 1000 samples).
- pangolin.blackjax.run_nuts(log_prob, key, initial_state, num_samples)[source]#
Sample from a density using the No-U-Turn Sampler (NUTS).
Warmup uses Blackjax’s window adaptation to tune the step size and mass matrix. Note that
num_samplesis used both as the length of the adaptation run and as the number of posterior draws.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into warmup and sampling keys.
initial_state – Initial position in the unconstrained latent space.
num_samples (int) – Number of adaptation steps and number of draws returned.
- Returns:
samples – Positions of the returned states, with leading axis
num_samples.
Examples
Draw from a 3-dimensional standard normal (no pangolin needed — any jax log-density works):
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_nuts(log_prob, key, jnp.zeros(3), num_samples=10) >>> samps.shape (10, 3)
- pangolin.blackjax.run_hmc(log_prob, key, initial_state, num_samples, num_integration_steps=60)[source]#
Sample from a density using Hamiltonian Monte Carlo (HMC).
Warmup uses Blackjax’s window adaptation to tune the step size and mass matrix; the trajectory length is fixed via
num_integration_stepsand is not adapted. As withrun_nuts,num_samplesis used both as the adaptation length and as the number of posterior draws.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into warmup and sampling keys.
initial_state – Initial position in the unconstrained latent space.
num_samples (int) – Number of adaptation steps and number of draws returned.
num_integration_steps (int, optional) – Fixed number of leapfrog steps per trajectory (default 60). Larger values explore further per iteration at higher cost; if you find yourself needing many steps, prefer NUTS, which adapts trajectory length automatically.
- Returns:
samples – Positions of the returned states, with leading axis
num_samples.
Examples
Draw from a 3-dimensional standard normal:
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_hmc(log_prob, key, jnp.zeros(3), num_samples=10) >>> samps.shape (10, 3)
- pangolin.blackjax.run_pathfinder(log_prob, key, initial_state, num_samples, elbo_samples=200, **lbfgs_kwargs)[source]#
Draw approximate posterior samples using Blackjax pathfinder.
Pathfinder runs L-BFGS optimization from the initial position and fits a Gaussian approximation along the optimization path, then draws from it. The draws are i.i.d. samples from the approximation, not a Markov chain: if the true posterior is badly non-Gaussian, results are biased in a way that more draws will not fix. Use
run_nutswhen exactness matters.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into approximation and sampling keys.
initial_state – Initial position in the unconstrained latent space, used as the L-BFGS starting point. Must not be exactly at the mode (zero gradient prevents curvature estimation).
num_samples (int) – Number of draws returned from the fitted approximation.
elbo_samples (int, optional) – Number of draws used internally to estimate the ELBO along the optimization path (default 200). Controls approximation quality, not the size of the output.
**lbfgs_kwargs – Additional keyword arguments forwarded to
blackjax.vi.pathfinder.approximate, e.g.maxiter,maxcor,ftol,gtol.
- Returns:
samples (jnp.ndarray) – Draws from the Gaussian approximation, with leading axis
num_samples.
Examples
Draw approximate samples from a 3-dimensional standard normal (starting away from the mode so L-BFGS can estimate curvature):
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_pathfinder(log_prob, key, jnp.ones(3), num_samples=10) >>> samps.shape (10, 3)
- pangolin.blackjax.run_meanfield_vi(log_prob, key, initial_state, num_samples, n_iter=500, learning_rate=0.05)[source]#
Approximate a density using mean-field variational inference.
Fits a factorized (diagonal-covariance) Gaussian to the target by stochastic optimization of the ELBO, then draws from it. The approximation cannot capture posterior correlations between latents; for a full-covariance Gaussian use
run_fullrank_vi, and for exact sampling userun_nuts.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into optimization and sampling keys.
initial_state – Initial position in the unconstrained latent space, used to initialize the variational mean.
num_samples (int) – Number of draws returned from the fitted approximation.
n_iter (int, optional) – Number of ELBO optimization steps (default 500).
learning_rate (float, optional) – Learning rate for the Adam optimizer (default 0.05).
- Returns:
samples – Draws from the fitted mean-field Gaussian, with leading axis
num_samples.
Examples
Fit a 2-dimensional standard normal and draw 10 samples:
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_meanfield_vi(log_prob, key, jnp.zeros(2), num_samples=10, n_iter=50) >>> samps.shape (10, 2)
- pangolin.blackjax.run_fullrank_vi(log_prob, key, initial_state, num_samples, n_iter=500, learning_rate=0.05)[source]#
Approximate a density using full-rank variational inference.
Fits a Gaussian with a full (Cholesky-parameterized) covariance to the target by stochastic optimization of the ELBO, then draws from it. Unlike
run_meanfield_vi, the approximation captures posterior correlations between latents; it is still a Gaussian approximation, so badly non-Gaussian posteriors are biased in a way that more optimization will not fix.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into optimization and sampling keys.
initial_state – Initial position in the unconstrained latent space, used to initialize the variational mean.
num_samples (int) – Number of draws returned from the fitted approximation.
n_iter (int, optional) – Number of ELBO optimization steps (default 500).
learning_rate (float, optional) – Learning rate for the Adam optimizer (default 0.05).
- Returns:
samples – Draws from the fitted full-rank Gaussian, with leading axis
num_samples.
Examples
Fit a 2-dimensional standard normal and draw 10 samples:
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_fullrank_vi(log_prob, key, jnp.zeros(2), num_samples=10, n_iter=50) >>> samps.shape (10, 2)
- pangolin.blackjax.run_smc(log_prob, key, initial_state, num_samples, step_size=1.0, inverse_mass_matrix=None, num_integration_steps=10, num_mcmc_steps=10, target_ess=0.5, max_iters=100)[source]#
Sample from a density using adaptive tempered Sequential Monte Carlo.
SMC maintains a population of
num_samplesparticles that is moved from a prior toward the posterior along a temperature schedule chosen adaptively to control the effective sample size. Unlike gradient-based MCMC (NUTS/HMC), SMC can move between separated posterior modes, and unlike VI it is asymptotically exact. It is the most expensive option per sample.Because tempered SMC is written as prior^tempering x likelihood, a Gaussian pseudo-prior centered at the initial position is used internally, with the target log-density playing the role of the likelihood.
initial_statetherefore seeds the initial particle cloud.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into initialization and iteration keys.
initial_state – Position in the unconstrained latent space around which the initial particle cloud is centered.
num_samples (int) – Number of particles, which is also the number of draws returned.
step_size (float, optional) – Step size of the inner HMC kernel (default 1.0).
inverse_mass_matrix (optional) – Inverse mass matrix of the inner HMC kernel. Defaults to the identity.
num_integration_steps (int, optional) – Number of leapfrog steps of the inner HMC kernel (default 10).
num_mcmc_steps (int, optional) – Number of MCMC kernel applications per particle per temperature step (default 10).
target_ess (float, optional) – Target effective sample size, as a fraction of
num_samples, used to choose the next temperature (default 0.5).max_iters (int, optional) – Safety cap on the number of temperature steps (default 100); raises if reached.
- Returns:
particles – Final particle population, with leading axis
num_samples.
Examples
Sample a 2-dimensional standard normal with 20 particles:
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_smc(log_prob, key, jnp.zeros(2), num_samples=20) >>> samps.shape (20, 2)
- pangolin.blackjax.run_rwm(log_prob, key, initial_state, num_samples, step_size=1.0)[source]#
Sample from a density using random-walk Metropolis (RWM).
Each step proposes a Gaussian perturbation of the current position and accepts or rejects it via the Metropolis ratio. No gradients are used, so this works on any (even non-differentiable) log-density; the cost is slow, diffusive exploration that scales poorly with dimension. Prefer NUTS whenever gradients are available. There is no adaptation:
step_sizemust be chosen by hand, and the firstnum_samplessteps are discarded as warmup.- Parameters:
log_prob (Callable) – Log-density of the target distribution, as a jitted jax function over the unconstrained latent space.
key (jax.random.PRNGKey) – Randomness source; split internally into warmup and sampling keys.
initial_state – Initial position in the unconstrained latent space.
num_samples (int) – Number of draws returned. An equal number of warmup steps is discarded first.
step_size (float, optional) – Standard deviation of the Gaussian proposal (default 1.0). Tune so that the acceptance rate is roughly 0.2-0.4.
- Returns:
samples – Positions of the returned states, with leading axis
num_samples.
Examples
Draw from a 3-dimensional standard normal:
>>> import jax >>> import jax.numpy as jnp >>> log_prob = lambda x: -0.5 * jnp.sum(x ** 2) >>> key = jax.random.PRNGKey(0) >>> samps = run_rwm(log_prob, key, jnp.zeros(3), num_samples=10) >>> samps.shape (10, 3)
- pangolin.blackjax.sample_flat(vars, given_vars, given_vals, *, run_inf=<function run_nuts>, bijector_dict={<class 'pangolin.ir.Beta'>: <function <lambda>>, <class 'pangolin.ir.Cauchy'>: None, <class 'pangolin.ir.Dirichlet'>: <function <lambda>>, <class 'pangolin.ir.Exponential'>: <function <lambda>>, <class 'pangolin.ir.Gamma'>: <function <lambda>>, <class 'pangolin.ir.Lognormal'>: <function <lambda>>, <class 'pangolin.ir.MultiNormal'>: None, <class 'pangolin.ir.Normal'>: None, <class 'pangolin.ir.NormalPrec'>: None, <class 'pangolin.ir.StudentT'>: None, <class 'pangolin.ir.Uniform'>: <function <lambda>>, <class 'pangolin.ir.Wishart'>: <function <lambda>>}, deferred=True, **inf_args)[source]#
Run inference over a flat list of variables.
Latent variables are split into:
MCMC vars: latents that are ancestors of a given (observed) variable. These are unconstrained via bijectors and sampled by
run_inf.Deferred vars: latents with no observed descendants. These are marginalized out of the MCMC target and ancestor-sampled at the end via
fill_in, conditioned on posterior draws of their parents.
The split is exact: no MCMC/given var has a deferred parent, the deferred factors integrate to 1 in constrained space, and deferred latents are drawn from their true conditional given the MCMC latents.
- pangolin.blackjax.blackjax_calculate(run_inf, frozen=(), **options)[source]#
Wrap an inference driver into a convenient
Calculateobject.The driver is always frozen: an engine bound to
run_nutscannot later be called with a differentrun_inf. All other options are overridable at call time.- Parameters:
run_inf – Inference driver, with signature
run_inf(log_prob, key, initial_state, num_samples, **options) -> samples, wherelog_probis a jitted jax function over the unconstrained latent space,keyis a jax PRNGKey, andinitial_stateis a position in that space. Seerun_nuts,run_hmc,run_pathfinderfor examples. Nothing requires the driver to use Blackjax internally.frozen (Iterable[str]) – Additional option names that cannot be overridden at call time.
"run_inf"is always included.**options – Default options forwarded to
run_inf(and typically includingnum_samples).
- Returns:
Calculate – Calculator bound to
sample_flatwith the given driver. Its runtime docstring forwards the driver’s documentation, sohelp(engine)shows the driver’s parameters.- Return type:
Examples
>>> my_nuts = blackjax_calculate(run_nuts, num_samples=500) >>> my_engine = blackjax_calculate(run_smc, num_particles=500)
- pangolin.blackjax.nuts = <pangolin.calculate.Calculate object>#
NUTS inference engine; options are forwarded to
run_nuts.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var, etc.).
- pangolin.blackjax.hmc = <pangolin.calculate.Calculate object>#
HMC inference engine; options are forwarded to
run_hmc.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var, etc.).
- pangolin.blackjax.pathfinder = <pangolin.calculate.Calculate object>#
Pathfinder inference engine; options are forwarded to
run_pathfinder.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var, etc.).
- pangolin.blackjax.meanfield_vi = <pangolin.calculate.Calculate object>#
Mean-field VI inference engine; options are forwarded to
run_meanfield_vi.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var, etc.).
- pangolin.blackjax.fullrank_vi = <pangolin.calculate.Calculate object>#
Full-rank VI inference engine; options are forwarded to
run_fullrank_vi.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var,std,sample_arviz).
- pangolin.blackjax.smc = <pangolin.calculate.Calculate object>#
Tempered SMC inference engine; options are forwarded to
run_smc.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var,std,sample_arviz).
- pangolin.blackjax.rwm = <pangolin.calculate.Calculate object>#
Random-walk Metropolis inference engine; options are forwarded to
run_rwm.This is a
pangolin.calculate.Calculate— see that class for the available methods (sample,E,var,std,sample_arviz).
- pangolin.blackjax.sample(vars, given_vars=None, given_vals=None, reduce_fn=None, **options)#
Default version of
Calculate.samplethat draws 1000 samples via NUTS.- Parameters:
vars (PyTree[pangolin.ir.RV])
given_vars (PyTree[pangolin.ir.RV])
given_vals (PyTree[ArrayLike])
reduce_fn (Callable | None)
- pangolin.blackjax.E(vars, given_vars=None, given_vals=None, **options)#
Default version of
Calculate.Ethat uses 1000 samples via NUTS.- Parameters:
vars (PyTree[pangolin.ir.RV])
given_vars (PyTree[pangolin.ir.RV])
given_vals (PyTree[ArrayLike])
- pangolin.blackjax.var(vars, given_vars=None, given_vals=None, **options)#
Default version of
Calculate.varthat uses 1000 samples via NUTS.- Parameters:
vars (PyTree[pangolin.ir.RV])
given_vars (PyTree[pangolin.ir.RV])
given_vals (PyTree[ArrayLike])
- pangolin.blackjax.std(vars, given_vars=None, given_vals=None, **options)#
Default version of
Calculate.stdthat uses 1000 samples via NUTS.- Parameters:
vars (PyTree[pangolin.ir.RV])
given_vars (PyTree[pangolin.ir.RV])
given_vals (PyTree[ArrayLike])
- pangolin.blackjax.sample_arviz(vars, given_vars=None, given_vals=None, **options)#
Default version of
Calculate.sample_arvizthat uses 1000 samples via NUTS.- Parameters:
vars (dict[str, RV])
given_vars (PyTree[pangolin.ir.RV])
given_vals (PyTree[ArrayLike])