How to choose sampling algorithms#

sbi implements several inference methods, and how you obtain posterior samples differs between them.

With NPE, the trained network approximates the posterior directly, so drawing samples is just a forward pass through the network: fast, and nothing to configure. The vector-field methods FMPE and NPSE also target the posterior directly, but sample by integrating an ODE or SDE.

With NLE and NRE, the network approximates the likelihood or the likelihood-to-evidence ratio. Combined with the prior, this gives a score proportional to the log posterior, but not posterior samples, so an additional sampling algorithm is needed. sbi implements four:

  • Markov-chain Monte Carlo (MCMC)

  • Rejection sampling

  • Variational inference (VI)

  • Importance sampling (IS)

Which one should you use, and how do you set it? This guide gives a recommendation per inference method first, then shows how to configure the sampler, and finally covers the low-level potential interface for full control. See how to choose an inference method for the differences between the inference families themselves.

Which sampler do you need?#

With NPE, FMPE, or NPSE#

There is nothing to choose. build_posterior() returns a sampler that draws from the trained network, and that is normally the right choice. For FMPE and NPSE, pick the integrator with sample_with="ode" or sample_with="sde"; see how to use FMPE and NPSE.

Two situations call for something more:

  • Leakage. With a bounded prior, an NPE density can place some mass outside the prior support. DirectPosterior rejects those samples automatically, but if leakage is severe — most often in multi-round NPE — this correction becomes inefficient, and MCMC targeting the NPE potential is the better option.

  • An evaluable likelihood. If the likelihood can be evaluated, importance sampling can refine the posterior; see how to refine a posterior with importance sampling.

With NLE or NRE#

Start with MCMC. It is the default and the accurate general-purpose choice. Check that independent chains explore the same regions, and compute standard convergence diagnostics such as \(\hat{R}\) and the effective sample size with ArviZ; see how to visualize MCMC diagnostics with ArviZ.

If MCMC is not a good fit, pick by the problem you have:

  • MCMC is too slow, or sampling latency matters → try variational inference. VI fits a distribution you can then sample from cheaply, and it comes with an amortized variant that serves many observations from a single fit. VI is approximate, so check it before relying on it; see how to use variational inference.

  • The posterior is not much more concentrated than the priorrejection sampling is simple and returns exact samples from the learned target. build_posterior(sample_with="rejection") proposes from the prior, which is what makes the concentration comparison the relevant one. Coverage cannot be checked in advance, so the practical test is to run it: sbi logs a warning when the acceptance rate falls below 1%, which is the signal that the prior is a poor proposal here. A low rate means many proposal evaluations per retained sample; it is not itself a measure of posterior accuracy. RejectionPosterior.sample() does not return the rate, so call sbi.samplers.rejection.rejection_sample() directly if the number itself is needed. If a better proposal is available, such as a trained NPE posterior, construct a RejectionPosterior directly with it.

  • A good proposal is already available — a trained NPE posterior, a VI fit, or a distribution informed by an evaluable likelihood → importance sampling. sample(method="importance") returns samples together with their log-weights, so the effective sample size can be computed before relying on the result.

All of this concerns sampler efficiency and fidelity to the learned target. For posterior accuracy in general, see how to choose a diagnostic tool.

Setting the sampler#

The simplest way is a single string. build_posterior(sample_with=...) selects the posterior class:

sample_with

posterior class

"direct"

DirectPosterior

"mcmc"

MCMCPosterior

"rejection"

RejectionPosterior

"vi"

VIPosterior

"importance"

ImportanceSamplingPosterior

"ode" / "sde"

VectorFieldPosterior

"direct" is available for NPE only, and is its default. NLE and NRE default to "mcmc". FMPE and NPSE use "ode" and "sde".

mcmc_posterior = inference.build_posterior(sample_with="mcmc")
mcmc_samples = mcmc_posterior.sample((1000,), x=x_o)

To configure the sampler, pass a PosteriorParameters dataclass instead. Its type already identifies the sampler, so sample_with is not needed alongside it:

from sbi.inference.posteriors import MCMCPosteriorParameters

mcmc_posterior = inference.build_posterior(
    posterior_parameters=MCMCPosteriorParameters(
        method="slice_np_vectorized", num_chains=4, warmup_steps=100
    ),
)

These dataclasses are typed, so editors and type checkers catch invalid or misspelled options, and they expose more settings than the older per-sampler dictionaries. Those dictionaries (mcmc_parameters, vi_parameters, rejection_sampling_parameters and their counterparts) are deprecated and raise a FutureWarning whenever they are passed. The mcmc_method and vi_method arguments are deprecated as well, and warn when set to a non-default value. See how to use PosteriorParameters for the options each class accepts.

FMPE and NPSE are the exception: VectorFieldPosteriorParameters does not encode the integrator, so sample_with="ode" or "sde" still selects it.

Full control: potentials and the sampler interface#

MCMC, rejection sampling, VI, and importance sampling do not need the normalized posterior density. They only need a potential: a score proportional to the log posterior. For NLE, this is the learned log likelihood plus the log prior; for NRE, it is the learned log likelihood-to-evidence ratio plus the log prior. build_posterior() constructs the appropriate potential automatically, so most workflows never need to touch it.

Building the potential yourself is the most flexible entry point: the posterior classes can be constructed from it directly, and it can also be handed to any external sampler.

from sbi.inference import NLE, likelihood_estimator_based_potential

inference = NLE(prior=prior)
likelihood_estimator = inference.append_simulations(theta, x).train()

potential_fn, theta_transform = likelihood_estimator_based_potential(
    likelihood_estimator=likelihood_estimator,
    prior=prior,
    x_o=x_o,
)

# f(theta) = log( p(x_o | theta) p(theta) ), up to a constant.
potential = potential_fn(theta_candidates)

The returned theta_transform maps constrained parameters to unconstrained coordinates that are easier for many samplers to explore. Passing it is optional, but it usually improves MCMC performance with bounded priors.

Any of the sampler classes can then be built on the potential, which is how to use a proposal other than the prior:

from sbi.inference import MCMCPosterior, RejectionPosterior

mcmc_posterior = MCMCPosterior(
    potential_fn, proposal=prior, theta_transform=theta_transform, warmup_steps=100
)
rejection_posterior = RejectionPosterior(potential_fn, proposal=better_proposal)

To evaluate the same potential at a different observation, use bind():

potential_fn_new = potential_fn.bind(x_o_new)

bind() returns a new potential and does not modify the original. A reference to the old potential keeps the old observation, so always use the returned object. The mutable set_x() is deprecated and will be removed in a future release.

The equivalent function for NPE and NRE estimators is posterior_estimator_based_potential and ratio_estimator_based_potential, respectively. See the abstraction levels guide for how this interface relates to the higher-level ones.