swh:1:snp:3a699297f000109a1bc833f294a54171df990207
Raw File
Tip revision: 8f9f3ac7fc59f63ce01f3b67e8436226efe5364f authored by Larne Pekowsky on 14 November 2015, 00:58:08 UTC
Set for 1.3.0 release
Tip revision: 8f9f3ac
pycbc_inspiral
#!/usr/bin/env python

# Copyright (C) 2014 Alex 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.

import sys
import logging, argparse, numpy, itertools
import pycbc
from pycbc import vetoes, psd, waveform, events, strain, scheme, fft, DYN_RANGE_FAC
from pycbc.filter import MatchedFilterControl, make_frequency_series
from pycbc.types import TimeSeries, FrequencySeries, zeros, float32, complex64
import pycbc.fft.fftw, pycbc.version
import pycbc.opt
import pycbc.weave

parser = argparse.ArgumentParser(usage='',
    description="Find single detector gravitational-wave triggers.")

parser.add_argument('--version', action='version',
                    version=pycbc.version.git_verbose_msg)
parser.add_argument("-V", "--verbose", action="store_true",
                  help="print extra debugging information", default=False )
parser.add_argument("--output", type=str, help="FIXME: ADD")
parser.add_argument("--bank-file", type=str, help="FIXME: ADD")
parser.add_argument("--snr-threshold",
                  help="SNR threshold for trigger generation", type=float)
parser.add_argument("--newsnr-threshold", type=float, metavar='THRESHOLD',
                    help="Cut triggers with NewSNR less than THRESHOLD")
parser.add_argument("--low-frequency-cutoff", type=float,
                  help="The low frequency cutoff to use for filtering (Hz)")
parser.add_argument("--approximant", type=str,
                  help="The name of the approximant to use for filtering. "
                       "One may choose a single approximant by simple assignment. "
                       "Choose multiple approximants to use in different mass "
                       "regions by using math functions and the params keys as "
                       " follows : \"'SPAtmplt' if (params.mass1 + params.mass2) < 10 "
                       "  else 'SEOBNRv2_ROM_DoubleSpin'\". Approximant names should "
                       "be single quoted with the entire expression in double quotes." )
parser.add_argument("--order", type=int,
                  help="The integer half-PN order at which to generate"
                       " the approximant. Default is -1 which indicates to use"
                       " approximant defined default.", default=-1,
                       choices = numpy.arange(-1, 9, 1))
taper_choices = ["start","end","startend"]
parser.add_argument("--taper-template", choices=taper_choices,
                    help="For time-domain approximants, taper the start and/or"
                    " end of the waveform before FFTing.")
parser.add_argument("--cluster-method", choices=["template", "window"],
                    help="FIXME: ADD")
parser.add_argument("--cluster-function", choices=["findchirp", "symmetric"],
                    help="How to cluster together triggers within a window. "
                    "'findchirp' uses a forward sliding window; 'symmetric' "
                    "will compare each window to the one before and after, keeping "
                    "only a local maximum.", default="findchirp")
parser.add_argument("--cluster-window", type=float, default = -1,
                    help="Length of clustering window in seconds."
                    " Set to 0 to disable clustering.")
parser.add_argument("--maximization-interval", type=float, default=0,
                    help="Maximize triggers over the template bank (ms)")
parser.add_argument("--bank-veto-bank-file", type=str, help="FIXME: ADD")
parser.add_argument("--chisq-snr-threshold", type=float, help="Minimum SNR to calculate the power chisq")
parser.add_argument("--chisq-bins", default=0, help=
                    "Number of frequency bins to use for power chisq. Specify"
                    " an integer for a constant number of bins, or a function "
                    "of template attributes.  Math functions are "
                    "allowed, ex. "
                    "'10./math.sqrt((params.mass1+params.mass2)/100.)'. "
                    "Non-integer values will be rounded down.")
parser.add_argument("--chisq-threshold", type=float, default=0,
                    help="FIXME: ADD")
parser.add_argument("--chisq-delta", type=float, default=0, help="FIXME: ADD")
parser.add_argument("--autochi-number-points", type=int, default=0,
                    help="The number of points to use, in both directions if"
                         "doing a two-sided auto-chisq, to calculate the"
                         "auto-chisq statistic.")
parser.add_argument("--autochi-stride", type=int, default=0,
                    help="The gap, in sample points, between the points at"
                         "which to calculate auto-chisq.")
parser.add_argument("--autochi-two-phase", action="store_true",
                    default=False,
                    help="If given auto-chisq will be calculated by testing "
                         "against both phases of the SNR time-series. "
                         "If not given, only the phase matching the trigger "
                         "will be used.")
parser.add_argument("--autochi-onesided", action='store', default=None,
                    choices=['left','right'],
                    help="Decide whether to calculate auto-chisq using"
                         "points on both sides of the trigger or only on one"
                         "side. If not given points on both sides will be"
                         "used. If given, with either 'left' or 'right',"
                         "only points on that side (right = forward in time,"
                         "left = back in time) will be used.")
parser.add_argument("--autochi-reverse-template", action="store_true",
                    default=False,
                    help="If given, time-reverse the template before"
                         "calculating the auto-chisq statistic. This will"
                         "come at additional computational cost as the SNR"
                         "time-series will need recomputing for the time-"
                         "reversed template.")
parser.add_argument("--autochi-max-valued", action="store_true",
                    default=False,
                    help="If given, store only the maximum value of the auto-"
                         "chisq over all points tested. A disadvantage of this "
                         "is that the mean value will not be known "
                         "analytically.")
parser.add_argument("--autochi-max-valued-dof", action="store", metavar="INT",
                    type=int,
                    help="If using --autochi-max-valued this value denotes "
                         "the pre-calculated mean value that will be stored "
                         "as the auto-chisq degrees-of-freedom value.")
parser.add_argument("--downsample-factor", type=int,
                    help="Factor that determines the interval between the "
                         "initial SNR sampling. If not set (or 1) no sparse sample "
                         "is created, and the standard full SNR is calculated.", default=1)
parser.add_argument("--upsample-threshold", type=float,
                    help="The fraction of the SNR threshold to check the sparse SNR sample.")
parser.add_argument("--upsample-method", choices=["pruned_fft"],
                    help="The method to find the SNR points between the sparse SNR sample.",
                    default='pruned_fft')
parser.add_argument("--user-tag", type=str, metavar="TAG", help="""
                    This is used to identify FULL_DATA jobs for
                    compatibility with pipedown post-processing.
                    Option will be removed when no longer needed.""")
parser.add_argument("--psd-recalculate-segments", type=int,
                    help="Number of segments to use before recalculating the PSD", default=0)
parser.add_argument("--keep-loudest-interval", type=float,
                    help="Window in seconds to maximize triggers over bank")
parser.add_argument("--keep-loudest-num", type=int,
                    help="Number of triggers to keep from each maximization interval")
parser.add_argument("--gpu-callback-method", default='none')

# Add options groups
psd.insert_psd_option_group(parser)
strain.insert_strain_option_group(parser)
strain.StrainSegments.insert_segment_option_group(parser)
scheme.insert_processing_option_group(parser)
fft.insert_fft_option_group(parser)
pycbc.opt.insert_optimization_option_group(parser)
pycbc.weave.insert_weave_option_group(parser)

opt = parser.parse_args()

# Check that the values returned for the options make sense
psd.verify_psd_options(opt, parser)
strain.verify_strain_options(opt, parser)
strain.StrainSegments.verify_segment_options(opt, parser)
scheme.verify_processing_options(opt, parser)
fft.verify_fft_options(opt,parser)
pycbc.opt.verify_optimization_options(opt, parser)
pycbc.weave.verify_weave_options(opt, parser)

def associate_psd(strain_segments, gwstrain, segments, nsegs, flen, delta_f, flow):
    logging.info("Computing noise PSD")
    def grouper(n, iterable):
        args = [iter(iterable)] * n
        return list([e for e in t if e != None] for t in itertools.izip_longest(*args))

    nsegs = nsegs if nsegs != 0 else len(strain_segments.full_segment_slices)
    groups = grouper(nsegs, strain_segments.full_segment_slices)
    if len(groups[-1]) != len(groups[0]):
        logging.warn('PSD recalculation does not divide equally among analysis'
                     'segments. Make sure that this is what you want')

    psds = []
    for psegs in groups:
        strain_part = gwstrain[psegs[0].start:psegs[-1].stop]
        ppsd = psd.from_cli(opt, flen, delta_f, flow, 
                            strain_part, DYN_RANGE_FAC).astype(float32)
        psds.append(ppsd)
        for seg in segments:
            if seg.seg_slice in psegs:
                seg.psd = ppsd
    return psds


pycbc.init_logging(opt.verbose)

ctx = scheme.from_cli(opt)
gwstrain = strain.from_cli(opt, DYN_RANGE_FAC)
strain_segments = strain.StrainSegments.from_cli(opt, gwstrain)

with ctx:
    fft.from_cli(opt)

    flow = opt.low_frequency_cutoff
    flen = strain_segments.freq_len
    tlen = strain_segments.time_len
    delta_f = strain_segments.delta_f

    logging.info("Making frequency-domain data segments")
    segments = strain_segments.fourier_segments()
    psds = associate_psd(strain_segments, gwstrain, segments,
                  opt.psd_recalculate_segments, flen, delta_f, flow)

    # storage for values and types to be passed to event manager
    out_types = {
        'time_index'     : int,
        'snr'            : complex64,
        'chisq'          : float32,
        'chisq_dof'      : int,
        'bank_chisq'     : float32,
        'bank_chisq_dof' : int,
        'cont_chisq'     : float32
                }
    out_vals = {
        'time_index'     : None,
        'snr'            : None,
        'chisq'          : None,
        'chisq_dof'      : None,
        'bank_chisq'     : None,
        'bank_chisq_dof' : None,
        'cont_chisq'     : None
               }
    names = sorted(out_vals.keys())

    event_mgr = events.EventManager(
            opt, names, [out_types[n] for n in names], psd=psds[0],
            gating_info=gwstrain.gating_info)

    if len(strain_segments.segment_slices) == 0:
        logging.info("--filter-inj-only specified and no injections in analysis time")
        event_mgr.finalize_template_events()
        event_mgr.write_events(opt.output)
        logging.info("Finished")
        sys.exit(0)

    template_mem = zeros(tlen, dtype = complex64)
    cluster_window = int(opt.cluster_window * gwstrain.sample_rate)

    if opt.cluster_window == 0.0:
        use_cluster = False
    else:
        use_cluster = True

    matched_filter = MatchedFilterControl(opt.low_frequency_cutoff, None,
                                   opt.snr_threshold, tlen, delta_f, complex64,
                                   segments, template_mem, use_cluster,
                                   downsample_factor=opt.downsample_factor,
                                   upsample_threshold=opt.upsample_threshold,
                                   upsample_method=opt.upsample_method,
                                   gpu_callback_method=opt.gpu_callback_method,
                                   cluster_function=opt.cluster_function)

    bank_chisq = vetoes.SingleDetBankVeto(opt.bank_veto_bank_file,
                                          flen, delta_f, flow, complex64,
                                          phase_order=opt.order,
                                          approximant=opt.approximant)

    power_chisq = vetoes.SingleDetPowerChisq(opt.chisq_bins, opt.chisq_snr_threshold)
    autochisq = vetoes.SingleDetAutoChisq(opt.autochi_stride,
                                 opt.autochi_number_points,
                                 onesided=opt.autochi_onesided,
                                 twophase=opt.autochi_two_phase,
                                 reverse_template=opt.autochi_reverse_template,
                                 take_maximum_value=opt.autochi_max_valued,
                                 maximal_value_dof=opt.autochi_max_valued_dof)

    logging.info("Overwhitening frequency-domain data segments")
    for seg in segments:
        seg /= seg.psd

    logging.info("Read in template bank")
    bank = waveform.FilterBank(opt.bank_file, flen, delta_f,
                    flow, dtype = complex64, phase_order = opt.order,
                    taper = opt.taper_template, approximant = opt.approximant,
                    out = template_mem)

    # Note: in the class-based approach used now, 'template' is not explicitly used
    # within the loop.  Rather, the iteration simply fills the memory specifed in
    # the 'template_mem' argument to MatchedFilterControl with the next template
    # from the bank.
    for t_num, template in enumerate(bank):
        event_mgr.new_template(tmplt=template.params, sigmasq=template.sigmasq(segments[0].psd))

        if opt.cluster_method == "window":
            cluster_window = int(opt.cluster_window * gwstrain.sample_rate)
        if opt.cluster_method == "template":
            cluster_window = int(template.chirp_length * gwstrain.sample_rate)


        for s_num, stilde in enumerate(segments):
            logging.info("Filtering template %d/%d segment %d/%d" %
                         (t_num + 1, len(bank), s_num + 1, len(segments)))

            snr, norm, corr, idx, snrv = \
               matched_filter.matched_filter_and_cluster(s_num, template.sigmasq(stilde.psd), cluster_window)

            if not len(idx):
                continue

            out_vals['bank_chisq'], out_vals['bank_chisq_dof'] = \
                  bank_chisq.values(template, stilde.psd, stilde, snrv, norm,
                                    idx+stilde.analyze.start)

            out_vals['chisq'], out_vals['chisq_dof'] = \
                  power_chisq.values(corr, snrv, norm, stilde.psd,
                                     idx+stilde.analyze.start, template)

            out_vals['cont_chisq'] = \
                  autochisq.values(snr, idx+stilde.analyze.start, template,
                                   stilde.psd, norm, stilde=stilde,
                                   low_frequency_cutoff=flow)

            idx += stilde.cumulative_index

            out_vals['time_index'] = idx
            out_vals['snr'] = snrv * norm

            event_mgr.add_template_events(names, [out_vals[n] for n in names])

        event_mgr.cluster_template_events("time_index", "snr", cluster_window)
        event_mgr.finalize_template_events()

logging.info("Found %s triggers" % str(len(event_mgr.events)))

if opt.chisq_threshold and opt.chisq_bins:
    logging.info("Removing triggers with poor chisq")
    event_mgr.chisq_threshold(opt.chisq_threshold, opt.chisq_bins,
                              opt.chisq_delta)
    logging.info("%d remaining triggers" % len(event_mgr.events))

if opt.newsnr_threshold and opt.chisq_bins:
    logging.info("Removing triggers with NewSNR below threshold")
    event_mgr.newsnr_threshold(opt.newsnr_threshold)
    logging.info("%d remaining triggers" % len(event_mgr.events))

if opt.keep_loudest_interval:
    logging.info("Removing triggers that are not within the top %s loudest"
                 " of a %s second interval" % (opt.keep_loudest_num,
                                               opt.keep_loudest_interval))
    event_mgr.keep_loudest_in_interval(opt.keep_loudest_interval * opt.sample_rate,
                                       opt.keep_loudest_num)
    logging.info("%d remaining triggers" % len(event_mgr.events))

if opt.injection_window and hasattr(gwstrain, 'injections'):
    logging.info("Keeping triggers within %s seconds of injection" % opt.injection_window)
    event_mgr.keep_near_injection(opt.injection_window, gwstrain.injections)
    logging.info("%d remaining triggers" % len(event_mgr.events))

if opt.maximization_interval:
    logging.info("Maximizing triggers over %s ms window" % opt.maximization_interval)
    window = int(opt.maximization_interval * gwstrain.sample_rate / 1000)
    event_mgr.maximize_over_bank("time_index", "snr", window)
    logging.info("%d remaining triggers" % len(event_mgr.events))

logging.info("Writing out triggers")
event_mgr.write_events(opt.output)

if opt.fftw_output_float_wisdom_file:
    fft.fftw.export_single_wisdom_to_filename(opt.fftw_output_float_wisdom_file)

if opt.fftw_output_double_wisdom_file:
    fft.fftw.export_double_wisdom_to_filename(opt.fftw_output_double_wisdom_file)

logging.info("Finished")
back to top