Source code for pints.toy._annulus

#
# Annulus toy log pdf.
#
# This file is part of PINTS (https://github.com/pints-team/pints/) which is
# released under the BSD 3-clause license. See accompanying LICENSE.md for
# copyright notice and full license details.
#
import numpy as np
import scipy

from . import ToyLogPDF


[docs]class AnnulusLogPDF(ToyLogPDF): r""" Toy distribution based on a d-dimensional distribution of the form .. math:: f(x|r_0, \sigma) \propto e^{-(|x|-r_0)^2 / {2\sigma^2}} where :math:`x` is a d-dimensional real, and :math:`|x|` is the Euclidean norm. This distribution is roughly a one-dimensional Gaussian distribution centred on :math:`r0`, that is smeared over the surface of a hypersphere of the same radius. In two dimensions, the density looks like a circular annulus. Extends :class:`pints.LogPDF`. Parameters ---------- dimensions : int The dimensionality of the space. r0 : float The radius of the hypersphere and is approximately the mean normed distance from the origin. sigma : float The width of the annulus; approximately the standard deviation of normed distance. """ def __init__(self, dimensions=2, r0=10, sigma=1): if dimensions < 1: raise ValueError('Dimensions must not be less than 1.') self._n_parameters = int(dimensions) r0 = float(r0) if r0 <= 0: raise ValueError('r0 must be positive.') self._r0 = r0 sigma = float(sigma) if sigma <= 0: raise ValueError('sigma must be positive.') self._sigma = sigma def __call__(self, x): if not len(x) == self._n_parameters: raise ValueError('x must be of same dimensions as density') return scipy.stats.norm.logpdf( np.linalg.norm(x), self._r0, self._sigma)
[docs] def distance(self, samples): """ Calculates a measure of normed distance of samples from exact mean and covariance matrix assuming uniform prior with bounds given by :meth:`suggested_bounds`. See :meth:`ToyLogPDF.distance()`. """ # Check size of input if not len(samples.shape) == 2: raise ValueError('Given samples list must be n x 2.') if samples.shape[1] != self.n_parameters(): raise ValueError( 'Given samples must have length ' + str(self.n_parameters())) # calculate normed distance d = list(map(lambda x: np.linalg.norm(x), samples)) dist = ( np.abs(self.mean_normed() - np.mean(d)) + np.abs(self.var_normed() - np.var(d)) ) return dist
[docs] def evaluateS1(self, x): """ See :meth:`LogPDF.evaluateS1()`. """ L = self.__call__(x) r = self._r0 norm = np.linalg.norm(x) sigma = self._sigma cons = -(norm - r) / (norm * sigma**2) dL = np.array([var * cons for var in x]) return L, dL
[docs] def mean(self): """ Returns the mean of this distribution. """ return np.zeros(self._n_parameters)
[docs] def mean_normed(self): """ Returns the mean of the normed distance from the origin. """ return self.moment_normed(1)
[docs] def moment_normed(self, order): """ Returns a given moment of the normed distance from the origin. """ n = self._n_parameters r = self._r0 a = order s = self._sigma g1 = scipy.special.gamma(0.5 * (n + a)) g2 = scipy.special.gamma(0.5 * (1 + n + a)) g3 = scipy.special.gamma(0.5 * n) g4 = scipy.special.gamma(0.5 * (1 + n)) h1 = scipy.special.hyp1f1(0.5 * (n + a), 0.5, r**2 / (2 * s**2)) h2 = scipy.special.hyp1f1(0.5 * (1 + n + a), 1.5, r**2 / (2 * s**2)) h3 = scipy.special.hyp1f1(0.5 * (1 - n), 0.5, -r**2 / (2 * s**2)) h4 = scipy.special.hyp1f1(1 - 0.5 * n, 1.5, -r**2 / (2 * s**2)) m = 2**(2 - 0.5 * n + 0.5 * (-4 + n + a)) m *= np.exp(-r**2 / (2 * s**2)) * s**a m *= (np.sqrt(2) * s * g1 * h1 + 2 * r * g2 * h2) m /= (np.sqrt(2) * s * g3 * h3 + 2 * r * g4 * h4) return m
[docs] def n_parameters(self): return self._n_parameters
[docs] def r0(self): """ Returns ``r0``. """ return self._r0
def _reject_sample(self, n_samples): """ Generates non-negative independent samples. """ r = np.ones(n_samples) * -1 f = r < 0 while np.any(f): r = np.random.normal(self._r0, self._sigma, size=np.sum(f)) f = r < 0 return r
[docs] def sample(self, n_samples): """ See :meth:`ToyLogPDF.sample()`. """ n_samples = int(n_samples) if n_samples < 1: raise ValueError( 'Number of samples must be greater than or equal to 1.') # First sample values of r r = self._reject_sample(n_samples) # uniformly sample X s.t. their normed distance is r0 X_norm = np.random.normal(size=(n_samples, self._n_parameters)) lambda_x = np.sqrt(np.sum(X_norm**2, axis=1)) x_unit = [r[i] * X_norm[i] / y for i, y in enumerate(lambda_x)] return np.array(x_unit)
[docs] def sigma(self): """ Returns ``sigma`` """ return self._sigma
[docs] def suggested_bounds(self): """ See :meth:`ToyLogPDF.suggested_bounds()`. """ # in higher dimensions reduce volume as otherwise gets too wide r0_magnitude = (self._r0 + self._sigma) * ( 5**(1.0 / (self._n_parameters - 1.0)) ) bounds = np.tile([-r0_magnitude, r0_magnitude], (self._n_parameters, 1)) return np.transpose(bounds).tolist()
[docs] def var_normed(self): """ Returns the variance of the normed distance from the origin. """ return self.moment_normed(2) - self.moment_normed(1)**2