Source code for pycbc.distributions.fixedsamples

# Copyright (C) 2020 Alexander Nitz
# 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 based on a fixed
set of points
"""
import logging
import numpy
import numpy.random

from pycbc import VARARGS_DELIM

logger = logging.getLogger('pycbc.distributions.fixedsamples')


[docs]class FixedSamples(object): """ A distribution consisting of a collection of a large number of fixed points. Only these values can be drawn from, so the number of points may need to be large to properly reflect the paramter space. This distribution is intended to aid in using nested samplers for semi-abitrary or complicated distributions where it is possible to provide or draw samples but less straightforward to provide an analytic invcdf. This class numerically approximates the invcdf for 1 or 2 dimensional distributions (but no higher). Parameters ---------- params : This of parameters this distribution should use samples : dict of arrays or FieldArray Sampled points of the distribution. May contain transformed parameters which are different from the original distribution. If so, an inverse mapping is provided to associate points with other parameters provided. """ name = "fixed_samples" def __init__(self, params, samples): self.params = params self.samples = samples self.p1 = self.samples[params[0]] self.frac = len(self.p1)**0.5 / len(self.p1) self.sort = self.p1.argsort() self.p1sorted = self.p1[self.sort] assert len(numpy.unique(self.p1)) == len(self.p1) if len(params) > 2: raise ValueError("Only one or two parameters supported " "for fixed sample distribution")
[docs] def rvs(self, size=1, **kwds): "Draw random value" i = numpy.random.randint(0, high=len(self.p1), size=size) return {p: self.samples[p][i] for p in self.params}
[docs] def cdfinv(self, **original): """Map unit cube to parameters in the space""" new = {} #First dimension u1 = original[self.params[0]] i1 = int(round(u1 * len(self.p1))) if i1 >= len(self.p1): i1 = len(self.p1) - 1 if i1 < 0: i1 = 0 new[self.params[0]] = p1v = self.p1sorted[i1] if len(self.params) == 1: return new # possible second dimension, probably shouldn't # do more dimensions than this u2 = original[self.params[1]] l = numpy.searchsorted(self.p1sorted, p1v * (1 - self.frac)) r = numpy.searchsorted(self.p1sorted, p1v * (1 + self.frac)) if r < l: l, r = r, l region = numpy.array(self.sort[l:r], ndmin=1) p2 = self.samples[self.params[1]] p2part = numpy.array(p2[region], ndmin=1) l = p2part.argsort() p2part = numpy.array(p2part[l], ndmin=1) i2 = int(round(u2 * len(p2part))) if i2 >= len(p2part): i2 = len(p2part) - 1 if i2 < 0: i2 = 0 new[self.params[1]] = p2part[i2] p1part = numpy.array(self.p1[region[l]], ndmin=1) new[self.params[0]] = p1part[i2] return new
[docs] def apply_boundary_conditions(self, **params): """ Apply boundary conditions (none here) """ return params
def __call__(self, **kwds): """ Dummy function, not the actual pdf """ return 0
[docs] @classmethod def from_config(cls, cp, section, tag): """ Return instance based on config file Return a new instance based on the config file. This will draw from a single distribution section provided in the config file and apply a single transformation section if desired. If a transformation is applied, an inverse mapping is also provided for use in the config file. """ from pycbc.distributions import read_distributions_from_config from pycbc.transforms import (read_transforms_from_config, apply_transforms, BaseTransform) from pycbc.transforms import transforms as global_transforms params = tag.split(VARARGS_DELIM) subname = cp.get_opt_tag(section, 'subname', tag) size = cp.get_opt_tag(section, 'sample-size', tag) distsec = '{}_sample'.format(subname) dist = read_distributions_from_config(cp, section=distsec) if len(dist) > 1: raise ValueError("Fixed sample distrubtion only supports a single" " distribution to sample from.") logger.info('Drawing samples for fixed sample distribution:%s', params) samples = dist[0].rvs(size=int(float(size))) samples = {p: samples[p] for p in samples.dtype.names} transec = '{}_transform'.format(subname) trans = read_transforms_from_config(cp, section=transec) if len(trans) > 0: trans = trans[0] samples = apply_transforms(samples, [trans]) p1 = samples[params[0]] # We have transformed parameters, so automatically provide the # inverse transform for use in passing to waveform approximants class Thook(BaseTransform): name = subname _inputs = trans.outputs _outputs = trans.inputs p1name = params[0] sort = p1.argsort() p1sorted = p1[sort] def transform(self, maps): idx = numpy.searchsorted(self.p1sorted, maps[self.p1name]) out = {p: samples[p][self.sort[idx]] for p in self.outputs} return self.format_output(maps, out) global_transforms[Thook.name] = Thook return cls(params, samples)
__all__ = ['FixedSamples']