Source code for gwin.models.base_data

# Copyright (C) 2018  Collin Capano
# 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.


#
# =============================================================================
#
#                                   Preamble
#
# =============================================================================
#

"""Base classes for models with data.
"""

import numpy
import logging
from abc import (ABCMeta, abstractmethod)

from pycbc import transforms
from pycbc.waveform import generator

from .base import BaseModel


[docs]class BaseDataModel(BaseModel): r"""Base class for models that require data and a waveform generator. This adds propeties for the log of the likelihood that the data contain noise, ``lognl``, and the log likelihood ratio ``loglr``. Classes that inherit from this class must define ``_loglr`` and ``_lognl`` functions, in addition to the ``_loglikelihood`` requirement inherited from ``BaseModel``. Parameters ---------- variable_params : (tuple of) string(s) A tuple of parameter names that will be varied. data : dict A dictionary of data, in which the keys are the detector names and the values are the data. waveform_generator : generator class A generator class that creates waveforms. waveform_transforms : list, optional List of transforms to use to go from the variable args to parameters understood by the waveform generator. \**kwargs : All other keyword arguments are passed to ``BaseModel``. Attributes ---------- waveform_generator : dict The waveform generator that the class was initialized with. data : dict The data that the class was initialized with. Properties ---------- lognl : Returns the log likelihood of the noise. loglr : Returns the log of the likelihood ratio. logplr : Returns the log of the prior-weighted likelihood ratio. See ``BaseModel`` for additional attributes and properties. """ __metaclass__ = ABCMeta def __init__(self, variable_params, data, waveform_generator, waveform_transforms=None, **kwargs): # we'll store a copy of the data self._data = {ifo: d.copy() for (ifo, d) in data.items()} self._waveform_generator = waveform_generator self._waveform_transforms = waveform_transforms super(BaseDataModel, self).__init__( variable_params, **kwargs) @property def _extra_stats(self): """Adds ``loglr`` and ``lognl`` to the ``default_stats``.""" return ['loglr', 'lognl'] @property def lognl(self): """The log likelihood of the model assuming the data is noise. This will initially try to return the ``current_stats.lognl``. If that raises an ``AttributeError``, will call `_lognl`` to calculate it and store it to ``current_stats``. """ return self._trytoget('lognl', self._lognl) @abstractmethod def _lognl(self): """Low-level function that calculates the lognl.""" pass @property def loglr(self): """The log likelihood ratio at the current parameters. This will initially try to return the ``current_stats.loglr``. If that raises an ``AttributeError``, will call `_loglr`` to calculate it and store it to ``current_stats``. """ return self._trytoget('loglr', self._loglr) @abstractmethod def _loglr(self): """Low-level function that calculates the loglr.""" pass @property def logplr(self): """Returns the log of the prior-weighted likelihood ratio at the current parameter values. The logprior is calculated first. If the logprior returns ``-inf`` (possibly indicating a non-physical point), then ``loglr`` is not called. """ logp = self.logprior if logp == -numpy.inf: return logp else: return logp + self.loglr @property def waveform_generator(self): """Returns the waveform generator that was set.""" return self._waveform_generator @property def data(self): """Returns the data that was set.""" return self._data def _transform_params(self, **params): """Adds waveform transforms to parent's ``_transform_params``.""" params = super(BaseDataModel, self)._transform_params(**params) # apply waveform transforms if self._waveform_transforms is not None: params = transforms.apply_transforms(params, self._waveform_transforms, inverse=False) return params @classmethod def _init_args_from_config(cls, cp): """Adds loading waveform_transforms to parent function. For details on parameters, see ``from_config``. """ args = super(BaseDataModel, cls)._init_args_from_config(cp) # add waveform transforms to the arguments if any(cp.get_subsections('waveform_transforms')): logging.info("Loading waveform transforms") args['waveform_transforms'] = \ transforms.read_transforms_from_config( cp, 'waveform_transforms') return args
[docs] @classmethod def from_config(cls, cp, data, delta_f=None, delta_t=None, gates=None, recalibration=None, **kwargs): """Initializes an instance of this class from the given config file. Parameters ---------- cp : WorkflowConfigParser Config file parser to read. data : dict A dictionary of data, in which the keys are the detector names and the values are the data. This is not retrieved from the config file, and so must be provided. delta_f : float The frequency spacing of the data; needed for waveform generation. delta_t : float The time spacing of the data; needed for time-domain waveform generators. recalibration : dict of pycbc.calibration.Recalibrate, optional Dictionary of detectors -> recalibration class instances for recalibrating data. gates : dict of tuples, optional Dictionary of detectors -> tuples of specifying gate times. The sort of thing returned by `pycbc.gate.gates_from_cli`. \**kwargs : All additional keyword arguments are passed to the class. Any provided keyword will over ride what is in the config file. """ if data is None: raise ValueError("must provide data") args = cls._init_args_from_config(cp) args['data'] = data args.update(kwargs) variable_params = args['variable_params'] try: static_params = args['static_params'] except KeyError: static_params = {} # set up waveform generator try: approximant = static_params['approximant'] except KeyError: raise ValueError("no approximant provided in the static args") generator_function = generator.select_waveform_generator(approximant) waveform_generator = generator.FDomainDetFrameGenerator( generator_function, epoch=data.values()[0].start_time, variable_args=variable_params, detectors=data.keys(), delta_f=delta_f, delta_t=delta_t, recalib=recalibration, gates=gates, **static_params) args['waveform_generator'] = waveform_generator return cls(**args)