def spher_harms(l, m, inclination): """Return spherical harmonic polarizations """ # FIXME: we are using spin -2 weighted spherical harmonics for now, # when possible switch to spheroidal harmonics. Y_lm = lal.SpinWeightedSphericalHarmonic(inclination, 0., -2, l, m).real Y_lminusm = lal.SpinWeightedSphericalHarmonic(inclination, 0., -2, l, -m).real Y_plus = Y_lm + (-1)**l * Y_lminusm Y_cross = Y_lm - (-1)**l * Y_lminusm return Y_plus, Y_cross
def sum_modes(hlms, inclination, phi): """Applies spherical harmonics and sums modes to produce a plus and cross polarization. Parameters ---------- hlms : dict Dictionary of ``(l, m)`` -> complex ``hlm``. The ``hlm`` may be a complex number or array, or complex ``TimeSeries``. All modes in the dictionary will be summed. inclination : float The inclination to use. phi : float The phase to use. Returns ------- complex float or array The plus and cross polarization as a complex number. The real part gives the plus, the negative imaginary part the cross. """ out = None for mode in hlms: l, m = mode hlm = hlms[l, m] ylm = lal.SpinWeightedSphericalHarmonic(inclination, phi, -2, l, m) if out is None: out = ylm * hlm else: out += ylm * hlm return out
def construct_Hlm(Ixx, Ixy, Ixz, Iyy, Iyz, Izz, l=2, abs_m=2, geom=False): """ Construct the expansion parameters Hlm from T1000553. Returns the expansion parameters for l, m= +/- abs_m as a dictionary with key names for the m-index """ if l!=2: print "l!=2 not supported" sys.exit() if abs_m>2: print "Only l=2 supported, |m| must be <=2" sys.exit() if abs_m!=2: print "Actually, only supporting |m|=2 for now, bye!" sys.exit() if abs_m==2: #H2n2 = np.sqrt(4.0*lal.PI/5.0) * (Ixx - Iyy + 2*1j*Ixy) #H2p2 = np.sqrt(4.0*lal.PI/5.0) * (Ixx - Iyy - 2*1j*Ixy) fac = 1/lal.SpinWeightedSphericalHarmonic(0, 0, -2, 2, 2).real H2n2 = fac * (Ixx - Iyy + 2*1j*Ixy) H2p2 = fac * (Ixx - Iyy - 2*1j*Ixy) if geom==False: H2n2 *= lal.G_SI / lal.C_SI**4 H2p2 *= lal.G_SI / lal.C_SI**4 return {'l=2, m=-2':H2n2,'l=2, m=2':H2p2}
def gen_pycbc_waveform(mass1, mass2, approximant, delta_t=1./8192, f_lower=30, distance=1, t1=None, t2=None, *args, **kwargs): q = mass1/mass2 mtot = mass1 + mass2 hp, hc = waveform.get_td_waveform(approximant=approximant, mass1=mass1, mass2=mass2, delta_t=delta_t, f_lower=f_lower, distance=distance) times = phenom.StoM(hp.sample_times.numpy(), mtot) ylm = np.abs(lal.SpinWeightedSphericalHarmonic(0,0,-2,2,2)) amp_scale = utils.td_amp_scale(mtot, distance) * ylm hp_array = hp.numpy() / amp_scale hc_array = hc.numpy() / amp_scale h_array = hp_array - 1.j*hc_array if t1 is None: t1 = times[0] if t2 is None: t2 = times[-1] mask = (times >= t1) & (times <= t2) times = times[mask] h_array = h_array[mask] return times, h_array
def project_waveform(Hlm, theta, phi, distance=20.0): """ Project the expansion parameters in the dictionary Hlm onto the sky for co-latitude theta, azimuth phi. Returns hplus, hcross for a given theta, phi """ colatitude_indices=[2] azimuth_indices=[-2,2] hplus=0.0 hcross=0.0 h = np.zeros(len(Hlm['l=2, m=2']), dtype=complex) # See e.g., pycbc/waveform/nr_waveform.py for l in colatitude_indices: for m in azimuth_indices: sYlm = lal.SpinWeightedSphericalHarmonic(theta, phi, -2, l, m) curr_Hlm = Hlm['l=%i, m=%i'%(l, m)] # h+=curr_Hlm * sYlm curr_hp = curr_Hlm.real * sYlm.real - curr_Hlm.imag * sYlm.imag curr_hc = -curr_Hlm.real*sYlm.imag - curr_Hlm.imag * sYlm.real hplus += curr_hp hcross += curr_hc # hplus = h.real # hcross = -1*h.imag # Scale by distance distance*=1e6*lal.PC_SI hplus /= distance hcross /= distance # Scale up by 40% for quadrupole approximation hplus*=1.4 hcross*=1.4 #hplus = taper_start(hplus) #hcross = taper_start(hcross) # Window: window = lal.CreateTukeyREAL8Window(len(hplus), 0.1) #hplus *= window.data.data #hcross *= window.data.data hplus = pycbc.types.TimeSeries(initial_array=hplus, delta_t = 1.0/16384) hcross = pycbc.types.TimeSeries(initial_array=hcross, delta_t = 1.0/16384) return hplus, hcross
def call_lalfunc(Lmax, theta, phi, selected_modes=None): for l in range(2, Lmax+1): for m in range(-l, l+1): if selected_modes is not None and (l,m) not in selected_modes: continue for i in xrange(0, len(theta)): lal.SpinWeightedSphericalHarmonic(theta[i], phi[i], -2, l, m) return
def compute_spherical_harmonics(Lmax, theta, phi, selected_modes=None): """ Return a dictionary keyed by tuples (l,m) that contains the values of all -2Y_lm(theta,phi) with l <= Lmax -l <= m <= l """ # PRB: would this be faster if we made it a 2d numpy array? Ylms = {} for l in range(2,Lmax+1): for m in range(-l,l+1): if selected_modes is not None and (l,m) not in selected_modes: continue Ylms[ (l,m) ] = lal.SpinWeightedSphericalHarmonic(theta, phi,-2, l, m) return Ylms
def complex_hoft(self, force_T=False, deltaT=1. / 16384, time_over_M_zero=0., sgn=-1): hlmT = self.hlmoft(force_T, deltaT, time_over_M_zero) npts = hlmT[(2, 2)].data.length wfmTS = lal.CreateCOMPLEX16TimeSeries( "Psi4", lal.LIGOTimeGPS(0.), 0., deltaT, lalsimutils.lsu_DimensionlessUnit, npts) wfmTS.data.data[:] = 0 # SHOULD NOT BE NECESARY, but the creation operator doesn't robustly clean memory wfmTS.epoch = hlmT[(2, 2)].epoch for mode in hlmT.keys(): # PROBLEM: Be careful with interpretation. The incl and phiref terms are NOT tied to L. if rosDebug: print(mode, np.max(hlmT[mode].data.data), " running max ", np.max(np.abs(wfmTS.data.data))) wfmTS.data.data += np.exp( -2 * sgn * 1j * self.P.psi ) * hlmT[mode].data.data * lal.SpinWeightedSphericalHarmonic( self.P.incl, -self.P.phiref, -2, int(mode[0]), int(mode[1])) return wfmTS
def get_glm(l, m, theta): r"""The maginitude of the :math:`{}_{-2}Y_{\ell m}`. The spin-weighted spherical harmonics can be written as :math:`{}_{-2}Y_{\ell m}(\theta, \phi) = g_{\ell m}(\theta)e^{i m \phi}`. This returns the `g_{\ell m}(\theta)` part. Note that this is real. Parameters ---------- l : int The :math:`\ell` index of the spherical harmonic. m : int The :math:`m` index of the spherical harmonic. theta : float The polar angle (in radians). Returns ------- float : The amplitude of the harmonic at the given polar angle. """ return lal.SpinWeightedSphericalHarmonic(theta, 0., -2, l, m).real
def _loglr(self, return_unmarginalized=False): r"""Computes the log likelihood ratio, .. math:: \log \mathcal{L}(\Theta) = \sum_i \left<h_i(\Theta)|d_i\right> - \frac{1}{2}\left<h_i(\Theta)|h_i(\Theta)\right>, at the current parameter values :math:`\Theta`. Returns ------- float The value of the log likelihood ratio. """ params = self.current_params try: wfs = self.waveform_generator.generate(**params) except NoWaveformError: return self._nowaveform_loglr() except FailedWaveformError as e: if self.ignore_failed_waveforms: return self._nowaveform_loglr() else: raise e # --------------------------------------------------------------------- # Some optimizations not yet taken: # * higher m calculations could have a lot of redundancy # * fp/fc need not be calculated except where polarization is different # * may be possible to simplify this by making smarter use of real/imag # --------------------------------------------------------------------- lr = 0. hds = {} hhs = {} for det, modes in wfs.items(): if det not in self.dets: self.dets[det] = Detector(det) fp, fc = self.dets[det].antenna_pattern(self.current_params['ra'], self.current_params['dec'], self.pol, self.current_params['tc']) # loop over modes and prepare the waveform modes # we will sum up zetalm = glm <ulm, d> + i glm <vlm, d> # over all common m so that we can apply the phase once zetas = {} rlms = {} slms = {} for mode in modes: l, m = mode ulm, vlm = modes[mode] # whiten the waveforms # the kmax of the waveforms may be different than internal kmax kmax = min(max(len(ulm), len(vlm)), self._kmax[det]) slc = slice(self._kmin[det], kmax) ulm[self._kmin[det]:kmax] *= self._weight[det][slc] vlm[self._kmin[det]:kmax] *= self._weight[det][slc] # the inner products # <ulm, d> ulmd = ulm[slc].inner(self._whitened_data[det][slc]).real # <vlm, d> vlmd = vlm[slc].inner(self._whitened_data[det][slc]).real # add inclination, and pack into a complex number import lal glm = lal.SpinWeightedSphericalHarmonic( self.current_params['inclination'], 0, -2, l, m).real if m not in zetas: zetas[m] = 0j zetas[m] += glm * (ulmd + 1j*vlmd) # Get condense set of the parts of the waveform that only diff # by m, this is used next to help calculate <h, h> r = glm * ulm s = glm * vlm if m not in rlms: rlms[m] = r slms[m] = s else: rlms[m] += r slms[m] += s # now compute all possible <hlm, hlm> rr_m = {} ss_m = {} rs_m = {} sr_m = {} combos = itertools.combinations_with_replacement(rlms.keys(), 2) for m, mprime in combos: r = rlms[m] s = slms[m] rprime = rlms[mprime] sprime = slms[mprime] rr_m[mprime, m] = r[slc].inner(rprime[slc]).real ss_m[mprime, m] = s[slc].inner(sprime[slc]).real rs_m[mprime, m] = s[slc].inner(rprime[slc]).real sr_m[mprime, m] = r[slc].inner(sprime[slc]).real # store the conjugate for easy retrieval later rr_m[m, mprime] = rr_m[mprime, m] ss_m[m, mprime] = ss_m[mprime, m] rs_m[m, mprime] = sr_m[mprime, m] sr_m[m, mprime] = rs_m[mprime, m] # now apply the phase to all the common ms hpd = 0. hcd = 0. hphp = 0. hchc = 0. hphc = 0. for m, zeta in zetas.items(): phase_coeff = self.phase_fac(m) # <h+, d> = (exp[i m phi] * zeta).real() # <hx, d> = -(exp[i m phi] * zeta).imag() z = phase_coeff * zeta hpd += z.real hcd -= z.imag # now calculate the contribution to <h, h> cosm = phase_coeff.real sinm = phase_coeff.imag for mprime in zetas: pcprime = self.phase_fac(mprime) cosmprime = pcprime.real sinmprime = pcprime.imag # needed components rr = rr_m[m, mprime] ss = ss_m[m, mprime] rs = rs_m[m, mprime] sr = sr_m[m, mprime] # <hp, hp> hphp += rr * cosm * cosmprime \ + ss * sinm * sinmprime \ - rs * cosm * sinmprime \ - sr * sinm * cosmprime # <hc, hc> hchc += rr * sinm * sinmprime \ + ss * cosm * cosmprime \ + rs * sinm * cosmprime \ + sr * cosm * sinmprime # <hp, hc> hphc += -rr * cosm * sinmprime \ + ss * sinm * cosmprime \ + sr * sinm * sinmprime \ - rs * cosm * cosmprime # Now apply the polarizations and calculate the loglr # We have h = Fp * hp + Fc * hc # loglr = <h, d> - <h, h>/2 # = Fp*<hp, d> + Fc*<hc, d> # - (1/2)*(Fp*Fp*<hp, hp> + Fc*Fc*<hc, hc> # + 2*Fp*Fc<hp, hc>) # (in the last line we have made use of the time series being # real, so that <a, b> = <b, a>). hd = fp * hpd + fc * hcd hh = fp * fp * hphp + fc * fc * hchc + 2 * fp * fc * hphc hds[det] = hd hhs[det] = hh lr += hd - 0.5 * hh if return_unmarginalized: return self.pol, self.phase, lr, hds, hhs lr_total = special.logsumexp(lr) - numpy.log(self.nsamples) # store the maxl values idx = lr.argmax() setattr(self._current_stats, 'maxl_polarization', self.pol[idx]) setattr(self._current_stats, 'maxl_phase', self.phase[idx]) return float(lr_total)
def rescale_wave(self, M=None, inclination=None, phi=None, distance=None): """ Rescale modes and polarizations to given mass, angles, distance. Note that this function re-sets the stored values of binary parameters and so all future calculations will assume new values unless otherwise specified. """ #{{{ # # If MASS has changed, rescale modes # if self.totalmass != M and M is not None: # Rescale the time-axis for all modes self.rescaledmodes_real, self.rescaledmodes_imag = {}, {} for modeL in np.arange( 2, self.modeLmax+1 ): self.rescaledmodes_real[modeL], self.rescaledmodes_imag[modeL] = {}, {} for modeM in np.arange( -1*modeL, modeL+1 ): if self.skipM0 and modeM==0: continue self.rescaledmodes_real[modeL][modeM], \ self.rescaledmodes_imag[modeL][modeM] = \ self.rescale_mode(M, modeL=modeL, modeM=modeM) self.totalmass = M elif self.totalmass == None and M == None: raise IOError("Please provide a total-mass value to rescale") # # Now rescale with distance and ANGLES # if inclination is not None: self.inclination = inclination if phi is not None: self.phi = phi if distance is not None: self.distance = distance # Mass / distance scaling pre-factor scalefac = self.totalmass * lal.MRSUN_SI / self.distance / lal.PC_SI # Orbital phase at the time of merger (time of amplitude peak) amp22 = self.get_mode_amplitude(totalmass=self.totalmass, modeL=2, modeM=2, dimensionless=False) iPeak, aPeak = self.get_peak_amplitude(amp=amp22) #phase22 = self.get_mode_phase(totalmass=self.totalmass, dimensionless=False) #phiOrbMerger = phase22[iPeak] / 2. # Combine all modes hp, hc = np.zeros(self.n, dtype=float), np.zeros(self.n, dtype=float) for modeL in np.arange( 2, self.modeLmax+1 ): for modeM in np.arange( -1*modeL, modeL+1 ): if self.skipM0 and modeM==0: continue # h+ - \ii hx = \Sum Ylm * hlm ArcTan2Merger = np.arctan2(self.rescaledmodes_imag[modeL][modeM].data[iPeak],\ self.rescaledmodes_real[modeL][modeM].data[iPeak]) if self.verbose: print "arctan2 at merger after: ", ArcTan2Merger #curr_ylm = np.exp(1 * modeM * phiOrbMerger * 1j) curr_ylm = np.exp(-1 * ArcTan2Merger * 1j) if self.debug and curr_ylm: print curr_ylm / np.abs(curr_ylm) curr_ylm *= lal.SpinWeightedSphericalHarmonic(\ self.inclination, self.phi, -2, modeL, modeM) if self.debug and curr_ylm: print curr_ylm / np.abs(curr_ylm) # if self.debug: print ( np.arctan2(curr_ylm.imag, curr_ylm.real) + (modeM*phiOrbMerger) ) / np.pi,\ ( np.arctan2(curr_ylm.imag, curr_ylm.real) + (ArcTan2Merger) ) / np.pi print ( np.arctan2(curr_ylm.imag, curr_ylm.real) - (modeM*phiOrbMerger) ) / np.pi,\ ( np.arctan2(curr_ylm.imag, curr_ylm.real) - (ArcTan2Merger) ) / np.pi ## hp += self.rescaledmodes_real[modeL][modeM].data * curr_ylm.real - \ self.rescaledmodes_imag[modeL][modeM].data * curr_ylm.imag hc -= self.rescaledmodes_real[modeL][modeM].data * curr_ylm.imag + \ self.rescaledmodes_imag[modeL][modeM].data * curr_ylm.real if self.debug: print "END\n\n" # Scale amplitude by mass and distance factors self.rescaled_hp = TimeSeries(scalefac * hp, delta_t=self.dt, epoch=0) self.rescaled_hc = TimeSeries(scalefac * hc, delta_t=self.dt, epoch=0) return [self.rescaled_hp, self.rescaled_hc]
def ApproximatePrecessingFisherMatrixElementFactor(P, p1,p2,m,s,psd,phase_deriv_cache=None,omit_geometric_factor=False,break_out_beta=False,**kwargs): """ ApproximatePrecessingFisherMatrixElementFactor computes the weighted integral appearing in the sum, and the SNR weight. Ideally this calculation should CACHE existing phase derivatives (eg. by top-level user) Arguments: - phase_deriv_cache : derivative of dphase/d\lambda for different parameters - omit_geometric_factor : """ # Create basic infrastructure for IP. This requires VERY DENSE sampling...try to avoid # hF0 = lalsimutils.complex_hoff(P) # IP = lalsimutils.CreateCompatibleComplexIP(hF0,psd=psd,**kwargs) # dPsi2F_func = interp1d(fvals,dPsi2F, fill_value=0, bounds_error=False) # dalphaF_func= interp # print " called on ", p1,p2, m, s # This geometric stuff is all in common. I should make one call to generate Sh once and for all beta_0 = P.extract_param('beta') thetaJN_0 = P.extract_param('thetaJN') mc_s = P.extract_param('mc')/lal.MSUN_SI *lalsimutils.MsunInSec d_s = P.extract_param('dist')/lal.C_SI # distance in seconds if omit_geometric_factor: geometric_factor=1 else: geometric_factor = np.abs(lal.SpinWeightedSphericalHarmonic(thetaJN_0,0,-2,2,int(m)) * lal.WignerDMatrix(2,m,s,0,beta_0,0))**2 if np.abs(geometric_factor) < 1e-5: return 0,0 # saves lots of time # Create phase derivatives. Interpolate onto grid? OR use lower-density sampling if phase_deriv_cache: if not (p1 in phase_deriv_cache.keys()): phase_deriv_cache[p1] = PhaseDerivativeSeries(P,p1) else: fvals,fmax_safe, dPsi2F,dPhiF, dalphaF,dgammaF = phase_deriv_cache[p1] if not (p2 in phase_deriv_cache.keys()): phase_deriv_cache[p2] = PhaseDerivativeSeries(P,p2) else: fvals,fmax_safe, dPsi2F_2,dPhiF_2, dalphaF_2,dgammaF_2 =phase_deriv_cache[p2] else: fvals,fmax_safe, dPsi2F,dPhiF, dalphaF,dgammaF = PhaseDerivativeSeries(P,p1) fvals,fmax_safe, dPsi2F_2,dPhiF_2, dalphaF_2,dgammaF_2 = PhaseDerivativeSeries(P,p2) dropme = np.logical_or(fvals <P.fmin, fvals>0.98*fmax_safe) Shvals = np.array(map(psd, np.abs(fvals))) Shvals[np.isnan(Shvals)]=float('inf') phase_weights = 4* (np.pi*mc_s*mc_s)**2/(3*d_s*d_s) *np.power((np.pi*mc_s*np.maximum(np.abs(fvals),P.fmin/2)),-7./3.)/Shvals # hopefully one-sided. Note the P.fmin removes nans phase_weights[np.isnan(phase_weights)]=0 phase_weights[dropme] =0 dalphaF[dropme] = 0 dPsi2F[dropme] = 0 dgammaF[dropme]=0 dalphaF_2[dropme] = 0 dPsi2F_2[dropme] = 0 dgammaF_2[dropme]=0 rhoms2 = np.sum(phase_weights*P.deltaF)*geometric_factor if (beta_0) < 1e-2: # Pathological, cannot calculate alpha or gamma ret_weights = P.deltaF*phase_weights*geometric_factor*( dPsi2F)*(dPsi2F_2) ret = np.sum(ret_weights) else: if not break_out_beta: ret_weights = P.deltaF*phase_weights*geometric_factor*( dPsi2F- 2 *dgammaF + m*s*dalphaF)*(dPsi2F_2 - 2*dgammaF_2+m*s*dalphaF_2) ret = np.sum(ret_weights) else: # Warming: this uses the explicit assumption \gamma - -alpha cos beta # Warning: you probably never want this unless geometric_factor=1 if not omit_geometric_factor: print(" You are extracting a breakdown of the fisher matrix versus beta, but are not fixing the geometric factor...are you sure?") ret_00 = np.sum(P.deltaF*phase_weights*geometric_factor*( dPsi2F)*(dPsi2F_2 )) ret_01 = np.sum(P.deltaF*phase_weights*geometric_factor*( dPsi2F)*(dalphaF_2 )) ret_10 = np.sum(P.deltaF*phase_weights*geometric_factor*( dalphaF)*(dPsi2F_2 )) ret_11 = np.sum(P.deltaF*phase_weights*geometric_factor*( dalphaF)*(dalphaF_2)) print(" -- submatrix ", p1,p2, ret_00, ret_01, ret_10, ret_11) return rhoms2,{"00":ret_00, "01":ret_01, "10": ret_10, "11": ret_11} # print " Internal fisher element", geometric_factor, rhoms2, ret # plt.plot(fvals,ret_weights) # plt.ylim(0,np.max(ret_weights)) # plt.show() return rhoms2, ret
def compute_spherical_harmonics(Lmax, theta, phi, selected_modes=None): """ Return a dictionary keyed by tuples (l,m) that contains the values of all -2Y_lm(theta,phi) with l <= Lmax -l <= m <= l """ Ylms = _compute_sph_l_eq_2(theta, phi, selected_modes) Ylms.update(_compute_sph_l_eq_3(theta, phi, selected_modes)) Ylms.update(_compute_sph_l_eq_4(theta, phi, selected_modes)) Ylms.update(_compute_sph_l_eq_5(theta, phi, selected_modes)) Ylms.update(_compute_sph_l_eq_6(theta, phi, selected_modes)) return Ylms if __name__ == "__main__": theta, phi = np.random.uniform(0, pi, 2) # Do unit tests Ylm = compute_spherical_harmonics(2, theta, phi, None) for (l, m), val in Ylm.iteritems(): if np.isclose(lal.SpinWeightedSphericalHarmonic(theta, phi, -2, l, m), val): print("Test successful for (l,m) = (%d, %d)" % (l, m)) else: print("Test unsucessful for (l,m) = (%d, %d)" % (l, m))
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. amp : float The amplitude of the ringdown (constant for now). phi : float The initial phase of the ringdown. Should also include the information from the azimuthal angle (phi_0 + m*Phi). inclination : {0., float}, optional Inclination of the system in radians. Default is 0 (face on). l : {2, int}, optional l mode for the spherical harmonics. Default is l=2. m : {2, int}, optional m mode for the spherical harmonics. Default is m=2. 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 inc = input_params.pop('inclination', 0.) l = input_params.pop('l', 2) m = input_params.pop('m', 2) 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 # FIXME: we are using spin -2 weighted spherical harmonics for now, # when possible switch to spheroidal harmonics. sph_lm = lal.SpinWeightedSphericalHarmonic(inc, 0., -2, l, m).real sph_lminusm = lal.SpinWeightedSphericalHarmonic(inc, 0., -2, l, -m).real spherical_plus = sph_lm + (-1)**l * sph_lminusm spherical_cross = sph_lm - (-1)**l * sph_lminusm 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 * spherical_plus * ( (1 + 2j * pi * freqs * tau) * numpy.cos(phi) - two_pi * f_0 * tau * numpy.sin(phi) ) hc_tilde = norm * spherical_cross * ( (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 get_td_qnm(template=None, taper=None, **kwargs): """Return a time 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. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. amp : float The amplitude of the ringdown (constant for now). phi : float The initial phase of the ringdown. Should also include the information from the azimuthal angle (phi_0 + m*Phi) inclination : {0., float}, optional Inclination of the system in radians. Default is 0 (face on). l : {2, int}, optional l mode for the spherical harmonics. Default is l=2. m : {2, int}, optional m mode for the spherical harmonics. Default is m=2. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time 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 may not be in input_params inc = input_params.pop('inclination', 0.) l = input_params.pop('l', 2) m = input_params.pop('m', 2) delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = 1. / qnm_freq_decay(f_0, tau, 1./1000) if delta_t < min_dt: delta_t = min_dt if t_final is None: t_final = qnm_time_decay(tau, 1./1000) kmax = int(t_final / delta_t) + 1 times = numpy.arange(kmax) * delta_t # FIXME: we are using spin -2 weighted spherical harmonics for now, # when possible switch to spheroidal harmonics. sph_lm = lal.SpinWeightedSphericalHarmonic(inc, 0., -2, l, m).real sph_lminusm = lal.SpinWeightedSphericalHarmonic(inc, 0., -2, l, -m).real spherical_plus = sph_lm + (-1)**l * sph_lminusm spherical_cross = sph_lm - (-1)**l * sph_lminusm hp = amp * spherical_plus * numpy.exp(-times/tau) * \ numpy.cos(two_pi*f_0*times + phi) hc = amp * spherical_cross * numpy.exp(-times/tau) * \ numpy.sin(two_pi*f_0*times + phi) # If size of tapering window is less than delta_t, do not apply taper. if taper is None or delta_t > taper*tau: hplus = TimeSeries(zeros(kmax), delta_t=delta_t) hcross = TimeSeries(zeros(kmax), delta_t=delta_t) hplus.data[:kmax] = hp hcross.data[:kmax] = hc return hplus, hcross else: taper_hp, taper_hc, taper_window, start = apply_taper(delta_t, taper, f_0, tau, amp, phi) hplus = TimeSeries(zeros(taper_window+kmax), delta_t=delta_t) hcross = TimeSeries(zeros(taper_window+kmax), delta_t=delta_t) hplus.data[:taper_window] = taper_hp hplus.data[taper_window:] = hp hplus._epoch = start hcross.data[:taper_window] = taper_hc hcross.data[taper_window:] = hc hcross._epoch = start return hplus, hcross
filename = args[1] + "_l2m%d.asc" % m elif len(args) == 1: filename = "MPI_BNSmerger_1-35_1-35_l2m%d.asc" % m else: print >> sys.stderr, "error, incorrect number of input arguments" # Ensure initialisation to zero hplus_sim.data.data = np.zeros(int(duration * sample_rate)) hcross_sim.data.data = np.zeros(int(duration * sample_rate)) # Spin-weighted spherical harmonic term if SI: # Then compute the spherical harmonic term explicitly theta *= lal.LAL_PI_180 phi *= lal.LAL_PI_180 sYlm = lal.SpinWeightedSphericalHarmonic(theta, phi, -2, 2, m) else: # otherwise, the spherical harmonic term is computed @ line 191 of # NRWaveInject.c sYlm = 1.0 for i in range(len(sim_data)): Idotdot[0, 0] = sim_data[i, 1] Idotdot[0, 1] = sim_data[i, 2] Idotdot[0, 2] = sim_data[i, 3] Idotdot[1, 0] = sim_data[i, 1] Idotdot[1, 1] = sim_data[i, 4] Idotdot[1, 2] = sim_data[i, 5]