Python samplers
Gravity can use sampling algorithms implemented in Python while keeping the model, prior transform, and likelihood calculation in Julia. Python support is provided by PythonCall.jl.
The implemented backends are UltraNest, Nautilus, and dynesty.
Installation
PythonCall is a dependency of Gravity, but Python sampling packages are optional. To install UltraNest into the Python environment managed by PythonCall, run from the same Julia project used for Gravity:
julia> import CondaPkg
julia> CondaPkg.add("ultranest")
julia> CondaPkg.add_pip("nautilus-sampler"; extras=["checkpointing"])
julia> CondaPkg.add_pip("dynesty")Alternatively, create a CondaPkg.toml beside the active Project.toml:
[deps]
ultranest = ""
[pip.deps]
dynesty = ""
[pip.deps.nautilus-sampler]
extras = ["checkpointing"]The Python environment is resolved the next time PythonCall is loaded. Verify the installation with:
julia> import PythonCall
julia> PythonCall.pyimport("ultranest").__version__
Python: '...'
julia> PythonCall.pyimport("nautilus").__version__
Python: '...'
julia> PythonCall.pyimport("dynesty").__version__
Python: '...'When a generated model selects Nautilus or dynesty, Gravity checks its PythonCall environment and automatically installs a missing distribution with CondaPkg. If Nautilus has a non-null filepath, Gravity also installs its checkpointing extra, which provides h5py. UltraNest remains manually installed. The commands above are also useful for preparing an environment before a run or diagnosing installation issues.
YAML configuration
Select the backend under sampling.algorithm:
sampling:
scheme: sourceplane
algorithm: ultranest
algorithm-options:
points: 400
dlogz: 0.5
min-ess: 400
max-ncalls: 100000
log-dir: results/ultranest
resume: resume
ndraw-min: 128
ndraw-max: 4096
show-visualization: false
include-logdensity: true
threads: auto
random-seed: 741For Nautilus:
sampling:
scheme: sourceplane
algorithm: nautilus
algorithm-options:
points: 400
min-ess: 1000
max-ncalls: 100000
n-batch: 200
n-networks: 4
filepath: results/nautilus.hdf5
resume: true
equal-weight-boost: 2.0
include-logdensity: true
threads: auto
random-seed: 741For dynesty:
sampling:
scheme: sourceplane
algorithm: dynesty
algorithm-options:
dynamic: true
points: 500
bounds: multi
proposal: rwalk
dlogz: 0.1
min-ess: 1000
max-ncalls: 100000
show-status: true
include-logdensity: true
threads: auto
random-seed: 741Then use the standard Gravity workflow:
using Gravity
model = gravitymodel("model.yml")
chain = runmodel(model)The generated prior transform is quantile(model.prior, u). All backends sample the generated model.loglikelihood(θ, model.options). This distinction is important: applying the prior transform and then sampling logposterior would count the prior twice and produce an incorrect evidence.
Options
Nested samplers maintain a set of live points and progressively replace the point with the lowest likelihood. The options fall into four groups:
- Exploration quality (
points, bounds, and proposal controls) determines how reliably the sampler finds and follows separate or narrow modes. - Convergence targets (
dlogz,min-ess, andf-live) determine when a normally progressing run is considered complete. - Safety limits (
max-ncalls,max-iters, andtimeout) stop a run even if its convergence targets have not been met. - Throughput and persistence (batch, display, and checkpoint options) affect speed and recoverability, not the statistical target.
Increasing points is usually the first response to missed modes or unstable evidence. Tightening dlogz primarily asks for a more complete evidence integral, while increasing min-ess primarily asks for more information in the posterior sample. These changes all increase the number of likelihood evaluations, sometimes substantially.
max-ncalls and max-iters are therefore best treated as emergency budgets, not convergence settings. If a run reaches one of them, inspect the backend status and diagnostics before treating its evidence or posterior as final. The backends count work differently: a likelihood-call limit is the most comparable budget across them.
UltraNest options
UltraNest uses a reactive strategy: points is a minimum rather than necessarily the final live-point count, and the run continues until all requested convergence criteria are satisfied.
| YAML option | Python argument | Meaning and use |
|---|---|---|
points | min_num_live_points | Minimum live-point population. More points improve resolution of modes and evidence reliability, at greater cost. |
dlogz | dlogz | Stop when the estimated remaining evidence is sufficiently small. Smaller values are stricter; 0.5 is a useful exploratory value, while evidence-sensitive work should test convergence with smaller values. |
min-ess | min_ess | Minimum effective sample size (ESS) of the weighted posterior. Increase it when posterior summaries are noisy even though the evidence criterion is satisfied. |
max-ncalls | max_ncalls | Hard ceiling on likelihood evaluations. Reaching it does not imply convergence. |
max-iters | max_iters | Hard ceiling on nested-sampling iterations. Prefer max-ncalls when budgeting expensive likelihoods. |
ndraw-min | ndraw_min | Smallest vectorized proposal batch. Larger batches reduce Julia–Python overhead and expose more Julia threading, but can evaluate more rejected points and use more memory. |
ndraw-max | ndraw_max | Largest batch UltraNest may use when draw-multiple is enabled. It must be at least ndraw-min; avoid very large values unless batched likelihood evaluation is demonstrably cheap. |
draw-multiple | draw_multiple | Allow UltraNest to enlarge proposal batches when sampling efficiency falls. Usually keep this enabled for Gravity's vectorized likelihood. |
num-test-samples | num_test_samples | Number of startup calls used to catch invalid prior transforms, shapes, or likelihood values. This is a correctness check, not an accuracy control. |
num-bootstraps | num_bootstraps | Number of bootstrap evidence estimators and region-construction rounds. More can make uncertainty/region estimates more robust, but increases region-update work. |
log-dir | log_dir | Directory for checkpoints, results, and diagnostic files. Use a distinct directory for each materially different model. |
resume | resume | overwrite starts fresh in log-dir; subfolder creates a new numbered run; resume continues an identical likelihood/prior; resume-similar may reuse points after a changed likelihood. |
storage-backend | storage_backend | UltraNest point-store format. The upstream-recommended default is hdf5; change it mainly for interoperability or debugging. |
warmstart-max-tau | warmstart_max_tau | For resume-similar, maximum tolerated change in live-point ordering. Values from 0 (conservative) to 1 (permissive); the default -1 disables this warm-start path. |
show-status | Julia callback / show_status | Show progress. With visualization disabled, Gravity uses a compact Julia progress meter. |
show-visualization | viz_callback | Enable UltraNest's multi-line live-point display. Useful for interactive diagnosis, noisy in logs. |
include-logdensity | — | Evaluate the generated log-posterior for the final equal-weight sample and store it as chain variable :lp. Disable to avoid this extra batched pass. |
With show-status: true and show-visualization: false, Gravity disables UltraNest's Python printer and renders a Julia-native progress meter containing the evidence, remaining evidence, iteration count, and likelihood-call count. The meter is active only when Julia's stderr is a real TTY, so redirected output remains quiet. show-visualization opts back into UltraNest's verbose multi-line live-point diagrams. Set both options to false for a completely quiet sampler.
If top-level sampling.iterations is explicitly present, it is used as max_iters unless algorithm-options.max-iters is also specified.
For a first serious run, set points, dlogz, and min-ess, enable a checkpoint directory, and leave the region-construction controls at their UltraNest defaults. Tune ndraw-min and ndraw-max only after timing the batched likelihood.
Nautilus options
Nautilus first explores the likelihood and constructs neural-network bounds, then samples shells to reach its posterior ESS target. Options controlling bounds can affect correctness and efficiency; change them only when diagnostics or repeated runs indicate that the defaults are inadequate.
| YAML option | Python argument | Meaning and use |
|---|---|---|
points | n_live | Live points used to construct bounds. More points help resolve complicated or separated modes but make exploration and bound fitting more expensive. |
n-update | n_update | Maximum additions to the live set before constructing a new bound. Smaller values update bounds more often; larger values reduce fitting overhead but may use stale, inefficient bounds. |
enlarge-per-dim | enlarge_per_dim | Linear enlargement of outer ellipsoids in each dimension. Larger values are safer but less efficient; values too close to one can exclude valid volume. |
n-points-min | n_points_min | Minimum points assigned to an ellipsoid, limiting how finely a multi-ellipsoid bound can split. Smaller values permit finer clusters but make their estimates noisier. |
split-threshold | split_threshold | Volume-ratio threshold for splitting a multi-ellipsoid bound. Lower values split more readily; use the default unless bound construction is known to be inefficient. |
n-networks | n_networks | Neural networks combined in each Nautilus bound. More networks can stabilize the learned boundary at extra fitting cost. |
n-batch | n_batch | Likelihood evaluations requested per batch. Larger values improve Julia threading and reduce callback overhead, but can overshoot stopping limits and consume more memory. |
n-like-new-bound | n_like_new_bound | Maximum likelihood calls before attempting a new bound. Lower values refresh bounds more frequently. |
f-live | f_live | Exploration stops after the estimated fraction of evidence still in the live set falls below this value. Smaller values explore farther and cost more. This is distinct from the later min-ess target. |
n-shell | n_shell | Minimum retained points per shell. Increasing it improves shell representation but adds likelihood calls. |
min-ess | n_eff | Target posterior ESS. Raise it for more precise posterior expectations; it does not by itself make the initial exploration more reliable. |
max-ncalls | n_like_max | Hard total likelihood-call ceiling, including calls restored from a checkpoint. A run stopped here reports chain.info.success == false. |
timeout | timeout | Wall-clock limit in seconds for the current run call. Like max-ncalls, it is a safety limit rather than convergence evidence. |
discard-exploration | discard_exploration | Discard exploration-phase points. Nautilus documents this as required for a fully unbiased posterior and evidence estimate, at the cost of throwing away useful samples. |
filepath | filepath | .h5/.hdf5 checkpoint file, or null for no checkpoint. Gravity installs Nautilus's checkpointing extra when this is non-null. |
resume | resume | With a checkpoint file, continue it when true; start from scratch and overwrite it when false. Do not resume after changing the model or prior. |
equal-weight-boost | equal_weight_boost | Controls conversion of the weighted posterior to the equal-weight chain. Values above 1 return more points and allow duplicates, approximating the weighted posterior more closely; they do not create new likelihood information. |
show-status | verbose | Print Nautilus progress information. |
include-logdensity | — | Store a final generated log-posterior evaluation as chain variable :lp. |
An explicit top-level sampling.iterations becomes n_like_max unless max-ncalls is set. random-seed initializes Nautilus directly.
equal-weight-boost only affects the returned MCMCChains.Chains, after the sampler has finished. Increasing it can reduce resampling granularity but cannot increase the ESS of the underlying weighted sample; use min-ess for that.
dynesty options
Dynamic nested sampling (the default) can allocate additional samples where they most improve the evidence or posterior. Static sampling keeps a fixed live-point population and has simpler, more predictable behavior. Dynamic sampling is normally preferable for posterior estimation; static sampling is useful when comparing against a conventional fixed-live-point run.
| YAML option | Python argument | Meaning and use |
|---|---|---|
dynamic | sampler class | true selects DynamicNestedSampler; false selects the static NestedSampler. |
points | nlive | Initial live-point count (or fixed count for static sampling). Increase it for complex multimodal or strongly curved likelihoods. |
bounds | bound | Shape used to approximate the constrained prior. Gravity accepts its generic names (nobounds, ellipsoid, multiellipsoid) and dynesty names such as none, single, or multi; multi is a robust general choice. |
proposal | sample | Method for drawing within the bound. Common choices are auto, unif/rejection, rwalk, slice, and rslice. Rejection is effective for tight, accurate bounds; random-walk or slice methods are often better in higher dimensions or difficult geometries but require tuning. |
update-interval | update_interval | Bound-update frequency. More frequent updates can improve proposal efficiency but spend more time rebuilding bounds. Leave automatic unless profiling shows a problem. |
enlarge | enlarge | Multiplicative bound enlargement. Larger bounds are safer but reduce rejection efficiency; too-small bounds risk under-covering the constrained prior. |
num-bootstraps | bootstrap | Bootstrap realizations used to estimate bound enlargement. More is more conservative and expensive; it interacts with enlarge. |
walks | walks | Number of accepted random-walk steps before proposing a new live point. Applies to random-walk proposals; too few can leave correlated proposals, too many waste likelihood calls. |
acceptance-fraction | facc | Target acceptance fraction used to tune random walks. Only relevant to proposal methods that use it. |
slices | slices | Number of slice updates per proposal. Larger values mix more thoroughly at higher cost. |
cluster-dimensions | ncdim | Number of leading dimensions used when clustering bounds. Useful when only part of a high-dimensional parameter space is strongly multimodal. |
dlogz | dlogz_init / dlogz | Evidence-based stopping tolerance (dlogz_init for the initial dynamic run, dlogz for static). Smaller is stricter and more expensive. |
min-ess | n_effective | Dynamic-only target ESS. Gravity ignores it for static sampling. Increase it for smoother posterior summaries. |
max-ncalls | maxcall | Hard likelihood-call ceiling; reaching it need not mean convergence. |
max-iters | maxiter | Hard iteration ceiling. Top-level sampling.iterations supplies this value unless max-iters is set. |
checkpoint-file | checkpoint_file | File used to save dynesty's sampler state. Use a new file after changing the model, prior, or relevant sampler setup. |
checkpoint-every | checkpoint_every | Approximate seconds between checkpoints. Short intervals improve recoverability but add I/O overhead. |
resume | resume | Resume from checkpoint-file when possible. This is dynesty checkpoint resumption, not extension from a Gravity initial_state. |
show-status | print_progress | Display dynesty's progress output. |
include-logdensity | — | Store a final generated log-posterior evaluation as chain variable :lp. |
When tuning proposal, change one family-specific control at a time: walks and acceptance-fraction for random walks, or slices for slice sampling. A low acceptance rate alone does not prove that the posterior is wrong; compare evidence, posterior summaries, and repeated seeded runs before changing bound enlargement or clustering.
Parallel and vectorized evaluation
UltraNest and Nautilus configure the Python likelihood with vectorized=true. Python passes a matrix with shape (number_of_points, number_of_parameters) to Julia. Generated Gravity functions already support this convention:
values = model.loglikelihood(points, model.options)Each matrix row is one parameter vector. Gravity evaluates these rows with its threaded tmap implementation. Larger ndraw-min and ndraw-max values reduce Julia–Python callback overhead and expose more parallel work, but also increase memory usage.
Start Julia with multiple threads to benefit from batched evaluation:
julia --project=. --threads=autoThe optimal batch size depends on the model. Benchmark model.loglikelihood(points, model.options) using representative batch sizes before a long run.
Dynesty does not provide a general vectorized-likelihood interface. Gravity batches direct pool calls used for live-point initialization and dynamic batches. Dynesty's internal proposal evolution uses the scalar Julia callback to avoid unsafe concurrent Python-to-Julia calls.
Result and evidence
runmodel returns an MCMCChains.Chains containing the backend's equally weighted posterior samples. The evidence is available as:
chain.logevidenceUltraNest diagnostics are stored in:
chain.info.logzerr
chain.info.niter
chain.info.ncall
chain.info.essFor Nautilus, chain.info contains success, ess, ncall (when the installed version exposes it), log_weights, and log_likelihood. The latter two preserve Nautilus's values even though the chain itself is equal-weighted.
The native result remains accessible through chain.info.state:
result = chain.info.state
result.result["weighted_samples"]
result.samplerWith include-logdensity: true (the default), Gravity performs one final batched evaluation of model.logposterior and stores it as :lp. This makes bestlp(chain) and report generation work as for other Gravity samplers. Set the option to false if this final evaluation is too expensive.
Low-level interface
The bridge can also be used without a generated Gravity model:
problem = Gravity.PythonNestedSamplingProblem(
loglikelihood,
prior_transform,
ndim;
batch_logdensity,
parameter_names=[:a, :b],
)
sampler = Gravity.UltraNestSampler(
ndraw_min=128,
run_kwargs=(min_num_live_points=400, dlogz=0.5),
julia_progress=true,
)
result = Gravity.python_sample(sampler, problem)
chain = MCMCChains.Chains(result)The corresponding Nautilus configuration is:
sampler = Gravity.NautilusSampler(
n_live=400,
n_batch=200,
run_kwargs=(n_eff=1000, f_live=0.01),
posterior_kwargs=(equal_weight_boost=2.0,),
)
result = Gravity.python_sample(sampler, problem)
chain = Gravity.nautilus_chains(result)The corresponding dynesty configuration is:
sampler = Gravity.DynestySampler(
nlive=500,
bound="multi",
sample="rwalk",
run_kwargs=(dlogz_init=0.1, n_effective=1000),
)
result = Gravity.python_sample(sampler, problem)
chain = Gravity.dynesty_chains(result)Density functions requiring static data can pass it without a closure:
problem = Gravity.PythonNestedSamplingProblem(
loglikelihood,
prior_transform,
ndim;
options,
batch_logdensity=loglikelihood,
)Troubleshooting
- Automatic installation fails: check network and package-index access, or manually install
ultranestornautilus-samplerin the same project and PythonCall environment from which Gravity is run. - UltraNest was installed but cannot be imported: inspect
PythonCall.python_executable_path()and ensure installation and execution use the same Julia project. NameError: name 'h5py' is not defined: the installed Nautilus lacks its checkpointing extra. RunCondaPkg.add_pip("nautilus-sampler"; extras=["checkpointing"]); current Gravity versions do this automatically whenfilepathis non-null.- Sampling is not parallel: start Julia with
--threads=auto, keepparameters.use_threadsconfigured to includesampling, and use batch sizes larger than one. - Evidence differs unexpectedly: ensure the sampler receives
loglikelihood, notlogposterior, when using a prior transform. - A resumed run rejects the likelihood: the model, parameter order, prior, or static likelihood options changed. Use a new
log-dirorresume: overwrite.