It appears that the PhenomPv2 waveforms are flipped along the x-axis as compared to LALSim, and even PhenomD.
Since this model consists of only the dominant modes, this issue could be due to either a π/2 phase shift or a flip in sign, which will require further investigation.
Blue is the waveform computed with Ripple, and the orange dashed one is from LALSim.
On the right is the results from PhenomD, and PhenomPv2 is on the left.
Both columns were injected with the same parameters (non-precessing), and it is clear that the orange ones are consistent in both columns whereas the blue one is flipped.
The legend also shows the (frequency-domain) match; in the PhenomPv2 case, it is a -1, a perfect anti-phase.
I am also attaching the MWE to produce this result.
import jax
import jax.numpy as jnp
jax.config.update("jax_enable_x64", True)
from jimgw.single_event.utils import Mc_eta_to_m1_m2
from jimgw.single_event.waveform import RippleIMRPhenomPv2, RippleIMRPhenomD
import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import simpson
import lalsimulation as lalsim
from lalsimulation import IMRPhenomD, IMRPhenomPv2
from lal import MSUN_SI, PC_SI
MPC_SI = 1e6 * PC_SI
import matplotlib.pyplot as plt
def waveform_fd_to_td(waveform_fd):
'''
Convert frequency domain waveform to time domain
'''
return np.fft.irfft(waveform_fd) * sampling_frequency
freqs, raw_psd = np.loadtxt('test_jim_psd/GW150914_psd_H1.dat', unpack=True)
interp_psd = interp1d(freqs, raw_psd,
fill_value=(raw_psd[0], raw_psd[-1]),
bounds_error=False)(conjugate_frequencies)
def noise_weighted_inner_product(frequencies, waveform_1, waveform_2):
'''
Convert frequency domain waveform to time domain
'''
numerator = np.conj(waveform_1) * waveform_2
return 2 * simpson(
numerator / interp_psd,
x=frequencies,
)
def FD_match(frequencies, waveform_1, waveform_2):
norm1 = noise_weighted_inner_product(frequencies, waveform_1, waveform_1).real * 2
norm2 = noise_weighted_inner_product(frequencies, waveform_2, waveform_2).real * 2
inner_product = noise_weighted_inner_product(frequencies, waveform_1, waveform_2).real * 2
return inner_product / np.sqrt(norm1 * norm2)
# Prepare conjugate time and frequency arrays
sampling_frequency = 2048.
duration = 8.
delta_t = 1 / sampling_frequency
N_samples = int(jnp.round(duration * sampling_frequency))
N_freqs = int(jnp.round(N_samples / 2) + 1)
time_array = jnp.linspace(0.0, duration - delta_t, N_samples)
conjugate_frequencies = jnp.linspace(0.0, sampling_frequency / 2, N_freqs)
f_min = 10.
f_max = sampling_frequency / 2
reference_frequency = 50.0 # Hz
frequency_mask = (conjugate_frequencies >= f_min) & (conjugate_frequencies < f_max)
masked_frequencies = conjugate_frequencies[frequency_mask]
# Injection parameters
injection_parameters = {
'M_c': 30.0,
'eta': 0.20,
's1_x': 0.0,
's1_y': 0.0,
's1_z': 0.1,
's2_x': 0.0,
's2_y': 0.0,
's2_z': 0.2,
'd_L': 1.0,
'luminosity_distance': 1.0,
'phase_c': 0.0,
'phase': 0.0,
'iota': 1.3,
}
m1, m2 = Mc_eta_to_m1_m2(
injection_parameters['M_c'],
injection_parameters['eta']
)
injection_parameters['mass_1_SI'] = float(m1) * MSUN_SI
injection_parameters['mass_2_SI'] = float(m2) * MSUN_SI
injection_parameters['luminosity_distance_SI'] = \
injection_parameters['luminosity_distance'] * MPC_SI
# Prepare Jim/Ripple waveforms
PhenomPv2 = RippleIMRPhenomPv2(f_ref=reference_frequency)
PhenomD = RippleIMRPhenomD(f_ref=reference_frequency)
ripple_PhenomD_hpc = PhenomD(masked_frequencies, injection_parameters)
ripple_PhenomPv2_hpc = PhenomPv2(masked_frequencies, injection_parameters)
ripple_D_hp, ripple_D_hc = np.zeros((2, N_freqs), dtype=np.complex128)
ripple_D_hp[frequency_mask] = ripple_PhenomD_hpc['p']
ripple_D_hc[frequency_mask] = ripple_PhenomD_hpc['c']
ripple_Pv2_hp, ripple_Pv2_hc = np.zeros((2, N_freqs), dtype=np.complex128)
ripple_Pv2_hp[frequency_mask] = ripple_PhenomPv2_hpc['p']
ripple_Pv2_hc[frequency_mask] = ripple_PhenomPv2_hpc['c']
# Prepare LALSimulation waveforms
parameters = [injection_parameters[key] for key in(
'mass_1_SI', 'mass_2_SI', 's1_x', 's1_y', 's1_z',
's2_x', 's2_y', 's2_z', 'luminosity_distance_SI', 'iota', 'phase',
)]
lal_PhenomD_hp, lal_PhenomD_hc = lalsim.SimInspiralChooseFDWaveform(
*parameters,
0, 0.0, 0.0,
1/duration, f_min, f_max,
reference_frequency, None, IMRPhenomD,
)
lal_PhenomPv2_hp, lal_PhenomPv2_hc = lalsim.SimInspiralChooseFDWaveform(
*parameters,
0, 0.0, 0.0,
1/duration, f_min, f_max,
reference_frequency, None, IMRPhenomPv2,
)
print('PhenomD, hp:', FD_match(conjugate_frequencies, ripple_D_hp, lal_PhenomD_hp.data.data))
print('PhenomD, hc:', FD_match(conjugate_frequencies, ripple_D_hc, lal_PhenomD_hc.data.data))
print('PhenomPv2, hp:', FD_match(conjugate_frequencies, ripple_Pv2_hp, lal_PhenomPv2_hp.data.data))
print('PhenomPv2, hc:', FD_match(conjugate_frequencies, ripple_Pv2_hc, lal_PhenomPv2_hc.data.data))
fig, Axes = plt.subplots(2, 2, figsize=(9, 3*2), sharex=True, sharey='row',
constrained_layout=True)
for ax, (jim_wfm, lal_wfm) in zip(
Axes.T.flatten(),
[
(ripple_D_hp, lal_PhenomD_hp.data.data),
(ripple_D_hc, lal_PhenomD_hc.data.data),
(ripple_Pv2_hp, lal_PhenomPv2_hp.data.data),
(ripple_Pv2_hc, lal_PhenomPv2_hc.data.data),
]
):
match = FD_match(conjugate_frequencies, jim_wfm, lal_wfm)
jim_td_wfm = waveform_fd_to_td(jim_wfm)
lal_td_wfm = waveform_fd_to_td(lal_wfm)
ax.plot(time_array, np.roll(jim_td_wfm, -100), label='Ripple')
ax.plot(time_array, np.roll(lal_td_wfm, -100), ls='--', label=fr'${{\cal M}} = {match:.3f}$')
ax.set_xlabel(r'$t \,/\,{\rm s}$')
ax.grid(False)
ax.tick_params(axis='both', which='both',
top=True, bottom=True, left=True, right=True)
ax.legend(loc='upper left')
Axes[0, 0].set_title('PhenomD')
Axes[0, 1].set_title('PhenomPv2')
Axes[0, 0].set_ylabel(r'$h_+(t)$')
Axes[1, 0].set_ylabel(r'$h_\times(t)$')
ax.set_xlim(7.5, 8)
plt.savefig('compare_ripple_lal_Pv2.png', dpi=300)
It appears that the PhenomPv2 waveforms are flipped along the x-axis as compared to LALSim, and even PhenomD.
Since this model consists of only the dominant modes, this issue could be due to either a π/2 phase shift or a flip in sign, which will require further investigation.
Here is a figure to illustrate the issue:
Blue is the waveform computed with Ripple, and the orange dashed one is from LALSim.
On the right is the results from PhenomD, and PhenomPv2 is on the left.
Both columns were injected with the same parameters (non-precessing), and it is clear that the orange ones are consistent in both columns whereas the blue one is flipped.
The legend also shows the (frequency-domain) match; in the PhenomPv2 case, it is a -1, a perfect anti-phase.
I am also attaching the MWE to produce this result.