Source code for pints._boundaries
#
# Parameter-space boundaries object
#
# 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 pints
import numpy as np
[docs]class Boundaries(object):
"""
Abstract class representing boundaries on a parameter space.
"""
[docs] def check(self, parameters):
"""
Returns ``True`` if and only if the given point in parameter space is
within the boundaries.
Parameters
----------
parameters
A point in parameter space
"""
raise NotImplementedError
[docs] def n_parameters(self):
"""
Returns the dimension of the parameter space these boundaries are
defined on.
"""
raise NotImplementedError
[docs] def sample(self, n=1):
"""
Returns ``n`` random samples from within the boundaries, for example to
use as starting points for an optimisation.
The returned value is a NumPy array with shape ``(n, d)`` where ``n``
is the requested number of samples, and ``d`` is the dimension of the
parameter space these boundaries are defined on.
*Note that implementing :meth:`sample()` is optional, so some boundary
types may not support it.*
Parameters
----------
n : int
The number of points to sample
"""
raise NotImplementedError
[docs]class RectangularBoundaries(Boundaries):
"""
Represents a set of lower and upper boundaries for model parameters.
A point ``x`` is considered within the boundaries if (and only if)
``lower <= x < upper``.
Extends :class:`pints.Boundaries`.
Parameters
----------
lower
A 1d array of lower boundaries.
upper
The corresponding upper boundaries
"""
def __init__(self, lower, upper):
super(RectangularBoundaries, self).__init__()
# Convert to shape (n,) vectors, copy to ensure they remain unchanged
self._lower = pints.vector(lower)
self._upper = pints.vector(upper)
# Get and check dimension
self._n_parameters = len(self._lower)
if len(self._upper) != self._n_parameters:
raise ValueError('Lower and upper bounds must have same length.')
# Check dimension is at least 1
if self._n_parameters < 1:
raise ValueError('The parameter space must have a dimension > 0')
# Check if upper > lower
if not np.all(self._upper > self._lower):
raise ValueError('Upper bounds must exceed lower bounds.')
[docs] def check(self, parameters):
""" See :meth:`pints.Boundaries.check()`. """
if np.any(parameters < self._lower):
return False
if np.any(parameters >= self._upper):
return False
return True
[docs] def n_parameters(self):
""" See :meth:`pints.Boundaries.n_parameters()`. """
return self._n_parameters
[docs] def lower(self):
"""
Returns the lower boundaries for all parameters (as a read-only NumPy
array).
"""
return self._lower
[docs] def range(self):
"""
Returns the size of the parameter space (i.e. ``upper - lower``).
"""
return self._upper - self._lower
[docs] def sample(self, n=1):
""" See :meth:`pints.Boundaries.sample()`. """
return np.random.uniform(
self._lower, self._upper, size=(n, self._n_parameters))
[docs] def upper(self):
"""
Returns the upper boundary for all parameters (as a read-only NumPy
array).
"""
return self._upper
[docs]class LogPDFBoundaries(Boundaries):
"""
Uses a :class:`pints.LogPDF` (e.g. a :class:`LogPrior`) as boundaries),
accepting log-likelihoods above a given threshold as within bounds.
For a :class:`pints.LogPrior` based on :class:`pints.Boundaries`, see
:class:`pints.UniformLogPrior`.
Extends :class:`pints.Boundaries`.
Parameters
----------
log_pdf
A :class:`pints.LogPdf` to use.
threshold
A threshold to determine whether a given log-prior value counts as
within bounds. Anything _above_ the threshold counts as within bounds.
"""
def __init__(self, log_pdf, threshold=-float('inf')):
super(LogPDFBoundaries, self).__init__()
# Check log pdf
if not isinstance(log_pdf, pints.LogPDF):
raise ValueError('First argument must be a pints.LogPDF.')
self._log_pdf = log_pdf
# Check threshold
self._threshold = float(threshold)
# Check if we can sample
self._pdf_is_prior = isinstance(log_pdf, pints.LogPrior)
[docs] def check(self, parameters):
""" See :meth:`pints.Boundaries.check()`. """
return self._log_pdf(parameters) > self._threshold
[docs] def n_parameters(self):
""" See :meth:`pints.Boundaries.n_parameters()`. """
return self._log_pdf.n_parameters()
[docs] def sample(self, n=1):
"""
See :meth:`pints.Boundaries.sample()`.
Note: This method is implemented only when the error measure is based
on a :class:`pints.LogPrior` that supports sampling.
"""
if not self._pdf_is_prior:
raise NotImplementedError
return self._log_pdf.sample(n)