def fakeGaussianDataTime(amp, sigma, deltaT, npts, window='Tukey', window_beta=0.01): # Create h(t) htGauss = lal.CreateCOMPLEX16TimeSeries("h", lal.LIGOTimeGPS(0.), 0., deltaT, lal.lalDimensionlessUnit, npts) htGauss.data.data[:] = 0.j # initialize the data # Populate first few samples with a half-gaussian , + tvals = np.arange(npts) * deltaT htGauss.data.data += amp * np.exp(-tvals * tvals / (sigma * sigma) / 2.) # Populate the last few samples with a half-gaussian, - tvalsMinus = np.arange(npts)[::-1] * deltaT htGauss.data.data += -amp * np.exp(-tvalsMinus * tvalsMinus / (sigma * sigma) / 2.) if window: windowArray = lal.CreateNamedREAL8Window( window, window_beta, len(htGauss.data.data)) # Window (start) htGauss.data.data *= windowArray.data.data return htGauss
def non_herm_hoff(self): """ Returns the 2-sided h(f) associated with the real-valued h(t) seen in a real instrument. Translates epoch as needed. Based on 'non_herm_hoff' in lalsimutils.py """ htR = self.real_hoft( ) # Generate real-valued TD waveform, including detector response if self.P.deltaF == None: # h(t) was not zero-padded, so do it now TDlen = nextPow2(htR.data.length) htR = lal.ResizeREAL8TimeSeries(htR, 0, TDlen) else: # Check zero-padding was done to expected length TDlen = int(1. / self.P.deltaF * 1. / self.P.deltaT) assert TDlen == htR.data.length fwdplan = lal.CreateForwardCOMPLEX16FFTPlan(htR.data.length, 0) htC = lal.CreateCOMPLEX16TimeSeries("hoft", htR.epoch, htR.f0, htR.deltaT, htR.sampleUnits, htR.data.length) # copy h(t) into a COMPLEX16 array which happens to be purely real htC.data.data[:htR.data.length] = htR.data.data # for i in range(htR.data.length): # htC.data.data[i] = htR.data.data[i] hf = lal.CreateCOMPLEX16FrequencySeries( "Template h(f)", htR.epoch, htR.f0, 1. / htR.deltaT / htR.data.length, lalsimutils.lsu_HertzUnit, htR.data.length) lal.COMPLEX16TimeFreqFFT(hf, htC, fwdplan) return hf
def lal(self): """ Returns a LAL Object that contains this data """ lal_data = None if type(self._data) is not _numpy.ndarray: raise TypeError("Cannot return lal type from the GPU") elif self._data.dtype == _numpy.float32: lal_data = _lal.CreateREAL4TimeSeries("", self._epoch, 0, self.delta_t, _lal.lalSecondUnit, len(self)) elif self._data.dtype == _numpy.float64: lal_data = _lal.CreateREAL8TimeSeries("", self._epoch, 0, self.delta_t, _lal.lalSecondUnit, len(self)) elif self._data.dtype == _numpy.complex64: lal_data = _lal.CreateCOMPLEX8TimeSeries("", self._epoch, 0, self.delta_t, _lal.lalSecondUnit, len(self)) elif self._data.dtype == _numpy.complex128: lal_data = _lal.CreateCOMPLEX16TimeSeries("", self._epoch, 0, self.delta_t, _lal.lalSecondUnit, len(self)) lal_data.data.data[:] = self._data return lal_data
def lal(self): """Produces a LAL time series object equivalent to self. Returns ------- lal_data : {lal.*TimeSeries} LAL time series object containing the same data as self. The actual type depends on the sample's dtype. If the epoch of self is 'None', the epoch of the returned LAL object will be LIGOTimeGPS(0,0); otherwise, the same as that of self. Raises ------ TypeError If time series is stored in GPU memory. """ lal_data = None ep = self._epoch if self._data.dtype == _numpy.float32: lal_data = _lal.CreateREAL4TimeSeries("",ep,0,self.delta_t,_lal.SecondUnit,len(self)) elif self._data.dtype == _numpy.float64: lal_data = _lal.CreateREAL8TimeSeries("",ep,0,self.delta_t,_lal.SecondUnit,len(self)) elif self._data.dtype == _numpy.complex64: lal_data = _lal.CreateCOMPLEX8TimeSeries("",ep,0,self.delta_t,_lal.SecondUnit,len(self)) elif self._data.dtype == _numpy.complex128: lal_data = _lal.CreateCOMPLEX16TimeSeries("",ep,0,self.delta_t,_lal.SecondUnit,len(self)) lal_data.data.data[:] = self.numpy() return lal_data
def __call__(self, tseries): """ Transform the real-valued time series stored in tseries into a complex-valued time series. The return value is a newly-allocated complex time series. The input time series is stored in the real part of the output time series, and the complex part stores the quadrature phase. """ # # transform to frequency series # lal.REAL8TimeFreqFFT(self.in_fseries, tseries, self.fwdplan) # # transform to complex time series # tseries = lal.CreateCOMPLEX16TimeSeries(length=self.n) lal.COMPLEX16FreqTimeFFT( tseries, self.add_quadrature_phase(self.in_fseries, self.n), self.revplan) # # done # return tseries
def generate_template(mass1, mass2, S, f_low, sample_rate, template_duration, approximant, amplitude_order, phase_order): template_length = sample_rate * template_duration if approximant == lalsimulation.TaylorF2: zf, _ = lalsimulation.SimInspiralChooseFDWaveform( 0, 1 / template_duration, mass1 * lal.LAL_MSUN_SI, mass2 * lal.LAL_MSUN_SI, 0, 0, 0, 0, 0, 0, f_low, 0, 1e6 * lal.LAL_PC_SI, 0, 0, 0, None, None, amplitude_order, phase_order, approximant) lal.ResizeCOMPLEX16FrequencySeries(zf, 0, template_length // 2 + 1) # Generate over-whitened template psd = lal.CreateREAL8FrequencySeries(None, zf.epoch, zf.f0, zf.deltaF, lal.lalDimensionlessUnit, len(zf.data.data)) psd.data.data = S(abscissa(psd)) zW = matched_filter_spa(zf, psd) elif approximant == lalsimulation.TaylorT4: hplus, hcross = lalsimulation.SimInspiralChooseTDWaveform( 0, 1 / sample_rate, mass1 * lal.LAL_MSUN_SI, mass2 * lal.LAL_MSUN_SI, 0, 0, 0, 0, 0, 0, f_low, f_low, 1e6 * lal.LAL_PC_SI, 0, 0, 0, None, None, amplitude_order, phase_order, approximant) ht = lal.CreateREAL8TimeSeries(None, lal.LIGOTimeGPS(-template_duration), hplus.f0, hplus.deltaT, hplus.sampleUnits, template_length) hf = lal.CreateCOMPLEX16FrequencySeries(None, lal.LIGOTimeGPS(0), 0, 0, lal.lalDimensionlessUnit, template_length // 2 + 1) plan = CreateForwardREAL8FFTPlan(template_length, 0) ht.data.data[:-len(hplus.data.data)] = 0 ht.data.data[-len(hplus.data.data):] = hplus.data.data lal.REAL8TimeFreqFFT(hf, ht, plan) psd = lal.CreateREAL8FrequencySeries(None, hf.epoch, hf.f0, hf.deltaF, lal.lalDimensionlessUnit, len(hf.data.data)) psd.data.data = S(abscissa(psd)) zWreal = matched_filter_real(hf, psd) ht.data.data[:-len(hcross.data.data)] = 0 ht.data.data[-len(hcross.data.data):] = hcross.data.data lal.REAL8TimeFreqFFT(hf, ht, plan) zWimag = matched_filter_real(hf, psd) zW = lal.CreateCOMPLEX16TimeSeries(None, zWreal.epoch, zWreal.f0, zWreal.deltaT, zWreal.sampleUnits, len(zWreal.data.data)) zW.data.data = zWreal.data.data + zWimag.data.data * 1j else: raise ValueError("unrecognized approximant") return zW.data.data[::-1].conj() * np.sqrt( 2) * template_duration / sample_rate / 2
def matched_filter_spa(template, psd): """Create a complex matched filter kernel from a stationary phase approximation template and a PSD.""" fdfilter = matched_filter_real_fd(template, psd) fdfilter2 = add_quadrature_phase(fdfilter) tdfilter = lal.CreateCOMPLEX16TimeSeries(None, lal.LIGOTimeGPS(0), 0, 0, lal.DimensionlessUnit, len(fdfilter2.data.data)) plan = CreateReverseCOMPLEX16FFTPlan(len(fdfilter2.data.data), 0) lal.COMPLEX16FreqTimeFFT(tdfilter, fdfilter2, plan) return tdfilter
def DataInverseFourier( hf ): # Complex fft wrapper (COMPLEX16Freq ->COMPLEX16Time. No error checking or padding! FDlen = hf.data.length dt = 1. / hf.deltaF / FDlen revplan = lal.CreateReverseCOMPLEX16FFTPlan(FDlen, 0) ht = lal.CreateCOMPLEX16TimeSeries("Template h(t)", hf.epoch, hf.f0, dt, lal.lalDimensionlessUnit, FDlen) lal.COMPLEX16FreqTimeFFT(ht, hf, revplan) # assume memory freed by swig python return ht
def create_FIR_whitener_kernel(length, duration, sample_rate, psd): assert psd # # Add another COMPLEX16TimeSeries and COMPLEX16FrequencySeries for kernel's FFT (Leo) # # Add another FFT plan for kernel FFT (Leo) fwdplan_kernel = lal.CreateForwardCOMPLEX16FFTPlan(length, 1) kernel_tseries = lal.CreateCOMPLEX16TimeSeries( name="timeseries of whitening kernel", epoch=LIGOTimeGPS(0.), f0=0., deltaT=1.0 / sample_rate, length=length, sampleUnits=lal.Unit("strain")) kernel_fseries = lal.CreateCOMPLEX16FrequencySeries( name="freqseries of whitening kernel", epoch=LIGOTimeGPS(0), f0=0.0, deltaF=1.0 / duration, length=length, sampleUnits=lal.Unit("strain s")) # # Obtain a kernel of zero-latency whitening filter and # adjust its length (Leo) # psd_fir_kernel = reference_psd.PSDFirKernel() (kernel, latency, fir_rate) = psd_fir_kernel.psd_to_linear_phase_whitening_fir_kernel( psd, nyquist=sample_rate / 2.0) ( kernel, theta ) = psd_fir_kernel.linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel( kernel, fir_rate) kernel = kernel[-1::-1] # FIXME this is off by one sample, but shouldn't be. Look at the miminum phase function # assert len(kernel) == length if len(kernel) < length: kernel = numpy.append(kernel, numpy.zeros(length - len(kernel))) else: kernel = kernel[:length] kernel_tseries.data.data = kernel # # FFT of the kernel # lal.COMPLEX16TimeFreqFFT(kernel_fseries, kernel_tseries, fwdplan_kernel) #FIXME return kernel_fseries
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 normalized_autocorrelation(fseries, revplan): data = fseries.data.data fseries = lal.CreateCOMPLEX16FrequencySeries( name=fseries.name, epoch=fseries.epoch, f0=fseries.f0, deltaF=fseries.deltaF, sampleUnits=fseries.sampleUnits, length=len(data)) fseries.data.data = data * numpy.conj(data) tseries = lal.CreateCOMPLEX16TimeSeries(name="timeseries", epoch=fseries.epoch, f0=fseries.f0, deltaT=1. / (len(data) * fseries.deltaF), length=len(data), sampleUnits=lal.DimensionlessUnit) tseries.data.data = numpy.empty((len(data), ), dtype="cdouble") lal.COMPLEX16FreqTimeFFT(tseries, fseries, revplan) data = tseries.data.data tseries.data.data = data / data[0] return tseries
def _make_series(self, array, epoch, row_number): """For internal use only.""" para = { "name": "%s_%d_%d" % (self.instrument, self.bank_number, row_number), "epoch": epoch, "deltaT": self.deltaT, "f0": 0, "sampleUnits": lal.DimensionlessUnit, "length": len(array) } if array.dtype == numpy.float32: tseries = lal.CreateREAL4TimeSeries(**para) elif array.dtype == numpy.float64: tseries = lal.CreateREAL8TimeSeries(**para) elif array.dtype == numpy.complex64: tseries = lal.CreateCOMPLEX8TimeSeries(**para) elif array.dtype == numpy.complex128: tseries = lal.CreateCOMPLEX16TimeSeries(**para) else: raise ValueError("unsupported type : %s " % array.dtype) tseries.data.data = array return tseries
def hlmoft(self, force_T=False, deltaT=1. / 16384, time_over_M_zero=0., taper_start_time=True): """ hlmoft uses stored interpolated values for hlm(t) generated via the standard cleaning process, scaling them to physical units for use in injection code. If the time window is sufficiently short, the result is NOT tapered (!!) -- no additional tapering is applied The code will ALWAYS have zero padding on the end -- half of the buffer is zero padding! This can cause loss of frequency content if you are not careful """ hlmT = {} # Define units m_total_s = MsunInSec * (self.P.m1 + self.P.m2) / lal.MSUN_SI distance_s = self.P.dist / lal.C_SI # insures valid units. Default distance is 1 Mpc ! # Create a suitable set of time samples. Zero pad to 2^n samples. # Note waveform is stored in s already T_estimated = np.real(self.waveform_modes_complex[(2, 2)][-1, 0] - self.waveform_modes_complex[(2, 2)][0, 0]) npts = 0 n_crit = 0 if not force_T: npts_estimated = int(T_estimated / deltaT) # print " Estimated length: ",npts_estimated, T_estimated npts = lalsimutils.nextPow2(npts_estimated) else: npts = int(force_T / deltaT) print(" Forcing length T=", force_T, " length ", npts) # WARNING: Time range may not cover the necessary time elements. # Plan on having a few seconds buffer at the end T_buffer_required = npts * deltaT print(" EOB internal: Estimated time window (sec) ", T_estimated, " versus buffer duration ", T_buffer_required) print(" EOB internal: Requested size vs buffer size", npts, len(self.waveform_modes_complex[(2, 2)])) # If the waveform is longer than the buffer, we need to avoid wraparound # If the buffer requested is SHORTER than the 2*waveform, work backwards # If the buffer requested is LONGER than the waveform, work forwards from the start of all data fac_safety = 1 # Previously had used a factor of 2 for safety. but this can accidentally truncate the waveform at too high an fmin. Remove. if T_buffer_required / fac_safety > T_estimated: tvals = np.arange(npts) * deltaT + float( self.waveform_modes_complex[(2, 2)][0, 0] ) # start at time t=0 and go forwards (zeros automatically padded by interpolation code) t_crit = float(-self.waveform_modes_complex[(2, 2)][0, 0]) n_crit = int( t_crit / deltaT ) # estiamted peak sample location in the t array, working forward else: print( " EOB internal: Warning LOSSY conversion to insure half of data is zeros " ) # Create time samples by walking backwards from the last sample of the waveform, a suitable duration # ASSUME we are running in a configuration with align_at_peak_l2m2_emission # FIXME: Change this tvals = T_buffer_required / fac_safety + ( -npts + 1 + np.arange(npts)) * deltaT + np.real( self.waveform_modes_complex[ (2, 2)][-1, 0]) # last insures we get some ringdown t_crit = T_buffer_required / fac_safety - (np.real( self.waveform_modes_complex[(2, 2)][-1, 0])) n_crit = int(t_crit / deltaT) # if rosDebug: # print " time range being sampled ", [min(tvals),max(tvals)], " corresponding to dimensionless range", [min(tvals)/m_total_s,max(tvals)/m_total_s] # print " estimated peak sample at ", n_crit # Loop over all modes in the system for mode in self.waveform_modes_complex.keys(): amp_vals = m_total_s / distance_s * self.waveform_modes_complex_interpolated_amplitude[ mode](tvals) # vectorized interpolation with piecewise phase_vals = self.waveform_modes_complex_interpolated_phase[mode]( tvals) phase_vals = lalsimutils.unwind_phase( phase_vals) # should not be necessary, but just in case if rosDebug: print(" Mode ", mode, " physical strain max, indx,", np.max(amp_vals), np.argmax(amp_vals)) # Copy into a new LIGO time series object wfmTS = lal.CreateCOMPLEX16TimeSeries( "h", lal.LIGOTimeGPS(0.), 0., deltaT, lalsimutils.lsu_DimensionlessUnit, npts) wfmTS.data.data[:] = 0 # lal initialization is sometimes ratty. wfmTS.data.data = amp_vals * np.exp(1j * phase_vals) # Set the epoch for the time series correctly: should have peak near center of series by construction # note all have the same length # wfmTS.epoch = -deltaT*wfmTS.data.length/2 # did not work #n_crit = np.argmax(wfmTS.data.data) #print n_crit*wfmTS.deltaT, wfmTS.epoch # this should be nearly zero # taper the start (1s. Only needed if I do not grab the whole range, because I taper the raw data) if taper_start_time: tTaper = 1 nTaper = int(tTaper / deltaT) hoft_window = lal.CreateTukeyREAL8Window(nTaper * 2, 0.8) factorTaper = hoft_window.data.data[0:nTaper] wfmTS.data.data[:nTaper] *= factorTaper # Store the resulting mode hlmT[mode] = wfmTS # Set time at peak of 22 mode. This is a hack, but good enough for us # n_crit = np.argmax(hlmT[(2,2)].data.data) epoch_crit = float(-t_crit) #-deltaT*n_crit print(" EOB internal: zero epoch sample location", n_crit, np.argmax(np.abs(hlmT[(2, 2)].data.data))) for mode in hlmT: hlmT[mode].epoch = epoch_crit return hlmT
coinc_inspiral_table.append(coinc_inspiral) # Record all sngl_inspiral records and associate them with coincidences. for sngl_inspiral, W in zip(sngl_inspirals, used_W): # Give this sngl_inspiral record an id and add it to the table. sngl_inspiral.event_id = sngl_inspiral_table.get_next_id() sngl_inspiral_table.append(sngl_inspiral) if opts.enable_snr_series: snr, sample_rate = filter.autocorrelation(W, max_abs_t) dt = 1 / sample_rate epoch = sngl_inspiral.end - (len(snr) - 1) / sample_rate snr = np.concatenate((snr[:0:-1].conj(), snr)) snr *= sngl_inspiral.snr * np.exp(1j * sngl_inspiral.coa_phase) snr_series = lal.CreateCOMPLEX16TimeSeries('snr', epoch, 0, dt, lal.StrainUnit, len(snr)) snr_series.data.data[:] = snr elem = lal.series.build_COMPLEX16TimeSeries(snr_series) elem.appendChild( ligolw_param.Param.from_pyvalue(u'event_id', sngl_inspiral.event_id)) out_xmldoc.childNodes[0].appendChild(elem) # Add CoincMap entry. coinc_map = lsctables.CoincMap() coinc_map.coinc_event_id = coinc.coinc_event_id coinc_map.table_name = sngl_inspiral_table.tableName coinc_map.event_id = sngl_inspiral.event_id coinc_map_table.append(coinc_map)
analyticPSD_Q=False) rhoExpected[det] = rhoDet = IP.norm(data_dict[det]) rho2Net += rhoDet * rhoDet print(det, " rho = ", rhoDet) print("Network : ", np.sqrt(rho2Net)) if opts.plot_ShowH: print( " == Plotting *raw* detector data data (time domain) via MANUAL INVERSE FFT == " ) print(" [This plot includes any windowing and data padding] ") for det in detectors: revplan = lal.CreateReverseCOMPLEX16FFTPlan( len(data_dict[det].data.data), 0) ht = lal.CreateCOMPLEX16TimeSeries("ht", lal.LIGOTimeGPS(0.), 0., Psig.deltaT, lal.lalDimensionlessUnit, len(data_dict[det].data.data)) lal.COMPLEX16FreqTimeFFT(ht, data_dict[det], revplan) tvals = float(ht.epoch - theEpochFiducial) + ht.deltaT * np.arange( len(ht.data.data)) # Not correct timing relative to zero - FIXME plt.figure(1) plt.plot(tvals, np.abs(ht.data.data), label=det) plt.legend() plt.show() print( " == Plotting detector data (time domain; requires regeneration, MANUAL TIMESHIFTS, and seperate code path! Argh!) == " ) P = Psig.copy() P.tref = Psig.tref
def __init__(self, template_table, approximant, psd, f_low, time_slices, autocorrelation_length=None, fhigh=None): self.template_table = template_table self.approximant = approximant self.f_low = f_low self.time_slices = time_slices self.autocorrelation_length = autocorrelation_length self.fhigh = fhigh self.sample_rate_max = max(time_slices["rate"]) self.duration = max(time_slices["end"]) self.length_max = int(round(self.duration * self.sample_rate_max)) if self.fhigh is None: self.fhigh = self.sample_rate_max / 2. # Some input checking to avoid incomprehensible error messages if not self.template_table: raise ValueError("template list is empty") if self.f_low < 0.: raise ValueError("f_low must be >= 0. %s" % repr(self.f_low)) # working f_low to actually use for generating the waveform. pick # template with lowest chirp mass, compute its duration starting # from f_low; the extra time is 10% of this plus 3 cycles (3 / # f_low); invert to obtain f_low corresponding to desired padding. # NOTE: because SimInspiralChirpStartFrequencyBound() does not # account for spin, we set the spins to 0 in the call to # SimInspiralChirpTimeBound() regardless of the component's spins. template = min(self.template_table, key=lambda row: row.mchirp) tchirp = lalsim.SimInspiralChirpTimeBound(self.f_low, template.mass1 * lal.MSUN_SI, template.mass2 * lal.MSUN_SI, 0., 0.) self.working_f_low = lalsim.SimInspiralChirpStartFrequencyBound( 1.1 * tchirp + 3. / self.f_low, template.mass1 * lal.MSUN_SI, template.mass2 * lal.MSUN_SI) # Add duration of PSD to template length for PSD ringing, round up to power of 2 count of samples self.working_length = templates.ceil_pow_2(self.length_max + round(1. / psd.deltaF * self.sample_rate_max)) self.working_duration = float( self.working_length) / self.sample_rate_max if psd is not None: # Smooth the PSD and interpolate to required resolution self.psd = condition_psd(psd, 1.0 / self.working_duration, minfs=(self.working_f_low, self.f_low), maxfs=(self.sample_rate_max / 2.0 * 0.90, self.sample_rate_max / 2.0)) if FIR_WHITENER: # Compute a frequency response of the time-domain whitening kernel and effectively taper the psd by zero-ing some elements for a FIR kernel self.kernel_fseries = taperzero_fseries(create_FIR_whitener_kernel(self.working_length, self.working_duration, self.sample_rate_max, self.psd),\ minfs = (self.working_f_low, self.f_low),\ maxfs = (self.sample_rate_max / 2.0 * 0.90, self.sample_rate_max / 2.0)\ ) self.revplan = lal.CreateReverseCOMPLEX16FFTPlan( self.working_length, 1) self.fwdplan = lal.CreateForwardREAL8FFTPlan(self.working_length, 1) self.tseries = lal.CreateCOMPLEX16TimeSeries( name="timeseries", epoch=LIGOTimeGPS(0.), f0=0., deltaT=1.0 / self.sample_rate_max, length=self.working_length, sampleUnits=lal.Unit("strain")) self.fworkspace = lal.CreateCOMPLEX16FrequencySeries( name="template", epoch=LIGOTimeGPS(0), f0=0.0, deltaF=1.0 / self.working_duration, length=self.working_length // 2 + 1, sampleUnits=lal.Unit("strain s")) # Calculate the maximum ring down time or maximum shift time if approximant in templates.gstlal_IMR_approximants: self.max_ringtime = max([ chirptime.ringtime( row.mass1 * lal.MSUN_SI + row.mass2 * lal.MSUN_SI, chirptime.overestimate_j_from_chi( max(row.spin1z, row.spin2z))) for row in self.template_table ]) else: if self.sample_rate_max > 2. * self.fhigh: # Calculate the maximum time we need to shift the early warning # waveforms forward by, calculated by the 3.5 approximation from # fhigh to ISCO. self.max_shift_time = max([ spawaveform.chirptime( row.mass1, row.mass2, 7, fhigh, 0., spawaveform.computechi(row.mass1, row.mass2, row.spin1z, row.spin2z)) for row in self.template_table ])