Raw File
pycbc_plot_waveform
#!/usr/bin/env python
# Copyright (C) 2016 Ian Harry
#
# 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.
""" Plot a waveform in both time and frequency domain.
"""

# imports
import sys, argparse, matplotlib, numpy, math
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from pycbc import waveform, io
from pycbc import version
from pycbc import results
from pycbc.fft import ifft
from pycbc.types import complex_same_precision_as
from pycbc.types import TimeSeries, zeros, complex64

parser = argparse.ArgumentParser(usage='',
    description="Plot a waveform in both time and frequency domain.")
parser.add_argument('--version', action='version',
                    version=version.git_verbose_msg)
parser.add_argument('--output-file', required=True)
parser.add_argument("--low-frequency-cutoff", type=float,
                  help="The low frequency cutoff to use for generation.")
# add the approximant argument
waveform.bank.add_approximant_arg(parser,
                  help="The name of the approximant to use for filtering. "
                      "Do not use if using --use-params-of-closest-injection.")
parser.add_argument("--mass1", type=float, required=True,
                  help="The mass of the first component object. "
                      "Do not use if using --use-params-of-closest-injection.")
parser.add_argument("--mass2", type=float, required=True,
                  help="The mass of the second component object. "
                      "Do not use if using --use-params-of-closest-injection.")
parser.add_argument("--spin1z", type=float, required=True,
                  help="The aligned spin of the first component object. "
                      "Do not use if using --use-params-of-closest-injection.")
parser.add_argument("--spin2z", type=float, required=True,
                  help="The aligned pin of the second component object. "
                      "Do not use if using --use-params-of-closest-injection.")
parser.add_argument("--sample-rate", type=float, required=True,
                  help="Sample rate to use when generating waveform.")
parser.add_argument("--waveform-length", type=float, required=True,
                  help="Used to set the value of delta-f when either generating"
                       "in frequency domain, or when FFTing.")
# Optional arguments for precession
parser.add_argument("--spin1x", type=float, default=0,
                  help="Non-aligned spin of the first component object. ")
parser.add_argument("--spin1y", type=float, default=0,
                  help="Non-aligned spin of the first component object. ")
parser.add_argument("--spin2x", type=float, default=0,
                  help="Non-aligned spin of the second component object. ")
parser.add_argument("--spin2y", type=float, default=0,
                  help="Non-aligned spin of the second component object. ")
parser.add_argument("--inclination", type=float, default=0,
                  help="Non-aligned spin of the second component object. ")
parser.add_argument("--u-val", type=float, default=0,
                  help="Ratio between h_+ and h_x according to "
                        "h(t) = h_+ * u_val + h_x. Only needed in the case of "
                        "waveforms where h_+ and h_x are not related by a "
                        "simple phase shift.")
# Other optional arguments
parser.add_argument("--taper-template", choices=["start","end","startend"],
                    help="For time-domain approximants, taper the start and/or"
                    " end of the waveform before FFTing.")
# Plotting options
parser.add_argument('--plot-title',
                    help="If given, use this as the plot title")
parser.add_argument('--plot-caption',
                    help="If given, use this as the plot caption")

opt = parser.parse_args()

delta_f = 1. / opt.waveform_length
delta_t = 1. / opt.sample_rate
tlen = int(opt.waveform_length * opt.sample_rate)
flen = tlen / 2 + 1

tmp_params = io.WaveformArray.from_kwargs(
                    mass1=opt.mass1,
                    mass2=opt.mass2,
                    spin1x=opt.spin1x,
                    spin1y=opt.spin1y,
                    spin1z=opt.spin1z,
                    spin2x=opt.spin2x,
                    spin2y=opt.spin2y,
                    spin2z=opt.spin2z,
                    inclination=opt.inclination)

# Deal with parameter-dependent approximant string
approximant = waveform.bank.parse_approximant_arg(opt.approximant,
                tmp_params)[0]

# This is a hack. We don't have a two-pol SPATmplt, so use TaylorF2
if approximant == 'SPAtmplt':
    approximant = 'TaylorF2'

hp, hc = waveform.get_two_pol_waveform_filter(zeros(flen, dtype=complex64),
                                    zeros(flen, dtype=complex64),
                                    tmp_params[0], approximant=approximant,
                                    taper=opt.taper_template,
                                    f_lower=opt.low_frequency_cutoff,
                                    delta_f=delta_f, delta_t=delta_t)

# 0.1s (post-merger) added for safety
tmplt_length = hp.length_in_time + 0.1
pre_merger_length = hp.chirp_length
post_merger_length = tmplt_length - pre_merger_length

# Find last non-zero frequency point
f_length = hp.sample_frequencies[numpy.nonzero(hp.data)][-1]

template = hp * opt.u_val + hc

template_td = template.to_timeseries()

# Figure out how best to plot waveform
template_td.roll(int(-post_merger_length * opt.sample_rate))

fig = plt.figure()
ax = plt.subplot(2,1,1)
ax.plot(template_td.sample_times, template_td)
ax.set_xlim([template_td.sample_times.max() - tmplt_length*1.1,
          template_td.sample_times.max()])
ax.set_xlabel('Time (s)')
ax.set_ylabel('$h(t)$')

if tmplt_length > 1.:
    # Make inset zoom on merger
    axins = zoomed_inset_axes(ax, 0.5, loc=3,bbox_to_anchor=(0.12, 0.69),
                              bbox_transform=ax.figure.transFigure)

    x_zoom_fac = tmplt_length / 0.2

    axins.plot(template_td.sample_times * x_zoom_fac, template_td)
    axins.set_xlim([(template_td.sample_times.max() - 0.2)*x_zoom_fac,
                    x_zoom_fac*template_td.sample_times.max()])
    axins.get_xaxis().set_ticks([])
    axins.get_yaxis().set_ticks([])
    # Can't get this to work for what I'm doing :-(
    #mark_inset(ax, axins, loc1=1, loc2=3, fc="none", ec="0.5")

plt.subplot(2,1,2)
plt.plot(template.sample_frequencies, abs(template))
plt.xlabel('Frequency (Hz)')
plt.xlim([0,f_length*1.1])
plt.ylabel('abs( $\\tilde{h}(f)$ )')


if opt.plot_title is None:
    opt.plot_title = 'Waveform plot'
if opt.plot_caption is None:
    opt.plot_caption = ("The first plot represents the template waveform"
                        "in the time domain with the inset showing a"
                        "zoom-up at merger. The second plot represents"
                        "the template waveform in the frequency domain.")

results.save_fig_with_metadata(fig, opt.output_file,
                             cmd=' '.join(sys.argv), fig_kwds={'dpi': 150},
                             title=opt.plot_title,
                             caption=opt.plot_caption)
back to top