Companion repository for the paper of that name (NSFlows), arXiv:2609.03193.
This repository contains nsflows, the Python package used to produce all
results reported in the paper: a nested sampling implementation in which the
constrained-prior sampling step is performed by a normalizing flow.
All of them read data included here, so every figure rebuilds from a fresh clone without re-running any simulation. The runs behind Figures 4 and 5 can also be reproduced, see Examples below.
- Python >= 3.10
- PyTorch >= 2.2
- NumPy, SciPy, Matplotlib, einops, tqdm
The results in the paper were produced on linux-64 with Python 3.12.2 and PyTorch 2.2.1 (CUDA 11.8 / cuDNN 8.7). Network training was run on GPU; the code also runs on CPU, more slowly.
Three routes are provided, from the most faithful to the paper's environment to
the most portable. In all cases the final step installs nsflows itself in
editable mode.
conda_envs/spec-file_nsflows.txt is an explicit conda spec pinning every
package to the exact build used for the published results.
conda create --name nsflows --file conda_envs/spec-file_nsflows.txt
conda activate nsflows
pip install -e .This is platform-specific: it hardcodes linux-64 package URLs and will not solve on macOS, Apple Silicon or other platforms. It may also fail if a pinned build has since been removed from the channels — use route 2 in that case.
conda_envs/environment.yml states the same dependencies as version ranges and
lets conda solve them for the current platform.
conda env create -f conda_envs/environment.yml
conda activate nsflows
pip install -e .This installs the CPU build of PyTorch by default. To get the GPU build used in
the paper, uncomment the pytorch-cuda=11.8 line in the file before creating
the environment.
requirements.txt pins the exact versions of the direct dependencies, without
the conda-level packages.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .For a GPU build of PyTorch, install it from the appropriate PyTorch index before running the command above; see https://pytorch.org/get-started/locally/.
nsflows/__init__.py is empty, so a bare import nsflows succeeds even if the
dependencies are missing. Import a module that actually pulls in PyTorch:
python -c "import nsflows.nested_sampling, nsflows.network.trainer; print('ok')"The 2D-testsystems/ directory contains two notebooks that run nested sampling on
the same system: a single particle in a two-dimensional double well, defined in
nsflows.systems.testsystems_2D. They are the smallest complete demonstration of
the method and run on a laptop CPU, the flow-based one considerably faster on a GPU.
| Notebook | What it does |
|---|---|
2D-testsystems/std_ns.ipynb |
Standard nested sampling, with a rejection Monte Carlo propagator only. Useful as the baseline the flow-based results are compared against. |
2D-testsystems/nsflows.ipynb |
Nested sampling with a normalizing flow, which is trained on the live set and then used to generate new configurations below the energy bound. |
Run the cells in order. Both notebooks define their parameters in one cell near the
top, so that is the only place you need to edit to change a run. The flow is
controlled entirely by turn_on_nf: it is the iteration at which the flow takes
over, and a negative value disables it, which is what std_ns.ipynb uses.
LJ-disks/ runs the same method on eight Lennard-Jones disks in two dimensions,
the system the paper's results are for. The two state points are selected by the
density parameter, 0.95 and 0.73, which fixes the box length and the input data.
| Notebook | What it does |
|---|---|
LJ-disks/std_ns.ipynb |
Standard nested sampling, the baseline |
LJ-disks/nsflows.ipynb |
Nested sampling with the flow |
LJ-disks/multiple_live_sets_conditioning.ipynb |
Flow efficiency against the energy bound, conditioned on it |
LJ-disks/multiple_live_sets_training.ipynb |
The same, retraining at each live set instead |
LJ-disks/plot_ljdisks_results.ipynb |
Figures 2, 3, 4, 5 and 7, from the data included here |
Only the last one is quick. The other four are full runs: the flow-based nested
sampling took between 8 and 32 hours per run on a GPU, and the two efficiency
scans take hours per live set. Reduce max_ns_iterations, or live_sets and
total_steps, to see them work before committing to a full run.
Each run creates its own directory under <system>/output/<run-id>/ and
writes there as it goes:
output.txt, one row per iteration: iteration, live points, acceptance, energy bound, index of the replaced walkersamples_<iter>.ptandU_max_<iter>.pt, snapshots of the live set and the energy bound everyisavesampiterationstimings.txt, wall-clock time split across sampling, flow sampling, training and pool generation
Each run also writes simulation_summary.txt and .json recording the parameters
it was started with. These directories are not tracked by git. A run interrupted
with Ctrl-C still returns its history and writes timings.txt, so a partial run
is usable: the plotting cells adapt to however many iterations completed.
data/ holds the inputs the notebooks need. data/dw/ covers the double well and
data/lj/ the Lennard-Jones disks. Both are grouped by the number of live points
K, zero-padded to five digits, and data/lj/ is grouped further by box length.
The notebooks build these paths from live_samples and, for the Lennard-Jones
case, from the selected density, so no path needs editing.
| File | Purpose |
|---|---|
data/dw/K<K>/samples_init.pt |
Initial live set of K points, used to start a run so that every run begins from the same configuration |
data/dw/K<K>/reference_from_std_ns.txt |
output.txt of a completed standard nested sampling run, used as the reference curve in the energy and density-of-states plots |
data/lj/K<K>/L<L>/samples_init.pt |
As above, for the Lennard-Jones disks at box length L |
data/lj/K<K>/L<L>/samples_ref.pt |
Deep live set of a completed run, used as the reference that configurations are aligned to before plotting |
data/lj/K<K>/L<L>/reference_from_std_ns.txt.gz |
As the double-well reference above, compressed (see below). Provided for L2.9 only |
data/lj/K10000/L2.9/live_sets/ |
Live sets taken along a nested sampling run, the input to the conditioning and training scans |
For the double well, sets are provided for K = 1024 (K01024) and K = 10000
(K10000). For the Lennard-Jones disks, K = 10000 at box lengths 2.9 and 3.3,
which are the densities 0.95 and 0.73 selected in the notebook. Asking for a value
that is not provided raises a FileNotFoundError listing what is available, rather
than silently starting from a mismatched configuration.
The standard nested sampling reference is the one exception: it is provided for
L2.9 only. At L3.3 the energy and density-of-states plots simply show the run
itself, and the notebook says so rather than failing.
data/lj/K10000/L2.9/runs/ holds the runs that
LJ-disks/plot_ljdisks_results.ipynb
reads. Only the files the figures need are included, not the full runs.
Six of them are the nested sampling runs compared in Figure 5 of the paper, the time decomposition of successive pool generations, split into network training and pool generation. They all start from the same initial live set and run for the same number of nested sampling iterations, and differ only in the learning-rate schedule and the pool size. The folder name states all three:
<scheduler>_P<pool size>_<optimisation steps>os
where the scheduler is One Cycle (1C), Cosine Annealing (CA), or the two
combined (1C-CA, 1C applied every fifth epoch and CA for the rest, whose folder
name gives the two step counts separately).
| Folder | Scheduler | Optimisation steps | Pool size |
|---|---|---|---|
1C_P1e5_4500os |
1C | 4500 | 10^5 |
1C_P2e4_1000os |
1C | 1000 | 2x10^4 |
1C-CA_P1e5_3375-1125os |
1C + CA | 3375 (1C) + 1125 (CA) | 10^5 |
1C-CA_P2e4_750-250os |
1C + CA | 750 (1C) + 250 (CA) | 2x10^4 |
CA_P1e5_500os |
CA | 500 | 10^5 |
CA_P2e4_250os |
CA | 250 | 2x10^4 |
In Figure 5 the 10^5 runs are the left column and the 2x10^4 runs the right,
one row per scheduler. The pool size is not only documented here: it can be read
back from column 6 of each run's output.txt, which records the number of
configurations left in the pool.
data/lj/K10000/L2.9/conditioning_efficiency/ holds the scans behind Figure 4:
how well the flow performs as a function of the energy bound, as the generation
efficiency, the identity efficiency and the RESS. The three folders correspond to
the three curves, and each is produced by one of the notebooks below.
| Folder | Produced by |
|---|---|
conditioning_window_10K |
multiple_live_sets_conditioning.ipynb |
training_window_10K |
multiple_live_sets_training.ipynb with collate_dataset = False |
training_window_10K_collated_dataset |
multiple_live_sets_training.ipynb with collate_dataset = True |
LJ-disks/multiple_live_sets_conditioning.ipynb and
LJ-disks/multiple_live_sets_training.ipynb train a flow
on live sets taken at several points along a nested sampling run and measure how
well it generates below the corresponding energy bound. They read the live sets
from data/lj/K10000/L2.9/live_sets/, which holds the samples_<iter>.pt and
U_max_<iter>.pt the two notebooks select.
Running them with the parameters as shipped takes hours per live set. The trained
networks are about 88 MB each and are not included, so the train = False branch,
which re-evaluates a finished run instead of training, needs a run you produced
yourself; only the resulting efficiency curves are provided here.
The phase diagram in the left panel of Figure 7 is not produced from any of this data. It is adapted from Y.-W. Li, Phase behavior of Lennard-Jones particles in two dimensions, Physical Review E (2020); only the configuration in the right panel comes from this work.
The Lennard-Jones reference is one row per nested sampling iteration over half a million iterations, so it is shipped gzipped: 9.2 MB instead of 26 MB. The notebook reads it as it is; nothing below is needed to run the examples.
NumPy reads it without unpacking anything:
umax = np.loadtxt("data/lj/K10000/L2.9/reference_from_std_ns.txt.gz", usecols=3, unpack=True)To unpack it anyway, for inspection or for tools that cannot read gzip:
# keep the archive
gunzip -k data/lj/K10000/L2.9/reference_from_std_ns.txt.gz
# or replace it with the plain file
gunzip data/lj/K10000/L2.9/reference_from_std_ns.txt.gzBoth produce reference_from_std_ns.txt; the first keeps the archive next to it,
the second removes it. .gitignore does not exclude the unpacked file, so delete it
again before committing if you unpack in place.
numerical_experiments/ holds the scripts for the three figures that are not
produced by the notebooks. Run them directly; each writes a PDF and a PNG into
numerical_experiments/figures/.
| Script | Figure | Reads |
|---|---|---|
make_fig_landscapes.py |
1 | coupling_mi.npz, gw_degeneracies.npz, hessian_spectra.npz, lj_symmetries.npz |
make_fig_internal_complexity_two_density_efficiency.py |
6 | internal_complexity_L2.9.npz, internal_complexity_L3.3.npz |
make_fig_concepts_internal_complexity.py |
8 | nothing, it is self-contained |
cd numerical_experiments
python make_fig_landscapes.pyTheir inputs are in data/numerical_experiments/, as the .npz files produced
by the analyses behind those figures. The scripts that generate those .npz are
not included here yet; the precomputed data lets the three figures be rebuilt
without them.
nsflows/
├── nested_sampling.py # the nested sampling driver
├── network/ # normalizing flow: splines, coupling blocks, trainer
├── samplers/ # Monte Carlo and flow-based samplers
├── systems/ # test systems: Gaussians, uniforms, Einstein crystal,
│ # Lennard-Jones, 2D test systems
├── transformations/ # coordinate transformations and normalization
└── tools/ # observables, plotting style, utilities
If you use this code, please cite:
A. Coretti, N. Unglert, S. Falkner, G. K. H. Madsen, and C. Dellago, Generative Nested Sampling of Atomistic Thermodynamic Landscapes, arXiv:2609.03193 (2026).
@article{coretti2026nsflows,
title = {Generative Nested Sampling of Atomistic Thermodynamic Landscapes},
author = {Coretti, Alessandro and Unglert, Nico and Falkner, Sebastian and Madsen, Georg K. H. and Dellago, Christoph},
journal = {arXiv preprint arXiv:2609.03193},
year = {2026},
eprint = {2609.03193},
archivePrefix = {arXiv},
url = {https://arxiv.org/abs/2609.03193}
}See CONTRIBUTORS.md.
See LICENSE.