def test_sg(self): ### use a sin-gaussian as a signal sigt = TimeSeries(self.sig2, self.del_t) sig_tilde = make_frequency_series(sigt) del_f = sig_tilde.get_delta_f() psd = FrequencySeries(self.Psd, del_f) flow = self.low_frequency_cutoff with _context: hautocor, hacorfr, hnrm = matched_filter_core(self.htilde, self.htilde, psd=psd, \ low_frequency_cutoff=flow, high_frequency_cutoff=self.fmax) hautocor = hautocor * float(np.real(1. / hautocor[0])) snr, cor, nrm = matched_filter_core(self.htilde, sig_tilde, psd=psd, \ low_frequency_cutoff=flow, high_frequency_cutoff=self.fmax) hacor = Array(hautocor.real(), copy=True) indx = np.array([301440, 301450, 301460]) snr = snr * nrm with _context: dof, achisq, indices= \ autochisq_from_precomputed(snr, snr, hacor, indx, stride=3, num_points=20) obt_snr = abs(snr[indices[1]]) obt_ach = achisq[1] self.assertTrue(obt_snr > 12.0 and obt_snr < 15.0) self.assertTrue(obt_ach > 6.8e3) self.assertTrue(achisq[0] > 6.8e3) self.assertTrue(achisq[2] > 6.8e3)
def interpolate(series, delta_f): """Return a new PSD that has been interpolated to the desired delta_f. Parameters ---------- series : FrequencySeries Frequency series to be interpolated. delta_f : float The desired delta_f of the output Returns ------- interpolated series : FrequencySeries A new FrequencySeries that has been interpolated. """ new_n = (len(series) - 1) * series.delta_f / delta_f + 1 samples = numpy.arange(0, numpy.rint(new_n)) * delta_f interpolated_series = numpy.interp(samples, series.sample_frequencies.numpy(), series.numpy()) return FrequencySeries(interpolated_series, epoch=series.epoch, delta_f=delta_f, dtype=series.dtype)
def inverse_spectrum_truncation(psd, max_filter_len, low_frequency_cutoff=None, trunc_method=None): """Modify a PSD such that the impulse response associated with its inverse square root is no longer than `max_filter_len` time samples. In practice this corresponds to a coarse graining or smoothing of the PSD. Parameters ---------- psd : FrequencySeries PSD whose inverse spectrum is to be truncated. max_filter_len : int Maximum length of the time-domain filter in samples. low_frequency_cutoff : {None, int} Frequencies below `low_frequency_cutoff` are zeroed in the output. trunc_method : {None, 'hann'} Function used for truncating the time-domain filter. None produces a hard truncation at `max_filter_len`. Returns ------- psd : FrequencySeries PSD whose inverse spectrum has been truncated. Raises ------ ValueError For invalid types or values of `max_filter_len` and `low_frequency_cutoff`. Notes ----- See arXiv:gr-qc/0509116 for details. """ # sanity checks if type(max_filter_len) is not int or max_filter_len <= 0: raise ValueError('max_filter_len must be a positive integer') if low_frequency_cutoff is not None and low_frequency_cutoff < 0 \ or low_frequency_cutoff > psd.sample_frequencies[-1]: raise ValueError('low_frequency_cutoff must be within the bandwidth of the PSD') N = (len(psd)-1)*2 inv_asd = FrequencySeries((1. / psd)**0.5, delta_f=psd.delta_f, \ dtype=complex_same_precision_as(psd)) inv_asd[0] = 0 inv_asd[N/2] = 0 q = TimeSeries(numpy.zeros(N), delta_t=(N / psd.delta_f), \ dtype=real_same_precision_as(psd)) if low_frequency_cutoff: kmin = int(low_frequency_cutoff / psd.delta_f) inv_asd[0:kmin] = 0 ifft(inv_asd, q) trunc_start = max_filter_len / 2 trunc_end = N - max_filter_len / 2 if trunc_method == 'hann': trunc_window = Array(numpy.hanning(max_filter_len), dtype=q.dtype) q[0:trunc_start] *= trunc_window[max_filter_len/2:max_filter_len] q[trunc_end:N] *= trunc_window[0:max_filter_len/2] q[trunc_start:trunc_end] = 0 psd_trunc = FrequencySeries(numpy.zeros(len(psd)), delta_f=psd.delta_f, \ dtype=complex_same_precision_as(psd)) fft(q, psd_trunc) psd_trunc *= psd_trunc.conj() psd_out = 1. / abs(psd_trunc) return psd_out
def logv_lookup(vmax, delta): vec = numpy.arange(0, vmax * 1.2, delta) vec[1:len(vec)] = numpy.log(vec[1:len(vec)]) return FrequencySeries(vec, delta_f=delta).astype(float32)
def get_fd_lm_allmodes(template=None, **kwargs): """Return frequency domain ringdown with all the modes specified. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. final_mass : float Mass of the final black hole. final_spin : float Spin of the final black hole. lmns : list Desired lmn modes as strings (lm modes available: 22, 21, 33, 44, 55). The n specifies the number of overtones desired for the corresponding lm pair (maximum n=8). Example: lmns = ['223','331'] are the modes 220, 221, 222, and 330 amp220 : float Amplitude of the fundamental 220 mode. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a ringdown with the lm modes specified and n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a ringdown with the lm modes specified and n overtones in frequency domain. """ input_params = props(template, lm_allmodes_required_args, **kwargs) # Get required args final_mass = input_params['final_mass'] final_spin = input_params['final_spin'] lmns = input_params['lmns'] for lmn in lmns: if int(lmn[2]) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) if delta_f is None: delta_f = lm_deltaf(final_mass, final_spin, lmns) if f_final is None: f_final = lm_ffinal(final_mass, final_spin, lmns) if f_lower is None: f_lower = delta_f kmax = int(f_final / delta_f) + 1 outplustilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) outcrosstilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) for lmn in lmns: l, m, nmodes = int(lmn[0]), int(lmn[1]), int(lmn[2]) hplustilde, hcrosstilde = get_fd_lm(l=l, m=m, nmodes=nmodes, delta_f=delta_f, f_lower=f_lower, f_final=f_final, **input_params) outplustilde.data += hplustilde.data outcrosstilde.data += hcrosstilde.data return outplustilde, outcrosstilde
def get_fd_qnm(template=None, **kwargs): """Return a frequency domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. phi : float The initial phase of the ringdown. amp : float The amplitude of the ringdown (constant for now). t_0 : {0, float}, optional The starting time of the ringdown. delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude. f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplustilde: FrequencySeries The plus phase of the ringdown in frequency domain. hcrosstilde: FrequencySeries The cross phase of the ringdown in frequency domain. """ input_params = props(template, qnm_required_args, **kwargs) f_0 = input_params.pop('f_0') tau = input_params.pop('tau') amp = input_params.pop('amp') phi = input_params.pop('phi') # the following have defaults, and so will be populated t_0 = input_params.pop('t_0') # the following may not be in input_params delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) if delta_f is None: delta_f = 1. / qnm_time_decay(tau, 1. / 1000) if f_lower is None: f_lower = delta_f kmin = 0 else: kmin = int(f_lower / delta_f) if f_final is None: f_final = qnm_freq_decay(f_0, tau, 1. / 1000) if f_final > max_freq: f_final = max_freq kmax = int(f_final / delta_f) + 1 freqs = numpy.arange(kmin, kmax) * delta_f denominator = 1 + (4j * pi * freqs * tau) - (4 * pi_sq * (freqs * freqs - f_0 * f_0) * tau * tau) norm = amp * tau / denominator if t_0 != 0: time_shift = numpy.exp(-1j * two_pi * freqs * t_0) norm *= time_shift # Analytical expression for the Fourier transform of the ringdown (damped sinusoid) hp_tilde = norm * ((1 + 2j * pi * freqs * tau) * numpy.cos(phi) - two_pi * f_0 * tau * numpy.sin(phi)) hc_tilde = norm * ((1 + 2j * pi * freqs * tau) * numpy.sin(phi) + two_pi * f_0 * tau * numpy.cos(phi)) hplustilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) hcrosstilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) hplustilde.data[kmin:kmax] = hp_tilde hcrosstilde.data[kmin:kmax] = hc_tilde return hplustilde, hcrosstilde
def calc_psd_variation(strain, psd_short_segment, psd_long_segment, overlap, low_freq, high_freq): """Calculates time series of PSD variability This function first splits the segment up in to 512 second chunks. It then calculates the PSD over this 512 second period as well as in 4 second chunks throughout each 512 second period. Next the function estimates how different the 4 second PSD is to the 512 second PSD and produces a timeseries of this variability. Parameters ---------- strain : TimeSeries Input strain time series to estimate PSDs psd_short_segment : {float, 4} Duration of the short segments for PSD estimation in seconds. psd_long_segment : {float, 512} Duration of the long segments for PSD estimation in seconds. overlap : {float, 0.5} Duration in seconds to use for each sample of the PSD. low_freq : {float, 20} Minimum frequency to consider the comparison between PSDs. high_freq : {float, 512} Maximum frequency to consider the comparison between PSDs. Returns ------- psd_var : TimeSeries Time series of the variability in the PSD estimation """ # Calculate strain precision if strain.precision == 'single': fs_dtype = numpy.float32 elif strain.precision == 'double': fs_dtype = numpy.float64 # Convert start and end times immediately to floats start_time = numpy.float(strain.start_time) end_time = numpy.float(strain.end_time) # Find the times of the long segments times_long = numpy.arange(start_time, end_time, psd_long_segment) # Set up the empty time series for the PSD variation estimate psd_var = TimeSeries(zeros( int(numpy.ceil((end_time - start_time) / psd_short_segment))), delta_t=psd_short_segment, copy=False, epoch=start_time) ind = 0 for tlong in times_long: # Calculate PSD for long segment and separate the long segment in to # overlapping shorter segments if tlong + psd_long_segment <= end_time: psd_long = strain.time_slice(tlong, tlong + psd_long_segment).psd(overlap) times_short = numpy.arange(tlong, tlong + psd_long_segment, psd_short_segment) else: psd_long = strain.time_slice(end_time - psd_long_segment, end_time).psd(overlap) times_short = numpy.arange(tlong, end_time, psd_short_segment) # Calculate the PSD of the shorter segments psd_short = [] for tshort in times_short: if tshort + psd_short_segment * 2 <= end_time: pshort = strain.time_slice(tshort, tshort + psd_short_segment * 2).psd(overlap) else: pshort = strain.time_slice(tshort - psd_short_segment, end_time).psd(overlap) psd_short.append(pshort) # Estimate the range of the PSD to compare kmin = int(low_freq / psd_long.delta_f) kmax = int(high_freq / psd_long.delta_f) # Comapre the PSD of the short segment to the long segment # The weight factor gives the rough response of a cbc template across # the defined frequency range given the expected PSD (i.e. long PSD) # Then integrate the weighted ratio of the actual PSD (i.e. short PSD) # with the expected PSD (i.e. long PSD) over the specified frequency # range freqs = FrequencySeries(psd_long.sample_frequencies, delta_f=psd_long.delta_f, epoch=psd_long.epoch, dtype=fs_dtype) weight = numpy.array(freqs[kmin:kmax]**(-7. / 3.) / psd_long[kmin:kmax]) weight /= weight.sum() diff = numpy.array([ (weight * numpy.array(p_short[kmin:kmax] / psd_long[kmin:kmax])).sum() for p_short in psd_short ]) # Store variation value for i, val in enumerate(diff): psd_var[ind + i] = val ind = ind + len(diff) return psd_var
def adjust_strain(self, strain, delta_fs=None, delta_qinv=None, delta_fc=None, kappa_c=1.0, kappa_tst_re=1.0, kappa_tst_im=0.0, kappa_pu_re=1.0, kappa_pu_im=0.0): """Adjust the FrequencySeries strain by changing the time-dependent calibration parameters kappa_c(t), kappa_a(t), f_c(t), fs, and qinv. Parameters ---------- strain : FrequencySeries The strain data to be adjusted. delta_fc : float Change in coupled-cavity (CC) pole at time t. kappa_c : float Scalar correction factor for sensing function c0 at time t. kappa_tst_re : float Real part of scalar correction factor for actuation function A_{tst0} at time t. kappa_tst_im : float Imaginary part of scalar correction factor for actuation function A_tst0 at time t. kappa_pu_re : float Real part of scalar correction factor for actuation function A_{pu0} at time t. kappa_pu_im : float Imaginary part of scalar correction factor for actuation function A_{pu0} at time t. fs : float Spring frequency for signal recycling cavity. qinv : float Inverse quality factor for signal recycling cavity. Returns ------- strain_adjusted : FrequencySeries The adjusted strain. """ fc = self.fc0 + delta_fc if delta_fc else self.fc0 fs = self.fs0 + delta_fs if delta_fs else self.fs0 qinv = self.qinv0 + delta_qinv if delta_qinv else self.qinv0 # calculate adjusted response function r_adjusted = self.update_r(fs=fs, qinv=qinv, fc=fc, kappa_c=kappa_c, kappa_tst_re=kappa_tst_re, kappa_tst_im=kappa_tst_im, kappa_pu_re=kappa_pu_re, kappa_pu_im=kappa_pu_im) # calculate error function k = r_adjusted / self.r0 # decompose into amplitude and unwrapped phase k_amp = np.abs(k) k_phase = np.unwrap(np.angle(k)) # convert to FrequencySeries by interpolating then resampling order = 1 k_amp_off = UnivariateSpline(self.freq, k_amp, k=order, s=0) k_phase_off = UnivariateSpline(self.freq, k_phase, k=order, s=0) freq_even = strain.sample_frequencies.numpy() k_even_sample = k_amp_off(freq_even) * \ np.exp(1.0j * k_phase_off(freq_even)) strain_adjusted = FrequencySeries(strain.numpy() * \ k_even_sample, delta_f=strain.delta_f) return strain_adjusted
def taylorf2(**kwds): """ Return a TaylorF2 waveform using CUDA to generate the phase and amplitude """ # Pull out the input arguments delta_f = kwds['delta_f'] distance = kwds['distance'] mass1 = kwds['mass1'] mass2 = kwds['mass2'] phase_order = int(kwds['phase_order']) amplitude_order = int(kwds['amplitude_order']) phi0 = kwds['phi0'] tC = -1.0 / delta_f #Calculate the spin corrections beta, sigma, gamma = pycbc.pnutils.mass1_mass2_spin1z_spin2z_to_beta_sigma_gamma( mass1, mass2, kwds['spin1z'], kwds['spin2z']) #Calculate teh PN terms #TODO: replace with functions in lalsimulation!### M = float(mass1) + float(mass2) eta = mass1 * mass2 / (M * M) theta = -11831. / 9240. lambdaa = -1987. / 3080.0 pfaN = 3.0 / (128.0 * eta) pfa2 = 5 * (743.0 / 84 + 11.0 * eta) / 9.0 pfa3 = -16.0 * lal.PI + 4.0 * beta pfa4 = 5.0*(3058.673/7.056 + 5429.0/7.0 * eta + 617.0 * eta*eta)/72.0 - \ 10.0*sigma pfa5 = 5.0 / 9.0 * (7729.0 / 84.0 - 13.0 * eta) * lal.PI - gamma pfl5 = 5.0 / 3.0 * (7729.0 / 84.0 - 13.0 * eta) * lal.PI - gamma * 3 pfa6 = (11583.231236531/4.694215680 - 640.0/3.0 * lal.PI * lal.PI- \ 6848.0/21.0*lal.GAMMA) + \ eta * (-15335.597827/3.048192 + 2255./12. * lal.PI * \ lal.PI - 1760./3.*theta +12320./9.*lambdaa) + \ eta*eta * 76055.0/1728.0 - \ eta*eta*eta* 127825.0/1296.0 pfl6 = -6848.0 / 21.0 pfa7 = lal.PI * 5.0/756.0 * ( 15419335.0/336.0 + 75703.0/2.0 * eta - \ 14809.0 * eta*eta) FTaN = 32.0 * eta * eta / 5.0 FTa2 = -(12.47 / 3.36 + 3.5 / 1.2 * eta) FTa3 = 4.0 * lal.PI FTa4 = -(44.711 / 9.072 - 92.71 / 5.04 * eta - 6.5 / 1.8 * eta * eta) FTa5 = -(81.91 / 6.72 + 58.3 / 2.4 * eta) * lal.PI FTa6 = (664.3739519 / 6.9854400 + 16.0 / 3.0 * lal.PI * lal.PI - 17.12 / 1.05 * lal.GAMMA + (4.1 / 4.8 * lal.PI * lal.PI - 134.543 / 7.776) * eta - 94.403 / 3.024 * eta * eta - 7.75 / 3.24 * eta * eta * eta) FTl6 = -8.56 / 1.05 FTa7 = -(162.85/5.04 - 214.745/1.728 * eta - 193.385/3.024 * eta*eta) \ * lal.PI dETaN = 2 * -eta / 2.0 dETa1 = 2 * -(3.0 / 4.0 + 1.0 / 12.0 * eta) dETa2 = 3 * -(27.0 / 8.0 - 19.0 / 8.0 * eta + 1. / 24.0 * eta * eta) dETa3 = 4 * -(67.5 / 6.4 - (344.45 / 5.76 - 20.5 / 9.6 * lal.PI * lal.PI) * eta + 15.5 / 9.6 * eta * eta + 3.5 / 518.4 * eta * eta * eta) amp0 = -4. * mass1 * mass2 / (1.0e+06 * float(distance) * lal.PC_SI )* \ lal.MRSUN_SI * lal.MTSUN_SI * sqrt(lal.PI/12.0) m_sec = M * lal.MTSUN_SI piM = lal.PI * m_sec kmin = int(kwds['f_lower'] / float(delta_f)) vISCO = 1. / sqrt(6.) fISCO = vISCO * vISCO * vISCO / piM kmax = int(fISCO / delta_f) f_max = fISCO n = int(f_max / delta_f) + 1 htilde = FrequencySeries(zeros(n, dtype=numpy.complex128), delta_f=delta_f, copy=False) taylorf2_kernel(htilde.data[kmin:kmax], kmin, phase_order, amplitude_order, delta_f, piM, pfaN, pfa2, pfa3, pfa4, pfa5, pfl5, pfa6, pfl6, pfa7, FTaN, FTa2, FTa3, FTa4, FTa5, FTa6, FTl6, FTa7, dETaN, dETa1, dETa2, dETa3, amp0, tC, phi0) hp = htilde hc = htilde * 1j return hp, hc
def get_fd_lm(template=None, **kwargs): """Return frequency domain lm mode with a given number of overtones. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. freqs : dict {lmn:f_lmn} Dictionary of the central frequencies for each overtone, as many as number of modes. taus : dict {lmn:tau_lmn} Dictionary of the damping times for each overtone, as many as number of modes. l : int l mode (lm modes available: 22, 21, 33, 44, 55). m : int m mode (lm modes available: 22, 21, 33, 44, 55). nmodes: int Number of overtones desired (maximum n=8) amplmn : float Amplitude of the lmn overtone, as many as the number of nmodes. philmn : float Phase of the lmn overtone, as many as the number of modes. Should also include the information from the azimuthal angle (phi + m*Phi). inclination : {0., float}, optional Inclination of the system in radians. Default is 0 (face on). delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a lm mode with n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a lm mode with n overtones in frequency domain. """ input_params = props(template, lm_required_args, **kwargs) # Get required args amps, phis = lm_amps_phases(**input_params) f_0 = input_params.pop('freqs') tau = input_params.pop('taus') l, m = input_params.pop('l'), input_params.pop('m') inc = input_params.pop('inclination', 0.) nmodes = input_params.pop('nmodes') if int(nmodes) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) if delta_f is None: delta_f = lm_deltaf(tau, ['%d%d%d' %(l,m,nmodes)]) if f_final is None: f_final = lm_ffinal(f_0, tau, ['%d%d%d' %(l, m, nmodes)]) kmax = int(f_final / delta_f) + 1 outplus = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) outcross = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) for n in range(nmodes): hplus, hcross = get_fd_qnm(template=None, f_0=f_0['%d%d%d' %(l,m,n)], tau=tau['%d%d%d' %(l,m,n)], amp=amps['%d%d%d' %(l,m,n)], phi=phis['%d%d%d' %(l,m,n)], inclination=inc, l=l, m=m, delta_f=delta_f, f_lower=f_lower, f_final=f_final) outplus.data += hplus.data outcross.data += hcross.data return outplus, outcross
def calculate_acf(data, delta_t=1.0, unbiased=False): r"""Calculates the one-sided autocorrelation function. Calculates the autocorrelation function (ACF) and returns the one-sided ACF. The ACF is defined as the autocovariance divided by the variance. The ACF can be estimated using .. math:: \hat{R}(k) = \frac{1}{n \sigma^{2}} \sum_{t=1}^{n-k} \left( X_{t} - \mu \right) \left( X_{t+k} - \mu \right) Where :math:`\hat{R}(k)` is the ACF, :math:`X_{t}` is the data series at time t, :math:`\mu` is the mean of :math:`X_{t}`, and :math:`\sigma^{2}` is the variance of :math:`X_{t}`. Parameters ----------- data : TimeSeries or numpy.array A TimeSeries or numpy.array of data. delta_t : float The time step of the data series if it is not a TimeSeries instance. unbiased : bool If True the normalization of the autocovariance function is n-k instead of n. This is called the unbiased estimation of the autocovariance. Note that this does not mean the ACF is unbiased. Returns ------- acf : numpy.array If data is a TimeSeries then acf will be a TimeSeries of the one-sided ACF. Else acf is a numpy.array. """ # if given a TimeSeries instance then get numpy.array if isinstance(data, TimeSeries): y = data.numpy() delta_t = data.delta_t else: y = data # Zero mean y = y - y.mean() ny_orig = len(y) npad = 1 while npad < 2*ny_orig: npad = npad << 1 ypad = numpy.zeros(npad) ypad[:ny_orig] = y # FFT data minus the mean fdata = TimeSeries(ypad, delta_t=delta_t).to_frequencyseries() # correlate # do not need to give the congjugate since correlate function does it cdata = FrequencySeries(zeros(len(fdata), dtype=numpy.complex64), delta_f=fdata.delta_f, copy=False) correlate(fdata, fdata, cdata) # IFFT correlated data to get unnormalized autocovariance time series acf = cdata.to_timeseries() acf = acf[:ny_orig] # normalize the autocovariance # note that dividing by acf[0] is the same as ( y.var() * len(acf) ) if unbiased: acf /= ( y.var() * numpy.arange(len(acf), 0, -1) ) else: acf /= acf[0] # return input datatype if isinstance(data, TimeSeries): return TimeSeries(acf, delta_t=delta_t) else: return acf
def get_fd_from_final_mass_spin(template=None, distance=None, **kwargs): """Return frequency domain ringdown with all the modes specified. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. distance : {None, float}, optional Luminosity distance of the system. If specified, the returned ringdown will contain the factor (final_mass/distance). final_mass : float Mass of the final black hole. final_spin : float Spin of the final black hole. lmns : list Desired lmn modes as strings (lm modes available: 22, 21, 33, 44, 55). The n specifies the number of overtones desired for the corresponding lm pair (maximum n=8). Example: lmns = ['223','331'] are the modes 220, 221, 222, and 330 amp220 : float Amplitude of the fundamental 220 mode. Note that if distance is given, this parameter will have a completely different order of magnitude. See table II in https://arxiv.org/abs/1107.0854 for an estimate. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. Should also include the information from the azimuthal angle (phi + m*Phi). inclination : float Inclination of the system in radians. delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a ringdown with the lm modes specified and n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a ringdown with the lm modes specified and n overtones in frequency domain. """ input_params = props(template, mass_spin_required_args, **kwargs) # Get required args final_mass = input_params['final_mass'] final_spin = input_params['final_spin'] lmns = input_params['lmns'] for lmn in lmns: if int(lmn[2]) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) f_0, tau = get_lm_f0tau_allmodes(final_mass, final_spin, lmns) if not delta_f: delta_f = lm_deltaf(tau, lmns) if not f_final: f_final = lm_ffinal(f_0, tau, lmns) if not f_lower: f_lower = delta_f kmax = int(f_final / delta_f) + 1 outplustilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) outcrosstilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) for lmn in lmns: l, m, nmodes = int(lmn[0]), int(lmn[1]), int(lmn[2]) hplustilde, hcrosstilde = get_fd_lm(freqs=f_0, taus=tau, l=l, m=m, nmodes=nmodes, delta_f=delta_f, f_lower=f_lower, f_final=f_final, **input_params) outplustilde.data += hplustilde.data outcrosstilde.data += hcrosstilde.data norm = Kerr_factor(final_mass, distance) if distance else 1. return norm * outplustilde, norm * outcrosstilde
def get_fd_from_freqtau(template=None, **kwargs): """Return frequency domain ringdown with all the modes specified. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. lmns : list Desired lmn modes as strings (lm modes available: 22, 21, 33, 44, 55). The n specifies the number of overtones desired for the corresponding lm pair (maximum n=8). Example: lmns = ['223','331'] are the modes 220, 221, 222, and 330 f_lmn: float Central frequency of the lmn overtone, as many as number of modes. tau_lmn: float Damping time of the lmn overtone, as many as number of modes. amp220 : float Amplitude of the fundamental 220 mode. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. Should also include the information from the azimuthal angle (phi + m*Phi). inclination : {None, float}, optional Inclination of the system in radians. If None, the spherical harmonics will be set to 1. delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a ringdown with the lm modes specified and n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a ringdown with the lm modes specified and n overtones in frequency domain. """ input_params = props(template, freqtau_required_args, **kwargs) # Get required args f_0, tau = lm_freqs_taus(**input_params) lmns = input_params['lmns'] for lmn in lmns: if int(lmn[2]) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params inc = input_params.pop('inclination', None) delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) if not delta_f: delta_f = lm_deltaf(tau, lmns) if not f_final: f_final = lm_ffinal(f_0, tau, lmns) if not f_lower: f_lower = delta_f kmax = int(f_final / delta_f) + 1 outplustilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) outcrosstilde = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) for lmn in lmns: l, m, nmodes = int(lmn[0]), int(lmn[1]), int(lmn[2]) hplustilde, hcrosstilde = get_fd_lm(freqs=f_0, taus=tau, l=l, m=m, nmodes=nmodes, inclination=inc, delta_f=delta_f, f_lower=f_lower, f_final=f_final, **input_params) outplustilde.data += hplustilde.data outcrosstilde.data += hcrosstilde.data return outplustilde, outcrosstilde
def colored_noise(psd, start_time, end_time, seed=0, low_frequency_cutoff=1.0): """ Create noise from a PSD Return noise from the chosen PSD. Note that if unique noise is desired a unique seed should be provided. Parameters ---------- psd : pycbc.types.FrequencySeries PSD to color the noise start_time : int Start time in GPS seconds to generate noise end_time : int End time in GPS seconds to generate nosie seed : {None, int} The seed to generate the noise. low_frequency_cutof : {10.0, float} The low frequency cutoff to pass to the PSD generation. Returns -------- noise : TimeSeries A TimeSeries containing gaussian noise colored by the given psd. """ psd = psd * 1 flen = int(SAMPLE_RATE / psd.delta_f) / 2 + 1 oldlen = len(psd) psd.resize(flen) # Want to avoid zeroes in PSD. max_val = psd.max() for i in xrange(len(psd)): if i >= (oldlen - 1): psd.data[i] = psd[oldlen - 2] if psd[i] == 0: psd.data[i] = max_val wn_dur = int(end_time - start_time) + 2 * FILTER_LENGTH if psd.delta_f >= 1. / (2. * FILTER_LENGTH): # If the PSD is short enough, this method is less memory intensive than # resizing and then calling inverse_spectrum_truncation psd = pycbc.psd.interpolate(psd, 1.0 / (2. * FILTER_LENGTH)) # inverse_spectrum_truncation truncates the inverted PSD. To truncate # the non-inverted PSD we give it the inverted PSD to truncate and then # invert the output. psd = 1. / pycbc.psd.inverse_spectrum_truncation( 1. / psd, FILTER_LENGTH * SAMPLE_RATE, low_frequency_cutoff=low_frequency_cutoff, trunc_method='hann') psd = FrequencySeries(psd, dtype=complex_same_precision_as(psd)) # Zero-pad the time-domain PSD to desired length. Zeroes must be added # in the middle, so some rolling between a resize is used. psd = psd.to_timeseries() psd.roll(SAMPLE_RATE * FILTER_LENGTH) psd.resize(wn_dur * SAMPLE_RATE) psd.roll(-SAMPLE_RATE * FILTER_LENGTH) # As time series is still mirrored the complex frequency components are # 0. But convert to real by using abs as in inverse_spectrum_truncate psd = psd.to_frequencyseries() else: wn_dur = (end_time - start_time) + 2 * FILTER_LENGTH psd = pycbc.psd.interpolate(psd, 1.0 / wn_dur) psd = 1. / pycbc.psd.inverse_spectrum_truncation( 1. / psd, FILTER_LENGTH * SAMPLE_RATE, low_frequency_cutoff=low_frequency_cutoff, trunc_method='hann') asd = (psd.real())**0.5 del psd white_noise = normal(start_time - FILTER_LENGTH, end_time + FILTER_LENGTH, seed=seed) white_noise = white_noise.to_frequencyseries() # Here we color. Do not want to duplicate memory here though so use '*=' white_noise *= asd del asd colored = white_noise.to_timeseries() del white_noise return colored.time_slice(start_time, end_time)
def spintaylorf2(**kwds): """ Return a SpinTaylorF2 waveform using CUDA to generate the phase and amplitude """ #####Pull out the input arguments##### f_lower = double(kwds['f_lower']) delta_f = double(kwds['delta_f']) distance = double(kwds['distance']) mass1 = double(kwds['mass1']) mass2 = double(kwds['mass2']) spin1x = double(kwds['spin1x']) spin1y = double(kwds['spin1y']) spin1z = double(kwds['spin1z']) phi0 = double(kwds['coa_phase']) #Orbital Phase at coalescence phase_order = int(kwds['phase_order']) amplitude_order = int(kwds['amplitude_order']) inclination = double(kwds['inclination']) lnhatx = sin(inclination) lnhaty = 0. lnhatz = cos(inclination) psi = 0. tC = -1.0 / delta_f M = mass1 + mass2 eta = mass1 * mass2 / (M * M) m_sec = M * lal.MTSUN_SI piM = lal.PI * m_sec vISCO = 1. / sqrt(6.) fISCO = vISCO * vISCO * vISCO / piM f_max = ceilpow2(fISCO) n = int(f_max / delta_f + 1) kmax = int(fISCO / delta_f) kmin = int(numpy.ceil(f_lower / delta_f)) kmax = kmax if (kmax < n) else n #####Calculate the Orientation##### v0 = pow(piM * kmin * delta_f, 1. / 3) chi = sqrt(spin1x**2 + spin1y**2 + spin1z**2) kappa = (lnhatx * spin1x + lnhaty * spin1y + lnhatz * spin1z) / chi if ( chi > 0.) else 1. Jx0 = mass1 * mass2 * lnhatx / v0 + mass1 * mass1 * spin1x Jy0 = mass1 * mass2 * lnhaty / v0 + mass1 * mass1 * spin1y Jz0 = mass1 * mass2 * lnhatz / v0 + mass1 * mass1 * spin1z thetaJ = acos(Jz0 / sqrt(Jx0**2 + Jy0**2 + Jz0**2)) psiJ = atan2(Jy0, -Jx0) # FIXME: check that Jy0 and Jx0 are not both 0 # Rotate Lnhat back to frame where J is along z, to figure out initial alpha rotLx = lnhatx * cos(thetaJ) * cos(psiJ) - lnhaty * cos(thetaJ) * sin( psiJ) + lnhatz * sin(thetaJ) rotLy = lnhatx * sin(psiJ) + lnhaty * cos(psiJ) alpha0 = atan2(rotLy, rotLx) # FIXME: check that rotLy and rotLx are not both 0 psiJ_P = psiJ + psi psiJ_C = psiJ + psi + lal.PI / 4. #####Calculate the Coefficients##### #quadparam = 1. gamma0 = mass1 * chi / mass2 #Calculate the spin corrections # FIXME should use pycbc's function, but sigma has different expression # in Andy's code, double check # pn_beta, pn_sigma, pn_gamma = pycbc.pnutils.mass1_mass2_spin1z_spin2z_to_beta_sigma_gamma( # mass1, mass2, chi*kappa, 0) # FIXME: spin2 is taken to be 0 pn_beta = (113. * mass1 / (12. * M) - 19. * eta / 6.) * chi * kappa pn_sigma = ( (5. * (3. * kappa * kappa - 1.) / 2.) + (7. - kappa * kappa) / 96.) * (mass1 * mass1 * chi * chi / M / M) pn_gamma = (5. * (146597. + 7056. * eta) * mass1 / (2268. * M) - 10. * eta * (1276. + 153. * eta) / 81.) * chi * kappa prec_fac0 = 5. * (4. + 3. * mass2 / mass1) / 64. dtdv2 = 743. / 336. + 11. * eta / 4. dtdv3 = -4. * lal.PI + pn_beta dtdv4 = 3058673. / 1016064. + 5429. * eta / 1008. + 617. * eta * eta / 144. - pn_sigma dtdv5 = (-7729. / 672. + 13. * eta / 8.) * lal.PI + 9. * pn_gamma / 40. #####Calculate the Initial Euler Angles alpha_ref, beta_ref=0 and zeta_ref##### gam = gamma0 * v0 sqrtfac = sqrt(1. + 2. * kappa * gam + gam * gam) logv0 = log(v0) logfac1 = log(1. + kappa * gam + sqrtfac) logfac2 = log(kappa + gam + sqrtfac) v02 = v0 * v0 v03 = v0 * v02 kappa2 = kappa * kappa kappa3 = kappa2 * kappa gamma02 = gamma0 * gamma0 gamma03 = gamma02 * gamma0 alpha_ref = prec_fac0 * ( logfac2 * (dtdv2 * gamma0 + dtdv3 * kappa - dtdv5 * kappa / (2. * gamma02) + dtdv4 / (2. * gamma0) - dtdv4 * kappa2 / (2. * gamma0) + (dtdv5 * kappa3) / (2. * gamma02)) + logfac1 * (-dtdv2 * gamma0 * kappa - dtdv3 + kappa * gamma03 / 2. - gamma03 * kappa3 / 2.) + logv0 * (dtdv2 * gamma0 * kappa + dtdv3 - kappa * gamma03 / 2. + gamma03 * kappa3 / 2.) + sqrtfac * (dtdv3 + dtdv4 * v0 / 2. + dtdv5 / gamma02 / 3. + dtdv4 * kappa / (2. * gamma0) + dtdv5 * kappa * v0 / (6. * gamma0) - dtdv5 * kappa2 / (2. * gamma02) - 1 / (3. * v03) - gamma0 * kappa / (6. * v02) - dtdv2 / v0 - gamma02 / (3. * v0) + gamma02 * kappa2 / (2. * v0) + dtdv5 * v02 / 3.)) - alpha0 zeta_ref = prec_fac0 * ( dtdv3 * gamma0 * kappa * v0 + dtdv4 * v0 + logfac2 * (-dtdv2 * gamma0 - dtdv3 * kappa + dtdv5 * kappa / (2. * gamma02) - dtdv4 / (2. * gamma0) + dtdv4 * kappa2 / (2. * gamma0) - dtdv5 * kappa3 / (2. * gamma02)) + logv0 * (kappa * gamma03 / 2. - gamma03 * kappa3 / 2.) + logfac1 * (dtdv2 * gamma0 * kappa + dtdv3 - kappa * gamma03 / 2. + gamma03 * kappa3 / 2.) - 1 / (3. * v03) - gamma0 * kappa / (2. * v02) - dtdv2 / v0 + dtdv4 * gamma0 * kappa * v02 / 2. + dtdv5 * v02 / 2. + sqrtfac * (-dtdv3 - dtdv4 * v0 / 2. - dtdv5 / (3. * gamma02) - dtdv4 * kappa / (2. * gamma0) - dtdv5 * kappa * v0 / (6. * gamma0) + dtdv5 * kappa2 / (2. * gamma02) + 1 / (3. * v03) + gamma0 * kappa / (6. * v02) + dtdv2 / v0 + gamma02 / (3. * v0) - gamma02 * kappa2 / (2. * v0) - dtdv5 * v02 / 3.) + dtdv5 * gamma0 * kappa * v03 / 3.) #####Calculate the Complex sideband factors, mm=2 is first entry##### RE_SBfac0 = (1. + cos(thetaJ)**2) / 2. RE_SBfac1 = sin(2. * thetaJ) RE_SBfac2 = 3. * sin(thetaJ)**2 RE_SBfac3 = -sin(2. * thetaJ) RE_SBfac4 = (1. + cos(thetaJ)**2) / 2. IM_SBfac0 = -cos(thetaJ) IM_SBfac1 = -2. * sin(thetaJ) IM_SBfac2 = 0. IM_SBfac3 = -2. * sin(thetaJ) IM_SBfac4 = cos(thetaJ) #####Calculate the PN terms # FIXME replace with functions in lalsimulation ##### theta = -11831. / 9240. lambdaa = -1987. / 3080.0 pfaN = 3.0 / (128.0 * eta) pfa2 = 5.0 * (743.0 / 84 + 11.0 * eta) / 9.0 pfa3 = -16.0 * lal.PI + 4.0 * pn_beta pfa4 = 5.0*(3058.673/7.056 + 5429.0/7.0 * eta + 617.0 * eta*eta)/72.0 - \ 10.0*pn_sigma pfa5 = 5.0 / 9.0 * (7729.0 / 84.0 - 13.0 * eta) * lal.PI - pn_gamma pfl5 = 5.0 / 3.0 * (7729.0 / 84.0 - 13.0 * eta) * lal.PI - pn_gamma * 3 pfa6 = (11583.231236531/4.694215680 - 640.0/3.0 * lal.PI * lal.PI- \ 6848.0/21.0*lal.GAMMA) + \ eta * (-15335.597827/3.048192 + 2255./12. * lal.PI * \ lal.PI - 1760./3.*theta +12320./9.*lambdaa) + \ eta*eta * 76055.0/1728.0 - \ eta*eta*eta* 127825.0/1296.0 pfl6 = -6848.0 / 21.0 pfa7 = lal.PI * 5.0/756.0 * ( 15419335.0/336.0 + 75703.0/2.0 * eta - \ 14809.0 * eta*eta) FTaN = 32.0 * eta * eta / 5.0 FTa2 = -(12.47 / 3.36 + 3.5 / 1.2 * eta) FTa3 = 4.0 * lal.PI FTa4 = -(44.711 / 9.072 - 92.71 / 5.04 * eta - 6.5 / 1.8 * eta * eta) FTa5 = -(81.91 / 6.72 + 58.3 / 2.4 * eta) * lal.PI FTa6 = (664.3739519 / 6.9854400 + 16.0 / 3.0 * lal.PI * lal.PI - 17.12 / 1.05 * lal.GAMMA + (4.1 / 4.8 * lal.PI * lal.PI - 134.543 / 7.776) * eta - 94.403 / 3.024 * eta * eta - 7.75 / 3.24 * eta * eta * eta) FTl6 = -8.56 / 1.05 FTa7 = -(162.85/5.04 - 214.745/1.728 * eta - 193.385/3.024 * eta*eta) \ * lal.PI dETaN = 2 * -eta / 2.0 dETa1 = 2 * -(3.0 / 4.0 + 1.0 / 12.0 * eta) dETa2 = 3 * -(27.0 / 8.0 - 19.0 / 8.0 * eta + 1. / 24.0 * eta * eta) dETa3 = 4 * -(67.5 / 6.4 - (344.45 / 5.76 - 20.5 / 9.6 * lal.PI * lal.PI) * eta + 15.5 / 9.6 * eta * eta + 3.5 / 518.4 * eta * eta * eta) amp0 = -4. * mass1 * mass2 / (1.0e+06 * distance * lal.PC_SI ) * \ lal.MRSUN_SI * lal.MTSUN_SI * sqrt(lal.PI/12.0) htildeP = FrequencySeries(zeros(n, dtype=complex128), delta_f=delta_f, copy=False) htildeC = FrequencySeries(zeros(n, dtype=complex128), delta_f=delta_f, copy=False) spintaylorf2_kernel(htildeP.data[kmin:kmax], htildeC.data[kmin:kmax], kmin, phase_order, amplitude_order, delta_f, piM, pfaN, pfa2, pfa3, pfa4, pfa5, pfl5, pfa6, pfl6, pfa7, FTaN, FTa2, FTa3, FTa4, FTa5, FTa6, FTl6, FTa7, dETaN, dETa1, dETa2, dETa3, amp0, tC, phi0, kappa, prec_fac0, alpha_ref, zeta_ref, dtdv2, dtdv3, dtdv4, dtdv5, RE_SBfac0, RE_SBfac1, RE_SBfac2, RE_SBfac3, RE_SBfac4, IM_SBfac0, IM_SBfac1, IM_SBfac2, IM_SBfac3, IM_SBfac4, psiJ_P, psiJ_C, gamma0) return htildeP, htildeC
def __init__(self, low_frequency_cutoff, high_frequency_cutoff, snr_threshold, tlen, delta_f, dtype, segment_list, template_output, use_cluster, downsample_factor=1, upsample_threshold=1, upsample_method='pruned_fft', gpu_callback_method='none'): """ Create a matched filter engine. Parameters ---------- low_frequency_cutoff : {None, float}, optional The frequency to begin the filter calculation. If None, begin at the first frequency after DC. high_frequency_cutoff : {None, float}, optional The frequency to stop the filter calculation. If None, continue to the the nyquist frequency. snr_threshold : float The minimum snr to return when filtering segment_list : list List of FrequencySeries that are the Fourier-transformed data segments template_output : complex64 Array of memory given as the 'out' parameter to waveform.FilterBank use_cluster : boolean If true, cluster triggers above threshold using a window; otherwise, only apply a threshold. downsample_factor : {1, int}, optional The factor by which to reduce the sample rate when doing a heirarchical matched filter upsample_threshold : {1, float}, optional The fraction of the snr_threshold to trigger on the subsampled filter. upsample_method : {pruned_fft, str} The method to upsample or interpolate the reduced rate filter. """ # Assuming analysis time is constant across templates and segments, also # delta_f is constant across segments. self.tlen = tlen self.flen = self.tlen / 2 + 1 self.delta_f = delta_f self.dtype = dtype self.snr_threshold = snr_threshold self.flow = low_frequency_cutoff self.fhigh = high_frequency_cutoff self.gpu_callback_method = gpu_callback_method if downsample_factor == 1: self.snr_mem = zeros(self.tlen, dtype=self.dtype) self.corr_mem = zeros(self.tlen, dtype=self.dtype) self.segments = segment_list if use_cluster: self.matched_filter_and_cluster = self.full_matched_filter_and_cluster # setup the threasholding/clustering operations for each segment self.threshold_and_clusterers = [] for seg in self.segments: thresh = events.ThresholdCluster(self.snr_mem[seg.analyze]) self.threshold_and_clusterers.append(thresh) else: self.matched_filter_and_cluster = self.full_matched_filter_thresh_only # Assuming analysis time is constant across templates and segments, also # delta_f is constant across segments. self.htilde = template_output self.kmin, self.kmax = get_cutoff_indices(self.flow, self.fhigh, self.delta_f, self.tlen) # Set up the correlation operations for each analysis segment corr_slice = slice(self.kmin, self.kmax) self.correlators = [] for seg in self.segments: corr = Correlator(self.htilde[corr_slice], seg[corr_slice], self.corr_mem[corr_slice]) self.correlators.append(corr) # setup up the ifft we will do self.ifft = IFFT(self.corr_mem, self.snr_mem) elif downsample_factor >= 1: self.matched_filter_and_cluster = self.heirarchical_matched_filter_and_cluster self.downsample_factor = downsample_factor self.upsample_method = upsample_method self.upsample_threshold = upsample_threshold N_full = self.tlen N_red = N_full / downsample_factor self.kmin_full, self.kmax_full = get_cutoff_indices( self.flow, self.fhigh, self.delta_f, N_full) self.kmin_red, _ = get_cutoff_indices(self.flow, self.fhigh, self.delta_f, N_red) if self.kmax_full < N_red: self.kmax_red = self.kmax_full else: self.kmax_red = N_red - 1 self.snr_mem = zeros(N_red, dtype=self.dtype) self.corr_mem_full = FrequencySeries(zeros(N_full, dtype=self.dtype), delta_f=self.delta_f) self.corr_mem = Array(self.corr_mem_full[0:N_red], copy=False) self.inter_vec = zeros(N_full, dtype=self.dtype) else: raise ValueError("Invalid downsample factor")
def spa_tmplt(**kwds): """ Generate a minimal TaylorF2 approximant with optimations for the sin/cos """ # Pull out the input arguments f_lower = kwds['f_lower'] delta_f = kwds['delta_f'] distance = kwds['distance'] mass1 = kwds['mass1'] mass2 = kwds['mass2'] s1z = kwds['spin1z'] s2z = kwds['spin2z'] phase_order = int(kwds['phase_order']) #amplitude_order = int(kwds['amplitude_order']) spin_order = int(kwds['spin_order']) if 'out' in kwds: out = kwds['out'] else: out = None amp_factor = spa_amplitude_factor(mass1=mass1, mass2=mass2) / distance lal_pars = lal.CreateDict() if phase_order != -1: lalsimulation.SimInspiralWaveformParamsInsertPNPhaseOrder( lal_pars, phase_order) if spin_order != -1: lalsimulation.SimInspiralWaveformParamsInsertPNSpinOrder( lal_pars, spin_order) #Calculate the PN terms phasing = lalsimulation.SimInspiralTaylorF2AlignedPhasing( float(mass1), float(mass2), float(s1z), float(s2z), lal_pars) pfaN = phasing.v[0] pfa2 = phasing.v[2] / pfaN pfa3 = phasing.v[3] / pfaN pfa4 = phasing.v[4] / pfaN pfa5 = phasing.v[5] / pfaN pfa6 = (phasing.v[6] - phasing.vlogv[6] * log(4)) / pfaN pfa7 = phasing.v[7] / pfaN pfl5 = phasing.vlogv[5] / pfaN pfl6 = phasing.vlogv[6] / pfaN piM = lal.PI * (mass1 + mass2) * lal.MTSUN_SI kmin = int(f_lower / float(delta_f)) vISCO = 1. / sqrt(6.) fISCO = vISCO * vISCO * vISCO / piM kmax = int(fISCO / delta_f) f_max = ceilpow2(fISCO) n = int(f_max / delta_f) + 1 if not out: htilde = FrequencySeries(zeros(n, dtype=numpy.complex64), delta_f=delta_f, copy=False) else: if type(out) is not Array: raise TypeError("Output must be an instance of Array") if len(out) < kmax: kmax = len(out) if out.dtype != complex64: raise TypeError("Output array is the wrong dtype") htilde = FrequencySeries(out, delta_f=delta_f, copy=False) spa_tmplt_engine(htilde[kmin:kmax], kmin, phase_order, delta_f, piM, pfaN, pfa2, pfa3, pfa4, pfa5, pfl5, pfa6, pfl6, pfa7, amp_factor) return htilde
filter_n = filter_N / 2 + 1 filter_delta_f = 1.0 / float(options.filter_signal_length) print("Number of Signal Waveforms: ", len(signal_table)) print("Number of Templates : ", len(template_table)) print("Reading and Interpolating PSD") if options.asd_file: psd = pycbc.psd.read.from_txt(options.asd_file, filter_n, filter_delta_f, options.filter_low_frequency_cutoff) elif options.psd: psd = pycbc.psd.analytic.from_string(options.psd, filter_n, filter_delta_f, options.filter_low_frequency_cutoff) psd *= DYN_RANGE_FAC**2 psd = FrequencySeries(psd, delta_f=psd.delta_f, dtype=float32) with ctx: print("Pregenerating Signals") signals = [] index = 0 for signal_params in signal_table: index += 1 update_progress(index / len(signal_table)) stilde = get_waveform(options.signal_approximant, options.signal_phase_order, options.signal_amplitude_order, signal_params, options.signal_start_frequency, options.filter_sample_rate, filter_N) s_norm = sigmasq( stilde,
def template_segment_checker(self, bank, t_num, segment, start_time): """Test if injections in segment are worth filtering with template. Using the current template, current segment, and injections within that segment. Test if the injections and sufficiently "similar" to any of the injections to justify actually performing a matched-filter call. Ther are two parts to this test: First we check if the chirp time of the template is within a provided window of any of the injections. If not then stop here, it is not worth filtering this template, segment combination for this injection set. If this check passes we compute a match between a coarse representation of the template and a coarse representation of each of the injections. If that match is above a user-provided value for any of the injections then filtering can proceed. This is currently only available if using frequency-domain templates. Parameters ----------- FIXME Returns -------- FIXME """ if not self.enabled: # If disabled, always filter (ie. return True) return True # Get times covered by segment analyze sample_rate = 2. * (len(segment) - 1) * segment.delta_f cum_ind = segment.cumulative_index diff = segment.analyze.stop - segment.analyze.start seg_start_time = cum_ind / sample_rate + start_time seg_end_time = (cum_ind + diff) / sample_rate + start_time # And add buffer seg_start_time = seg_start_time - self.seg_buffer seg_end_time = seg_end_time + self.seg_buffer # Chirp time test if self.chirp_time_window is not None: m1 = bank.table[t_num]['mass1'] m2 = bank.table[t_num]['mass2'] tau0_temp, _ = mass1_mass2_to_tau0_tau3(m1, m2, self.f_lower) for inj in self.injection_params.table: end_time = inj.geocent_end_time + \ 1E-9 * inj.geocent_end_time_ns if not (seg_start_time <= end_time <= seg_end_time): continue tau0_inj, _ = \ mass1_mass2_to_tau0_tau3(inj.mass1, inj.mass2, self.f_lower) tau_diff = abs(tau0_temp - tau0_inj) if tau_diff <= self.chirp_time_window: break else: # Get's here if all injections are outside chirp-time window return False # Coarse match test if self.match_threshold: if self._short_template_mem is None: # Set the memory for the short templates wav_len = 1 + int( self.coarsematch_fmax / self.coarsematch_deltaf) self._short_template_mem = zeros(wav_len, dtype=np.complex64) # Set the current short PSD to red_psd try: red_psd = self._short_psd_storage[id(segment.psd)] except KeyError: # PSD doesn't exist yet, so make it! curr_psd = segment.psd.numpy() step_size = int(self.coarsematch_deltaf / segment.psd.delta_f) max_idx = int(self.coarsematch_fmax / segment.psd.delta_f) + 1 red_psd_data = curr_psd[:max_idx:step_size] red_psd = FrequencySeries( red_psd_data, #copy=False, delta_f=self.coarsematch_deltaf) self._short_psd_storage[id(curr_psd)] = red_psd # Set htilde to be the current short template if not t_num == self._short_template_id: # Set the memory for the short templates if unset if self._short_template_mem is None: wav_len = 1 + int( self.coarsematch_fmax / self.coarsematch_deltaf) self._short_template_mem = zeros(wav_len, dtype=np.complex64) # Generate short waveform htilde = bank.generate_with_delta_f_and_max_freq( t_num, self.coarsematch_fmax, self.coarsematch_deltaf, low_frequency_cutoff=bank.table[t_num].f_lower, cached_mem=self._short_template_mem) self._short_template_id = t_num self._short_template_wav = htilde else: htilde = self._short_template_wav for inj in self.injection_params.table: end_time = inj.geocent_end_time + \ 1E-9 * inj.geocent_end_time_ns if not (seg_start_time < end_time < seg_end_time): continue curr_inj = self.short_injections[inj.simulation_id] o, _ = match(htilde, curr_inj, psd=red_psd, low_frequency_cutoff=self.f_lower) if o > self.match_threshold: break else: # Get's here if all injections are outside match threshold return False return True
def fd_decompress(amp, phase, sample_frequencies, out=None, df=None, f_lower=None, interpolation='linear'): """Decompresses an FD waveform using the given amplitude, phase, and the frequencies at which they are sampled at. Parameters ---------- amp : array The amplitude of the waveform at the sample frequencies. phase : array The phase of the waveform at the sample frequencies. sample_frequencies : array The frequency (in Hz) of the waveform at the sample frequencies. out : {None, FrequencySeries} The output array to save the decompressed waveform to. If this contains slots for frequencies > the maximum frequency in sample_frequencies, the rest of the values are zeroed. If not provided, must provide a df. df : {None, float} The frequency step to use for the decompressed waveform. Must be provided if out is None. f_lower : {None, float} The frequency to start the decompression at. If None, will use whatever the lowest frequency is in sample_frequencies. All values at frequencies less than this will be 0 in the decompressed waveform. interpolation : {'linear', str} The interpolation to use for the amplitude and phase. Default is 'linear'. If 'linear' a custom interpolater is used. Otherwise, ``scipy.interpolate.interp1d`` is used; for other options, see possible values for that function's ``kind`` argument. Returns ------- out : FrqeuencySeries If out was provided, writes to that array. Otherwise, a new FrequencySeries with the decompressed waveform. """ precision = _precision_map[sample_frequencies.dtype.name] if _precision_map[amp.dtype.name] != precision or \ _precision_map[phase.dtype.name] != precision: raise ValueError("amp, phase, and sample_points must all have the " "same precision") if out is None: if df is None: raise ValueError("Either provide output memory or a df") hlen = int(numpy.ceil(sample_frequencies.max() / df + 1)) out = FrequencySeries(numpy.zeros(hlen, dtype=_complex_dtypes[precision]), copy=False, delta_f=df) else: # check for precision compatibility if out.precision == 'double' and precision == 'single': raise ValueError("cannot cast single precision to double") df = out.delta_f hlen = len(out) if f_lower is None: imin = 0 f_lower = sample_frequencies[0] else: if f_lower >= sample_frequencies.max(): raise ValueError("f_lower is > than the maximum sample frequency") imin = int(numpy.searchsorted(sample_frequencies, f_lower)) start_index = int(numpy.floor(f_lower / df)) # interpolate the amplitude and the phase if interpolation == "linear": if precision == 'single': code = _linear_decompress_code32 else: code = _linear_decompress_code # use custom interpolation sflen = len(sample_frequencies) h = numpy.array(out.data, copy=False) delta_f = float(df) inline(code, ['h', 'hlen', 'sflen', 'delta_f', 'sample_frequencies', 'amp', 'phase', 'start_index', 'imin'], extra_compile_args=[WEAVE_FLAGS + '-march=native -O3 -w'] +\ omp_flags, libraries=omp_libs) else: # use scipy for fancier interpolation outfreq = out.sample_frequencies.numpy() amp_interp = interpolate.interp1d(sample_frequencies.numpy(), amp.numpy(), kind=interpolation, bounds_error=False, fill_value=0., assume_sorted=True) phase_interp = interpolate.interp1d(sample_frequencies.numpy(), phase.numpy(), kind=interpolation, bounds_error=False, fill_value=0., assume_sorted=True) A = amp_interp(outfreq) phi = phase_interp(outfreq) out.data[:] = A * numpy.cos(phi) + (1j) * A * numpy.sin(phi) return out
def get_two_pol_waveform_filter(outplus, outcross, template, **kwargs): """Return a frequency domain waveform filter for the specified approximant. Unlike get_waveform_filter this function returns both h_plus and h_cross components of the waveform, which are needed for searches where h_plus and h_cross are not related by a simple phase shift. """ n = len(outplus) # If we don't have an inclination column alpha3 might be used if not hasattr(template, 'inclination') and 'inclination' not in kwargs: if hasattr(template, 'alpha3'): kwargs['inclination'] = template.alpha3 input_params = props(template, **kwargs) if input_params['approximant'] in fd_approximants(_scheme.mgr.state): wav_gen = fd_wav[type(_scheme.mgr.state)] hp, hc = wav_gen[input_params['approximant']](**input_params) hp.resize(n) hc.resize(n) outplus[0:len(hp)] = hp[:] hp = FrequencySeries(outplus, delta_f=hp.delta_f, copy=False) outcross[0:len(hc)] = hc[:] hc = FrequencySeries(outcross, delta_f=hc.delta_f, copy=False) hp.chirp_length = get_waveform_filter_length_in_time(**input_params) hp.length_in_time = hp.chirp_length hc.chirp_length = hp.chirp_length hc.length_in_time = hp.length_in_time return hp, hc elif input_params['approximant'] in td_approximants(_scheme.mgr.state): # N: number of time samples required N = (n-1)*2 delta_f = 1.0 / (N * input_params['delta_t']) wav_gen = td_wav[type(_scheme.mgr.state)] hp, hc = wav_gen[input_params['approximant']](**input_params) # taper the time series hp if required if 'taper' in input_params.keys() and \ input_params['taper'] is not None: hp = wfutils.taper_timeseries(hp, input_params['taper'], return_lal=False) hc = wfutils.taper_timeseries(hc, input_params['taper'], return_lal=False) # total duration of the waveform tmplt_length = len(hp) * hp.delta_t # for IMR templates the zero of time is at max amplitude (merger) # thus the start time is minus the duration of the template from # lower frequency cutoff to merger, i.e. minus the 'chirp time' tChirp = - float( hp.start_time ) # conversion from LIGOTimeGPS hp.resize(N) hc.resize(N) k_zero = int(hp.start_time / hp.delta_t) hp.roll(k_zero) hc.roll(k_zero) hp_tilde = FrequencySeries(outplus, delta_f=delta_f, copy=False) hc_tilde = FrequencySeries(outcross, delta_f=delta_f, copy=False) fft(hp.astype(real_same_precision_as(hp_tilde)), hp_tilde) fft(hc.astype(real_same_precision_as(hc_tilde)), hc_tilde) hp_tilde.length_in_time = tmplt_length hp_tilde.chirp_length = tChirp hc_tilde.length_in_time = tmplt_length hc_tilde.chirp_length = tChirp return hp_tilde, hc_tilde else: raise ValueError("Approximant %s not available" % (input_params['approximant']))
def do_test(self, n_ifos, n_ifos_followup): # choose a random selection of interferometers # n_ifos will be used to generate the simulated trigger # n_ifos_followup will be used as followup-only all_ifos = random.sample(self.possible_ifos, n_ifos + n_ifos_followup) trig_ifos = all_ifos[0:n_ifos] results = { 'foreground/stat': np.random.uniform(4, 20), 'foreground/ifar': np.random.uniform(0.01, 1000) } followup_data = {} for ifo in all_ifos: offset = 10000 + np.random.uniform(-0.02, 0.02) amplitude = np.random.uniform(4, 20) # generate a mock SNR time series with a peak n = 201 dt = 1. / 2048. t = np.arange(n) * dt t_peak = dt * n / 2 snr = np.exp(-(t - t_peak)**2 * 3e-3**-2) * amplitude snr_series = TimeSeries((snr + 1j * 0).astype(np.complex64), delta_t=dt, epoch=offset) # generate a mock PSD psd_samples = np.random.exponential(size=1024) psd = FrequencySeries(psd_samples, delta_f=1.) # fill in the various fields if ifo in trig_ifos: base = 'foreground/' + ifo + '/' results[base + 'end_time'] = t_peak + offset results[base + 'snr'] = amplitude results[base + 'sigmasq'] = np.random.uniform(1e6, 2e6) followup_data[ifo] = {'snr_series': snr_series, 'psd': psd} for ifo, k in itertools.product(trig_ifos, self.template): results['foreground/' + ifo + '/' + k] = self.template[k] kwargs = { 'psds': {ifo: followup_data[ifo]['psd'] for ifo in all_ifos}, 'low_frequency_cutoff': 20., 'followup_data': followup_data } coinc = SingleCoincForGraceDB(trig_ifos, results, **kwargs) tempdir = tempfile.mkdtemp() coinc_file_name = os.path.join(tempdir, 'coinc.xml.gz') if GraceDb is not None: # pretend to upload the event to GraceDB. # The upload will fail, but it should not raise an exception # and it should still leave the event file around coinc.upload(coinc_file_name, gracedb_server='localhost', testing=True) else: # no GraceDb module, so just save the coinc file coinc.save(coinc_file_name) # read back and check the coinc document read_coinc = ligolw_utils.load_filename(coinc_file_name, verbose=False, contenthandler=ContentHandler) single_table = table.get_table(read_coinc, lsctables.SnglInspiralTable.tableName) self.assertEqual(len(single_table), len(all_ifos)) coinc_table = table.get_table(read_coinc, lsctables.CoincInspiralTable.tableName) self.assertEqual(len(coinc_table), 1) # make sure lalseries can read the PSDs psd_doc = ligolw_utils.load_filename( coinc_file_name, verbose=False, contenthandler=lalseries.PSDContentHandler) psd_dict = lalseries.read_psd_xmldoc(psd_doc) self.assertEqual(set(psd_dict.keys()), set(all_ifos)) shutil.rmtree(tempdir)
def get_fd_lm(template=None, **kwargs): """Return frequency domain lm mode with a given number of overtones. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. final_mass : float Mass of the final black hole. final_spin : float Spin of the final black hole. l : int l mode (lm modes available: 22, 21, 33, 44, 55). m : int m mode (lm modes available: 22, 21, 33, 44, 55). nmodes: int Number of overtones desired (maximum n=8) amplmn : float Amplitude of the lmn overtone, as many as the number of nmodes. philmn : float Phase of the lmn overtone, as many as the number of nmodes. delta_f : {None, float}, optional The frequency step used to generate the ringdown. If None, it will be set to the inverse of the time at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). f_lower: {None, float}, optional The starting frequency of the output frequency series. If None, it will be set to delta_f. f_final : {None, float}, optional The ending frequency of the output frequency series. If None, it will be set to the frequency at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a lm mode with n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a lm mode with n overtones in frequency domain. """ input_params = props(template, lm_required_args, **kwargs) # Get required args amps, phis = lm_amps_phases(**input_params) final_mass = input_params.pop('final_mass') final_spin = input_params.pop('final_spin') l, m = input_params.pop('l'), input_params.pop('m') nmodes = input_params.pop('nmodes') if int(nmodes) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_f = input_params.pop('delta_f', None) f_lower = input_params.pop('f_lower', None) f_final = input_params.pop('f_final', None) if delta_f is None: delta_f = lm_deltaf(final_mass, final_spin, ['%d%d%d' % (l, m, nmodes)]) if f_final is None: f_final = lm_ffinal(final_mass, final_spin, ['%d%d%d' % (l, m, nmodes)]) kmax = int(f_final / delta_f) + 1 outplus = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) outcross = FrequencySeries(zeros(kmax, dtype=complex128), delta_f=delta_f) f_0, tau = get_lm_f0tau(final_mass, final_spin, l, m, nmodes) for n in range(nmodes): hplus, hcross = get_fd_qnm(template=None, f_0=f_0[n], tau=tau[n], phi=phis['%d%d%d' % (l, m, n)], amp=amps['%d%d%d' % (l, m, n)], delta_f=delta_f, f_lower=f_lower, f_final=f_final) outplus.data += hplus.data outcross.data += hcross.data return outplus, outcross
def get_waveform(approximant, phase_order, amplitude_order, spin_order, template_params, start_frequency, sample_rate, length, datafile=None, verbose=False): #{{{ print("IN hERE") delta_t = 1. / sample_rate delta_f = 1. / length filter_N = int(length) filter_n = filter_N / 2 + 1 if approximant in fd_approximants() and 'Eccentric' not in approximant: print("NORMAL FD WAVEFORM for", approximant) delta_f = sample_rate / length hplus, hcross = get_fd_waveform(template_params, approximant=approximant, spin_order=spin_order, phase_order=phase_order, delta_f=delta_f, f_lower=start_frequency, amplitude_order=amplitude_order) elif approximant in td_approximants() and 'Eccentric' not in approximant: print("NORMAL TD WAVEFORM for", approximant) hplus, hcross = get_td_waveform(template_params, approximant=approximant, spin_order=spin_order, phase_order=phase_order, delta_t=1.0 / sample_rate, f_lower=start_frequency, amplitude_order=amplitude_order) elif 'EccentricIMR' in approximant: #{{{ # Legacy support import sys sys.path.append('/home/kuma/grav/kuma/src/Eccentric_IMR/Codes/Python/') import EccentricIMR as Ecc try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') rtrans = getattr(template_params, 'alpha') beta = 0 except: raise RuntimeError(\ "template_params does not have alpha{,1,2} or inclination") tol = 1.e-16 fmin = start_frequency sample_rate = sample_rate # print(" Using phase order: %d" % phase_order, file=sys.stdout) sys.stdout.flush() hplus, hcross = Ecc.generate_eccentric_waveform(mass1, mass2,\ ecc, anom, inc, beta,\ tol,\ r_transition=rtrans,\ phase_order=phase_order,\ fmin=fmin,\ sample_rate=sample_rate,\ inspiral_only=False) #}}} elif 'EccentricInspiral' in approximant: #{{{ # Legacy support import sys sys.path.append('/home/kuma/grav/kuma/src/Eccentric_IMR/Codes/Python/') import EccentricIMR as Ecc try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') beta = getattr(template_params, 'alpha') except: raise RuntimeError(\ "template_params does not have alpha{,1,2} or inclination") tol = 1.e-16 fmin = start_frequency sample_rate = sample_rate # hplus, hcross = Ecc.generate_eccentric_waveform(mass1, mass2,\ ecc, anom, inc, beta,\ tol,\ phase_order=phase_order,\ fmin=fmin,\ sample_rate=sample_rate,\ inspiral_only=True) #}}} elif 'EccentricFD' in approximant: #{{{ # Legacy support import lalsimulation as ls import lal delta_f = sample_rate / length try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') except: raise RuntimeError(\ "template_params does not have alpha{1,2} or inclination") eccPar = ls.SimInspiralCreateTestGRParam("inclination_azimuth", inc) ls.SimInspiralAddTestGRParam(eccPar, "e_min", ecc) fmin = start_frequency fmax = sample_rate / 2 # thp, thc = ls.SimInspiralChooseFDWaveform(0, delta_f,\ mass1*lal.MSUN_SI, mass2*lal.MSUN_SI,\ 0,0,0,0,0,0,\ fmin, fmax, 0, 1.e6 * lal.PC_SI,\ inc, 0, 0, None, eccPar, -1, 7, ls.EccentricFD) hplus = FrequencySeries(thp.data.data[:], delta_f=thp.deltaF, epoch=thp.epoch) hcross = FrequencySeries(thc.data.data[:], delta_f=thc.deltaF, epoch=thc.epoch) #}}} elif 'FromDataFile' in approximant: #{{{ # Legacy support if not os.path.exists(datafile): raise IOError("File %s not found!" % datafile) if verbose: print("Reading from data file %s" % datafile) # Figure out waveform parameters from filename #q_value, M_value, w_value, _, _ = EA.get_q_m_e_pn_o_from_filename(datafile) q_value, M_value, w_value = EA.get_q_m_e_from_filename(datafile) # Read data, down-sample (assume data file is more finely sampled than # needed, i.e. interpolation is NOT supported, nor will be) data = np.loadtxt(datafile) dt = data[1, 0] - data[0, 0] delta_t = 1. / sample_rate downsample_ratio = delta_t / dt if not approx_equal(downsample_ratio, np.int(downsample_ratio)): raise RuntimeError( "Cannot handling resampling at a fractional factor = %e" % downsample_ratio) elif verbose: print("Downsampling by a factor of %d" % int(downsample_ratio)) h_real = TimeSeries(data[::int(downsample_ratio), 1] / DYN_RANGE_FAC, delta_t=delta_t) h_imag = TimeSeries(data[::int(downsample_ratio), 2] / DYN_RANGE_FAC, delta_t=delta_t) if verbose: print("max, min,len of h_real = ", max(h_real.data), min(h_real.data), len(h_real.data)) # Compute Strain tmplt_pars = template_params wav = generate_detector_strain(tmplt_pars, h_real, h_imag) wav = extend_waveform_TimeSeries(wav, filter_N) # Return TimeSeries with (m1, m2, w_value) m1, m2 = mtotal_eta_to_mass1_mass2(M_value, q_value / (1. + q_value)**2) htilde = make_frequency_series(wav) htilde = extend_waveform_FrequencySeries(htilde, filter_n) if verbose: print("ISNAN(htilde from file) = ", np.any(np.isnan(htilde.data))) return htilde, [m1, m2, w_value, dt] #}}} else: raise IOError(".. APPROXIMANT %s not found.." % approximant) ## hvec = hplus htilde = make_frequency_series(hvec) htilde = extend_waveform_FrequencySeries(htilde, filter_n) # print("type of hplus, hcross = ", type(hplus.data), type(hcross.data)) if any(isnan(hplus.data)) or any(isnan(hcross.data)): print("..### %s hplus or hcross have NANS!!" % approximant) # if any(isinf(hplus.data)) or any(isinf(hcross.data)): print("..### %s hplus or hcross have INFS!!" % approximant) # if any(isnan(htilde.data)): print("..### %s Fourier transform htilde has NANS!!" % approximant) # if any(isinf(htilde.data)): print("..### %s Fourier transform htilde has INFS!!" % approximant) # return htilde
def cbrt_lookup(vmax, delta): vec = numpy.arange(0, vmax * 1.2, delta) return FrequencySeries(vec**(1.0 / 3.0), delta_f=delta).astype(float32)
def imrphenomc_tmplt(**kwds): """ Return an IMRPhenomC waveform using CUDA to generate the phase and amplitude Main Paper: arXiv:1005.3306 """ # Pull out the input arguments f_min = float128(kwds['f_lower']) f_max = float128(kwds['f_final']) delta_f = float128(kwds['delta_f']) distance = float128(kwds['distance']) mass1 = float128(kwds['mass1']) mass2 = float128(kwds['mass2']) spin1z = float128(kwds['spin1z']) spin2z = float128(kwds['spin2z']) if 'out' in kwds: out = kwds['out'] else: out = None # Calculate binary parameters M = mass1 + mass2 eta = mass1 * mass2 / (M * M) Xi = (mass1 * spin1z / M) + (mass2 * spin2z / M) Xisum = 2. * Xi Xiprod = Xi * Xi Xi2 = Xi * Xi m_sec = M * lal.MTSUN_SI piM = lal.PI * m_sec ## The units of distance given as input is taken to pe Mpc. Converting to SI distance *= ( 1.0e6 * lal.PC_SI / (2. * sqrt(5. / (64. * lal.PI)) * M * lal.MRSUN_SI * M * lal.MTSUN_SI)) # Check if the value of f_max is correctly given, else replace with the fCut # used in the PhenomB code in lalsimulation. The various coefficients come # from Eq.(4.18) of http://arxiv.org/pdf/0710.2335 and # Table I of http://arxiv.org/pdf/0712.0343 if not f_max: f_max = (1.7086 * eta * eta - 0.26592 * eta + 0.28236) / piM # Transform the eta, chi to Lambda parameters, using Eq 5.14, Table II of Main # paper. z101 = -2.417e-03 z102 = -1.093e-03 z111 = -1.917e-02 z110 = 7.267e-02 z120 = -2.504e-01 z201 = 5.962e-01 z202 = -5.600e-02 z211 = 1.520e-01 z210 = -2.970e+00 z220 = 1.312e+01 z301 = -3.283e+01 z302 = 8.859e+00 z311 = 2.931e+01 z310 = 7.954e+01 z320 = -4.349e+02 z401 = 1.619e+02 z402 = -4.702e+01 z411 = -1.751e+02 z410 = -3.225e+02 z420 = 1.587e+03 z501 = -6.320e+02 z502 = 2.463e+02 z511 = 1.048e+03 z510 = 3.355e+02 z520 = -5.115e+03 z601 = -4.809e+01 z602 = -3.643e+02 z611 = -5.215e+02 z610 = 1.870e+03 z620 = 7.354e+02 z701 = 4.149e+00 z702 = -4.070e+00 z711 = -8.752e+01 z710 = -4.897e+01 z720 = 6.665e+02 z801 = -5.472e-02 z802 = 2.094e-02 z811 = 3.554e-01 z810 = 1.151e-01 z820 = 9.640e-01 z901 = -1.235e+00 z902 = 3.423e-01 z911 = 6.062e+00 z910 = 5.949e+00 z920 = -1.069e+01 eta2 = eta * eta Xi2 = Xiprod # Calculate alphas, gamma, deltas from Table II and Eq 5.14 of Main paper a1 = z101 * Xi + z102 * Xi2 + z111 * eta * Xi + z110 * eta + z120 * eta2 a2 = z201 * Xi + z202 * Xi2 + z211 * eta * Xi + z210 * eta + z220 * eta2 a3 = z301 * Xi + z302 * Xi2 + z311 * eta * Xi + z310 * eta + z320 * eta2 a4 = z401 * Xi + z402 * Xi2 + z411 * eta * Xi + z410 * eta + z420 * eta2 a5 = z501 * Xi + z502 * Xi2 + z511 * eta * Xi + z510 * eta + z520 * eta2 a6 = z601 * Xi + z602 * Xi2 + z611 * eta * Xi + z610 * eta + z620 * eta2 g1 = z701 * Xi + z702 * Xi2 + z711 * eta * Xi + z710 * eta + z720 * eta2 del1 = z801 * Xi + z802 * Xi2 + z811 * eta * Xi + z810 * eta + z820 * eta2 del2 = z901 * Xi + z902 * Xi2 + z911 * eta * Xi + z910 * eta + z920 * eta2 # Get the spin of the final BH afin = FinalSpin(Xi, eta) Q = Qa(abs(afin)) # Get the fRD frd = fRD(abs(afin), M) Mfrd = frd * m_sec # Define the frequencies where SPA->PM->RD f1 = 0.1 * frd Mf1 = m_sec * f1 f2 = frd Mf2 = m_sec * f2 d1 = 0.005 d2 = 0.005 f0 = 0.98 * frd Mf0 = m_sec * f0 d0 = 0.015 # Now use this frequency for calculation of betas # calculate beta1 and beta2, that appear in Eq 5.7 in the main paper. b2 = ((-5./3.)* a1 * pow(Mfrd,(-8./3.)) - a2/(Mfrd*Mfrd) - \ (a3/3.)*pow(Mfrd,(-4./3.)) + (2./3.)* a5 * pow(Mfrd,(-1./3.)) + a6)/eta psiPMrd = (a1 * pow(Mfrd,(-5./3.)) + a2/Mfrd + a3 * pow(Mfrd,(-1./3.)) + \ a4 + a5 * pow(Mfrd,(2./3.)) + a6 * Mfrd)/eta b1 = psiPMrd - (b2 * Mfrd) ### Calculate the PN coefficients, Eq A3 - A5 of main paper ### pfaN = 3.0 / (128.0 * eta) pfa2 = (3715. / 756.) + (55. * eta / 9.0) pfa3 = -16.0 * lal.PI + (113. / 3.) * Xi - 38. * eta * Xisum / 3. pfa4 = (152.93365/5.08032) - 50.*Xi2 + eta*(271.45/5.04 + 1.25*Xiprod) + \ 3085.*eta2/72. pfa5 = lal.PI*(386.45/7.56 - 65.*eta/9.) - \ Xi*(735.505/2.268 + 130.*eta/9.) + Xisum*(1285.0*eta/8.1 + 170.*eta2/9.) - \ 10.*Xi2*Xi/3. + 10.*eta*Xi*Xiprod pfa6 = 11583.231236531/4.694215680 - 640.0*lal.PI*lal.PI/3. - \ 6848.0*lal.GAMMA/21. - 684.8*log(64.)/6.3 + \ eta*(2255.*lal.PI*lal.PI/12. - 15737.765635/3.048192) + \ 76.055*eta2/1.728 - (127.825*eta2*eta/1.296) + \ 2920.*lal.PI*Xi/3. - (175. - 1490.*eta)*Xi2/3. - \ (1120.*lal.PI/3. - 1085.*Xi/3.)*eta*Xisum + \ (269.45*eta/3.36 - 2365.*eta2/6.)*Xiprod pfa6log = -6848. / 63. pfa7 = lal.PI*(770.96675/2.54016 + 378.515*eta/1.512 - 740.45*eta2/7.56) - \ Xi*(20373.952415/3.048192 + 1509.35*eta/2.24 - 5786.95*eta2/4.32) + \ Xisum*(4862.041225*eta/1.524096 + 1189.775*eta2/1.008 - 717.05*eta2*eta/2.16 - 830.*eta*Xi2/3. + 35.*eta2*Xiprod/3.) - \ 560.*lal.PI*Xi2 + 20.*lal.PI*eta*Xiprod + \ Xi2*Xi*(945.55/1.68 - 85.*eta) + Xi*Xiprod*(396.65*eta/1.68 + 255.*eta2) xdotaN = 64. * eta / 5. xdota2 = -7.43 / 3.36 - 11. * eta / 4. xdota3 = 4. * lal.PI - 11.3 * Xi / 1.2 + 19. * eta * Xisum / 6. xdota4 = 3.4103 / 1.8144 + 5 * Xi2 + eta * (13.661 / 2.016 - Xiprod / 8.) + 5.9 * eta2 / 1.8 xdota5 = -lal.PI*(41.59/6.72 + 189.*eta/8.) - Xi*(31.571/1.008 - 116.5*eta/2.4) + \ Xisum*(21.863*eta/1.008 - 79.*eta2/6.) - 3*Xi*Xi2/4. + \ 9.*eta*Xi*Xiprod/4. xdota6 = 164.47322263/1.39708800 - 17.12*lal.GAMMA/1.05 + \ 16.*lal.PI*lal.PI/3 - 8.56*log(16.)/1.05 + \ eta*(45.1*lal.PI*lal.PI/4.8 - 561.98689/2.17728) + \ 5.41*eta2/8.96 - 5.605*eta*eta2/2.592 - 80.*lal.PI*Xi/3. + \ eta*Xisum*(20.*lal.PI/3. - 113.5*Xi/3.6) + \ Xi2*(64.153/1.008 - 45.7*eta/3.6) - \ Xiprod*(7.87*eta/1.44 - 30.37*eta2/1.44) xdota6log = -856. / 105. xdota7 = -lal.PI*(4.415/4.032 - 358.675*eta/6.048 - 91.495*eta2/1.512) - \ Xi*(252.9407/2.7216 - 845.827*eta/6.048 + 415.51*eta2/8.64) + \ Xisum*(158.0239*eta/5.4432 - 451.597*eta2/6.048 + 20.45*eta2*eta/4.32 + 107.*eta*Xi2/6. - 5.*eta2*Xiprod/24.) + \ 12.*lal.PI*Xi2 - Xi2*Xi*(150.5/2.4 + eta/8.) + \ Xi*Xiprod*(10.1*eta/2.4 + 3.*eta2/8.) AN = 8. * eta * sqrt(lal.PI / 5.) A2 = (-107. + 55. * eta) / 42. A3 = 2. * lal.PI - 4. * Xi / 3. + 2. * eta * Xisum / 3. A4 = -2.173 / 1.512 - eta * (10.69 / 2.16 - 2. * Xiprod) + 2.047 * eta2 / 1.512 A5 = -10.7 * lal.PI / 2.1 + eta * (3.4 * lal.PI / 2.1) A5imag = -24. * eta A6 = 270.27409/6.46800 - 8.56*lal.GAMMA/1.05 + \ 2.*lal.PI*lal.PI/3. + \ eta*(4.1*lal.PI*lal.PI/9.6 - 27.8185/3.3264) - \ 20.261*eta2/2.772 + 11.4635*eta*eta2/9.9792 - \ 4.28*log(16.)/1.05 A6log = -428. / 105. A6imag = 4.28 * lal.PI / 1.05 ### Define other parameters needed by waveform generation ### kmin = int(f_min / delta_f) kmax = int(f_max / delta_f) n = kmax + 1 if not out: htilde = FrequencySeries(zeros(n, dtype=numpy.complex128), delta_f=delta_f, copy=False) else: if type(out) is not Array: raise TypeError("Output must be an instance of Array") if len(out) < kmax: raise TypeError("Output array is too small") if out.dtype != complex64: raise TypeError("Output array is the wrong dtype") htilde = FrequencySeries(out, delta_f=delta_f, copy=False) phenomC_kernel(htilde.data[kmin:kmax], kmin, delta_f, eta, Xi, distance, m_sec, piM, Mfrd, pfaN, pfa2, pfa3, pfa4, pfa5, pfa6, pfa6log, pfa7, a1, a2, a3, a4, a5, a6, b1, b2, Mf1, Mf2, Mf0, d1, d2, d0, xdota2, xdota3, xdota4, xdota5, xdota6, xdota6log, xdota7, xdotaN, AN, A2, A3, A4, A5, A5imag, A6, A6log, A6imag, g1, del1, del2, Q) hp = htilde hc = htilde * 1j return hp, hc
def fd_decompress(amp, phase, sample_frequencies, out=None, df=None, f_lower=None, interpolation='inline_linear'): """Decompresses an FD waveform using the given amplitude, phase, and the frequencies at which they are sampled at. Parameters ---------- amp : array The amplitude of the waveform at the sample frequencies. phase : array The phase of the waveform at the sample frequencies. sample_frequencies : array The frequency (in Hz) of the waveform at the sample frequencies. out : {None, FrequencySeries} The output array to save the decompressed waveform to. If this contains slots for frequencies > the maximum frequency in sample_frequencies, the rest of the values are zeroed. If not provided, must provide a df. df : {None, float} The frequency step to use for the decompressed waveform. Must be provided if out is None. f_lower : {None, float} The frequency to start the decompression at. If None, will use whatever the lowest frequency is in sample_frequencies. All values at frequencies less than this will be 0 in the decompressed waveform. interpolation : {'inline_linear', str} The interpolation to use for the amplitude and phase. Default is 'inline_linear'. If 'inline_linear' a custom interpolater is used. Otherwise, ``scipy.interpolate.interp1d`` is used; for other options, see possible values for that function's ``kind`` argument. Returns ------- out : FrequencySeries If out was provided, writes to that array. Otherwise, a new FrequencySeries with the decompressed waveform. """ precision = _precision_map[sample_frequencies.dtype.name] if _precision_map[amp.dtype.name] != precision or \ _precision_map[phase.dtype.name] != precision: raise ValueError("amp, phase, and sample_points must all have the " "same precision") if out is None: if df is None: raise ValueError("Either provide output memory or a df") hlen = int(numpy.ceil(sample_frequencies.max() / df + 1)) out = FrequencySeries(numpy.zeros(hlen, dtype=_complex_dtypes[precision]), copy=False, delta_f=df) else: # check for precision compatibility if out.precision == 'double' and precision == 'single': raise ValueError("cannot cast single precision to double") df = out.delta_f hlen = len(out) if f_lower is None: imin = 0 # pylint:disable=unused-variable f_lower = sample_frequencies[0] start_index = 0 else: if f_lower >= sample_frequencies.max(): raise ValueError("f_lower is > than the maximum sample frequency") if f_lower < sample_frequencies.min(): raise ValueError("f_lower is < than the minimum sample frequency") imin = int( numpy.searchsorted(sample_frequencies, f_lower, side='right')) - 1 # pylint:disable=unused-variable start_index = int(numpy.ceil(f_lower / df)) if start_index >= hlen: raise ValueError('requested f_lower >= largest frequency in out') # interpolate the amplitude and the phase if interpolation == "inline_linear": # Call the scheme-dependent function inline_linear_interp(amp, phase, sample_frequencies, out, df, f_lower, imin, start_index) else: # use scipy for fancier interpolation sample_frequencies = numpy.array(sample_frequencies) amp = numpy.array(amp) phase = numpy.array(phase) outfreq = out.sample_frequencies.numpy() amp_interp = interpolate.interp1d(sample_frequencies, amp, kind=interpolation, bounds_error=False, fill_value=0., assume_sorted=True) phase_interp = interpolate.interp1d(sample_frequencies, phase, kind=interpolation, bounds_error=False, fill_value=0., assume_sorted=True) A = amp_interp(outfreq) phi = phase_interp(outfreq) out.data[:] = A * numpy.cos(phi) + (1j) * A * numpy.sin(phi) return out
def calc_filt_psd_variation(strain, segment, short_segment, psd_long_segment, psd_duration, psd_stride, psd_avg_method, low_freq, high_freq): """ Calculates time series of PSD variability This function first splits the segment up into 512 second chunks. It then calculates the PSD over this 512 second. The PSD is used to to create a filter that is the composition of three filters: 1. Bandpass filter between f_low and f_high. 2. Weighting filter which gives the rough response of a CBC template. 3. Whitening filter. Next it makes the convolution of this filter with the stretch of data. This new time series is given to the "mean_square" function, which computes the mean square of the timeseries within an 8 seconds window, once per second. The result, which is the variance of the S/N in that stride for the Parseval theorem, is then stored in a timeseries. Parameters ---------- strain : TimeSeries Input strain time series to estimate PSDs segment : {float, 8} Duration of the segments for the mean square estimation in seconds. short_segment : {float, 0.25} Duration of the short segments for the outliers removal. psd_long_segment : {float, 512} Duration of the long segments for PSD estimation in seconds. psd_duration : {float, 8} Duration of FFT segments for long term PSD estimation, in seconds. psd_stride : {float, 4} Separation between FFT segments for long term PSD estimation, in seconds. psd_avg_method : {string, 'median'} Method for averaging PSD estimation segments. low_freq : {float, 20} Minimum frequency to consider the comparison between PSDs. high_freq : {float, 480} Maximum frequency to consider the comparison between PSDs. Returns ------- psd_var : TimeSeries Time series of the variability in the PSD estimation """ # Calculate strain precision if strain.precision == 'single': fs_dtype = numpy.float32 elif strain.precision == 'double': fs_dtype = numpy.float64 # Convert start and end times immediately to floats start_time = numpy.float(strain.start_time) end_time = numpy.float(strain.end_time) # Resample the data strain = resample_to_delta_t(strain, 1.0 / 2048) srate = int(strain.sample_rate) # Fix the step for the PSD estimation and the time to remove at the # edge of the time series. step = 1.0 strain_crop = 8.0 # Find the times of the long segments times_long = numpy.arange( start_time, end_time, psd_long_segment - 2 * strain_crop - segment + step) # Set up the empty time series for the PSD variation estimate ts_duration = end_time - start_time - 2 * strain_crop - segment + 1 psd_var = TimeSeries(zeros(int(numpy.floor(ts_duration / step))), delta_t=step, copy=False, epoch=start_time + strain_crop + segment) # Create a bandpass filter between low_freq and high_freq filt = sig.firwin(4 * srate, [low_freq, high_freq], pass_zero=False, window='hann', nyq=srate / 2) filt.resize(int(psd_duration * srate)) # Fourier transform the filter and take the absolute value to get # rid of the phase. Save the filter as a frequency series. filt = abs(rfft(filt)) my_filter = FrequencySeries(filt, delta_f=1. / psd_duration, dtype=fs_dtype) ind = 0 for tlong in times_long: # Calculate PSD for long segment if tlong + psd_long_segment <= float(end_time): astrain = strain.time_slice(tlong, tlong + psd_long_segment) plong = pycbc.psd.welch( astrain, seg_len=int(psd_duration * strain.sample_rate), seg_stride=int(psd_stride * strain.sample_rate), avg_method=psd_avg_method) else: astrain = strain.time_slice(tlong, end_time) plong = pycbc.psd.welch( strain.time_slice(end_time - psd_long_segment, end_time), seg_len=int(psd_duration * strain.sample_rate), seg_stride=int(psd_stride * strain.sample_rate), avg_method=psd_avg_method) # Make the weighting filter - bandpass, which weight by f^-7/6, # and whiten. The normalization is chosen so that the variance # will be one if this filter is applied to white noise which # already has a variance of one. freqs = FrequencySeries(plong.sample_frequencies, delta_f=plong.delta_f, epoch=plong.epoch, dtype=fs_dtype) fweight = freqs**(-7. / 6.) * my_filter / numpy.sqrt(plong) fweight[0] = 0. norm = (sum(abs(fweight)**2) / (len(fweight) - 1.))**-0.5 fweight = norm * fweight fwhiten = numpy.sqrt(2. / srate) / numpy.sqrt(plong) fwhiten[0] = 0. full_filt = sig.hann(int(psd_duration * srate)) * numpy.roll( irfft(fwhiten * fweight), int(psd_duration / 2) * srate) # Convolve the filter with long segment of data wstrain = TimeSeries(sig.fftconvolve(astrain, full_filt, mode='same'), delta_t=strain.delta_t, epoch=astrain.start_time) wstrain = wstrain[int(strain_crop * srate):-int(strain_crop * srate)] # compute the mean square of the chunk of data delta_t = wstrain.end_time.gpsSeconds - wstrain.start_time.gpsSeconds variation = mean_square(wstrain, delta_t, short_segment, segment) # Store variation value for i, val in enumerate(variation): psd_var[ind + i] = val ind = ind + len(variation) return psd_var
def welch(timeseries, seg_len=4096, seg_stride=2048, window='hann', avg_method='median', num_segments=None, require_exact_data_fit=False): """PSD estimator based on Welch's method. Parameters ---------- timeseries : TimeSeries Time series for which the PSD is to be estimated. seg_len : int Segment length in samples. seg_stride : int Separation between consecutive segments, in samples. window : {'hann'} Function used to window segments before Fourier transforming. avg_method : {'median', 'mean', 'median-mean'} Method used for averaging individual segment PSDs. Returns ------- psd : FrequencySeries Frequency series containing the estimated PSD. Raises ------ ValueError For invalid choices of `seg_len`, `seg_stride` `window` and `avg_method` and for inconsistent combinations of len(`timeseries`), `seg_len` and `seg_stride`. Notes ----- See arXiv:gr-qc/0509116 for details. """ window_map = { 'hann': numpy.hanning } # sanity checks if not window in window_map: raise ValueError('Invalid window') if not avg_method in ('mean', 'median', 'median-mean'): raise ValueError('Invalid averaging method') if type(seg_len) is not int or type(seg_stride) is not int \ or seg_len <= 0 or seg_stride <= 0: raise ValueError('Segment length and stride must be positive integers') if timeseries.precision == 'single': fs_dtype = numpy.complex64 elif timeseries.precision == 'double': fs_dtype = numpy.complex128 num_samples = len(timeseries) if num_segments is None: num_segments = int(num_samples // seg_stride) # NOTE: Is this not always true? if (num_segments - 1) * seg_stride + seg_len > num_samples: num_segments -= 1 if not require_exact_data_fit: data_len = (num_segments - 1) * seg_stride + seg_len # Get the correct amount of data if data_len < num_samples: diff = num_samples - data_len start = diff // 2 end = num_samples - diff // 2 # Want this to be integers so if diff is odd, catch it here. if diff % 2: start = start + 1 timeseries = timeseries[start:end] num_samples = len(timeseries) if data_len > num_samples: err_msg = "I was asked to estimate a PSD on %d " %(data_len) err_msg += "data samples. However the data provided only contains " err_msg += "%d data samples." %(num_samples) if num_samples != (num_segments - 1) * seg_stride + seg_len: raise ValueError('Incorrect choice of segmentation parameters') w = Array(window_map[window](seg_len).astype(timeseries.dtype)) # calculate psd of each segment delta_f = 1. / timeseries.delta_t / seg_len segment_tilde = FrequencySeries(numpy.zeros(seg_len / 2 + 1), \ delta_f=delta_f, dtype=fs_dtype) segment_psds = [] for i in xrange(num_segments): segment_start = i * seg_stride segment_end = segment_start + seg_len segment = timeseries[segment_start:segment_end] assert len(segment) == seg_len fft(segment * w, segment_tilde) seg_psd = abs(segment_tilde * segment_tilde.conj()).numpy() #halve the DC and Nyquist components to be consistent with TO10095 seg_psd[0] /= 2 seg_psd[-1] /= 2 segment_psds.append(seg_psd) segment_psds = numpy.array(segment_psds) if avg_method == 'mean': psd = numpy.mean(segment_psds, axis=0) elif avg_method == 'median': psd = numpy.median(segment_psds, axis=0) / median_bias(num_segments) elif avg_method == 'median-mean': odd_psds = segment_psds[::2] even_psds = segment_psds[1::2] odd_median = numpy.median(odd_psds, axis=0) / \ median_bias(len(odd_psds)) even_median = numpy.median(even_psds, axis=0) / \ median_bias(len(even_psds)) psd = (odd_median + even_median) / 2 psd *= 2 * delta_f * seg_len / (w*w).sum() return FrequencySeries(psd, delta_f=delta_f, dtype=timeseries.dtype, epoch=timeseries.start_time)
def matched_filter_core(template, data, psd=None, low_frequency_cutoff=None, high_frequency_cutoff=None, h_norm=None, out=None, corr_out=None): """ Return the complex snr and normalization. Return the complex snr, along with its associated normalization of the template, matched filtered against the data. Parameters ---------- template : TimeSeries or FrequencySeries The template waveform data : TimeSeries or FrequencySeries The strain data to be filtered. psd : {FrequencySeries}, optional The noise weighting of the filter. low_frequency_cutoff : {None, float}, optional The frequency to begin the filter calculation. If None, begin at the first frequency after DC. high_frequency_cutoff : {None, float}, optional The frequency to stop the filter calculation. If None, continue to the the nyquist frequency. h_norm : {None, float}, optional The template normalization. If none, this value is calculated internally. out : {None, Array}, optional An array to use as memory for snr storage. If None, memory is allocated internally. corr_out : {None, Array}, optional An array to use as memory for correlation storage. If None, memory is allocated internally. If provided, management of the vector is handled externally by the caller. No zero'ing is done internally. Returns ------- snr : TimeSeries A time series containing the complex snr. corrrelation: FrequencySeries A frequency series containing the correlation vector. norm : float The normalization of the complex snr. """ if corr_out is not None: _qtilde = corr_out else: global _qtilde_t _qtilde = _qtilde_t htilde = make_frequency_series(template) stilde = make_frequency_series(data) if len(htilde) != len(stilde): raise ValueError("Length of template and data must match") N = (len(stilde) - 1) * 2 kmin, kmax = get_cutoff_indices(low_frequency_cutoff, high_frequency_cutoff, stilde.delta_f, N) if out is None: _q = zeros(N, dtype=complex_same_precision_as(data)) elif (len(out) == N) and type(out) is Array and out.kind == 'complex': _q = out else: raise TypeError('Invalid Output Vector: wrong length or dtype') if corr_out: pass elif (_qtilde is None) or (len(_qtilde) != N) or _qtilde.dtype != data.dtype: _qtilde_t = _qtilde = zeros(N, dtype=complex_same_precision_as(data)) else: _qtilde.clear() correlate(htilde[kmin:kmax], stilde[kmin:kmax], _qtilde[kmin:kmax]) if psd is not None: if isinstance(psd, FrequencySeries): if psd.delta_f == stilde.delta_f: _qtilde[kmin:kmax] /= psd[kmin:kmax] else: raise TypeError("PSD delta_f does not match data") else: raise TypeError("PSD must be a FrequencySeries") ifft(_qtilde, _q) if h_norm is None: h_norm = sigmasq(htilde, psd, low_frequency_cutoff, high_frequency_cutoff) norm = (4.0 * stilde.delta_f) / sqrt(h_norm) delta_t = 1.0 / (N * stilde.delta_f) return (TimeSeries(_q, epoch=stilde._epoch, delta_t=delta_t, copy=False), FrequencySeries(_qtilde, epoch=stilde._epoch, delta_f=htilde.delta_f, copy=False), norm)