class CosAngle(SinAngle): r"""A cosine distribution. This is the same thing as a sine distribution, but with the domain shifted to `[-pi/2, pi/2]`. See SinAngle for more details. Parameters ---------- \**params : The keyword arguments should provide the names of parameters and (optionally) their corresponding bounds, as either `boundaries.Bounds` instances or tuples. The bounds must be in [-0.5, 0.5]. These are converted to radians for storage. None may also be passed; in that case, the domain bounds will be used. Class Attributes ---------------- name : 'cos_angle' The name of this distribution. Attributes ---------- params : list of strings The list of parameter names. bounds : dict A dictionary of the parameter names and their bounds, in radians. """ name = 'cos_angle' _func = numpy.sin _dfunc = numpy.cos _arcfunc = numpy.arcsin _domain = boundaries.Bounds(-numpy.pi / 2., numpy.pi / 2., btype_min='closed', btype_max='closed', cyclic=False)
def __init__(self, **params): # save distribution parameters as dict # calculate the norm and exponential norm ahead of time # and save to self._norm, self._lognorm, and self._expnorm self._bounds = {} self._mean = {} self._var = {} self._norm = {} self._lognorm = {} self._expnorm = {} # pull out specified means, variance mean_args = [p for p in params if p.endswith('_mean')] var_args = [p for p in params if p.endswith('_var')] self._mean = dict([[p[:-5], params.pop(p)] for p in mean_args]) self._var = dict([[p[:-4], params.pop(p)] for p in var_args]) # if any param is set to None, make its bounds -inf, inf for param in params: if params[param] is None: # Bounds defaults to -inf, inf params[param] = boundaries.Bounds() # initialize the bounds super(Gaussian, self).__init__(**params) # check that there are no params in mean/var that are not in params missing = set(self._mean.keys()) - set(params.keys()) if any(missing): raise ValueError("means provided for unknow params {}".format( ', '.join(missing))) missing = set(self._var.keys()) - set(params.keys()) if any(missing): raise ValueError("vars provided for unknow params {}".format( ', '.join(missing))) # set default mean/var for params not specified self._mean.update( dict([[p, 0.] for p in params if p not in self._mean])) self._var.update(dict([[p, 1.] for p in params if p not in self._var])) # compute norms for p, bnds in self._bounds.items(): sigmasq = self._var[p] mu = self._mean[p] a, b = bnds invnorm = scipy.stats.norm.cdf(b, loc=mu, scale=sigmasq**0.5) \ - scipy.stats.norm.cdf(a, loc=mu, scale=sigmasq**0.5) invnorm *= numpy.sqrt(2 * numpy.pi * sigmasq) self._norm[p] = 1. / invnorm self._lognorm[p] = numpy.log(self._norm[p]) self._expnorm[p] = -1. / (2 * sigmasq)
def __init__(self, **params): # convert input bounds to Bounds class, if necessary for param, bnds in params.items(): if not isinstance(bnds, boundaries.Bounds): params[param] = boundaries.Bounds(bnds[0], bnds[1]) # warn the user about reflected boundaries if isinstance( bnds, boundaries.Bounds) and (bnds.min.name == 'reflected' or bnds.max.name == 'reflected'): warnings.warn("Param {} has one or more ".format(param) + "reflected boundaries. Reflected boundaries " "can cause issues when used in an MCMC.") self._bounds = params self._params = sorted(params.keys())
def __init__(self, **params): for p, bnds in params.items(): if bnds is None: bnds = self._domain elif isinstance(bnds, boundaries.Bounds): # convert to radians bnds._min = bnds._min.__class__(bnds._min * numpy.pi) bnds._max = bnds._max.__class__(bnds._max * numpy.pi) else: # create a Bounds instance from the given tuple bnds = boundaries.Bounds(bnds[0] * numpy.pi, bnds[1] * numpy.pi) # check that the bounds are in the domain if bnds.min < self._domain.min or bnds.max > self._domain.max: raise ValueError("bounds must be in [{x},{y}); " "got [{a},{b})".format( x=self._domain.min / numpy.pi, y=self._domain.max / numpy.pi, a=bnds.min / numpy.pi, b=bnds.max / numpy.pi)) # update params[p] = bnds super(UniformAngle, self).__init__(**params)
class SinAngle(UniformAngle): r"""A sine distribution; the pdf of each parameter `\theta` is given by: ..math:: p(\theta) = \frac{\sin \theta}{\cos\theta_0 - \cos\theta_1}, \theta_0 \leq \theta < \theta_1, and 0 otherwise. Here, :math:`\theta_0, \theta_1` are the bounds of the parameter. The domain of this distribution is `[0, pi]`. This is accomplished by putting hard boundaries at `[0, pi]`. Bounds may be provided to further limit the range for which the pdf has support. As with `UniformAngle`, these are initizliaed as multiples of pi, while the stored bounds are in radians. Parameters ---------- \**params : The keyword arguments should provide the names of parameters and (optionally) their corresponding bounds, as either `boundaries.Bounds` instances or tuples. The bounds must be in [0,1]. These are converted to radians for storage. None may also be passed; in that case, the domain bounds will be used. Class Attributes ---------------- name : 'sin_angle' The name of this distribution. Attributes ---------- params : list of strings The list of parameter names. bounds : dict A dictionary of the parameter names and their bounds, in radians. """ name = 'sin_angle' _func = numpy.cos _dfunc = numpy.sin _arcfunc = numpy.arccos # _domain applies the reflection off of 0, pi _domain = boundaries.Bounds(0, numpy.pi, btype_min='closed', btype_max='closed', cyclic=False) 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. """ if kwargs not in self: return 0. return self._norm * \ self._dfunc(numpy.array([kwargs[p] for p in self._params])).prod() 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. """ if kwargs not in self: return -numpy.inf return self._lognorm + \ numpy.log(self._dfunc( numpy.array([kwargs[p] for p in self._params]))).sum() def rvs(self, size=1, param=None): """Gives a set of random values drawn from this distribution. Parameters ---------- size : {1, int} The number of values to generate; default is 1. param : {None, string} If provided, will just return values for the given parameter. Otherwise, returns random values for each parameter. Returns ------- structured array The random values in a numpy structured array. If a param was specified, the array will only have an element corresponding to the given parameter. Otherwise, the array will have an element for each parameter in self's params. """ if param is not None: dtype = [(param, float)] else: dtype = [(p, float) for p in self.params] arr = numpy.zeros(size, dtype=dtype) for (p, _) in dtype: arr[p] = self._arcfunc( numpy.random.uniform(self._func(self._bounds[p][0]), self._func(self._bounds[p][1]), size=size)) return arr
class UniformAngle(Uniform): """A uniform distribution in which the dependent variable is cyclic between `[0,2pi)`. Bounds may be provided to limit the range for which the pdf has support. If provided, the parameter bounds are initialized as multiples of pi, while the stored bounds are in radians. Parameters ---------- \**params : The keyword arguments should provide the names of parameters and (optionally) their corresponding bounds, as either `boundaries.Bounds` instances or tuples. The bounds must be in [0,2). These are converted to radians for storage. None may also be passed; in that case, the domain bounds will be used. Class Attributes ---------------- name : 'uniform_angle' The name of this distribution. Attributes ---------- params : list of strings The list of parameter names. bounds : dict A dictionary of the parameter names and their bounds, in radians. For more information, see Uniform. """ name = 'uniform_angle' # _domain is a bounds instance used apply the cyclic conditions; this is # applied first, before any bounds specified in the initialization are used _domain = boundaries.Bounds(0., 2 * numpy.pi, cyclic=True) def __init__(self, **params): for p, bnds in params.items(): if bnds is None: bnds = self._domain elif isinstance(bnds, boundaries.Bounds): # convert to radians bnds._min = bnds._min.__class__(bnds._min * numpy.pi) bnds._max = bnds._max.__class__(bnds._max * numpy.pi) else: # create a Bounds instance from the given tuple bnds = boundaries.Bounds(bnds[0] * numpy.pi, bnds[1] * numpy.pi) # check that the bounds are in the domain if bnds.min < self._domain.min or bnds.max > self._domain.max: raise ValueError("bounds must be in [{x},{y}); " "got [{a},{b})".format( x=self._domain.min / numpy.pi, y=self._domain.max / numpy.pi, a=bnds.min / numpy.pi, b=bnds.max / numpy.pi)) # update params[p] = bnds super(UniformAngle, self).__init__(**params) def apply_boundary_conditions(self, **kwargs): """Maps values to be in [0, 2pi) (the domain) first, before applying any additional boundary conditions. Parameters ---------- \**kwargs : The keyword args should be the name of a parameter and value to apply its boundary conditions to. The arguments need not include all of the parameters in self. Returns ------- dict A dictionary of the parameter names and the conditioned values. """ # map values to be within the domain kwargs = dict([[p, self._domain.apply_conditions(val)] for p, val in kwargs.items()]) # now apply additional conditions return super(UniformAngle, self).apply_boundary_conditions(**kwargs) @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 ------- UniformAngle A distribution instance from the pycbc.inference.prior module. """ return _bounded_from_config(cls, cp, section, variable_args, bounds_required=False)
def get_param_bounds_from_config(cp, section, tag, param): """Gets bounds for the given parameter from a section in a config file. Minimum and maximum values for bounds are specified by adding `min-{param}` and `max-{param}` options, where `{param}` is the name of the paramter. The types of boundary (open, closed, or reflected) to create may also be specified by adding options `bytime-min-{param}` and `btype-max-{param}`. Cyclic conditions can be adding option `cyclic-{param}`. If no `btype` arguments are provided, the left bound will be closed and the right open. For example, the following will create right-open bounds for parameter `foo`: .. code:: [{section}-{tag}] min-foo = -1 max-foo = 1 This would make the boundaries cyclic: .. code:: [{section}-{tag}] min-foo = -1 max-foo = 1 cyclic-foo = For more details on boundary types and their meaning, see `boundaries.Bounds`. If the parameter is not found in the section will just return None (in this case, all `btype` and `cyclic` arguments are ignored for that parameter). If bounds are specified, both a minimum and maximum must be provided, else a Value or Type Error will be raised. Parameters ---------- cp : ConfigParser instance The config file. section : str The name of the section. tag : str Any tag in the section name. The full section name searched for in the config file is `{section}(-{tag})`. param : str The name of the parameter to retrieve bounds for. Returns ------- bounds : {Bounds instance | None} If bounds were provided, a `boundaries.Bounds` instance representing the bounds. Otherwise, `None`. """ try: minbnd = float(cp.get_opt_tag(section, 'min-' + param, tag)) except Error: minbnd = None try: maxbnd = float(cp.get_opt_tag(section, 'max-' + param, tag)) except Error: maxbnd = None if minbnd is None and maxbnd is None: bnds = None elif minbnd is None or maxbnd is None: raise ValueError("if specifying bounds for %s, " % (param) + "you must provide both a minimum and a maximum") else: bndargs = {'min_bound': minbnd, 'max_bound': maxbnd} # try to get any other conditions, if provided try: minbtype = cp.get_opt_tag(section, 'btype-min-{}'.format(param), tag) except Error: minbtype = 'closed' try: maxbtype = cp.get_opt_tag(section, 'btype-max-{}'.format(param), tag) except Error: maxbtype = 'open' bndargs.update({'btype_min': minbtype, 'btype_max': maxbtype}) cyclic = cp.has_option_tag(section, 'cyclic-{}'.format(param), tag) bndargs.update({'cyclic': cyclic}) bnds = boundaries.Bounds(**bndargs) return bnds