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

MultiNest #1319

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
82348f1
added pseudocode for multinest and corrected pseudocode for ellipsoid
ben18785 Feb 10, 2020
4a52bda
corrected references in ellipsoid and rejection and added skeleton fo…
ben18785 Feb 10, 2020
7365948
ask sketched out
ben18785 Feb 10, 2020
dc5a450
alphabeticised multinest function ordering
ben18785 Feb 10, 2020
b3214d8
skeleton of algorithm there
ben18785 Feb 10, 2020
6d2f03c
added notebook and skeleton of multinest complete (untested)
ben18785 Feb 10, 2020
f67bf4b
created various docs files and tested doc building
ben18785 Feb 10, 2020
1293a5d
draft of multinest working with "unit-cubed" data
ben18785 Feb 11, 2020
70c45bf
draft of multinest working on multimodal problem
ben18785 Feb 11, 2020
b88bc72
Merge branch 'master' into i282-multinest
ben18785 Feb 17, 2020
1f07e16
added transforming from/to unit cube
ben18785 Feb 17, 2020
c3fad40
corrected logging error
ben18785 Feb 18, 2020
3d5815a
Merge branch 'master' into i282-multinest
ben18785 Mar 2, 2021
9ab1259
moved notebook
ben18785 Mar 2, 2021
3a6cc29
Merge branch 'master' into i282-multinest
ben18785 Mar 4, 2021
e2f0cc5
began to add tests
ben18785 Mar 4, 2021
fc8564e
created sampler() method
ben18785 Mar 4, 2021
2437908
Merge branch 'i1312-nested-controller-sampler-method' into i282-multi…
ben18785 Mar 4, 2021
80052b1
added ellipsoid class
ben18785 Mar 4, 2021
2afc8f6
corrected logging issue for ellipsoidal nested sampling
ben18785 Mar 4, 2021
b803fd7
switched ellipsoidal nested sampling to use Ellipsoid class methods
ben18785 Mar 4, 2021
1ca047b
added volume tests and corrected error with ellipsoidal nested sampling
ben18785 Mar 4, 2021
13460da
added tests of ellipsoid sampling
ben18785 Mar 5, 2021
40df3e7
added tests for min volume calc
ben18785 Mar 5, 2021
394cc72
added test for mahalanobis distance
ben18785 Mar 5, 2021
c2b8149
added some tests for multinest
ben18785 Mar 5, 2021
c90c2a1
started to think about ellipsoid sets
ben18785 Mar 5, 2021
cb0b1b1
started to add binary tree
ben18785 Mar 6, 2021
734f37b
added number of points to ellipsoid class
ben18785 Mar 6, 2021
8e684f1
started to add h_k
ben18785 Mar 6, 2021
7c55a8d
first draft of ellipsoidtree
ben18785 Mar 6, 2021
61dfce4
overloaded addition to combine ellipsoids
ben18785 Mar 6, 2021
e4100e7
removed ellipsoidset
ben18785 Mar 6, 2021
7bf0ef2
cleaned up ellipsoidtree
ben18785 Mar 6, 2021
a602418
cleaned up style issues
ben18785 Mar 7, 2021
64136f7
more style issues
ben18785 Mar 7, 2021
8baa289
added safeguard against few points
ben18785 Mar 7, 2021
c2b445a
added tests of vs and vsk
ben18785 Mar 7, 2021
e0b71e9
added ellipse plotting to multinest notebook
ben18785 Mar 8, 2021
1af9adc
commented out while loop for algorithmic oscillation in multinest
ben18785 Mar 8, 2021
eefd07e
added sampling within ellipsoids method to ellipsoidtree
ben18785 Mar 8, 2021
aaf3657
integrated ellipsoidtree in multinest
ben18785 Mar 8, 2021
2a9e5a7
typo in multinest
ben18785 Mar 9, 2021
e7cb8cd
added max_recursion
ben18785 Mar 9, 2021
2b45b98
#282 cleaned up multinest
ben18785 Mar 11, 2021
316c275
Cleans up docstrings for MultiNest
ben18785 Mar 11, 2021
c084e01
multiNest notebook cleaned up
ben18785 Mar 12, 2021
6110010
removed recursion from MultiNest
ben18785 Mar 12, 2021
1996e4b
Added error handling and recursion for multiNest
ben18785 Mar 12, 2021
a9eed9f
Added tests to Multinest
ben18785 Mar 12, 2021
4f3c3a7
Added test coverage for MultiNest
ben18785 Mar 13, 2021
bb29984
Cleaned up MultiNest notebook and ellipsoidal one
ben18785 Mar 13, 2021
532cd67
Added @rcw5890 comment
ben18785 Mar 13, 2021
93d9330
Merge branch 'master' into i282-multinest
ben18785 Mar 13, 2021
cdfba3f
Corrected failing docs and copyright test for MultiNest
ben18785 Mar 13, 2021
09f0975
removed redundant method from Ellipsoid nested
ben18785 Mar 13, 2021
ae3dce7
Added coverage for MultiNest logging
ben18785 Mar 13, 2021
a01a9d1
Changed from integer to float division in Python 2.7 for multinest test
ben18785 Mar 13, 2021
bb24a1f
Removed redundant add function from Ellipsoid
ben18785 Mar 20, 2021
2c88bae
Changed add to set in MultiNest
ben18785 Mar 20, 2021
9d949dc
Changed Multinest to MultiNest
ben18785 Mar 22, 2021
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 docs/source/nested_samplers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Nested samplers
nested_sampler
nested_ellipsoid_sampler
nested_rejection_sampler

nested_multinest_sampler
MichaelClerx marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions docs/source/nested_samplers/nested_multinest_sampler.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*****************
MultiNest sampler
*****************

.. currentmodule:: pints

.. autoclass:: MultiNestSampler
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ relevant code.

### Nested sampling
- [Ellipsoidal nested sampling](./sampling/nested-ellipsoidal-sampling.ipynb)
- [MultiNest sampling](./sampling/nested-multinest.ipynb)
- [Rejection nested sampling](./sampling/nested-rejection-sampling.ipynb)

### Analysing sampling results
Expand Down
590 changes: 290 additions & 300 deletions examples/sampling/nested-ellipsoidal-sampling.ipynb

Large diffs are not rendered by default.

1,330 changes: 1,330 additions & 0 deletions examples/sampling/nested-multinest.ipynb

Large diffs are not rendered by default.

394 changes: 208 additions & 186 deletions examples/sampling/nested-rejection-sampling.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def version(formatted=False):
from ._nested import NestedController
from ._nested._rejection import NestedRejectionSampler
from ._nested._ellipsoid import NestedEllipsoidSampler
from ._nested._multinest import MultiNestSampler


#
Expand Down
4 changes: 2 additions & 2 deletions pints/_log_priors.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ def pseudo_cdf(self, xs):
u_2 = \int_{-\infty}^{\theta_2} \pi_2(\theta_2|\theta_1)d\theta_2

So that we return a vector of cdfs (u_1,u_2,...,u_d).
Note that, this function is mainly to facilitate Multinest sampling
Note that, this function is mainly to facilitate MultiNest sampling
since the distribution (u_1,u_2,...,u_d) is uniform within the unit
cube.
"""
Expand Down Expand Up @@ -934,7 +934,7 @@ def pseudo_icdf(self, ps):
u_2 = \int_{-\infty}^{\theta_2} \pi_2(\theta_2|\theta_1)d\theta_2

So that we return a vector of icdfs (theta_1,theta_2,...,theta_d)
Note that, this function is mainly to facilitate Multinest sampling
Note that, this function is mainly to facilitate MultiNest sampling
since the distribution (u_1,u_2,...,u_d) is uniform within the unit
cube.
"""
Expand Down
204 changes: 197 additions & 7 deletions pints/_nested/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from __future__ import print_function, unicode_literals
import pints
import numpy as np
import scipy.special
Copy link
Member

Choose a reason for hiding this comment

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

No you're scipy special 🥰


try:
from scipy.special import logsumexp
Expand Down Expand Up @@ -49,6 +50,10 @@ def __init__(self, log_prior):
self._accept_count = 0
self._n_evals = 0

# multiple ellipsoid indicator
self._multiple_ellipsoids = False
Copy link
Member

Choose a reason for hiding this comment

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

I'm guessing there's a technical difference between multiple ellipsoids and ellipsoid count being above one?

Copy link
Member

Choose a reason for hiding this comment

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

So do all nested samplers use ellipsoids?

self._ellipsoid_count = 0

def active_points(self):
"""
Returns the active points from nested sampling run.
Expand Down Expand Up @@ -414,6 +419,8 @@ def _initialise_logger(self):
self._logger.add_time('Time m:s')
self._logger.add_float('Delta_log(z)')
self._logger.add_float('Acceptance rate')
if self._sampler._multiple_ellipsoids:
Copy link
Member

Choose a reason for hiding this comment

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

In the other controllers we handle this in a more general way, by letting each sampler/optimiser define a method _log_init (that does nothing by default) which can update the logging

https://github.com/pints-team/pints/blob/master/pints/_mcmc/__init__.py#L623-L624

and _log_write:

https://github.com/pints-team/pints/blob/master/pints/_mcmc/__init__.py#L820-L821

self._logger.add_float('Ellipsoid count')

def _initial_points(self):
"""
Expand All @@ -429,8 +436,12 @@ def _initial_points(self):
# Show progress
if self._logging and i >= self._next_message:
# Log state
self._logger.log(0, self._sampler._n_evals,
self._timer.time(), self._diff, 1.0)
if not self._sampler._multiple_ellipsoids:
self._logger.log(0, self._sampler._n_evals,
self._timer.time(), self._diff, 1.0)
else:
self._logger.log(0, self._sampler._n_evals,
self._timer.time(), self._diff, 1.0, 0.0)

# Choose next logging point
if i > self._message_warm_up:
Expand Down Expand Up @@ -667,6 +678,10 @@ def run(self):

return self._m_posterior_samples

def sampler(self):
""" Returns the underlying :class:`NestedSampler` object. """
return self._sampler

def sample_from_posterior(self, posterior_samples):
"""
Draws posterior samples based on nested sampling run using importance
Expand Down Expand Up @@ -780,13 +795,188 @@ def _update_logger(self):
self._i_message += 1
if self._i_message >= self._next_message:
# Log state
self._logger.log(self._i_message, self._sampler._n_evals,
self._timer.time(), self._diff,
float(self._sampler._accept_count /
(self._sampler._n_evals -
self._sampler._n_active_points)))
if not self._sampler._multiple_ellipsoids:
self._logger.log(self._i_message, self._sampler._n_evals,
self._timer.time(), self._diff,
float(self._sampler._accept_count /
(self._sampler._n_evals -
self._sampler._n_active_points)))
else:
self._logger.log(self._i_message, self._sampler._n_evals,
self._timer.time(), self._diff,
float(self._sampler._accept_count /
(self._sampler._n_evals -
self._sampler._n_active_points)),
self._sampler._ellipsoid_count)

# Choose next logging point
if self._i_message > self._message_warm_up:
self._next_message = self._message_interval * (
1 + self._i_message // self._message_interval)


class Ellipsoid():
"""
A class to represent N dimensional ellipsoids, which are needed by both
Copy link
Member

Choose a reason for hiding this comment

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

N

Copy link
Member

Choose a reason for hiding this comment

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

Links to the classes with :class:pints.X ?

Copy link
Member

Choose a reason for hiding this comment

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

Or should it be a maths N, and maths again below?

ellipsoidal nested sampling and MultiNest.

In "center form" the equation of an ellipsoid is given by:

``(x-c).T * A * (x-c) = 1``

where ``A`` is a NxN dimensional positive definite symmetric matrix and
``c`` is a N dimensional vector indicating the center of the ellipsoid.

Parameters
----------
A : NxN dimensional positive definite symmetric matrix
represents the orientation and size of ellipsoid
c : N dimensional vector
center of ellipsoid
"""
def __init__(self, A, c):

self._c = c
self._n_parameters = len(c)

if A.shape != (self._n_parameters, self._n_parameters):
raise ValueError(
'Sigma must have same dimension as mean, or be a square ' +
'matrix with the same dimension as the center.')
self._A = np.copy(A)

# calculate useful quantities
self._A_inv = np.linalg.inv(A)
# don't calculate volume unless needed
self._volume = None

# don't cache points unless constructed using minimum_volume_ellipsoid
self._points = None
self._n_points = 0

def set_points(self, points):
""" Sets points contained within bounding ellipsoid. """
self._points = points
self._n_points = len(points)

def centroid(self):
""" Returns centroid of ellipsoid. """
return self._c

def enlarge(self, enlargement_factor):
""" Enlarges ellipsoid by a factor. """
self._A *= (1 / enlargement_factor)
self._A_inv *= enlargement_factor
self._volume = None

@staticmethod
MichaelClerx marked this conversation as resolved.
Show resolved Hide resolved
def mahalanobis_distance(point, A, c):
"""
Finds Mahalanobis distance between a point and the centroid of
of an ellipsoid.
"""
return np.matmul(np.matmul(point - c, A), point - c)

@classmethod
def minimum_volume_ellipsoid(cls, points):
"""
Creates an approximate minimum bounding ellipsoid in "center form":
``(x-c).T * A * (x-c) = 1``.
"""
cov = np.cov(np.transpose(points))
cov_inv = np.linalg.inv(cov)
c = np.mean(points, axis=0)
dist = np.zeros(len(points))
for i in range(len(points)):
dist[i] = Ellipsoid.mahalanobis_distance(points[i], cov_inv, c)
enlargement_factor = np.max(dist)
A = (1.0 / enlargement_factor) * cov_inv
obj = cls(A, c)
obj.set_points(points)
return obj

def n_points(self):
""" Returns number of points within bounding ellipsoid. """
MichaelClerx marked this conversation as resolved.
Show resolved Hide resolved
return self._n_points

def points(self):
""" Returns points within bounding ellipsoid. """
return self._points

def sample(self, npts, enlargement_factor=1):
"""
Draws ``npts`` random uniform points from within the ellipsoid.

Most of this functionality has been borrowed from:
http://www.astro.gla.ac.uk/~matthew/blog/?p=368
"""
ndims = self._n_parameters
covmat = self._A_inv * enlargement_factor
cent = self._c

# calculate eigen_values (e) and eigen_vectors (v)
eigen_values, eigen_vectors = np.linalg.eig(covmat)
idx = (-eigen_values).argsort()[::-1][:ndims]
e = eigen_values[idx]
v = eigen_vectors[:, idx]
e = np.diag(e)

# generate radii of hyperspheres
rs = np.random.uniform(0, 1, npts)

# generate points
pt = np.random.normal(0, 1, [npts, ndims])

# get scalings for each point onto the surface of a unit
# hypersphere
fac = np.sum(pt**2, axis=1)

# calculate scaling for each point to be within the unit
# hypersphere with radii rs
fac = (rs**(1 / ndims)) / np.sqrt(fac)
pnts = np.zeros((npts, ndims))

# scale points to the ellipsoid using the eigen_values and rotate
# with the eigen_vectors and add centroid
d = np.sqrt(np.diag(e))
d.shape = (ndims, 1)

for i in range(0, npts):
# scale points to a uniform distribution within unit
# hypersphere
pnts[i, :] = fac[i] * pt[i, :]
pnts[i, :] = np.dot(
np.multiply(pnts[i, :], np.transpose(d)),
np.transpose(v)
) + cent

if npts > 1:
return pnts
else:
return pnts[0]

def volume(self):
"""
Calculates volume of ellipsoid.
See: https://math.stackexchange.com/questions/2751632/solve-for-volume-of-ellipsoid-mathbb-x-mathbf-mut-sigma-1-mathbb-x # noqa
"""
if self._volume is None:
d = self._n_parameters
r = np.linalg.det(self._A_inv)
vol = (
(np.pi**(d / 2.0) / scipy.special.gamma((d / 2.0) + 1.0))
* np.sqrt(r)
)
# cache volume calculation to avoid recomputation
self._volume = vol
return self._volume

def weight_matrix(self):
""" Returns weight matrix. """
return self._A

def within_ellipsoid(self, point):
""" Determines if point is within ellipsoid. """
return Ellipsoid.mahalanobis_distance(point,
self.weight_matrix(),
self.centroid()) <= 1
Loading