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
Exemple #2
0
 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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
    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
Exemple #6
0
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
Exemple #7
0
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
Exemple #9
0
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
Exemple #10
0
 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
Exemple #11
0
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
Exemple #12
0
    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
Exemple #13
0
    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
Exemple #16
0
    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
                ])