Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ultranest import #313

Merged
merged 30 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a6286cd
add ultranest import
JohannesBuchner Jul 11, 2023
592eefe
add test and output data from ultranest and script that creates it; f…
JohannesBuchner Jul 11, 2023
4877bd7
typo
JohannesBuchner Jul 11, 2023
fcabe8e
minimize ultranest data included
JohannesBuchner Jul 11, 2023
caca2f2
move h5py import into function, to make it only run when needed
JohannesBuchner Jul 11, 2023
6283f88
version bump to 2.1.0
lukashergt Jul 11, 2023
bb1eebd
make ultranest part of gui tests
lukashergt Jul 11, 2023
b8cd8ab
fix `test_gui_params` to work for both `pc` and `un`
lukashergt Jul 11, 2023
adb1ebc
put `json` and `h5py` into optional dependencies
lukashergt Jul 11, 2023
8d02f60
remove `json` from optional dependencies as it is a built-in package
lukashergt Jul 11, 2023
070b75e
fix labelling of AxesDataFrame for the case where there are exact mat…
lukashergt Jul 11, 2023
7033eab
skip tests related to ultranest if `h5py` not in `sys.modules`
lukashergt Jul 11, 2023
441c1d7
remove trailing print statement
lukashergt Jul 11, 2023
639bb09
update docs for reading UltraNest samples
lukashergt Jul 11, 2023
ce41b63
rename `makeun.py` to `un_gen_data.py` such that it gets grouped with…
lukashergt Jul 11, 2023
58baf2c
add Johannes Buchner to list of contributors
lukashergt Jul 11, 2023
8b09789
revert commit 070b75e173698a2012563d038d2e66bb82b88774, which checked…
lukashergt Jul 19, 2023
8a060fd
pass `labels=self.get_labels_map()` rather than `labels=self.get_labe…
lukashergt Jul 19, 2023
63e264f
add test for axes labels when dropping labels or when only having par…
lukashergt Jul 19, 2023
c9891f9
correct `read_ultranest` docstring to refer to UltraNest files, not t…
lukashergt Jul 19, 2023
b894efb
add UltraNest to the list of supported file structures in `read_chains`
lukashergt Jul 19, 2023
f6c62cf
improve `read_cobaya`, `read_multinest` and `read_polychord` docstrings
lukashergt Jul 19, 2023
298d8d9
move `bin/gen_data.py` to `tests/example_data/gen_data.py` to have da…
lukashergt Jul 19, 2023
64e7349
bring `gen_data.py` to the anesthetic 2.0.0 century
lukashergt Jul 19, 2023
1acbf53
make `gen_data.py` flake8 conform
lukashergt Jul 19, 2023
71c8cf0
temporarily remove `un/info/results.json`
lukashergt Jul 19, 2023
0ab5210
add `un/info/results.json` back in, hopefully in the right format
lukashergt Jul 19, 2023
d6da757
add `mn` to the gui tests
lukashergt Jul 19, 2023
57b00e1
Merge branch 'master' into master
williamjameshandley Jul 19, 2023
76c982f
Added back in infuriating newline which github automerge fails to do …
williamjameshandley Jul 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
anesthetic: nested sampling post-processing
===========================================
:Authors: Will Handley and Lukas Hergt
:Version: 2.0.0
:Version: 2.1.0
:Homepage: https://github.com/handley-lab/anesthetic
:Documentation: http://anesthetic.readthedocs.io/

Expand Down
2 changes: 1 addition & 1 deletion anesthetic/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.0.0'
__version__ = '2.1.0'
3 changes: 2 additions & 1 deletion anesthetic/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ def _set_labels(axes, labels, **kwargs):
all_params = list(axes.columns) + list(axes.index)
if labels is None:
labels = {}
labels = {p: labels[p] if p in labels else p for p in all_params}
labels = {p: labels[p] if isinstance(labels, dict) and p in labels
else p for p in all_params}
lukashergt marked this conversation as resolved.
Show resolved Hide resolved

for y, axes_row in axes.iterrows():
if axes_row.size:
Expand Down
7 changes: 6 additions & 1 deletion anesthetic/read/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from anesthetic.read.getdist import read_getdist
from anesthetic.read.cobaya import read_cobaya
from anesthetic.read.multinest import read_multinest
from anesthetic.read.ultranest import read_ultranest


def read_chains(root, *args, **kwargs):
Expand Down Expand Up @@ -45,7 +46,11 @@ def read_chains(root, *args, **kwargs):
"anesthetic.html#anesthetic.samples.MCMCSamples.remove_burn_in"
)
errors = []
for read in [read_polychord, read_multinest, read_cobaya, read_getdist]:
readers = [
read_polychord, read_multinest, read_cobaya,
read_ultranest, read_getdist
]
for read in readers:
try:
samples = read(root, *args, **kwargs)
samples.root = root
Expand Down
33 changes: 33 additions & 0 deletions anesthetic/read/ultranest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Read NestedSamples from ultranest results."""
import os
import json
from anesthetic.samples import NestedSamples
try:
import h5py
except ImportError:
pass


def read_ultranest(root, *args, **kwargs):
"""Read <root>ev.dat and <root>phys_live.points in MultiNest format."""
lukashergt marked this conversation as resolved.
Show resolved Hide resolved
with open(os.path.join(root, 'info', 'results.json')) as infofile:
labels = json.load(infofile)['paramnames']
num_params = len(labels)

filepath = os.path.join(root, 'results', 'points.hdf5')
with h5py.File(filepath, 'r') as fileobj:
points = fileobj['points']
_, ncols = points.shape
x_dim = ncols - 3 - num_params
logL_birth = points[:, 0]
logL = points[:, 1]

Check warning on line 23 in anesthetic/read/ultranest.py

View check run for this annotation

Codecov / codecov/patch

anesthetic/read/ultranest.py#L19-L23

Added lines #L19 - L23 were not covered by tests
# u_samples = points[:,3:x_dim]
samples = points[:, 3 + x_dim:3 + x_dim + num_params]

Check warning on line 25 in anesthetic/read/ultranest.py

View check run for this annotation

Codecov / codecov/patch

anesthetic/read/ultranest.py#L25

Added line #L25 was not covered by tests

kwargs['label'] = kwargs.get('label', os.path.basename(root))
columns = kwargs.pop('columns', labels)
labels = kwargs.pop('labels', labels)
data = samples

Check warning on line 30 in anesthetic/read/ultranest.py

View check run for this annotation

Codecov / codecov/patch

anesthetic/read/ultranest.py#L27-L30

Added lines #L27 - L30 were not covered by tests

return NestedSamples(data=data, logL=logL, logL_birth=logL_birth,

Check warning on line 32 in anesthetic/read/ultranest.py

View check run for this annotation

Codecov / codecov/patch

anesthetic/read/ultranest.py#L32

Added line #L32 was not covered by tests
columns=columns, labels=labels, *args, **kwargs)
25 changes: 17 additions & 8 deletions docs/source/reading_writing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ Reading and writing

.. _reading chains:

Reading chain files from PolyChord, MultiNest, CosmoMC, or Cobaya
=================================================================
Reading chain files from PolyChord, MultiNest, UltraNest, CosmoMC, or Cobaya
============================================================================

`MultiNest <https://github.com/farhanferoz/MultiNest>`_
If you have finished nested sampling or MCMC runs from one of:

* `PolyChord <https://polychord.io>`_: https://github.com/PolyChord/PolyChordLite
* MultiNest: https://github.com/farhanferoz/MultiNest
* `UltraNest <https://johannesbuchner.github.io/UltraNest/index.html>`_: https://github.com/JohannesBuchner/UltraNest
* `CosmoMC <https://cosmologist.info/cosmomc/readme.html>`_: https://github.com/cmbant/CosmoMC
* `Cobaya <https://cobaya.readthedocs.io>`_: https://github.com/CobayaSampler/cobaya
williamjameshandley marked this conversation as resolved.
Show resolved Hide resolved

If you have finished nested sampling or MCMC runs from
`PolyChord <https://github.com/PolyChord/PolyChordLite>`_,
`MultiNest <https://github.com/farhanferoz/MultiNest>`_,
`CosmoMC <https://github.com/cmbant/CosmoMC>`_, or
`Cobaya <https://github.com/CobayaSampler/cobaya>`_,
then you should be able to read in the chain files directly, by passing the
``root`` to the :func:`anesthetic.read.chain.read_chains` function.

Expand All @@ -29,6 +30,14 @@ out the examples listed here.
from anesthetic import read_chains
samples = read_chains("anesthetic/tests/example_data/pc")

* UltraNest samples, which will be an instance of the
:class:`anesthetic.samples.NestedSamples` class:

::

from anesthetic import read_chains
samples = read_chains("anesthetic/tests/example_data/un")

* Cobaya samples, which will be an instance of the
:class:`anesthetic.samples.MCMCSamples` class:

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ authors = [
{ name="Lukas Hergt", email="[email protected]" },
{ name="Adam Ormondroyd", email="[email protected]" },
{ name="Harry Bevins", email="[email protected]" },
{ name="Johannes Buchner", email="[email protected]" },
{ name="Ethan Carragher", email="[email protected]" },
{ name="Andrew Fowlie", email="[email protected]" },
{ name="Thomas Gessey-Jones", email="[email protected]"},
Expand Down Expand Up @@ -59,11 +60,12 @@ classifiers = [
[project.optional-dependencies]
docs = ["sphinx", "sphinx_rtd_theme", "numpydoc"]
test = ["pytest", "pytest-cov", "flake8", "pydocstyle", "packaging", "pre-commit"]
ultranest = ["h5py"]
astropy = ["astropy"]
fastkde = ["fastkde"]
getdist = ["getdist"]
hdf5 = ["tables"]
all = ["astropy", "fastkde", "getdist", "tables"]
all = ["h5py", "astropy", "fastkde", "getdist", "tables"]

[project.scripts]
anesthetic = "anesthetic.scripts:gui"
Expand Down
77 changes: 77 additions & 0 deletions tests/example_data/un/info/results.json
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the only thing we need from this is the paramnames entry, although it is nice that it has a maximum likelihood point. Presuming this is found by an optimisation procedure (like e.g. polychord's polishing settings.maximise=true gives) then this is could be useful in future iterations.

Copy link
Collaborator Author

@JohannesBuchner JohannesBuchner Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the last live point discarded, but for the default frac_remain=0.01 this is good enough.

Copy link
Collaborator

@williamjameshandley williamjameshandley Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically I disagree -- the rule of thumb is that logLmax ~ <logL>_P + d/2, and the width of the typical set in logL space is sqrt(d/2), so in even moderate dimensions the true likelihood peak lies some distance away from where nested sampling terminates, regardless of stopping criterion (see figure 2), but this isn't relevant to this PR!

Copy link
Collaborator

@lukashergt lukashergt Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been wondering if we could somehow make use of PolyChord's maximise=true output in anesthetic...

  • What happens when one appends the list of nested samples with the maximum likelihood point at the very end?
  • For safety we could do it with zero weight...?
  • Or is there a way of turning the maximum likelihood point into the final nested sampling point?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in practice all of these would work fine for a run that had reached convergence. I would worry if you also started trying to use it at different temperatures.

As an API, a better solution would be for the maximum to be appended with a logL_birth=logL=logL_max, so it's officially 'beyond the last live point'.

We'd then have to write the volume calculation to ensure anesthetic set these to zero or nan weight by default, since there's no way to determine the volume if there's a gap.

I could imagine gaps occurring naturally in the case of importance weighting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue raised in #317.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically I disagree -- the rule of thumb is that logLmax ~ <logL>_P + d/2, and the width of the typical set in logL space is sqrt(d/2), so in even moderate dimensions the true likelihood peak lies some distance away from where nested sampling terminates, regardless of stopping criterion (see figure 2), but this isn't relevant to this PR!

As an aside, I have been working through this analytically and experimentally this afternoon, and this statement only holds in higher dimensions.

Here is an analytic result for f=0.01, n=1000 for how close you get in loglikelihood to the maximum as a function of dimension:

plot

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"niter": 16650,
"logz": -12.414914594805953,
"logzerr": 0.7649516021328628,
"logz_bs": -12.378097297348035,
"logz_single": -12.414914594805953,
"logzerr_tail": 0.405716490463611,
"logzerr_bs": 0.6484944741256662,
"ess": 988.8766189106832,
"H": 38.639675527033575,
"Herr": 0.22373181701346842,
"posterior": {
"mean": [
0.5025783329483371,
0.7370934893843076,
0.9206375311748793,
0.9987450408249887
],
"stdev": [
0.09929557150158441,
0.0021313377831688133,
4.6033000067476255e-05,
1.0182458288564012e-06
],
"median": [
0.506654697190454,
0.7370548529653416,
0.920636543102102,
0.9987450545520016
],
"errlo": [
0.4016698354319299,
0.7350305173203235,
0.9205913816528906,
0.9987439938546689
],
"errup": [
0.601282668275072,
0.7393071000969336,
0.9206855255078583,
0.9987460211457329
],
"information_gain_bits": [
3.7847253159407837,
4.14647919764959,
4.14647919764959,
4.14647919764959
]
},
"maximum_likelihood": {
"logl": 28.526361154649237,
"point": [
0.4912994020358319,
0.7373000808358334,
0.9206344043582959,
0.9987452206617764
],
"point_untransformed": [
0.5245649701017916,
0.5368650040417917,
0.5460317202179148,
0.5499372610330888
]
},
"ncall": 39885,
"paramnames": [
"a",
"b",
"c",
"d"
],
"logzerr_single": 0.31080410038734035,
"insertion_order_MWW_test": {
"independent_iterations": Infinity,
"converged": true
}
}
lukashergt marked this conversation as resolved.
Show resolved Hide resolved
Binary file added tests/example_data/un/results/points.hdf5
Binary file not shown.
31 changes: 31 additions & 0 deletions tests/example_data/un_gen_data.py
williamjameshandley marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import numpy as np
from ultranest import ReactiveNestedSampler

ndim = 4
sigma = np.logspace(-1, -6, ndim)
width = 1 - 5 * sigma
width[width < 1e-20] = 1e-20
centers = (np.sin(np.arange(ndim) / 2.) * width + 1.) / 2.


def loglike(theta):
"""compute log-likelihood."""
like = - 0.5 * (((theta - centers) / sigma)**2).sum(axis=1) \
- 0.5 * np.log(2 * np.pi * sigma**2).sum()
return like


def transform(x):
"""transform according to prior."""
return x * 20 - 10


paramnames = ['a', 'b', 'c', 'd']

sampler = ReactiveNestedSampler(
paramnames, loglike, transform=transform,
log_dir='un', resume=True, vectorized=True)

sampler.run(frac_remain=0.5, min_num_live_points=400)
sampler.print_results()
sampler.plot()
40 changes: 31 additions & 9 deletions tests/test_gui.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import sys
import anesthetic.examples._matplotlib_agg # noqa: F401
from anesthetic import read_chains
import pytest
import pandas._testing as tm
try:
import h5py # noqa: F401
except ImportError:
pass


@pytest.fixture(autouse=True)
def close_figures_on_teardown():
tm.close()


def test_gui():
samples = read_chains('./tests/example_data/pc')
@pytest.mark.parametrize('root', ["./tests/example_data/pc",
"./tests/example_data/un"])
def test_gui(root):
if 'un' in root and 'h5py' not in sys.modules:
pytest.skip("`h5py` not in sys.modules, but needed for ultranest.")
samples = read_chains(root)
plotter = samples.gui()

# Type buttons
Expand All @@ -30,8 +39,11 @@ def test_gui():
assert len(plotter.triangle.ax) == 4

# Sliders
old = plotter.evolution()
plotter.evolution.slider.set_val(5)
assert plotter.evolution() == 627
assert plotter.evolution() != old
plotter.evolution.slider.set_val(0)
assert plotter.evolution() == old
plotter.type.buttons.set_active(1)

plotter.beta.slider.set_val(0)
Expand All @@ -50,15 +62,25 @@ def test_gui():
plotter.reset.button.on_clicked(plotter.reset_range(None))


def test_gui_params():
plotter = read_chains('./tests/example_data/pc').gui()
assert len(plotter.param_choice.buttons.labels) == 8
@pytest.mark.parametrize('root', ["./tests/example_data/pc",
"./tests/example_data/un"])
williamjameshandley marked this conversation as resolved.
Show resolved Hide resolved
def test_gui_params(root):
if 'un' in root and 'h5py' not in sys.modules:
pytest.skip("`h5py` not in sys.modules, but needed for ultranest.")
samples = read_chains(root)
params = samples.columns.get_level_values(0).to_list()
plotter = samples.gui()
assert len(plotter.param_choice.buttons.labels) == len(params)

plotter = read_chains('./tests/example_data/pc').gui(params=['x0', 'x1'])
plotter = samples.gui(params=params[:2])
assert len(plotter.param_choice.buttons.labels) == 2


def test_slider_reset_range():
plotter = read_chains('./tests/example_data/pc').gui()
@pytest.mark.parametrize('root', ["./tests/example_data/pc",
"./tests/example_data/un"])
def test_slider_reset_range(root):
if 'un' in root and 'h5py' not in sys.modules:
pytest.skip("`h5py` not in sys.modules, but needed for ultranest.")
plotter = read_chains(root).gui()
plotter.evolution.reset_range(-3, 3)
assert plotter.evolution.ax.get_xlim() == (-3.0, 3.0)
27 changes: 27 additions & 0 deletions tests/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@
from anesthetic.read.getdist import read_getdist
from anesthetic.read.cobaya import read_cobaya
from anesthetic.read.multinest import read_multinest
from anesthetic.read.ultranest import read_ultranest
import pandas._testing as tm
from anesthetic.read.hdf import HDFStore, read_hdf
try:
import h5py # noqa: F401
except ImportError:
pass
try:
import getdist
except ImportError:
Expand Down Expand Up @@ -184,6 +189,28 @@ def test_read_multinest():
ns.plot_1d(['x0', 'x1', 'x2', 'x3'])


@pytest.mark.xfail('h5py' not in sys.modules,
raises=NameError,
reason="ultranest reading requires `h5py` package")
def test_read_ultranest():
np.random.seed(3)
ns = read_ultranest('./tests/example_data/un')
params = ['a', 'b', 'c', 'd', 'logL', 'logL_birth', 'nlive']
assert_array_equal(ns.drop_labels().columns, params)
labels = ['a',
'b',
'c',
'd',
r'$\ln\mathcal{L}$',
r'$\ln\mathcal{L}_\mathrm{birth}$',
r'$n_\mathrm{live}$']
assert_array_equal(ns.get_labels(), labels)

assert isinstance(ns, NestedSamples)
ns.plot_2d(['a', 'b', 'c', 'd'])
ns.plot_1d(['a', 'b', 'c', 'd'])


def test_read_polychord():
np.random.seed(3)
ns = read_polychord('./tests/example_data/pc')
Expand Down
Loading