Source code for pycbc.distributions.power_law
# Copyright (C) 2016 Christopher M. Biwer
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
This modules provides classes for evaluating distributions where the
probability density function is a power law.
"""
import logging
import numpy
from pycbc.distributions import bounded
logger = logging.getLogger('pycbc.distributions.power_law')
[docs]
class UniformPowerLaw(bounded.BoundedDist):
r"""
For a uniform distribution in power law. The parameters are
independent of each other. Instances of this class can be called like
a function. By default, logpdf will be called, but this can be changed
by setting the class's __call__ method to its pdf method.
The cumulative distribution function (CDF) will be the ratio of volumes:
.. math::
F(r) = \frac{V(r)}{V(R)}
Where :math:`R` is the radius of the sphere. So we can write our
probability density function (PDF) as:
.. math::
f(r) = c r^n
For generality we use :math:`n` for the dimension of the volume element,
eg. :math:`n=2` for a 3-dimensional sphere. And use
:math:`c` as a general constant.
So now we calculate the CDF in general for this type of PDF:
.. math::
F(r) = \int f(r) dr = \int c r^n dr = \frac{1}{n + 1} c r^{n + 1} + k
Now with the definition of the CDF at radius :math:`r_{l}` is equal to 0
and at radius :math:`r_{h}` is equal to 1 we find that the constant from
integration from this system of equations:
.. math::
1 = \frac{1}{n + 1} c ((r_{h})^{n + 1} - (r_{l})^{n + 1}) + k
Can see that :math:`c = (n + 1) / ((r_{h})^{n + 1} - (r_{l})^{n + 1}))`.
And :math:`k` is:
.. math::
k = - \frac{r_{l}^{n + 1}}{(r_{h})^{n + 1} - (r_{l})^{n + 1}}
Can see that :math:`c= \frac{n + 1}{R^{n + 1}}`. So can see that the CDF is:
.. math::
F(r) = \frac{1}{(r_{h})^{n + 1} - (r_{l})^{n + 1}} r^{n + 1} - \frac{r_{l}^{n + 1}}{(r_{h})^{n + 1} - (r_{l})^{n + 1}}
And the PDF is the derivative of the CDF:
.. math::
f(r) = \frac{(n + 1)}{(r_{h})^{n + 1} - (r_{l})^{n + 1}} (r)^n
Now we use the probabilty integral transform method to get sampling on
uniform numbers from a continuous random variable. To do this we find
the inverse of the CDF evaluated for uniform numbers:
.. math::
F(r) = u = \frac{1}{(r_{h})^{n + 1} - (r_{l})^{n + 1}} r^{n + 1} - \frac{r_{l}^{n + 1}}{(r_{h})^{n + 1} - (r_{l})^{n + 1}}
And find :math:`F^{-1}(u)` gives:
.. math::
u = \frac{1}{n + 1} \frac{(r_{h})^{n + 1} - (r_{l})^{n + 1}} - \frac{r_{l}^{n + 1}}{(r_{h})^{n + 1} - (r_{l})^{n + 1}}
And solving for :math:`r` gives:
.. math::
r = ( ((r_{h})^{n + 1} - (r_{l})^{n + 1}) u + (r_{l})^{n + 1})^{\frac{1}{n + 1}}
Therefore the radius can be sampled by taking the n-th root of uniform
numbers and multiplying by the radius offset by the lower bound radius.
\**params :
The keyword arguments should provide the names of parameters and their
corresponding bounds, as either tuples or a `boundaries.Bounds`
instance.
Attributes
----------
dim : int
The dimension of volume space. In the notation above `dim`
is :math:`n+1`. For a 3-dimensional sphere this is 3.
"""
name = "uniform_power_law"
def __init__(self, dim=None, **params):
super(UniformPowerLaw, self).__init__(**params)
self.dim = dim
self._norm = 1.0
self._lognorm = 0.0
for p in self._params:
self._norm *= self.dim / \
(self._bounds[p][1]**(self.dim) -
self._bounds[p][0]**(self.dim))
self._lognorm = numpy.log(self._norm)
@property
def norm(self):
"""float: The normalization of the multi-dimensional pdf."""
return self._norm
@property
def lognorm(self):
"""float: The log of the normalization."""
return self._lognorm
def _cdfinv_param(self, param, value):
"""Return inverse of cdf to map unit interval to parameter bounds.
"""
n = self.dim - 1
r_l = self._bounds[param][0]
r_h = self._bounds[param][1]
new_value = ((r_h**(n+1) - r_l**(n+1))*value + r_l**(n+1))**(1./(n+1))
return new_value
def _pdf(self, **kwargs):
"""Returns the pdf at the given values. The keyword arguments must
contain all of parameters in self's params. Unrecognized arguments are
ignored.
"""
for p in self._params:
if p not in kwargs.keys():
raise ValueError(
'Missing parameter {} to construct pdf.'.format(p))
if kwargs in self:
pdf = self._norm * \
numpy.prod([(kwargs[p])**(self.dim - 1)
for p in self._params])
return float(pdf)
else:
return 0.0
def _logpdf(self, **kwargs):
"""Returns the log of the pdf at the given values. The keyword
arguments must contain all of parameters in self's params. Unrecognized
arguments are ignored.
"""
for p in self._params:
if p not in kwargs.keys():
raise ValueError(
'Missing parameter {} to construct pdf.'.format(p))
if kwargs in self:
log_pdf = self._lognorm + \
(self.dim - 1) * \
numpy.log([kwargs[p] for p in self._params]).sum()
return log_pdf
else:
return -numpy.inf
[docs]
@classmethod
def from_config(cls, cp, section, variable_args):
"""Returns a distribution based on a configuration file. The parameters
for the distribution are retrieved from the section titled
"[`section`-`variable_args`]" in the config file.
Parameters
----------
cp : pycbc.workflow.WorkflowConfigParser
A parsed configuration file that contains the distribution
options.
section : str
Name of the section in the configuration file.
variable_args : str
The names of the parameters for this distribution, separated by
`prior.VARARGS_DELIM`. These must appear in the "tag" part
of the section header.
Returns
-------
Uniform
A distribution instance from the pycbc.inference.prior module.
"""
return super(UniformPowerLaw, cls).from_config(cp, section,
variable_args,
bounds_required=True)
[docs]
class UniformRadius(UniformPowerLaw):
""" For a uniform distribution in volume using spherical coordinates, this
is the distriubtion to use for the radius.
For more details see UniformPowerLaw.
"""
name = "uniform_radius"
def __init__(self, dim=3, **params):
super(UniformRadius, self).__init__(dim=3, **params)
__all__ = ["UniformPowerLaw", "UniformRadius"]