def test_get_observed_strain(self): # Let's check with Hanford theta_GW, phi_GW = np.pi / 3, 0 antennas = gwu.antenna_responses_from_sky_localization( 8, -70, "2015-09-14 09:50:45") Fc_H, Fp_H = antennas.hanford expected_strain = self.psi4.get_strain(theta_GW, phi_GW, 0.1, trim_ends=False) expected_strain = (expected_strain.real() * Fp_H - expected_strain.imag() * Fc_H) strain = self.psi4.get_observed_strain( 8, -70, "2015-09-14 09:50:45", theta_GW, phi_GW, 0.1, trim_ends=False, ) self.assertEqual(strain.hanford, expected_strain)
def test_antenna_responses(self): antenna_gw150914 = gwu.antenna_responses_from_sky_localization( 8, -70, "2015-09-14 09:50:45") # This test is extremely weak: the numbers that are here were # obtained with the function itself self.assertAlmostEqual(antenna_gw150914.hanford[0], 0.173418558) self.assertAlmostEqual(antenna_gw150914.hanford[1], 0.734266762) self.assertAlmostEqual(antenna_gw150914.livingston[0], 0.030376784) self.assertAlmostEqual(antenna_gw150914.livingston[1], -0.569292709) self.assertAlmostEqual(antenna_gw150914.virgo[0], -0.11486789) self.assertAlmostEqual(antenna_gw150914.virgo[1], 0.57442590)
def get_observed_strain( self, right_ascension, declination, time_utc, theta_gw, phi_gw, pcut, *args, window_function=None, polarization=0, l_max=None, trim_ends=True, **kwargs, ): r"""Return the strain accounting for all the multipoles and the spin weighted spherical harmonics as observed by Hanford, Livingston and Virgo. .. math:: h_+(r,t) - i h_\times(r,t) = \sum_{l=2}^{l=l_{\mathrm{max}}} \sum_{m=-l}^{m=l} h(r, t)^{lm} {}_{-2}Y_{lm}(\theta, \phi) Here \theta and \phi are the arguments ``theta_gw`` and ``phi_gw``. Then, for each detector .. math:: h(r,t) = F_\times h_\times(theta_gw, phi_gw) + F_+ h_+(theta_gw, phi_gw) :param right_ascension: Right ascension of the source in degrees. :type right_ascension: float :param declination: Declination of the source in degrees. :type declination: float :param time_utc: UTC time of the event :type declination: str :param theta_gw, phi_gw: Spherical coordinates of the observer from the binary's frame, taking the angular momentum of the binary to point along the z-axis. :type theta_gw, phi_gw: floats :param pcut: Period that enters the fixed-frequency integration. Typically, the longest physical period in the signal. :type pcut: float :param window_function: If not None, apply ``window_function`` to the series before computing the strain. :type window_function: callable, str, or None :param trim_ends: If True, a portion of the resulting strain is removed at both the initial and final times. The amount removed is equal to pcut. :type trim_ends: bool :param l_max: Ignore multipoles with l > l_max :type l_max: int :returns: :math:`r (h^+ - i rh^\times)` :rtype: Detectors of :py:class:`~.TimeSeries` """ # Detectors contains three fields, one for each detector antennas = gw_utils.antenna_responses_from_sky_localization( right_ascension, declination, time_utc, polarization) # We collect all the strains in a list, then we convert it in a # nameduples Detectors strains = [] # Loop over the detectors in Detectors # antennas and coords are namedtuples Detectors for (Fc, Fp) in antennas: strain = self.get_strain( theta_gw, phi_gw, pcut, *args, window_function=window_function, l_max=l_max, trim_ends=trim_ends, **kwargs, ) # What have that: # strain.real = hp # strain.imag = -hc strains.append(strain.real() * Fp - strain.imag() * Fc) return Detectors(*strains)
def network_mismatch( h1, h2, right_ascension, declination, time_utc, fmin=0, fmax=np.inf, noises=None, num_polarization_shifts=200, num_time_shifts=1000, time_shift_start=-20, time_shift_end=20, force_numba=False, ): """Compute network mismatch between strains h1 and h2. This is a wrapper around :py:meth:`~.mismatch_from_strains` (read the docstring for more information) that prepares the correct antenna patterns from the sky localization of the event. Moreover, it makes sure that noises (that has to be a gw_utils.Detectors object, or None) is correctly ordered. :param h1: First strain. :type h1: :py:class:`~.TimeSeries` :param h2: Second strain (the one that will be modified). :type h2: :py:class:`~.TimeSeries` :param right_ascension: Right ascension of the source in the sky. :type right_ascension: float :param declination: Declination of the source in the sky. :type declination: float :param time_utc: Time UTC of the event. :type time_utc: float :param fmin: Lower limit of the integration. :type fmin: float :param fmax: Higher limit of the integration. :type fmax: float :param noises: Power spectral density of the noise for all the detectors. :type noises: :py:class:`~.Detector`, or None :param num_polarization_shifts: How many points to divide the range (0, 2 pi) in the polarization shift. :type num_polarization_shifts: int :param num_time_shifts: How many points to divide the range (time_shift_start, time_shift_end) in the time shift. :type num_time_shifts: int :param time_shift_start: Minimum time shift applied. Search will be done linearly up to time_shift_end. :type time_shift_start: float :param time_shift_end: Largest value of time shift applied. :type time_shift_end: float :param force_numba: Use numba irrespectively of the size of the input. :type force_numba: bool """ antenna_patterns = gwu.antenna_responses_from_sky_localization( right_ascension, declination, time_utc) # Transform Detectors to lists (they are already properly ordered) if noises is not None: if isinstance(noises, gwu.Detectors): # We select thos antennas for which the corresponding noise is not # -1 antenna_patterns = [ ap for ap, noise in zip(antenna_patterns, noises) if noise != -1 ] # We remove all the noises that are -1. This modifies the list. noises = [noise for noise in noises if noise != -1] else: raise TypeError("noises has to be None or of type Detectors") else: # All three detectors antenna_patterns = list(antenna_patterns) return mismatch_from_strains( h1, h2, fmin, fmax, noises=noises, antenna_patterns=antenna_patterns, num_polarization_shifts=num_polarization_shifts, num_time_shifts=num_time_shifts, time_shift_start=time_shift_start, time_shift_end=time_shift_end, force_numba=force_numba, )
def test_mismatch(self): fmin = 5 fmax = 15 # Invalid noise with self.assertRaises(TypeError): gwm.network_mismatch(self.ts1, self.ts2, 8, -70, "2015-09-14 09:50:45", noises=1) # No noise, three detectors antennas = gwu.antenna_responses_from_sky_localization( 8, -70, "2015-09-14 09:50:45") self.assertAlmostEqual( gwm.mismatch_from_strains( self.ts1, self.ts2, fmin=fmin, fmax=fmax, noises=None, antenna_patterns=list(antennas), num_polarization_shifts=30, num_time_shifts=30, time_shift_start=-70, time_shift_end=70, )[0], gwm.network_mismatch( self.ts1, self.ts2, 8, -70, "2015-09-14 09:50:45", fmin=fmin, fmax=fmax, noises=None, num_polarization_shifts=30, num_time_shifts=30, time_shift_start=-70, time_shift_end=70, )[0], ) # Only one active detector only_virgo = gwu.Detectors(hanford=-1, livingston=-1, virgo=None) self.assertAlmostEqual( gwm.mismatch_from_strains( self.ts1, self.ts2, fmin=fmin, fmax=fmax, noises=None, antenna_patterns=[antennas.virgo], num_polarization_shifts=30, num_time_shifts=30, time_shift_start=-70, time_shift_end=70, )[0], gwm.network_mismatch( self.ts1, self.ts2, 8, -70, "2015-09-14 09:50:45", fmin=fmin, fmax=fmax, noises=only_virgo, num_polarization_shifts=30, num_time_shifts=30, time_shift_start=-70, time_shift_end=70, )[0], ) # Test with a "gw-looking" singal from PyCBC # # First, we test the overlap by giving num_polarizations, # num_time_shifts=1 try: with warnings.catch_warnings(): warnings.simplefilter("ignore") from pycbc.filter import match, overlap from pycbc.types import timeseries as pycbcts from pycbc.waveform import get_td_waveform fmin_gw = 50 fmax_gw = 100 delta_t = 1 / 4096 hp1, hc1 = get_td_waveform( approximant="IMRPhenomPv2", mass1=10, mass2=10, spin1z=0.9, delta_t=delta_t, f_lower=40, ) hp2, hc2 = get_td_waveform( approximant="IMRPhenomPv2", mass1=10, mass2=25, spin1z=-0.5, delta_t=delta_t, f_lower=40, ) # PyCBC does not work well with series with different length. So, we # crop the longer one to the length of the shorter one. For the choice # of paramters, it is 2 that is shorter than 1. 1 starts earlier in the # past. However, they have the same frequencies, so we can simply crop # away the part we are not interested in. time_offset = 2 # Manually computed looking at the times hp1 = hp1.crop(time_offset, 0) hc1 = hc1.crop(time_offset, 0) # We apply the "antenna pattern" h1_pycbc = pycbcts.TimeSeries(0.33 * hp1 + 0.66 * hc1, delta_t=hp1.delta_t) h2_pycbc = pycbcts.TimeSeries(0.33 * hp2 + 0.66 * hc2, delta_t=hp2.delta_t) overlap_m = overlap( h1_pycbc, h2_pycbc, psd=None, low_frequency_cutoff=fmin_gw, high_frequency_cutoff=fmax_gw, ) h1_postcac = ts.TimeSeries(h1_pycbc.sample_times, hp1 - 1j * hc1) h2_postcac = ts.TimeSeries(h2_pycbc.sample_times, hp2 - 1j * hc2) o = gwm.mismatch_from_strains( h1_postcac, h2_postcac, fmin=fmin_gw, fmax=fmax_gw, noises=None, antenna_patterns=[(0.66, 0.33)], num_polarization_shifts=1, num_time_shifts=1, time_shift_start=0, time_shift_end=0, force_numba=False, ) self.assertAlmostEqual(1 - o[0], overlap_m, places=2) # Now we can test the mismatch pycbc_m, _ = match( h1_pycbc, h2_pycbc, psd=None, low_frequency_cutoff=fmin_gw, high_frequency_cutoff=fmax_gw, ) pycbc_m = 1 - pycbc_m mat = gwm.mismatch_from_strains( h1_postcac, h2_postcac, fmin=fmin_gw, fmax=fmax_gw, noises=None, antenna_patterns=[(0.66, 0.33)], num_polarization_shifts=100, num_time_shifts=800, time_shift_start=-0.3, time_shift_end=0.3, force_numba=False, ) self.assertAlmostEqual(mat[0], pycbc_m, places=2) except ImportError: # pragma: no cover pass