def phase_from_polarizations(h_plus, h_cross, remove_start_phase=True): """Return gravitational wave phase Return the gravitation-wave phase from the h_plus and h_cross polarizations of the waveform. The returned phase is always positive and increasing with an initial phase of 0. Parameters ---------- h_plus : TimeSeries An PyCBC TmeSeries vector that contains the plus polarization of the gravitational waveform. h_cross : TimeSeries A PyCBC TmeSeries vector that contains the cross polarization of the gravitational waveform. Returns ------- GWPhase : TimeSeries A TimeSeries containing the gravitational wave phase. Examples --------s >>> from pycbc.waveform import get_td_waveform, phase_from_polarizations >>> hp, hc = get_td_waveform(approximant="TaylorT4", mass1=10, mass2=10, f_lower=30, delta_t=1.0/4096) >>> phase = phase_from_polarizations(hp, hc) """ p = numpy.unwrap(numpy.arctan2(h_cross.data, h_plus.data)).astype( real_same_precision_as(h_plus)) if remove_start_phase: p += -p[0] return TimeSeries(p, delta_t=h_plus.delta_t, epoch=h_plus.start_time, copy=False)
def shift_sum(v1, shifts, bins): real_type = real_same_precision_as(v1) shifts = numpy.array(shifts, dtype=real_type) bins = numpy.array(bins, dtype=numpy.uint32) blen = len(bins) - 1 v1 = numpy.array(v1.data, copy=False) slen = len(v1) if v1.dtype.name == 'complex64': code = point_chisq_code_single else: code = point_chisq_code_double n = int(len(shifts)) # Create some output memory chisq = numpy.zeros(n, dtype=real_type) inline(code, ['v1', 'n', 'chisq', 'slen', 'shifts', 'bins', 'blen'], extra_compile_args=[WEAVE_FLAGS] + omp_flags, libraries=omp_libs ) return chisq
def make_padded_frequency_series(vec,filter_N=None): """Pad a TimeSeries with a length of zeros greater than its length, such that the total length is the closest power of 2. This prevents the effects of wraparound. """ if filter_N is None: power = ceil(log(len(vec),2))+1 N = 2 ** power else: N = filter_N n = N/2+1 if isinstance(vec,FrequencySeries): vectilde = FrequencySeries(zeros(n, dtype=complex_same_precision_as(vec)), delta_f=1.0,copy=False) if len(vectilde) < len(vec): cplen = len(vectilde) else: cplen = len(vec) vectilde[0:cplen] = vec[0:cplen] delta_f = vec.delta_f if isinstance(vec,TimeSeries): vec_pad = TimeSeries(zeros(N),delta_t=vec.delta_t, dtype=real_same_precision_as(vec)) vec_pad[0:len(vec)] = vec delta_f = 1.0/(vec.delta_t*N) vectilde = FrequencySeries(zeros(n),delta_f=1.0, dtype=complex_same_precision_as(vec)) fft(vec_pad,vectilde) vectilde = FrequencySeries(vectilde * DYN_RANGE_FAC,delta_f=delta_f,dtype=complex64) return vectilde
def bandlimited_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. """ series = FrequencySeries(series, dtype=complex_same_precision_as(series), delta_f=series.delta_f) N = (len(series) - 1) * 2 delta_t = 1.0 / series.delta_f / N new_N = int(1.0 / (delta_t * delta_f)) new_n = new_N / 2 + 1 series_in_time = TimeSeries(zeros(N), dtype=real_same_precision_as(series), delta_t=delta_t) ifft(series, series_in_time) padded_series_in_time = TimeSeries(zeros(new_N), dtype=series_in_time.dtype, delta_t=delta_t) padded_series_in_time[0:N/2] = series_in_time[0:N/2] padded_series_in_time[new_N-N/2:new_N] = series_in_time[N/2:N] interpolated_series = FrequencySeries(zeros(new_n), dtype=series.dtype, delta_f=delta_f) fft(padded_series_in_time, interpolated_series) return interpolated_series
def td_waveform_to_fd_waveform(waveform, out=None, length=None, buffer_length=100): """ Convert a time domain into a frequency domain waveform by FFT. As a waveform is assumed to "wrap" in the time domain one must be careful to ensure the waveform goes to 0 at both "boundaries". To ensure this is done correctly the waveform must have the epoch set such the merger time is at t=0 and the length of the waveform should be shorter than the desired length of the FrequencySeries (times 2 - 1) so that zeroes can be suitably pre- and post-pended before FFTing. If given, out is a memory array to be used as the output of the FFT. If not given memory is allocated internally. If present the length of the returned FrequencySeries is determined from the length out. If out is not given the length can be provided expicitly, or it will be chosen as the nearest power of 2. If choosing length explicitly the waveform length + buffer_length is used when choosing the nearest binary number so that some zero padding is always added. """ # Figure out lengths and set out if needed if out is None: if length is None: N = pnutils.nearest_larger_binary_number(len(waveform) + \ buffer_length) n = int(N//2) + 1 else: n = length N = (n-1)*2 out = zeros(n, dtype=complex_same_precision_as(waveform)) else: n = len(out) N = (n-1)*2 delta_f = 1. / (N * waveform.delta_t) # total duration of the waveform tmplt_length = len(waveform) * waveform.delta_t if len(waveform) > N: err_msg = "The time domain template is longer than the intended " err_msg += "duration in the frequency domain. This situation is " err_msg += "not supported in this function. Please shorten the " err_msg += "waveform appropriately before calling this function or " err_msg += "increase the allowed waveform length. " err_msg += "Waveform length (in samples): {}".format(len(waveform)) err_msg += ". Intended length: {}.".format(N) raise ValueError(err_msg) # 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( waveform.start_time ) # conversion from LIGOTimeGPS waveform.resize(N) k_zero = int(waveform.start_time / waveform.delta_t) waveform.roll(k_zero) htilde = FrequencySeries(out, delta_f=delta_f, copy=False) fft(waveform.astype(real_same_precision_as(htilde)), htilde) htilde.length_in_time = tmplt_length htilde.chirp_length = tChirp return htilde
def abs_arg_max(self): if self.kind == 'real': return _np.argmax(abs(self.data)) else: data = _np.array(self._data, # pylint:disable=unused-variable copy=False).view(real_same_precision_as(self)) loc = _np.array([0]) N = len(self) # pylint:disable=unused-variable inline(code_abs_arg_max, ['data', 'loc', 'N'], libraries=omp_libs, extra_compile_args=code_flags) return loc[0]
def get_waveform_filter(out, template=None, **kwargs): """Return a frequency domain waveform filter for the specified approximant """ n = len(out) input_params = props(template, **kwargs) if input_params['approximant'] in filter_approximants(_scheme.mgr.state): wav_gen = filter_wav[type(_scheme.mgr.state)] htilde = wav_gen[input_params['approximant']](out=out, **input_params) htilde.resize(n) htilde.chirp_length = get_waveform_filter_length_in_time(**input_params) htilde.length_in_time = htilde.chirp_length return htilde 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) out[0:len(hp)] = hp[:] hp = FrequencySeries(out, delta_f=hp.delta_f, copy=False) hp.chirp_length = get_waveform_filter_length_in_time(**input_params) hp.length_in_time = hp.chirp_length return hp 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) # 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) k_zero = int(hp.start_time / hp.delta_t) hp.roll(k_zero) htilde = FrequencySeries(out, delta_f=delta_f, copy=False) fft(hp.astype(real_same_precision_as(htilde)), htilde) htilde.length_in_time = tmplt_length htilde.chirp_length = tChirp return htilde else: raise ValueError("Approximant %s not available" % (input_params['approximant']))
def time_from_frequencyseries(htilde, sample_frequencies=None, discont_threshold=0.99*numpy.pi): """Computes time as a function of frequency from the given frequency-domain waveform. This assumes the stationary phase approximation. Any frequencies lower than the first non-zero value in htilde are assigned the time at the first non-zero value. Times for any frequencies above the next-to-last non-zero value in htilde will be assigned the time of the next-to-last non-zero value. .. note:: Some waveform models (e.g., `SEOBNRv2_ROM_DoubleSpin`) can have discontinuities in the phase towards the end of the waveform due to numerical error. We therefore exclude any points that occur after a discontinuity in the phase, as the time estimate becomes untrustworthy beyond that point. What determines a discontinuity in the phase is set by the `discont_threshold`. To turn this feature off, just set `discont_threshold` to a value larger than pi (due to the unwrapping of the phase, no two points can have a difference > pi). Parameters ---------- htilde : FrequencySeries The waveform to get the time evolution of; must be complex. sample_frequencies : {None, array} The frequencies at which the waveform is sampled. If None, will retrieve from ``htilde.sample_frequencies``. discont_threshold : {0.99*pi, float} If the difference in the phase changes by more than this threshold, it is considered to be a discontinuity. Default is 0.99*pi. Returns ------- FrequencySeries The time evolution of the waveform as a function of frequency. """ if sample_frequencies is None: sample_frequencies = htilde.sample_frequencies.numpy() phase = phase_from_frequencyseries(htilde).data dphi = numpy.diff(phase) time = -dphi / (2.*numpy.pi*numpy.diff(sample_frequencies)) nzidx = numpy.nonzero(abs(htilde.data))[0] kmin, kmax = nzidx[0], nzidx[-2] # exclude everything after a discontinuity discont_idx = numpy.where(abs(dphi[kmin:]) >= discont_threshold)[0] if discont_idx.size != 0: kmax = min(kmax, kmin + discont_idx[0]-1) time[:kmin] = time[kmin] time[kmax:] = time[kmax] return FrequencySeries(time.astype(real_same_precision_as(htilde)), delta_f=htilde.delta_f, epoch=htilde.epoch, copy=False)
def td_waveform_to_fd_waveform(waveform, out=None, length=None, buffer_length=100): """ Convert a time domain into a frequency domain waveform by FFT. As a waveform is assumed to "wrap" in the time domain one must be careful to ensure the waveform goes to 0 at both "boundaries". To ensure this is done correctly the waveform must have the epoch set such the merger time is at t=0 and the length of the waveform should be shorter than the desired length of the FrequencySeries (times 2 - 1) so that zeroes can be suitably pre- and post-pended before FFTing. If given, out is a memory array to be used as the output of the FFT. If not given memory is allocated internally. If present the length of the returned FrequencySeries is determined from the length out. If out is not given the length can be provided expicitly, or it will be chosen as the nearest power of 2. If choosing length explicitly the waveform length + buffer_length is used when choosing the nearest binary number so that some zero padding is always added. """ # Figure out lengths and set out if needed if out is None: if length is None: N = pnutils.nearest_larger_binary_number(len(waveform) + \ buffer_length) n = int(N // 2) + 1 else: n = length N = (n - 1) * 2 out = zeros(n, dtype=complex_same_precision_as(waveform)) else: n = len(out) N = (n - 1) * 2 delta_f = 1. / (N * waveform.delta_t) # total duration of the waveform tmplt_length = len(waveform) * waveform.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(waveform.start_time) # conversion from LIGOTimeGPS waveform.resize(N) k_zero = int(waveform.start_time / waveform.delta_t) waveform.roll(k_zero) htilde = FrequencySeries(out, delta_f=delta_f, copy=False) fft(waveform.astype(real_same_precision_as(htilde)), htilde) htilde.length_in_time = tmplt_length htilde.chirp_length = tChirp return htilde
def interpolate_complex_frequency(series, delta_f, zeros_offset=0, side='right'): """Interpolate complex frequency series to desired delta_f. Return a new complex frequency series 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 zeros_offset : optional, {0, int} Number of sample to delay the start of the zero padding side : optional, {'right', str} The side of the vector to zero pad Returns ------- interpolated series : FrequencySeries A new FrequencySeries that has been interpolated. """ new_n = int((len(series) - 1) * series.delta_f / delta_f + 1) old_N = int((len(series) - 1) * 2) new_N = int((new_n - 1) * 2) time_series = TimeSeries(zeros(old_N), delta_t=1.0 / (series.delta_f * old_N), dtype=real_same_precision_as(series)) ifft(series, time_series) time_series.roll(-zeros_offset) time_series.resize(new_N) if side == 'left': time_series.roll(zeros_offset + new_N - old_N) elif side == 'right': time_series.roll(zeros_offset) out_series = FrequencySeries(zeros(new_n), epoch=series.epoch, delta_f=delta_f, dtype=series.dtype) fft(time_series, out_series) return out_series
def amplitude_from_frequencyseries(htilde): """Returns the amplitude of the given frequency-domain waveform as a FrequencySeries. Parameters ---------- htilde : FrequencySeries The waveform to get the amplitude of. Returns ------- FrequencySeries The amplitude of the waveform as a function of frequency. """ amp = abs(htilde.data).astype(real_same_precision_as(htilde)) return FrequencySeries(amp, delta_f=htilde.delta_f, epoch=htilde.epoch, copy=False)
def sigmasq_series(htilde, psd=None, low_frequency_cutoff=None, high_frequency_cutoff=None): """Return a cumulative sigmasq frequency series. Return a frequency series containing the accumulated power in the input up to that frequency. Parameters ---------- htilde : TimeSeries or FrequencySeries The input vector psd : {None, FrequencySeries}, optional The psd used to weight the accumulated power. low_frequency_cutoff : {None, float}, optional The frequency to begin accumulating power. If None, start at the beginning of the vector. high_frequency_cutoff : {None, float}, optional The frequency to stop considering accumulated power. If None, continue until the end of the input vector. Returns ------- Frequency Series: FrequencySeries A frequency series containing the cumulative sigmasq. """ htilde = make_frequency_series(htilde) N = (len(htilde) - 1) * 2 norm = 4.0 * htilde.delta_f kmin, kmax = get_cutoff_indices(low_frequency_cutoff, high_frequency_cutoff, htilde.delta_f, N) sigma_vec = FrequencySeries(zeros(len(htilde), dtype=real_same_precision_as(htilde)), delta_f=htilde.delta_f, copy=False) mag = htilde.squared_norm() if psd is not None: mag /= psd sigma_vec[kmin:kmax] = mag[kmin:kmax].cumsum() return sigma_vec * norm
def interpolate_complex_frequency(series, delta_f, zeros_offset=0, side='right'): """Interpolate complex frequency series to desired delta_f. Return a new complex frequency series 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 zeros_offset : optional, {0, int} Number of sample to delay the start of the zero padding side : optional, {'right', str} The side of the vector to zero pad Returns ------- interpolated series : FrequencySeries A new FrequencySeries that has been interpolated. """ new_n = int( (len(series)-1) * series.delta_f / delta_f + 1) samples = numpy.arange(0, new_n) * delta_f old_N = int( (len(series)-1) * 2 ) new_N = int( (new_n - 1) * 2 ) time_series = TimeSeries(zeros(old_N), delta_t =1.0/(series.delta_f*old_N), dtype=real_same_precision_as(series)) ifft(series, time_series) time_series.roll(-zeros_offset) time_series.resize(new_N) if side == 'left': time_series.roll(zeros_offset + new_N - old_N) elif side == 'right': time_series.roll(zeros_offset) out_series = FrequencySeries(zeros(new_n), epoch=series.epoch, delta_f=delta_f, dtype=series.dtype) fft(time_series, out_series) return out_series
def to_timeseries(self, delta_t=None): """ Return the Fourier transform of this time series. Note that this assumes even length time series! Parameters ---------- delta_t : {None, float}, optional The time resolution of the returned series. By default the resolution is determined by length and delta_f of this frequency series. Returns ------- TimeSeries: The inverse fourier transform of this frequency series. """ from pycbc.fft import ifft from pycbc.types import TimeSeries, real_same_precision_as nat_delta_t = 1.0 / ((len(self)-1)*2) / self.delta_f if not delta_t: delta_t = nat_delta_t # add 0.5 to round integer tlen = int(1.0 / self.delta_f / delta_t + 0.5) flen = int(tlen / 2 + 1) if flen < len(self): raise ValueError("The value of delta_t (%s) would be " "undersampled. Maximum delta_t " "is %s." % (delta_t, nat_delta_t)) if not delta_t: tmp = self else: tmp = FrequencySeries(zeros(flen, dtype=self.dtype), delta_f=self.delta_f, epoch=self.epoch) tmp[:len(self)] = self[:] f = TimeSeries(zeros(tlen, dtype=real_same_precision_as(self)), delta_t=delta_t) ifft(tmp, f) f._delta_t = delta_t return f
def to_timeseries(self, delta_t=None): """ Return the Fourier transform of this time series. Note that this assumes even length time series! Parameters ---------- delta_t : {None, float}, optional The time resolution of the returned series. By default the resolution is determined by length and delta_f of this frequency series. Returns ------- TimeSeries: The inverse fourier transform of this frequency series. """ from pycbc.fft import ifft from pycbc.types import TimeSeries, real_same_precision_as nat_delta_t = 1.0 / ((len(self)-1)*2) / self.delta_f if not delta_t: delta_t = nat_delta_t # add 0.5 to round integer tlen = int(1.0 / self.delta_f / delta_t + 0.5) flen = tlen / 2 + 1 if flen < len(self): raise ValueError("The value of delta_t (%s) would be " "undersampled. Maximum delta_t " "is %s." % (delta_t, nat_delta_t)) if not delta_t: tmp = self else: tmp = FrequencySeries(zeros(flen, dtype=self.dtype), delta_f=self.delta_f, epoch=self.epoch) tmp[:len(self)] = self[:] f = TimeSeries(zeros(tlen, dtype=real_same_precision_as(self)), delta_t=delta_t) ifft(tmp, f) return f
def bandlimited_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. """ series = FrequencySeries(series, dtype=complex_same_precision_as(series), delta_f=series.delta_f) N = (len(series) - 1) * 2 delta_t = 1.0 / series.delta_f / N new_N = int(1.0 / (delta_t * delta_f)) new_n = new_N // 2 + 1 series_in_time = TimeSeries(zeros(N), dtype=real_same_precision_as(series), delta_t=delta_t) ifft(series, series_in_time) padded_series_in_time = TimeSeries(zeros(new_N), dtype=series_in_time.dtype, delta_t=delta_t) padded_series_in_time[0:N // 2] = series_in_time[0:N // 2] padded_series_in_time[new_N - N // 2:new_N] = series_in_time[N // 2:N] interpolated_series = FrequencySeries(zeros(new_n), dtype=series.dtype, delta_f=delta_f) fft(padded_series_in_time, interpolated_series) return interpolated_series
def sigmasq_series(htilde, psd=None, low_frequency_cutoff=None, high_frequency_cutoff=None): """Return a cumulative sigmasq frequency series. Return a frequency series containing the accumulated power in the input up to that frequency. Parameters ---------- htilde : TimeSeries or FrequencySeries The input vector psd : {None, FrequencySeries}, optional The psd used to weight the accumulated power. low_frequency_cutoff : {None, float}, optional The frequency to begin accumulating power. If None, start at the beginning of the vector. high_frequency_cutoff : {None, float}, optional The frequency to stop considering accumulated power. If None, continue until the end of the input vector. Returns ------- Frequency Series: FrequencySeries A frequency series containing the cumulative sigmasq. """ htilde = make_frequency_series(htilde) N = (len(htilde)-1) * 2 norm = 4.0 * htilde.delta_f kmin, kmax = get_cutoff_indices(low_frequency_cutoff, high_frequency_cutoff, htilde.delta_f, N) sigma_vec = FrequencySeries(zeros(len(htilde), dtype=real_same_precision_as(htilde)), delta_f = htilde.delta_f, copy=False) mag = htilde.squared_norm() if psd is not None: mag /= psd sigma_vec[kmin:kmax] = mag[kmin:kmax].cumsum() return sigma_vec*norm
def make_padded_frequency_series(vec, filter_N=None): """Pad a TimeSeries with a length of zeros greater than its length, such that the total length is the closest power of 2. This prevents the effects of wraparound. """ if filter_N is None: power = ceil(log(len(vec), 2)) + 1 N = 2**power else: N = filter_N n = N / 2 + 1 if isinstance(vec, FrequencySeries): vectilde = FrequencySeries(zeros(n, dtype=complex_same_precision_as(vec)), delta_f=1.0, copy=False) if len(vectilde) < len(vec): cplen = len(vectilde) else: cplen = len(vec) vectilde[0:cplen] = vec[0:cplen] delta_f = vec.delta_f if isinstance(vec, TimeSeries): vec_pad = TimeSeries(zeros(N), delta_t=vec.delta_t, dtype=real_same_precision_as(vec)) vec_pad[0:len(vec)] = vec delta_f = 1.0 / (vec.delta_t * N) vectilde = FrequencySeries(zeros(n), delta_f=1.0, dtype=complex_same_precision_as(vec)) fft(vec_pad, vectilde) vectilde = FrequencySeries(vectilde * DYN_RANGE_FAC, delta_f=delta_f, dtype=complex64) return vectilde
def phase_from_frequencyseries(htilde, remove_start_phase=True): """Returns the phase from the given frequency-domain waveform. This assumes that the waveform has been sampled finely enough that the phase cannot change by more than pi radians between each step. Parameters ---------- htilde : FrequencySeries The waveform to get the phase for; must be a complex frequency series. remove_start_phase : {True, bool} Subtract the initial phase before returning. Returns ------- FrequencySeries The phase of the waveform as a function of frequency. """ p = numpy.unwrap(numpy.angle(htilde.data)).astype( real_same_precision_as(htilde)) if remove_start_phase: p += -p[0] return FrequencySeries(p, delta_f=htilde.delta_f, epoch=htilde.epoch, copy=False)
def frequency_from_polarizations(h_plus, h_cross): """Return gravitational wave frequency Return the gravitation-wave frequency as a function of time from the h_plus and h_cross polarizations of the waveform. It is 1 bin shorter than the input vectors and the sample times are advanced half a bin. Parameters ---------- h_plus : TimeSeries A PyCBC TimeSeries vector that contains the plus polarization of the gravitational waveform. h_cross : TimeSeries A PyCBC TimeSeries vector that contains the cross polarization of the gravitational waveform. Returns ------- GWFrequency : TimeSeries A TimeSeries containing the gravitational wave frequency as a function of time. Examples -------- >>> from pycbc.waveform import get_td_waveform, phase_from_polarizations >>> hp, hc = get_td_waveform(approximant="TaylorT4", mass1=10, mass2=10, f_lower=30, delta_t=1.0/4096) >>> freq = frequency_from_polarizations(hp, hc) """ phase = phase_from_polarizations(h_plus, h_cross) freq = numpy.diff(phase) / (2 * lal.PI * phase.delta_t) start_time = phase.start_time + phase.delta_t / 2 return TimeSeries(freq.astype(real_same_precision_as(h_plus)), delta_t=phase.delta_t, epoch=start_time)
def shift_sum(v1, shifts, bins): real_type = real_same_precision_as(v1) shifts = numpy.array(shifts, dtype=real_type) bins = numpy.array(bins, dtype=numpy.uint32) blen = len(bins) - 1 v1 = numpy.array(v1.data, copy=False) slen = len(v1) if v1.dtype.name == 'complex64': code = point_chisq_code_single else: code = point_chisq_code_double n = int(len(shifts)) # Create some output memory chisq = numpy.zeros(n, dtype=real_type) inline(code, ['v1', 'n', 'chisq', 'slen', 'shifts', 'bins', 'blen'], extra_compile_args=[WEAVE_FLAGS] + omp_flags, libraries=omp_libs) return chisq
def frequency_from_polarizations(h_plus, h_cross): """Return gravitational wave frequency Return the gravitation-wave frequency as a function of time from the h_plus and h_cross polarizations of the waveform. It is 1 bin shorter than the input vectors and the sample times are advanced half a bin. Parameters ---------- h_plus : TimeSeries A PyCBC TimeSeries vector that contains the plus polarization of the gravitational waveform. h_cross : TimeSeries A PyCBC TimeSeries vector that contains the cross polarization of the gravitational waveform. Returns ------- GWFrequency : TimeSeries A TimeSeries containing the gravitational wave frequency as a function of time. Examples -------- >>> from pycbc.waveform import get_td_waveform, phase_from_polarizations >>> hp, hc = get_td_waveform(approximant="TaylorT4", mass1=10, mass2=10, f_lower=30, delta_t=1.0/4096) >>> freq = frequency_from_polarizations(hp, hc) """ phase = phase_from_polarizations(h_plus, h_cross) freq = numpy.diff(phase) / ( 2 * lal.PI * phase.delta_t ) start_time = phase.start_time + phase.delta_t / 2 return TimeSeries(freq.astype(real_same_precision_as(h_plus)), delta_t=phase.delta_t, epoch=start_time)
hp_size = len(hp) hp.resize(time_duration * f_sample) psd = noise.psd(4) psd = interpolate(psd, hp.delta_f) sigma = matchedfilter.sigma(hp, psd=psd, low_frequency_cutoff=f_min) Amplitude = snr / (sigma) hp *= Amplitude hp.resize(hp_size) merger_time = 69 merger_index = int(69 / delta_t) + 1 start_index = merger_index + len(hp) waveform = TimeSeries(numpy.zeros(len(noise)), delta_t=delta_t, \ dtype=real_same_precision_as(noise)) waveform[merger_index:start_index] = hp signal = noise + waveform pylab.figure(figsize=(10, 5)) pylab.plot(signal.sample_times, signal, label='Waveform + Noise') pylab.plot(waveform.sample_times, waveform, label='Waveform') pylab.legend() pylab.xlabel('Time (s)') pylab.ylabel('Strain') pylab.title('Injected Waveform') zoom_signal = signal.time_slice(merger_time - 0.5, merger_time + 0.5)
def compress_waveform(htilde, sample_points, tolerance, interpolation, precision, decomp_scratch=None, psd=None): """Retrieves the amplitude and phase at the desired sample points, and adds frequency points in order to ensure that the interpolated waveform has a mismatch with the full waveform that is <= the desired tolerance. The mismatch is computed by finding 1-overlap between `htilde` and the decompressed waveform; no maximimization over phase/time is done, a PSD may be used. .. note:: The decompressed waveform is only garaunteed to have a true mismatch <= the tolerance for the given `interpolation` and for no PSD. However, since no maximization over time/phase is performed when adding points, the actual mismatch between the decompressed waveform and `htilde` is better than the tolerance, using no PSD. Using a PSD does increase the mismatch, and can lead to mismatches > than the desired tolerance, but typically by only a factor of a few worse. Parameters ---------- htilde : FrequencySeries The waveform to compress. sample_points : array The frequencies at which to store the amplitude and phase. More points may be added to this, depending on the desired tolerance. tolerance : float The maximum mismatch to allow between a decompressed waveform and `htilde`. interpolation : str The interpolation to use for decompressing the waveform when computing overlaps. precision : str The precision being used to generate and store the compressed waveform points. decomp_scratch : {None, FrequencySeries} Optionally provide scratch space for decompressing the waveform. The provided frequency series must have the same `delta_f` and length as `htilde`. psd : {None, FrequencySeries} The psd to use for calculating the overlap between the decompressed waveform and the original full waveform. Returns ------- CompressedWaveform The compressed waveform data; see `CompressedWaveform` for details. """ fmin = sample_points.min() df = htilde.delta_f sample_index = (sample_points / df).astype(int) amp = utils.amplitude_from_frequencyseries(htilde) phase = utils.phase_from_frequencyseries(htilde) comp_amp = amp.take(sample_index) comp_phase = phase.take(sample_index) if decomp_scratch is None: outdf = df else: outdf = None hdecomp = fd_decompress(comp_amp, comp_phase, sample_points, out=decomp_scratch, df=outdf, f_lower=fmin, interpolation=interpolation) kmax = min(len(htilde), len(hdecomp)) htilde = htilde[:kmax] hdecomp = hdecomp[:kmax] mismatch = 1. - filter.overlap( hdecomp, htilde, psd=psd, low_frequency_cutoff=fmin) if mismatch > tolerance: # we'll need the difference in the waveforms as a function of frequency vecdiffs = vecdiff(htilde, hdecomp, sample_points, psd=psd) # We will find where in the frequency series the interpolated waveform # has the smallest overlap with the full waveform, add a sample point # there, and re-interpolate. We repeat this until the overall mismatch # is > than the desired tolerance added_points = [] while mismatch > tolerance: minpt = vecdiffs.argmax() # add a point at the frequency halfway between minpt and minpt+1 add_freq = sample_points[[minpt, minpt + 1]].mean() addidx = int(round(add_freq / df)) # ensure that only new points are added if addidx in sample_index: diffidx = vecdiffs.argsort() addpt = -1 while addidx in sample_index: addpt -= 1 try: minpt = diffidx[addpt] except IndexError: raise ValueError("unable to compress to desired tolerance") add_freq = sample_points[[minpt, minpt + 1]].mean() addidx = int(round(add_freq / df)) new_index = numpy.zeros(sample_index.size + 1, dtype=int) new_index[:minpt + 1] = sample_index[:minpt + 1] new_index[minpt + 1] = addidx new_index[minpt + 2:] = sample_index[minpt + 1:] sample_index = new_index sample_points = (sample_index * df).astype( real_same_precision_as(htilde)) # get the new compressed points comp_amp = amp.take(sample_index) comp_phase = phase.take(sample_index) # update the vecdiffs and mismatch hdecomp = fd_decompress(comp_amp, comp_phase, sample_points, out=decomp_scratch, df=outdf, f_lower=fmin, interpolation=interpolation) hdecomp = hdecomp[:kmax] new_vecdiffs = numpy.zeros(vecdiffs.size + 1) new_vecdiffs[:minpt] = vecdiffs[:minpt] new_vecdiffs[minpt + 2:] = vecdiffs[minpt + 1:] new_vecdiffs[minpt:minpt + 2] = vecdiff(htilde, hdecomp, sample_points[minpt:minpt + 2], psd=psd) vecdiffs = new_vecdiffs mismatch = 1. - filter.overlap( hdecomp, htilde, psd=psd, low_frequency_cutoff=fmin) added_points.append(addidx) logging.info("mismatch: %f, N points: %i (%i added)" % (mismatch, len(comp_amp), len(added_points))) return CompressedWaveform(sample_points, comp_amp, comp_phase, interpolation=interpolation, tolerance=tolerance, mismatch=mismatch, precision=precision)
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 bank_chisq_from_filters(tmplt_snr, tmplt_norm, bank_snrs, bank_norms, tmplt_bank_matches, indices=None): """ This function calculates and returns a TimeSeries object containing the bank veto calculated over a segment. Parameters ---------- tmplt_snr: TimeSeries The SNR time series from filtering the segment against the current search template tmplt_norm: float The normalization factor for the search template bank_snrs: list of TimeSeries The precomputed list of SNR time series between each of the bank veto templates and the segment bank_norms: list of floats The normalization factors for the list of bank veto templates (usually this will be the same for all bank veto templates) tmplt_bank_matches: list of floats The complex overlap between the search template and each of the bank templates indices: {None, Array}, optional Array of indices into the snr time series. If given, the bank chisq will only be calculated at these values. Returns ------- bank_chisq: TimeSeries of the bank vetos """ if indices is not None: tmplt_snr = Array(tmplt_snr, copy=False) bank_snrs_tmp = [] for bank_snr in bank_snrs: bank_snrs_tmp.append(bank_snr.take(indices)) bank_snrs=bank_snrs_tmp # Initialise bank_chisq as 0s everywhere bank_chisq = zeros(len(tmplt_snr), dtype=real_same_precision_as(tmplt_snr)) # Loop over all the bank templates for i in range(len(bank_snrs)): bank_match = tmplt_bank_matches[i] if (abs(bank_match) > 0.99): # Not much point calculating bank_chisquared if the bank template # is very close to the filter template. Can also hit numerical # error due to approximations made in this calculation. # The value of 2 is the expected addition to the chisq for this # template bank_chisq += 2. continue bank_norm = sqrt((1 - bank_match*bank_match.conj()).real) bank_SNR = bank_snrs[i] * (bank_norms[i] / bank_norm) tmplt_SNR = tmplt_snr * (bank_match.conj() * tmplt_norm / bank_norm) bank_SNR = Array(bank_SNR, copy=False) tmplt_SNR = Array(tmplt_SNR, copy=False) bank_chisq += (bank_SNR - tmplt_SNR).squared_norm() if indices is not None: return bank_chisq else: return TimeSeries(bank_chisq, delta_t=tmplt_snr.delta_t, epoch=tmplt_snr.start_time, copy=False)
def power_chisq_from_precomputed(corr, snr, snr_norm, bins, indices=None): """Calculate the chisq timeseries from precomputed values This function calculates the chisq at all times by performing an inverse FFT of each bin. Parameters ---------- corr: FrequencySeries The produce of the template and data in the frequency domain. snr: TimeSeries The unnormalized snr time series. snr_norm: The snr normalization factor (true snr = snr * snr_norm) EXPLAINME - define 'true snr'? bins: List of integers The edges of the chisq bins. indices: {Array, None}, optional Index values into snr that indicate where to calculate chisq values. If none, calculate chisq for all possible indices. Returns ------- chisq: TimeSeries """ # Get workspace memory global _q_l, _qtilde_l, _chisq_l if _q_l is None or len(_q_l) != len(snr): q = zeros(len(snr), dtype=complex_same_precision_as(snr)) qtilde = zeros(len(snr), dtype=complex_same_precision_as(snr)) _q_l = q _qtilde_l = qtilde else: q = _q_l qtilde = _qtilde_l if indices is not None: snr = snr.take(indices) if _chisq_l is None or len(_chisq_l) < len(snr): chisq = zeros(len(snr), dtype=real_same_precision_as(snr)) _chisq_l = chisq else: chisq = _chisq_l[0:len(snr)] chisq.clear() num_bins = len(bins) - 1 for j in range(num_bins): k_min = int(bins[j]) k_max = int(bins[j+1]) qtilde[k_min:k_max] = corr[k_min:k_max] pycbc.fft.ifft(qtilde, q) qtilde[k_min:k_max].clear() if indices is not None: chisq_accum_bin(chisq, q.take(indices)) else: chisq_accum_bin(chisq, q) chisq = (chisq * num_bins - snr.squared_norm()) * (snr_norm ** 2.0) if indices is not None: return chisq else: return TimeSeries(chisq, delta_t=snr.delta_t, epoch=snr.start_time, copy=False)
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 power_chisq_from_precomputed(corr, snr, snr_norm, bins, indices=None, return_bins=False): """Calculate the chisq timeseries from precomputed values. This function calculates the chisq at all times by performing an inverse FFT of each bin. Parameters ---------- corr: FrequencySeries The produce of the template and data in the frequency domain. snr: TimeSeries The unnormalized snr time series. snr_norm: The snr normalization factor (true snr = snr * snr_norm) EXPLAINME - define 'true snr'? bins: List of integers The edges of the chisq bins. indices: {Array, None}, optional Index values into snr that indicate where to calculate chisq values. If none, calculate chisq for all possible indices. return_bins: {boolean, False}, optional Return a list of the SNRs for each chisq bin. Returns ------- chisq: TimeSeries """ # Get workspace memory global _q_l, _qtilde_l, _chisq_l bin_snrs = [] if _q_l is None or len(_q_l) != len(snr): q = zeros(len(snr), dtype=complex_same_precision_as(snr)) qtilde = zeros(len(snr), dtype=complex_same_precision_as(snr)) _q_l = q _qtilde_l = qtilde else: q = _q_l qtilde = _qtilde_l if indices is not None: snr = snr.take(indices) if _chisq_l is None or len(_chisq_l) < len(snr): chisq = zeros(len(snr), dtype=real_same_precision_as(snr)) _chisq_l = chisq else: chisq = _chisq_l[0:len(snr)] chisq.clear() num_bins = len(bins) - 1 for j in range(num_bins): k_min = int(bins[j]) k_max = int(bins[j + 1]) qtilde[k_min:k_max] = corr[k_min:k_max] pycbc.fft.ifft(qtilde, q) qtilde[k_min:k_max].clear() if return_bins: bin_snrs.append( TimeSeries(q * snr_norm * num_bins**0.5, delta_t=snr.delta_t, epoch=snr.start_time)) if indices is not None: chisq_accum_bin(chisq, q.take(indices)) else: chisq_accum_bin(chisq, q) chisq = (chisq * num_bins - snr.squared_norm()) * (snr_norm**2.0) if indices is None: chisq = TimeSeries(chisq, delta_t=snr.delta_t, epoch=snr.start_time, copy=False) if return_bins: return chisq, bin_snrs else: return chisq
def bank_chisq_from_filters(tmplt_snr, tmplt_norm, bank_snrs, bank_norms, tmplt_bank_matches, indices=None): """ This function calculates and returns a TimeSeries object containing the bank veto calculated over a segment. Parameters ---------- tmplt_snr: TimeSeries The SNR time series from filtering the segment against the current search template tmplt_norm: float The normalization factor for the search template bank_snrs: list of TimeSeries The precomputed list of SNR time series between each of the bank veto templates and the segment bank_norms: list of floats The normalization factors for the list of bank veto templates (usually this will be the same for all bank veto templates) tmplt_bank_matches: list of floats The complex overlap between the search template and each of the bank templates indices: {None, Array}, optional Array of indices into the snr time series. If given, the bank chisq will only be calculated at these values. Returns ------- bank_chisq: TimeSeries of the bank vetos """ if indices is not None: tmplt_snr = Array(tmplt_snr, copy=False) bank_snrs_tmp = [] for bank_snr in bank_snrs: bank_snrs_tmp.append(bank_snr.take(indices)) bank_snrs = bank_snrs_tmp # Initialise bank_chisq as 0s everywhere bank_chisq = zeros(len(tmplt_snr), dtype=real_same_precision_as(tmplt_snr)) # Loop over all the bank templates for i in range(len(bank_snrs)): bank_match = tmplt_bank_matches[i] if (abs(bank_match) > 0.99): # Not much point calculating bank_chisquared if the bank template # is very close to the filter template. Can also hit numerical # error due to approximations made in this calculation. # The value of 2 is the expected addition to the chisq for this # template bank_chisq += 2. continue bank_norm = sqrt((1 - bank_match * bank_match.conj()).real) bank_SNR = bank_snrs[i] * (bank_norms[i] / bank_norm) tmplt_SNR = tmplt_snr * (bank_match.conj() * tmplt_norm / bank_norm) bank_SNR = Array(bank_SNR, copy=False) tmplt_SNR = Array(tmplt_SNR, copy=False) bank_chisq += (bank_SNR - tmplt_SNR).squared_norm() if indices is not None: return bank_chisq else: return TimeSeries(bank_chisq, delta_t=tmplt_snr.delta_t, epoch=tmplt_snr.start_time, copy=False)
def compress_waveform(htilde, sample_points, tolerance, interpolation, decomp_scratch=None): """Retrieves the amplitude and phase at the desired sample points, and adds frequency points in order to ensure that the interpolated waveform has a mismatch with the full waveform that is <= the desired tolerance. The mismatch is computed by finding 1-overlap between `htilde` and the decompressed waveform; no maximimization over phase/time is done, nor is any PSD used. .. note:: The decompressed waveform is only garaunteed to have a true mismatch <= the tolerance for the given `interpolation` and for no PSD. However, since no maximization over time/phase is performed when adding points, the actual mismatch between the decompressed waveform and `htilde` is better than the tolerance, using no PSD. Using a PSD does increase the mismatch, and can lead to mismatches > than the desired tolerance, but typically by only a factor of a few worse. Parameters ---------- htilde : FrequencySeries The waveform to compress. sample_points : array The frequencies at which to store the amplitude and phase. More points may be added to this, depending on the desired tolerance. tolerance : float The maximum mismatch to allow between a decompressed waveform and `htilde`. interpolation : str The interpolation to use for decompressing the waveform when computing overlaps. decomp_scratch : {None, FrequencySeries} Optionally provide scratch space for decompressing the waveform. The provided frequency series must have the same `delta_f` and length as `htilde`. Returns ------- CompressedWaveform The compressed waveform data; see `CompressedWaveform` for details. """ fmin = sample_points.min() df = htilde.delta_f sample_index = (sample_points / df).astype(int) amp = utils.amplitude_from_frequencyseries(htilde) phase = utils.phase_from_frequencyseries(htilde) comp_amp = amp.take(sample_index) comp_phase = phase.take(sample_index) if decomp_scratch is None: outdf = df else: outdf = None out = decomp_scratch hdecomp = fd_decompress(comp_amp, comp_phase, sample_points, out=decomp_scratch, df=outdf, f_lower=fmin, interpolation=interpolation) mismatch = 1. - filter.overlap(hdecomp, htilde, low_frequency_cutoff=fmin) if mismatch > tolerance: # we'll need the difference in the waveforms as a function of frequency vecdiffs = vecdiff(htilde, hdecomp, sample_points) # We will find where in the frequency series the interpolated waveform # has the smallest overlap with the full waveform, add a sample point # there, and re-interpolate. We repeat this until the overall mismatch # is > than the desired tolerance added_points = [] while mismatch > tolerance: minpt = vecdiffs.argmax() # add a point at the frequency halfway between minpt and minpt+1 add_freq = sample_points[[minpt, minpt+1]].mean() addidx = int(add_freq/df) new_index = numpy.zeros(sample_index.size+1, dtype=int) new_index[:minpt+1] = sample_index[:minpt+1] new_index[minpt+1] = addidx new_index[minpt+2:] = sample_index[minpt+1:] sample_index = new_index sample_points = (sample_index * df).astype( real_same_precision_as(htilde)) # get the new compressed points comp_amp = amp.take(sample_index) comp_phase = phase.take(sample_index) # update the vecdiffs and mismatch hdecomp = fd_decompress(comp_amp, comp_phase, sample_points, out=decomp_scratch, df=outdf, f_lower=fmin, interpolation=interpolation) new_vecdiffs = numpy.zeros(vecdiffs.size+1) new_vecdiffs[:minpt] = vecdiffs[:minpt] new_vecdiffs[minpt+2:] = vecdiffs[minpt+1:] new_vecdiffs[minpt:minpt+2] = vecdiff(htilde, hdecomp, sample_points[minpt:minpt+2]) vecdiffs = new_vecdiffs mismatch = 1. - filter.overlap(hdecomp, htilde, low_frequency_cutoff=fmin) added_points.append(addidx) logging.info("mismatch: %f, N points: %i (%i added)" %(mismatch, len(comp_amp), len(added_points))) return CompressedWaveform(sample_points, comp_amp, comp_phase, interpolation=interpolation, tolerance=tolerance, mismatch=mismatch)