Figure Generation#

Imports#

import os
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.lines import Line2D
from matplotlib.gridspec import GridSpec
from matplotlib.colors import Normalize
import scipy
import scipy.special
import h5py
from pycbc.waveform.bank import PhenomXPTemplate, compute_beta
from pycbc.types import FrequencySeries
from pycbc.io import SingleDetTriggers
from pycbc.sensitivity import volume_binned_pylal
from pycbc.psd import aLIGOZeroDetHighPower
from pycbc.waveform import get_fd_waveform, get_td_waveform
from pycbc.detector import Detector
import lal
from lal import YRJUL_SI as lal_YRJUL_SI
import lalsimulation as lalsim
import tqdm
%matplotlib inline

Helper functions and classes#

class PhenomXPTemplateAlt(PhenomXPTemplate):

    def __init__(self, m1, m2, spin1x, spin1y, spin1z, spin2x, spin2y, spin2z, sample_rate, f_lower):
        from pycbc.pnutils import get_imr_duration
        self.flow = float(f_lower)
        self.f_final = float(sample_rate / 2.)

        self.mass1 = m1
        self.mass2 = m2
        self.spin1x = spin1x
        self.spin1y = spin1y
        self.spin1z = spin1z
        self.spin2x = spin2x
        self.spin2y = spin2y
        self.spin2z = spin2z
        self.fref = self.flow
        self.beta = compute_beta(self)
        
        self.duration = get_imr_duration(
            self.mass1, self.mass2, self.spin1z, self.spin2z, self.flow, "IMRPhenomD"
        )

        self.comps = {}
        self.has_comps = False
        
        
def compute_fplus_fcross(theta, phi, psi):
    # compute antenna factors Fplus and Fcross
    fp = 0.5 * (1 + np.cos(theta) ** 2) * np.cos(2 * phi) * np.cos(2 * psi)
    fp -= np.cos(theta) * np.sin(2 * phi) * np.sin(2 * psi)
    fc = 0.5 * (1 + np.cos(theta) ** 2) * np.cos(2 * phi) * np.sin(2 * psi)
    fc += np.cos(theta) * np.sin(2 * phi) * np.cos(2 * psi)
    return fp, fc

def project_hplus_hcross(hplus, hcross, theta, phi, psi):
    # compute antenna factors Fplus and Fcross
    fp, fc = compute_fplus_fcross(theta, phi, psi)

    # form strain signal in detector
    h = fp * hplus + fc * hcross
    return h

Define location of banks#

# These bank files are available from our data release page, or can be regenerated following our instructions
precession_dir = "/home/ian.harry/FILES_FOR_PAPER"
precession_bank = f"{precession_dir}/PREC_BANK.h5"
aligned_dir = "/home/ian.harry/FILES_FOR_PAPER"
aligned_bank = f"{aligned_dir}/ALIGNED_BANK.h5"

Define search locations#

# The key files here are available from our data release page, but some of the diagnostic plots require
# very large (>5GB) files. We're happy to share these on request, and will look for some
# solution for hosting the large files. The instructions for generating all files used are provided
# on the data release pages.
aligned_search = "/home/ian.harry/FILES_FOR_PAPER/aligned_search"
comp2_search = "/home/ian.harry/FILES_FOR_PAPER/2comp_search"
comp3_search = "/home/ian.harry/FILES_FOR_PAPER/3comp_search"

Define consistent colorscheme#

harmonic_colors = ["#0173B2", "#DE8F05", "#029E73", "#D55E00", "#CC78BC"]

Figure 3#

sample_rate = 2048.
f_final = sample_rate / 2.
df = 1. / 256
psi = 0.
template = PhenomXPTemplateAlt(10., 1.5, 0.5, 0.5, 0.5, 0., 0., 0., sample_rate, 20.)
hp, hc = template.gen_hp_hc(np.pi / 4., 0., 0., df, f_final)
hp = FrequencySeries(hp.data.data[:], delta_f=hp.deltaF, epoch=0.)
hc = FrequencySeries(hc.data.data[:], delta_f=hc.deltaF, epoch=0.)
time = 1126259462.4
ra, dec = Detector("L1").optimal_orientation(time)
fp, fc = Detector("L1").antenna_pattern(ra, dec, psi, time)
prec = fp * hp + fc * hc
prec = prec.to_timeseries()
prec = prec.cyclic_time_shift(3.)
harmonics = template.compute_waveform_five_comps(prec.delta_f, f_final, interp=False)
fig = plt.figure(figsize=(8, 4.5))
gs = GridSpec(nrows=2, ncols=2, width_ratios=[4,1])
ax0 = fig.add_subplot(gs[0, 0])
ax1 = fig.add_subplot(gs[0, 1])
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[1, 1])

axs = [ax0, ax1, ax2, ax3]
axs[0].plot(prec.sample_times, prec, color='grey')
axs[1].plot(prec.sample_times, prec, color='grey')

for ii in range(5):
    h = harmonics[ii].to_timeseries()
    h._epoch = 0.
    h = h.cyclic_time_shift(3.)
    axs[2].plot(h.sample_times, h, color=harmonic_colors[ii], label=f"Harmonic {ii + 1}", alpha=0.9)
    axs[3].plot(h.sample_times, h, color=harmonic_colors[ii], label=f"Harmonic {ii + 1}", alpha=0.9)

axs[0].set_xlim([-2, -0.2])
axs[0].set_xticklabels([])
axs[1].set_xlim([-0.05, 0.01])
axs[1].set_xticks([-0.04, 0.0])
axs[1].set_xticklabels([])
axs[2].set_xlim([-2, -0.2])
axs[1].set_xlim([-0.05, 0.01])
axs[3].set_xticks([-0.04, 0.0])
axs[3].set_xlim([-0.05, 0.01])
axs[1].set_yticklabels([])
axs[3].set_yticklabels([])
axs[0].set_ylabel("h(t)")
axs[2].set_ylabel("h(t)")
axs[2].set_xlabel(r"Time [s]")
axs[3].set_xlabel(r"Time [s]")
for ax in axs:
    ax.set_ylim([-4.75e-20, 4.75e-20])
axs[2].legend(loc="upper left", ncol=3)
axs[0].grid(alpha=0.3)
axs[1].grid(alpha=0.3)
axs[2].grid(alpha=0.3)
axs[3].grid(alpha=0.3)
plt.subplots_adjust(wspace=0.08, hspace=0.12)
plt.savefig("harmonics.pdf", format="pdf")
images/1fe7e15d2f5f03caf22db32c5be186f7298f5ba51de5058a930474c87835404a.png

Figure 4#

sample_rate = 2048.
f_lower = 20.
df = 1. / 64.

m1, m2 = 10., 1.5
chi_1, chi_2 = 0.3, 0.3
chi_p0 = 0.1
chi_p1 = 0.3
chi_p2 = 0.6
chi_p3 = 0.9

template_gen0 = PhenomXPTemplateAlt(m1, m2, chi_p0, 0., chi_1, 0., 0., chi_2, sample_rate, f_lower)
template_gen1 = PhenomXPTemplateAlt(m1, m2, chi_p1, 0., chi_1, 0., 0., chi_2, sample_rate, f_lower)
template_gen2 = PhenomXPTemplateAlt(m1, m2, chi_p2, 0., chi_1, 0., 0., chi_2, sample_rate, f_lower)
template_gen3 = PhenomXPTemplateAlt(m1, m2, chi_p3, 0., chi_1, 0., 0., chi_2, sample_rate, f_lower)
psd = aLIGOZeroDetHighPower(int(2048. / df), df, 20.)
asd = psd ** 0.5
comps0 = template_gen0.get_whitened_normalized_comps(df, psd, num_comps=5)
comps0 = [
    FrequencySeries(c.data.data[:], delta_f=df, epoch=c.epoch)
    for c in comps0
]

comps1 = template_gen1.get_whitened_normalized_comps(df, psd, num_comps=5)
comps1 = [
    FrequencySeries(c.data.data[:], delta_f=df, epoch=c.epoch)
    for c in comps1
]

comps2 = template_gen2.get_whitened_normalized_comps(df, psd, num_comps=5)
comps2 = [
    FrequencySeries(c.data.data[:], delta_f=df, epoch=c.epoch)
    for c in comps2
]

comps3 = template_gen3.get_whitened_normalized_comps(df, psd, num_comps=5)
comps3 = [
    FrequencySeries(c.data.data[:], delta_f=df, epoch=c.epoch)
    for c in comps3
]
/home/ian.harry/.conda/envs/thapycbcsbank/lib/python3.9/site-packages/pycbc/types/array.py:217: RuntimeWarning: divide by zero encountered in true_divide
  ret = getattr(ufunc, method)(*inputs, **kwargs)
/home/ian.harry/.conda/envs/thapycbcsbank/lib/python3.9/site-packages/pycbc/types/array.py:217: RuntimeWarning: invalid value encountered in true_divide
  ret = getattr(ufunc, method)(*inputs, **kwargs)
num = 1000
thetajn = np.arccos(np.random.uniform(-1., 1., size=num))
alpha0 = np.random.uniform(0, 2 * np.pi, size=num)
phi0 = np.random.uniform(0, 2 * np.pi, size=num)

theta = np.arccos(np.random.uniform(-1., 1., size=num))
phi = np.random.uniform(0, 2 * np.pi, size=num)
psi = np.random.uniform(0, 2 * np.pi, size=num)

overlaps = [
    np.zeros((num, 5), dtype=np.complex64),
    np.zeros((num, 5), dtype=np.complex64),
    np.zeros((num, 5), dtype=np.complex64),
    np.zeros((num, 5), dtype=np.complex64)
]
comps = [comps0, comps1, comps2, comps3]

# for i in tqdm.tqdm(range(num)):
for i in range(num):
    for j, template in enumerate([template_gen0, template_gen1, template_gen2, template_gen3]):
        hp, hc = template.gen_hp_hc(thetajn[i], alpha0[i], phi0[0], df, sample_rate / 2.)
        hp = FrequencySeries(hp.data.data[:], delta_f=df, epoch=hp.epoch)
        hc = FrequencySeries(hc.data.data[:], delta_f=df, epoch=hc.epoch)
        h = project_hplus_hcross(hp, hc, theta[i], phi[i], psi[i])
        h /= asd[:len(h)]
        h[:int(f_lower / df)] = 0.
        h[int(sample_rate / 2. / df):len(h)] = 0.

        sigmasq = h.inner(h).real * 4. * df
        h /= sigmasq ** 0.5

        for k in range(5):
            overlaps[j][i, k] = comps[j][k].inner(h) * 4. * df
/home/ian.harry/.conda/envs/thapycbcsbank/lib/python3.9/site-packages/pycbc/types/array.py:396: RuntimeWarning: invalid value encountered in true_divide
  self._data /= other
overlaps0, overlaps1, overlaps2, overlaps3 = overlaps
matches0 = np.cumsum(np.abs(overlaps0) ** 2., axis=1)
matches1 = np.cumsum(np.abs(overlaps1) ** 2., axis=1)
matches2 = np.cumsum(np.abs(overlaps2) ** 2., axis=1)
matches3 = np.cumsum(np.abs(overlaps3) ** 2., axis=1)

fig, ax = plt.subplots(nrows=4, figsize=[12, 24], sharex=True)

min_match = np.min([matches0.min(), matches1.min(), matches2.min(), matches3.min()])
bins = np.linspace(min_match, 1., num=1000, endpoint=True)

hist01, _ = np.histogram(matches0[:, 0], bins=bins, density=True)
hist02, _ = np.histogram(matches0[:, 1], bins=bins, density=True)
hist03, _ = np.histogram(matches0[:, 2], bins=bins, density=True)
hist04, _ = np.histogram(matches0[:, 3], bins=bins, density=True)
hist05, _ = np.histogram(matches0[:, 4], bins=bins, density=True)

hist11, _ = np.histogram(matches1[:, 0], bins=bins, density=True)
hist12, _ = np.histogram(matches1[:, 1], bins=bins, density=True)
hist13, _ = np.histogram(matches1[:, 2], bins=bins, density=True)
hist14, _ = np.histogram(matches1[:, 3], bins=bins, density=True)
hist15, _ = np.histogram(matches1[:, 4], bins=bins, density=True)

hist21, _ = np.histogram(matches2[:, 0], bins=bins, density=True)
hist22, _ = np.histogram(matches2[:, 1], bins=bins, density=True)
hist23, _ = np.histogram(matches2[:, 2], bins=bins, density=True)
hist24, _ = np.histogram(matches2[:, 3], bins=bins, density=True)
hist25, _ = np.histogram(matches2[:, 4], bins=bins, density=True)

hist31, _ = np.histogram(matches3[:, 0], bins=bins, density=True)
hist32, _ = np.histogram(matches3[:, 1], bins=bins, density=True)
hist33, _ = np.histogram(matches3[:, 2], bins=bins, density=True)
hist34, _ = np.histogram(matches3[:, 3], bins=bins, density=True)
hist35, _ = np.histogram(matches3[:, 4], bins=bins, density=True)

ax[0].plot(bins[1:], hist01.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[0], alpha=0.9, label=r'$N = 1$')
ax[0].plot(bins[1:], hist02.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[1], alpha=0.9, label=r'$N = 2$')
ax[0].plot(bins[1:], hist03.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[2], alpha=0.9, label=r'$N = 3$')
ax[0].plot(bins[1:], hist04.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[3], alpha=0.9, label=r'$N = 4$')
ax[0].plot(bins[1:], hist05.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[4], alpha=0.9, label=r'$N = 5$')

ax[1].plot(bins[1:], hist11.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[0], alpha=0.9, label=r'$N = 1$')
ax[1].plot(bins[1:], hist12.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[1], alpha=0.9, label=r'$N = 2$')
ax[1].plot(bins[1:], hist13.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[2], alpha=0.9, label=r'$N = 3$')
ax[1].plot(bins[1:], hist14.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[3], alpha=0.9, label=r'$N = 4$')
ax[1].plot(bins[1:], hist15.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[4], alpha=0.9, label=r'$N = 5$')

ax[2].plot(bins[1:], hist21.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[0], alpha=0.9, label=r'$N = 1$')
ax[2].plot(bins[1:], hist22.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[1], alpha=0.9, label=r'$N = 2$')
ax[2].plot(bins[1:], hist23.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[2], alpha=0.9, label=r'$N = 3$')
ax[2].plot(bins[1:], hist24.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[3], alpha=0.9, label=r'$N = 4$')
ax[2].plot(bins[1:], hist25.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[4], alpha=0.9, label=r'$N = 5$')

ax[-1].plot(bins[1:], hist31.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[0], alpha=0.9, label=r'$N = 1$')
ax[-1].plot(bins[1:], hist32.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[1], alpha=0.9, label=r'$N = 2$')
ax[-1].plot(bins[1:], hist33.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[2], alpha=0.9, label=r'$N = 3$')
ax[-1].plot(bins[1:], hist34.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[3], alpha=0.9, label=r'$N = 4$')
ax[-1].plot(bins[1:], hist35.cumsum() * (bins[1] - bins[0]), linewidth=3., linestyle="--", c=harmonic_colors[4], alpha=0.9, label=r'$N = 5$')

#ax.set_yscale('log')
ax[0].set_xlim((0.5, 1.))
ax[0].set_ylim((1e-3, 1.1))
ax[0].legend(loc='upper left', fontsize=28.)
#ax[0].set_xlabel('Match', fontsize=24.)
ax[0].set_ylabel('Fraction of Samples', fontsize=28.)
ax[0].tick_params(labelsize=24)
ax[0].grid(alpha=0.3)

ax[1].set_xlim((0.5, 1.))
ax[1].set_ylim((1e-3, 1.1))
#ax[1].legend(loc='upper left', fontsize=16.)
#ax[1].set_xlabel('Match', fontsize=24.)
ax[1].set_ylabel('Fraction of Samples', fontsize=28.)
ax[1].tick_params(labelsize=24)
ax[1].grid(alpha=0.3)

ax[2].set_xlim((0.5, 1.))
ax[2].set_ylim((1e-3, 1.1))
#ax[2].legend(loc='upper left', fontsize=16.)
#ax[2].set_xlabel('Match', fontsize=24.)
ax[2].set_ylabel('Fraction of Samples', fontsize=28.)
ax[2].tick_params(labelsize=24)
ax[2].grid(alpha=0.3)

ax[-1].set_xlim((0.5, 1.))
ax[-1].set_ylim((1e-3, 1.1))
#ax[-1].legend(loc='upper left', fontsize=16.)
ax[-1].set_xlabel('Match', fontsize=28.)
ax[-1].set_ylabel('Fraction of Samples', fontsize=28.)
ax[-1].tick_params(labelsize=24)
ax[-1].grid(alpha=0.3)

plt.subplots_adjust(hspace=.1)
fig.savefig('overlaps.pdf', bbox_inches='tight')
images/58325c7e80ca234782eca0a7cbea54d34160b9466418ac2f2b86966877e9b2d7.png

Figure 5#

Figure 5 requires some input data files. Please see

https://icg-gravwaves.github.io/precessing_search_paper/Figure5_data

for details on how to produce these if wanting to reproduce this plot.

m1 = []
chip = []
overlaps = []
sigmas = []

for i in range(1000):
    with h5py.File(f'/home/ian.harry/FILES_FOR_PAPER/overlaps/overlaps_{i}.hdf', 'r') as f:
        m1 += [f['m1'][:]]
        chip += [f['chip'][:]]
        overlaps += [f['overlaps'][:]]
        sigmas += [f['sigmas'][:]]
        
m1 = np.concatenate(m1, axis=0)
chip = np.concatenate(chip, axis=0)
overlaps = np.concatenate(overlaps, axis=0)
sigmas = np.concatenate(sigmas, axis=0)
match = np.cumsum(np.abs(overlaps) ** 2., axis=2)
above = match > 0.97
meff = np.sum(above, axis=1) / above.shape[1]
m1_2d = np.reshape(m1, (100, 100))
chip_2d = np.reshape(chip, (100, 100))
meff_c1_2d = np.reshape(meff[:, 0], (100, 100))
meff_c2_2d = np.reshape(meff[:, 1], (100, 100))
meff_c3_2d = np.reshape(meff[:, 2], (100, 100))
meff_c4_2d = np.reshape(meff[:, 3], (100, 100))
meff_c5_2d = np.reshape(meff[:, 4], (100, 100))
fig, ax = plt.subplots(nrows=5, figsize=[12, 26])
#fig, ax = plt.subplots(nrows=3, figsize=[12, 16])


mesh1 = ax[0].pcolormesh(m1_2d, chip_2d, meff_c1_2d, vmin=0., vmax=1., cmap='viridis', shading='gouraud')
mesh2 = ax[1].pcolormesh(m1_2d, chip_2d, meff_c2_2d, vmin=0., vmax=1., cmap='viridis', shading='gouraud')
mesh3 = ax[2].pcolormesh(m1_2d, chip_2d, meff_c3_2d, vmin=0., vmax=1., cmap='viridis', shading='gouraud')
mesh4 = ax[3].pcolormesh(m1_2d, chip_2d, meff_c4_2d, vmin=0., vmax=1., cmap='viridis', shading='gouraud')
mesh5 = ax[4].pcolormesh(m1_2d, chip_2d, meff_c5_2d, vmin=0., vmax=1., cmap='viridis', shading='gouraud')

#ax[0].set_xlabel('$m_{1}$ ($M_\odot$)', fontsize=28.)
handles = [Line2D([0], [0])]
ax[0].legend(handles, [r'$N = 1$'], handlelength=0, fontsize=24.)
ax[0].set_ylabel('$\chi_{p}$', fontsize=28.)
ax[0].tick_params(labelsize=24)

#ax[1].set_xlabel('$m_{1}$ ($M_\odot$)', fontsize=28.)
ax[1].legend(handles, [r'$N = 2$'], handlelength=0, fontsize=24.)
ax[1].set_ylabel('$\chi_{p}$', fontsize=28.)
ax[1].tick_params(labelsize=24)

#ax[2].set_xlabel('$m_{1}$ ($M_\odot$)', fontsize=28.)
ax[2].legend(handles, [r'$N = 3$'], handlelength=0, fontsize=24.)
ax[2].set_ylabel('$\chi_{p}$', fontsize=28.)
ax[2].tick_params(labelsize=24)

#ax[3].set_xlabel('$m_{1}$ ($M_\odot$)', fontsize=28.)
ax[3].legend(handles, [r'$N = 4$'], handlelength=0, fontsize=24.)
ax[3].set_ylabel('$\chi_{p}$', fontsize=28.)
ax[3].tick_params(labelsize=24)

ax[4].set_xlabel('$m_{1}$ ($M_\odot$)', fontsize=28.)
ax[4].legend(handles, [r'$N = 5$'], handlelength=0, fontsize=24.)
ax[4].set_ylabel('$\chi_{p}$', fontsize=28.)
ax[4].tick_params(labelsize=24)

cax = plt.axes([0.94, 0.125, 0.025, 0.7525])
cbar = fig.colorbar(mesh1, cax=cax, ticks=np.arange(0, 1.1, 0.1))
#cbar.set_label('$m_{eff}$', labelpad=1., fontsize=24.)
cbar.set_label('Fraction with match above 0.97', labelpad=10., fontsize=28.)
cbar.ax.tick_params(labelsize=24)

#cbar1 = fig.colorbar(mesh1, ax=ax[0])
#cbar2 = fig.colorbar(mesh2, ax=ax[1])
#cbar3 = fig.colorbar(mesh3, ax=ax[2])

#cbar1.set_label('$m_{eff}$', labelpad=1., fontsize=24.)
#cbar2.set_label('$m_{eff}$', labelpad=1., fontsize=24.)
#cbar3.set_label('$m_{eff}$', labelpad=1., fontsize=24.)

#cbar1.ax.tick_params(labelsize=16)
#cbar2.ax.tick_params(labelsize=16)
#cbar3.ax.tick_params(labelsize=16)

plt.subplots_adjust(hspace=.15)
fig.savefig('fraction_match.pdf', bbox_inches='tight')
images/1ac7ef697d7577547804e9e8effed80cbc89380a445648c429eaeaa6ac822999.png

Figure 6#

with h5py.File(precession_bank, 'r') as f:
    num_comps = f['num_comps'][:]
    mass1 = f['mass1'][:]
    mass2 = f['mass2'][:]
    mass = mass1 + mass2
    a1 = 2 + (3 * mass2) / (2 * mass1)
    a2 = 2 + (3 * mass1) / (2 * mass2)
    s1p = (f['spin1x'][:] ** 2. + f['spin1y'][:] ** 2.) ** 0.5
    s2p = (f['spin2x'][:] ** 2. + f['spin2y'][:] ** 2.) ** 0.5
    chip = 1 / (a1 * mass1 ** 2.) * np.maximum(a1 * s1p * mass1 ** 2., a2 * s2p * mass2 ** 2.)
    print(chip.min(), chip.max())
0.00017546969 0.9898638
labels = [r'$N = 1$', r'$N = 2$', r'$N = 3$', r'$N = 4$', r'$N = 5$']
values = [
    np.sum(num_comps == 1), np.sum(num_comps == 2), np.sum(num_comps == 3), np.sum(num_comps == 4),
    np.sum(num_comps >= 5)
]

n = 5
mass_bins = np.linspace(11.2, 21.7, num=n+1, endpoint=True)
chip_bins = np.linspace(0., 1., num=n+1, endpoint=True)

mass_mesh = np.zeros((n, n), dtype=np.float32)
chip_mesh = np.zeros((n, n), dtype=np.float32)

average_n = np.zeros((n, n), dtype=np.float32)

for i in range(len(mass_bins) - 1):
    mlow = mass_bins[i]
    mhigh = mass_bins[i+1]
    for j in range(len(chip_bins) - 1):
        clow = chip_bins[j]
        chigh = chip_bins[j+1]
    
        lgc = (mass > mlow) & (mass <= mhigh) & (chip > clow) & (chip <= chigh)
        average_n[j, i] = np.mean(num_comps[lgc])
            
        mass_mesh[j, i] = (mlow + mhigh) / 2.
        chip_mesh[j, i] = (clow + chigh) / 2.
# These files are available from our data release page
with h5py.File(f"{aligned_dir}/ALIGNED_BANKSIM.h5", 'r') as f:
    aligned_match = f['/trig_params/match'][:]
    
with h5py.File(f"{precession_dir}/PRECESSING_BANKSIM.h5", 'r') as f:
    prec_match_comp1 = f['/trig_params_1/match'][:]
    prec_match_comp2 = f['/trig_params_2/match'][:]
    prec_match_comp3 = f['/trig_params_3/match'][:]
    prec_match_comp4 = f['/trig_params_4/match'][:]
    prec_match_comp5 = f['/trig_params_5/match'][:]
    prec_match_vary2 = f['/trig_params_vary_max2/match'][:]
    prec_match_vary3 = f['/trig_params_vary_max3/match'][:]
    prec_match_vary4 = f['/trig_params_vary_max4/match'][:]
    prec_match_vary5 = f['/trig_params_vary/match'][:]
    
bins = np.linspace(aligned_match.min(), 1., num=10000, endpoint=True)
hista, _ = np.histogram(aligned_match, bins=bins, density=True)
hist1, _ = np.histogram(prec_match_comp1, bins=bins, density=True)
hist2v, _ = np.histogram(prec_match_vary2, bins=bins, density=True)
hist2, _ = np.histogram(prec_match_comp2, bins=bins, density=True)
hist3v, _ = np.histogram(prec_match_vary3, bins=bins, density=True)
hist3, _ = np.histogram(prec_match_comp3, bins=bins, density=True)
hist4v, _ = np.histogram(prec_match_vary4, bins=bins, density=True)
hist4, _ = np.histogram(prec_match_comp4, bins=bins, density=True)
hist5v, _ = np.histogram(prec_match_vary5, bins=bins, density=True)
hist5, _ = np.histogram(prec_match_comp5, bins=bins, density=True)
gs = GridSpec(nrows=2, ncols=5, width_ratios=[1, 14, 2, 10, 3])
fig = plt.figure(figsize=(14, 8))
axs = [fig.add_subplot(gs[0,1]), fig.add_subplot(gs[0,3]), fig.add_subplot(gs[1, :])]

axs[0].pie(values, labels=labels, autopct='%1.1f%%', colors=harmonic_colors[:5],
          textprops={'fontsize': 14.})

mesh = axs[1].pcolormesh(mass_mesh, chip_mesh, average_n, vmin=1, vmax=5, cmap='viridis')

axs[1].set_xlabel('$M$ ($M_\odot$)', fontsize=14.)
axs[1].set_ylabel('$\chi_{p}$', fontsize=14.)
axs[1].tick_params(labelsize=12)
cbar1 = fig.colorbar(mesh, ax=axs[1], location='top')
cbar1.set_label('Average number of harmonics used', labelpad=10., fontsize=14.)
cbar1.ax.tick_params(labelsize=12)

axs[2].plot(bins[1:], hista.cumsum() * (bins[1] - bins[0]), c='k', alpha=0.9, label='Aligned')
axs[2].plot(bins[1:], hist1.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[0], linestyle='--', alpha=0.9, label=r'$N = 1$')
axs[2].plot(bins[1:], hist2v.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[1], alpha=0.9, label=r'$N_{\mathrm{max}} = 2$')
axs[2].plot(bins[1:], hist2.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[1], linestyle='--', alpha=0.9, label=r'$N = 2$')
axs[2].plot(bins[1:], hist3v.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[2], alpha=0.9, label=r'$N_{\mathrm{max}} = 3$')
axs[2].plot(bins[1:], hist3.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[2], linestyle='--', alpha=0.9, label=r'$N = 3$')
axs[2].plot(bins[1:], hist4v.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[3], alpha=0.9, label=r'$N_{\mathrm{max}} = 4$')
axs[2].plot(bins[1:], hist4.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[3], linestyle='--', alpha=0.9, label=r'$N = 4$')
axs[2].plot(bins[1:], hist5v.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[4], alpha=0.9, label=r'$N_{\mathrm{max}} = 5$')
axs[2].plot(bins[1:], hist5.cumsum() * (bins[1] - bins[0]), c=harmonic_colors[4], linestyle='--', alpha=0.9, label=r'$N = 5$')

axs[2].set_yscale('log')
axs[2].set_xlim((0.44, 1.))
axs[2].set_ylim((1e-3, 1.1))
axs[2].legend(loc='upper left', fontsize=14., ncol=2)
axs[2].set_xlabel('Fitting Factor', fontsize=14.)
axs[2].set_ylabel('Fraction of Samples', fontsize=14.)
axs[2].tick_params(labelsize=12)
axs[2].grid(alpha=0.3)

plt.subplots_adjust(wspace=0.2, hspace=0.2)
fig.savefig('fitting_factor.pdf', bbox_inches='tight')
images/aa7738be0267f50ca72a50b20e878834f46793fd514a1a9a5eeb129f230aef26.png

Figure 7#

def get_snr_chi(search_dir, ifo='H1'):
    # These are the large files that are hard to host
    _filename = ifo + "-HDF_TRIGGER_MERGE_FULL_DATA-1242484974-676544.hdf"
    sngl_file = search_dir + "/" + _filename
    coinc_file = search_dir + "/H1L1-COMBINE_STATMAP_FULL_DATA-1242484974-676544.hdf"
    _filename = ifo + "-HDF_TRIGGER_MERGE_NSBH_FULL_INJECTIONS-1242484974-676544.hdf"
    inj_file = search_dir + "/" + _filename
    injfind_file = search_dir + "/H1L1-HDFINJFIND_NSBH_FULL_INJECTIONS-1242484974-676544.hdf"
       
    with h5py.File(coinc_file, 'r') as f:
        bid = f[f'background_exc/{ifo}/trigger_id'][:]
        
    with h5py.File(sngl_file, 'r') as f:
        bsnr = f[f'{ifo}/snr'][:][bid]
        bchisq = f[f'{ifo}/chisq'][:][bid]
        dof = f[f'{ifo}/chisq_dof'][:][bid]
        bchisq /= (dof * 2 - 2)

    lgc = bchisq > 0.
    bsnr = bsnr[lgc]
    bchisq = bchisq[lgc]
    
    with h5py.File(injfind_file, 'r') as f:
        tid = f[f'found_after_vetoes/{ifo}/trigger_id'][:]
        inj_idx = f['found_after_vetoes/injection_index'][:]

        dist = f['injections/distance'][:][inj_idx]
        mass1 = f['injections/mass1'][:][inj_idx]
        mass2 = f['injections/mass2'][:][inj_idx]
        mass = mass1 + mass2
        a1 = 2 + (3 * mass2) / (2 * mass1)
        a2 = 2 + (3 * mass1) / (2 * mass2)
        s1p = (f['injections/spin1x'][:][inj_idx] ** 2. + f['injections/spin1y'][:][inj_idx] ** 2.) ** 0.5
        s2p = (f['injections/spin2x'][:][inj_idx] ** 2. + f['injections/spin2y'][:][inj_idx] ** 2.) ** 0.5
        chip = 1 / (a1 * mass1 ** 2.) * np.maximum(a1 * s1p * mass1 ** 2., a2 * s2p * mass2 ** 2.)

    with h5py.File(inj_file, 'r') as f:
        isnr = f[f'{ifo}/snr'][:][tid]
        ichisq = f[f'{ifo}/chisq'][:][tid]
        dof = f[f'{ifo}/chisq_dof'][:][tid]
        ichisq /= (dof * 2 - 2)

    lgc = ichisq > 0.
    isnr = isnr[lgc]
    ichisq = ichisq[lgc]

    return bsnr, bchisq, isnr, ichisq, chip
bsnr1, bchisq1, isnr1, ichisq1, chip1 = get_snr_chi(aligned_search, ifo='H1')
bsnr2, bchisq2, isnr2, ichisq2, chip2 = get_snr_chi(comp2_search, ifo='H1')
bsnr3, bchisq3, isnr3, ichisq3, chip3 = get_snr_chi(comp3_search, ifo='H1')
fig = plt.figure(figsize=(9, 16))
gs = GridSpec(nrows=3, ncols=2, width_ratios=[25, 1])
ax1 = fig.add_subplot(gs[0,0])
ax2 = fig.add_subplot(gs[1,0], sharex=ax1)
ax3 = fig.add_subplot(gs[2,0], sharex=ax2)
ax4 = fig.add_subplot(gs[:,1])
ax = [ax1, ax2, ax3, ax4]

chi_cont = np.logspace(-2, 4, 300)
for c in [5, 7, 9, 11, 13]:
    chisq_mod = (chi_cont ** 3. + 1.) / 2.
    chisq_mod = np.maximum(chisq_mod, np.ones_like(chisq_mod))
    snr_cont = c * (chisq_mod ** (1. / 6.))
    ax[0].plot(snr_cont, chi_cont, color='black', alpha=0.5, linestyle='--', linewidth=2.)
    ax[1].plot(snr_cont, chi_cont, color='black', alpha=0.5, linestyle='--', linewidth=2.)
    ax[2].plot(snr_cont, chi_cont, color='black', alpha=0.5, linestyle='--', linewidth=2.)
    
#sc1 = ax[0].scatter(bsnr1, bchisq1, c='black', s=20., marker='o', label='Noise', rasterized=True)
sc1 = ax[0].scatter(isnr1, ichisq1, s=50., marker='^', label='Signals', c=chip1, vmin=0., vmax=1., rasterized=True)
handles = [Line2D([0], [0])]
ax[0].legend(handles, ['Aligned'], handlelength=0, fontsize=20.)

#sc2 = ax[1].scatter(bsnr2, bchisq2, c='black', s=20., marker='o', label='Noise', rasterized=True)
sc2 = ax[1].scatter(isnr2, ichisq2, s=50., marker='^', label='Signals', c=chip2, vmin=0., vmax=1., rasterized=True)
ax[1].legend(handles, [r'$N_{\mathrm{max}} = 2$'], handlelength=0, fontsize=20.)

#sc3 = ax[2].scatter(bsnr3, bchisq3, c='black', s=20., marker='o', label='Noise', rasterized=True)
sc3 = ax[2].scatter(isnr3, ichisq3, s=50., marker='^', label='Signals', c=chip3, vmin=0., vmax=1., rasterized=True)
ax[2].legend(handles, [r'$N_{\mathrm{max}} = 3$'], handlelength=0, fontsize=20.)

ax[0].set_xscale('log')
ax[0].set_yscale('log')
ax[1].set_xscale('log')
ax[1].set_yscale('log')
ax[2].set_xscale('log')
ax[2].set_yscale('log')


ax[0].set_ylim((0.2, 1000.))
ax[1].set_ylim((0.2, 1000.))
ax[2].set_ylim((0.2, 1000.))
ax[2].set_xlim((5., 200.))

ax[0].set_ylabel('Reduced $\chi^2$', fontsize=24.)
ax[0].tick_params(labelsize=20)
ax[0].grid(alpha=0.3)
ax[1].set_ylabel('Reduced $\chi^2$', fontsize=24.)
ax[1].tick_params(labelsize=20)
ax[1].grid(alpha=0.3)
ax[2].set_xlabel('SNR', fontsize=24.)
ax[2].set_ylabel('Reduced $\chi^2$', fontsize=24.)
ax[2].tick_params(labelsize=20)
ax[2].grid(alpha=0.3)

cbar = fig.colorbar(sc1, cax=ax[-1])
cbar.set_label('$\chi_{p}$', labelpad=2., fontsize=24.)
cbar.ax.tick_params(labelsize=20)
plt.subplots_adjust(hspace=0.15, wspace=0.15)
plt.savefig('harmonic_snr_chi.pdf', bbox_inches='tight', dpi=150)
images/086da89c05ca7963a105b45b6be1b3c632c189dc78cbe985d74022338a461981.png

Figure 8#

# Also large files that are hard to host
bank_file = aligned_search + "/H1L1-BANK2HDF-1242484974-676544.hdf"
veto_file = aligned_search + "/H1L1-FOREGROUND_CENSOR-1242484974-676544.xml"
trig_file = aligned_search + "/L1-HDF_TRIGGER_MERGE_FULL_DATA-1242484974-676544.hdf"

trigs = SingleDetTriggers(trig_file, bank_file, veto_file, 'closed_box', 'self.snr > 6.', 'L1')
coinc_snr = getattr(trigs, 'snr')
coinc_new = getattr(trigs, 'newsnr_sgveto')
bank_file = comp2_search + "/H1L1-BANK2HDF-1242484974-676544.hdf"
veto_file = comp2_search + "/H1L1-FOREGROUND_CENSOR-1242484974-676544.xml"
trig_file = comp2_search + "/L1-HDF_TRIGGER_MERGE_FULL_DATA-1242484974-676544.hdf"

trigs = SingleDetTriggers(trig_file, bank_file, veto_file, 'closed_box', 'self.snr > 6.', 'L1')
prec2_snr = getattr(trigs, 'snr')
prec2_new = getattr(trigs, 'newsnr_sgveto')
bank_file = comp3_search + "/H1L1-BANK2HDF-1242484974-676544.hdf"
veto_file = comp3_search + "/H1L1-FOREGROUND_CENSOR-1242484974-676544.xml"
trig_file = comp3_search + "/L1-HDF_TRIGGER_MERGE_FULL_DATA-1242484974-676544.hdf"

trigs = SingleDetTriggers(trig_file, bank_file, veto_file, 'closed_box', 'self.snr > 6.', 'L1')
prec3_snr = getattr(trigs, 'snr')
prec3_new = getattr(trigs, 'newsnr_sgveto')
fig, ax = plt.subplots(figsize=(12, 8))
bins = np.linspace(0, 200, 10000, endpoint=True)

coinc_snr_hist, _ = np.histogram(coinc_snr, bins=bins)
coinc_new_hist, _ = np.histogram(coinc_new, bins=bins)
    
coinc_snr_cumnum = np.cumsum(coinc_snr_hist[::-1])[::-1]
coinc_new_cumnum = np.cumsum(coinc_new_hist[::-1])[::-1]

prec2_snr_hist, _ = np.histogram(prec2_snr, bins=bins)
prec2_new_hist, _ = np.histogram(prec2_new, bins=bins)
    
prec2_snr_cumnum = np.cumsum(prec2_snr_hist[::-1])[::-1]
prec2_new_cumnum = np.cumsum(prec2_new_hist[::-1])[::-1]

prec3_snr_hist, _ = np.histogram(prec3_snr, bins=bins)
prec3_new_hist, _ = np.histogram(prec3_new, bins=bins)
    
prec3_snr_cumnum = np.cumsum(prec3_snr_hist[::-1])[::-1]
prec3_new_cumnum = np.cumsum(prec3_new_hist[::-1])[::-1]

xs = np.linspace(6, 20, 1000)
chi2 = 1. - scipy.special.gammainc(1, xs ** 2. / 2.)
chi6 = 1. - scipy.special.gammainc(3, xs ** 2. / 2.)

coinc_count = np.sum(coinc_new > 6.)
prec2_count = np.sum(prec2_new > 6.)
prec3_count = np.sum(prec3_new > 6.)

chi2_norm = coinc_count / (1. - scipy.special.gammainc(1, 6. ** 2. / 2.))
chi4_norm = prec2_count / (1. - scipy.special.gammainc(2, 6. ** 2. / 2.))
chi6_norm = prec3_count / (1. - scipy.special.gammainc(3, 6. ** 2. / 2.))

ax.loglog(bins[:-1], coinc_new_cumnum, alpha=0.9, label='Aligned',
          linewidth=2., color='k')
ax.loglog(bins[:-1], prec2_new_cumnum, alpha=0.9, label=r'$N_{\mathrm{max}} = 2$',
          linewidth=2., color=harmonic_colors[1])
#ax.loglog(bins[:-1], prec3_new_cumnum, alpha=0.9, label='$N_{\mathrm{max}} = 3$',
#          linewidth=2., color=harmonic_colors[2])

ax.set_ylabel('Number of Triggers Above', fontsize=24.)
ax.set_xlabel('Re-weighted SNR', fontsize=24.)
ax.set_ylim(ymin=1, ymax=2e8)
ax.set_xlim(xmin=6., xmax=11.)
ax.grid()
ax.legend(loc='upper right', fontsize=24)
ax.tick_params(which='both', labelsize=16)

plt.savefig('harmonic_hist.pdf', bbox_inches='tight')
images/0822205e8c63bca5b72a98fbd692365033a2822298303722a8ebef590de44c08.png

Figure 9#

# These files are all available on our data release pages
files = {
    'aligned_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_ALIGNED_SNR_INJECTIONS-1242484974-676544.hdf',
    'aligned_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_ALIGNED_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
    'prec_c2_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_PREC2COMP_SNR_INJECTIONS-1242484974-676544.hdf',
    'prec_c2_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_PREC2COMP_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
    'prec_c3_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_PREC3COMP_SNR_INJECTIONS-1242484974-676544.hdf',
    'prec_c3_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_FULL_PREC3COMP_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
}

vols = {}
errs = {}

thresh = 100.
nmass = 3
nchip = 3

mass_bins = np.linspace(11.2, 21.7, num=nmass+1, endpoint=True)
chip_bins = np.linspace(0., 1., num=nchip+1, endpoint=True)

mass_mesh = np.zeros((nchip, nmass), dtype=np.float32)
chip_mesh = np.zeros((nchip, nmass), dtype=np.float32)

for k, v in files.items():
    
    with h5py.File(v, 'r') as f:

        found = f['found_after_vetoes/injection_index'][:]
        missed = f['missed/after_vetoes'][:]

        mass1 = f['injections/mass1'][:]
        mass2 = f['injections/mass2'][:]
        mass = mass1 + mass2
        a1 = 2 + (3 * mass2) / (2 * mass1)
        a2 = 2 + (3 * mass1) / (2 * mass2)
        s1p = (f['injections/spin1x'][:] ** 2. + f['injections/spin1y'][:] ** 2.) ** 0.5
        s2p = (f['injections/spin2x'][:] ** 2. + f['injections/spin2y'][:] ** 2.) ** 0.5
        chip = 1 / (a1 * mass1 ** 2.) * np.maximum(a1 * s1p * mass1 ** 2., a2 * s2p * mass2 ** 2.)
        print(chip.min(), chip.max())
        
        dist = f['injections/distance'][:]
        print(f"distance: {dist.min():.2f} -> {dist.max():.2f}")

        ifar_exc = f['found_after_vetoes/ifar_exc'][:]
        t = f.attrs['foreground_time_exc'] / lal_YRJUL_SI

    quiet = found[ifar_exc < thresh]
    missed = np.concatenate([missed, quiet])
    found = found[ifar_exc >= thresh]

    missed_dist = dist[missed]
    found_dist = dist[found]

    missed_mass = mass[missed]
    found_mass = mass[found]
    
    missed_chip = chip[missed]
    found_chip = chip[found]

    vol = np.zeros((nchip, nmass), dtype=np.float32)
    err = np.zeros((nchip, nmass), dtype=np.float32)

    for i in range(len(mass_bins) - 1):
        mlow = mass_bins[i]
        mhigh = mass_bins[i+1]
        for j in range(len(chip_bins) - 1):
            clow = chip_bins[j]
            chigh = chip_bins[j+1]
    
            missed_lgc = (missed_mass > mlow) & (missed_mass <= mhigh) & (missed_chip > clow) & (missed_chip <= chigh)
            found_lgc = (found_mass > mlow) & (found_mass <= mhigh) & (found_chip > clow) & (found_chip <= chigh)
    
            missed_dist_bin = missed_dist[missed_lgc]
            found_dist_bin = found_dist[found_lgc]
    
            v, e = volume_binned_pylal(found_dist_bin, missed_dist_bin, bins=50)
            vol[j, i] = v
            err[j, i] = e
            
            mass_mesh[j, i] = (mlow + mhigh) / 2.
            chip_mesh[j, i] = (clow + chigh) / 2.
    
    vols[k] = vol
    errs[k] = err
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
0.0007725729171931529 0.9894160260630762
distance: 1.68 -> 285.05
vmax = 150
vmin = -vmax

vmax_reduced = 30
vmin_reduced = -vmax_reduced
fig = plt.figure(figsize=(12., 16.))
gs = fig.add_gridspec(2, 1, width_ratios=[1.], height_ratios=[1., 1.],
                      left=0.1, right=0.9, bottom=0.1, top=0.9,
                      wspace=0.32, hspace=0.3)

gst = gs[0].subgridspec(1, 2, width_ratios=[0.5, 0.5], height_ratios=[1.],
                        wspace=0.32, hspace=0.)
gsb = gs[1].subgridspec(1, 3, width_ratios=[0.25, 0.5, 0.25], height_ratios=[1.],
                        wspace=0.32, hspace=0.)

ax1 = fig.add_subplot(gst[0])
ax2 = fig.add_subplot(gst[1])
ax3 = fig.add_subplot(gsb[1])

vt_ratio = vols['prec_c2_coinc'] / vols['aligned_coinc']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax={vaxm} when max change is {maxr}")

mesh1 = ax1.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')

vt_ratio = vols['prec_c3_coinc'] / vols['aligned_coinc']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax={vmax} when max change is {maxr}")
mesh2 = ax2.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')

vt_ratio = vols['prec_c3_coinc'] / vols['prec_c2_coinc']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax_reduced:
    print(f"Warning: using vmax={vmax_reduced} when max change is {maxr}")
mesh3 = ax3.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin_reduced, vmax=vmax_reduced, cmap='RdBu')

ax1.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax1.set_ylabel('$\chi_{p}$', fontsize=28)
ax1.tick_params(labelsize=24)
cbar1 = fig.colorbar(mesh1, ax=ax1, location='top')
cbar1.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 2$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar1.ax.tick_params(labelsize=24)

ax2.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax2.set_ylabel('$\chi_{p}$', fontsize=28)
ax2.tick_params(labelsize=24)
cbar2 = fig.colorbar(mesh2, ax=ax2, location='top')
cbar2.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar2.ax.tick_params(labelsize=24)

ax3.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax3.set_ylabel('$\chi_{p}$', fontsize=28)
ax3.tick_params(labelsize=24)
cbar3 = fig.colorbar(mesh3, ax=ax3, location='top')
cbar3.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   $N_{\mathrm{max}} = 2$ (%)', labelpad=10., fontsize=22.5)
cbar3.ax.tick_params(labelsize=24)

fig.savefig('coinc_vt_diff.pdf', bbox_inches='tight')
[[1.0994637 1.0372905 1.0788873]
 [1.3876009 1.4066858 1.7780114]
 [1.7622256 1.781097  2.1903028]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
[[0.86619973 0.8646784  0.84913325]
 [1.062639   1.1656826  1.4719933 ]
 [1.5758059  1.4171075  1.6252705 ]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
[[0.7878384  0.83359337 0.7870454 ]
 [0.7658103  0.82867295 0.8278875 ]
 [0.89421344 0.7956375  0.74203   ]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
images/e820fd1384d2561311c0e1b69e14620c6d7654dacee8b3b457db63262abdb236.png

Figure 10#

fig = plt.figure(figsize=(12., 16.))
gs = fig.add_gridspec(2, 1, width_ratios=[1.], height_ratios=[1., 1.],
                      left=0.1, right=0.9, bottom=0.1, top=0.9,
                      wspace=0.32, hspace=0.3)

gst = gs[0].subgridspec(1, 2, width_ratios=[0.5, 0.5], height_ratios=[1.],
                        wspace=0.32, hspace=0.)
gsb = gs[1].subgridspec(1, 3, width_ratios=[0.25, 0.5, 0.25], height_ratios=[1.],
                        wspace=0.32, hspace=0.)

ax1 = fig.add_subplot(gst[0])
ax2 = fig.add_subplot(gst[1])
ax3 = fig.add_subplot(gsb[1])

vt_ratio = vols['prec_c2_phase'] / vols['aligned_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax=100 when max change is {maxr}")

mesh1 = ax1.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')
print(np.mean(schange))

vt_ratio = vols['prec_c3_phase'] / vols['aligned_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax=100 when max change is {maxr}")
mesh2 = ax2.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')
print(np.mean(schange))

vt_ratio = vols['prec_c3_phase'] / vols['prec_c2_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax_reduced:
    print(f"Warning: using vmax={vmax_reduced} when max change is {maxr}")
mesh3 = ax3.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin_reduced, vmax=vmax_reduced, cmap='RdBu')
print(np.mean(schange))

ax1.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax1.set_ylabel('$\chi_{p}$', fontsize=28)
ax1.tick_params(labelsize=24)
cbar1 = fig.colorbar(mesh1, ax=ax1, location='top')
cbar1.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 2$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar1.ax.tick_params(labelsize=24)

ax2.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax2.set_ylabel('$\chi_{p}$', fontsize=28)
ax2.tick_params(labelsize=24)
cbar2 = fig.colorbar(mesh2, ax=ax2, location='top')
cbar2.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar2.ax.tick_params(labelsize=24)

ax3.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax3.set_ylabel('$\chi_{p}$', fontsize=28)
ax3.tick_params(labelsize=24)
cbar3 = fig.colorbar(mesh3, ax=ax3, location='top')
cbar3.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   $N_{\mathrm{max}} = 2$ (%)', labelpad=10., fontsize=22.5)
cbar3.ax.tick_params(labelsize=24)

fig.savefig('phase_vt_diff.pdf', bbox_inches='tight')
[[1.1366459 1.0620338 1.0499184]
 [1.495719  1.4322251 1.8300629]
 [1.7362732 1.7812232 2.188604 ]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
52.36339
[[1.1261916 1.0830809 1.0017176]
 [1.4782109 1.528371  1.9514999]
 [1.9784212 2.0206559 2.4258335]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
62.15536
[[0.9908024  1.0198178  0.9540909 ]
 [0.98829454 1.0671304  1.0663568 ]
 [1.1394643  1.1344203  1.108393  ]] [[0.16666667 0.16666667 0.16666667]
 [0.5        0.5        0.5       ]
 [0.8333333  0.8333333  0.8333333 ]]
5.2085595
images/4c0debd8533e0e71fe8d4b7b18a023b8d7cb0e4f312cd28e6032a990ab2f5a79.png

Figure 11#

# These files are available on our data release pages
files = {
    'aligned_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_ALIGNED_SNR_INJECTIONS-1242484974-676544.hdf',
    'aligned_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_ALIGNED_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
    'prec_c2_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_PREC2COMP_SNR_INJECTIONS-1242484974-676544.hdf',
    'prec_c2_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_PREC2COMP_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
    'prec_c3_coinc': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_PREC3COMP_SNR_INJECTIONS-1242484974-676544.hdf',
    'prec_c3_phase': '/home/ian.harry/FILES_FOR_PAPER/H1L1-HDFINJFIND_NSBH_HIGHBOTH_PREC3COMP_RANKSTAT_INJECTIONS-1242484974-676544.hdf',
}

vols = {}
errs = {}

thresh = 100.
nmass = 3
nchip = 3

mass_bins = np.linspace(11.2, 21.7, num=nmass+1, endpoint=True)
chip_bins = np.linspace(0.6, 1., num=nchip+1, endpoint=True)

mass_mesh = np.zeros((nchip, nmass), dtype=np.float32)
chip_mesh = np.zeros((nchip, nmass), dtype=np.float32)

for k, v in files.items():
    
    with h5py.File(v, 'r') as f:

        found = f['found_after_vetoes/injection_index'][:]
        missed = f['missed/after_vetoes'][:]

        mass1 = f['injections/mass1'][:]
        mass2 = f['injections/mass2'][:]
        mass = mass1 + mass2
        a1 = 2 + (3 * mass2) / (2 * mass1)
        a2 = 2 + (3 * mass1) / (2 * mass2)
        s1p = (f['injections/spin1x'][:] ** 2. + f['injections/spin1y'][:] ** 2.) ** 0.5
        s2p = (f['injections/spin2x'][:] ** 2. + f['injections/spin2y'][:] ** 2.) ** 0.5
        chip = 1 / (a1 * mass1 ** 2.) * np.maximum(a1 * s1p * mass1 ** 2., a2 * s2p * mass2 ** 2.)
        print(chip.min(), chip.max())
        
        dist = f['injections/distance'][:]
        print(f"distance: {dist.min():.2f} -> {dist.max():.2f}")

        ifar_exc = f['found_after_vetoes/ifar_exc'][:]
        t = f.attrs['foreground_time_exc'] / lal_YRJUL_SI

    quiet = found[ifar_exc < thresh]
    missed = np.concatenate([missed, quiet])
    found = found[ifar_exc >= thresh]

    missed_dist = dist[missed]
    found_dist = dist[found]

    missed_mass = mass[missed]
    found_mass = mass[found]
    
    missed_chip = chip[missed]
    found_chip = chip[found]

    vol = np.zeros((nchip, nmass), dtype=np.float32)
    err = np.zeros((nchip, nmass), dtype=np.float32)

    for i in range(len(mass_bins) - 1):
        mlow = mass_bins[i]
        mhigh = mass_bins[i+1]
        for j in range(len(chip_bins) - 1):
            clow = chip_bins[j]
            chigh = chip_bins[j+1]
            missed_lgc = (missed_mass > mlow) & (missed_mass <= mhigh) & (missed_chip > clow) & (missed_chip <= chigh)
            found_lgc = (found_mass > mlow) & (found_mass <= mhigh) & (found_chip > clow) & (found_chip <= chigh)
    
            missed_dist_bin = missed_dist[missed_lgc]
            found_dist_bin = found_dist[found_lgc]

            v, e = volume_binned_pylal(found_dist_bin, missed_dist_bin, bins=50)
            vol[j, i] = v
            err[j, i] = e
            
            mass_mesh[j, i] = (mlow + mhigh) / 2.
            chip_mesh[j, i] = (clow + chigh) / 2.
    
    vols[k] = vol
    errs[k] = err
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
0.6692015806785456 0.9897126824295699
distance: 1.68 -> 290.47
fig = plt.figure(figsize=(12., 16.))
gs = fig.add_gridspec(2, 1, width_ratios=[1.], height_ratios=[1., 1.],
                      left=0.1, right=0.9, bottom=0.1, top=0.9,
                      wspace=0.32, hspace=0.3)

gst = gs[0].subgridspec(1, 2, width_ratios=[0.5, 0.5], height_ratios=[1.],
                        wspace=0.32, hspace=0.)
gsb = gs[1].subgridspec(1, 3, width_ratios=[0.25, 0.5, 0.25], height_ratios=[1.],
                        wspace=0.32, hspace=0.)

ax1 = fig.add_subplot(gst[0])
ax2 = fig.add_subplot(gst[1])
ax3 = fig.add_subplot(gsb[1])

vt_ratio = vols['prec_c2_phase'] / vols['aligned_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax={vaxm} when max change is {maxr}")
mesh1 = ax1.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')
print(np.min(chip_mesh), np.mean(schange))

vt_ratio = vols['prec_c3_phase'] / vols['aligned_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax:
    print(f"Warning: using vmax={vaxm} when max change is {maxr}")
mesh2 = ax2.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin, vmax=vmax, cmap='RdBu')
print(np.min(chip_mesh), np.mean(schange))

vt_ratio = vols['prec_c3_phase'] / vols['prec_c2_phase']
print(vt_ratio, chip_mesh)
schange = (vt_ratio - 1) * 100
maxr = np.abs(schange).max()
if maxr > vmax_reduced:
    print(f"Warning: using vmax={vmax_reduced} when max change is {maxr}")
mesh3 = ax3.pcolormesh(mass_mesh, chip_mesh, schange, vmin=vmin_reduced, vmax=vmax_reduced, cmap='RdBu')
print(np.min(chip_mesh), np.mean(schange))

ax1.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax1.set_ylabel('$\chi_{p}$', fontsize=28)
ax1.tick_params(labelsize=24)
cbar1 = fig.colorbar(mesh1, ax=ax1, location='top')
cbar1.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 2$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar1.ax.tick_params(labelsize=24)

ax2.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax2.set_ylabel('$\chi_{p}$', fontsize=28)
ax2.tick_params(labelsize=24)
cbar2 = fig.colorbar(mesh2, ax=ax2, location='top')
cbar2.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   Aligned (%)', labelpad=10., fontsize=22.5)
cbar2.ax.tick_params(labelsize=24)

ax3.set_xlabel('$M$ ($M_\odot$)', fontsize=28)
ax3.set_ylabel('$\chi_{p}$', fontsize=28)
ax3.tick_params(labelsize=24)
cbar3 = fig.colorbar(mesh3, ax=ax3, location='top')
cbar3.set_label('Change in sensitive volume\n$N_{\mathrm{max}} = 3$   vs   $N_{\mathrm{max}} = 2$ (%)', labelpad=10., fontsize=22.5)
cbar3.ax.tick_params(labelsize=24)

fig.savefig('phase_vt_diff_high_both.pdf', bbox_inches='tight')
[[1.5403168 1.8804876 1.6065083]
 [1.703347  2.1958764 1.9867226]
 [1.7654036 1.8593735 1.9047189]] [[0.6666667  0.6666667  0.6666667 ]
 [0.8        0.8        0.8       ]
 [0.93333334 0.93333334 0.93333334]]
0.6666667 82.69727
[[1.6065733 1.9004574 1.7222289]
 [1.8403904 2.3051085 2.0355573]
 [1.7882128 1.9607774 2.1351497]] [[0.6666667  0.6666667  0.6666667 ]
 [0.8        0.8        0.8       ]
 [0.93333334 0.93333334 0.93333334]]
0.6666667 92.160614
[[1.0430149 1.0106195 1.0720323]
 [1.0804554 1.0497441 1.0245805]
 [1.0129201 1.0545367 1.120979 ]] [[0.6666667  0.6666667  0.6666667 ]
 [0.8        0.8        0.8       ]
 [0.93333334 0.93333334 0.93333334]]
0.6666667 5.209806
images/192f7f640e486f126ed274db472c752ad62b500ccee61327f2839cfe7b851668.png

Figure 12#

from pycbc.conversions import chi_p
params = {
    "mass1": 13.4239,
    "mass2": 1.31179,
    "spin1x": 0.000333624,
    "spin1y": -0.000180359,
    "spin1z": -0.483778,
    "spin2x": -0.0240528,
    "spin2y": -0.104305,
    "spin2z": -0.195768,
    "inclination": 2*np.pi - 4.79938,
    "coa_phase": 4.9661,
    "f_lower": 30.
}
print(
    f"chi_p = {chi_p(params['mass1'], params['mass2'], params['spin1x'], params['spin1y'], params['spin2x'], params['spin2y'])}"
)
chi_p = 0.008261840276878504
hp, hc = get_fd_waveform(
    approximant="IMRPhenomXP", **params, delta_f=1./256
)
xp = hp.to_timeseries()
xp = xp.cyclic_time_shift(-1.)

_params = params.copy()
_params["spin1x"] = 0.
_params["spin1y"] = 0.
_params["spin2x"] = 0.
_params["spin2y"] = 0.
_hp, _hc = get_fd_waveform(
    approximant="IMRPhenomXAS", **_params, delta_f=1./256
)
xas = _hp.to_timeseries()
xas = xas.cyclic_time_shift(-1.)
template = PhenomXPTemplateAlt(
    params["mass1"], params["mass2"], params["spin1x"], params["spin1y"], params["spin1z"],
    params["spin2x"], params["spin2y"], params["spin2z"], 1./2048, 20.
)
harmonics = template.compute_waveform_five_comps(xp.delta_f, 1024., interp=False)
eob, _ = get_td_waveform(
    approximant="SEOBNRv4P", **params, delta_t=1./2048
)
eobf = eob.to_frequencyseries()
fig = plt.figure(figsize=(8, 6))
gs = GridSpec(nrows=3, ncols=2, width_ratios=[4,1])
ax0 = fig.add_subplot(gs[0, 0])
ax1 = fig.add_subplot(gs[0, 1], sharey=ax0)
ax2 = fig.add_subplot(gs[1,:])
ax3 = fig.add_subplot(gs[2,:])
axs = [ax0, ax1, ax2, ax3]
axs[0].plot(eob.sample_times, eob, color='tab:orange', alpha=0.5, label="SEOBNRv4P")
axs[0].plot(xas.sample_times, xas, color='tab:red', alpha=0.8, linestyle="-", label="IMRPhenomXAS")
axs[0].plot(xp.sample_times, xp, color='grey', label="IMRPhenomXP")
axs[0].set_xlabel(r"Time [s]")
axs[0].set_ylabel(r"$h_{+}$(t)")
axs[0].grid(alpha=0.3)
axs[0].set_xlim([-4.5, -1.5])
axs[0].legend(loc="upper center", ncols=3)

axs[1].plot(eob.sample_times, eob, color='tab:orange', alpha=0.5)
axs[1].plot(xas.sample_times, xas, color='tab:red', alpha=0.8, linestyle="-")
axs[1].plot(xp.sample_times, xp, color='grey')
axs[1].grid(alpha=0.3)
axs[1].set_xlabel(r"Time [s]")
axs[1].set_yticklabels([])
axs[1].set_xticks([-0.04, 0.0])
axs[1].set_xlim([-0.05, 0.01])

#axs[2].plot(eobf.sample_frequencies, np.abs(eobf), color='tab:orange', alpha=0.5)
axs[2].plot(_hp.sample_frequencies, np.abs(_hp), color='tab:red', alpha=0.8, linestyle="-")
axs[2].plot(hp.sample_frequencies, np.abs(hp), color='grey')
axs[2].axvline(46.5, color='k', linestyle=":")
axs[2].set_xlabel(r"Frequency [Hz]")
axs[2].set_ylabel(r"$h_{+}$(f)")
axs[2].grid(alpha=0.3)
axs[2].set_xlim([30, 100])

for ii in range(3):
    h = harmonics[ii]
    axs[3].plot(h.sample_frequencies, np.abs(h), color=harmonic_colors[ii], label=f"Harmonic {ii + 1}", alpha=0.9)
axs[3].set_xlim([30, 100])
axs[3].set_xticks([30, 40, 50, 60, 70, 80, 90, 100])
axs[3].set_yscale("log")
_, yhigh = axs[2].get_ylim()
axs[3].set_ylim([1e-24, 1e-20])
axs[3].grid(alpha=0.3)
axs[3].legend()
axs[3].axvline(46.5, color='k', linestyle=":")
axs[3].set_ylabel(r"$h_{+}$(f)")
axs[3].set_xlabel(r"Frequency [Hz]")
plt.subplots_adjust(wspace=0.05, hspace=0.4)
plt.savefig("waveform_inconsistency.pdf")
images/97dddcf15718a0d429617f6d4162370fdba280faeb19dc5cd7c5fb08f9613417.png