def normal(start, end, seed=0): """ Generate data with a white Gaussian (normal) distribution Parameters ---------- start_time : int Start time in GPS seconds to generate noise end_time : int End time in GPS seconds to generate nosie seed : {None, int} The seed to generate the noise. Returns -------- noise : TimeSeries A TimeSeries containing gaussian noise """ # This is reproduceable because we used fixed seeds from known values s = int(start / BLOCK_SIZE) e = int(end / BLOCK_SIZE) # The data evenly divides so the last block would be superfluous if end % BLOCK_SIZE == 0: e -= 1 sv = RandomState(seed).randint(-2**50, 2**50) data = numpy.concatenate([block(i + sv) for i in numpy.arange(s, e + 1, 1)]) ts = TimeSeries(data, delta_t=1.0 / SAMPLE_RATE, epoch=start) return ts.time_slice(start, end)
def line_model(freq, data, tref, amp=1, phi=0): """ Simple time-domain model for a frequency line. Parameters ---------- freq: float Frequency of the line. data: pycbc.types.TimeSeries Reference data, to get delta_t, start_time, duration and sample_times. tref: float Reference time for the line model. amp: {1., float}, optional Amplitude of the frequency line. phi: {0. float}, optional Phase of the frequency line (radians). Returns ------- freq_line: pycbc.types.TimeSeries A timeseries of the line model with frequency 'freq'. The returned data are complex to allow measuring the amplitude and phase of the corresponding frequency line in the strain data. For extraction, use only the real part of the data. """ freq_line = TimeSeries(zeros(len(data)), delta_t=data.delta_t, epoch=data.start_time) times = data.sample_times - float(tref) alpha = 2 * numpy.pi * freq * times + phi freq_line.data = amp * numpy.exp(1.j * alpha) return freq_line
def noise_from_psd(length, delta_t, psd, seed=None): """ Create noise with a given psd. Return noise with a given psd. Note that if unique noise is desired a unique seed should be provided. Parameters ---------- length : int The length of noise to generate in samples. delta_t : float The time step of the noise. psd : FrequencySeries The noise weighting to color the noise. seed : {0, int} The seed to generate the noise. Returns -------- noise : TimeSeries A TimeSeries containing gaussian noise colored by the given psd. """ noise_ts = TimeSeries(zeros(length), delta_t=delta_t) if seed is None: seed = numpy.random.randint(2**32) randomness = lal.gsl_rng("ranlux", seed) N = int (1.0 / delta_t / psd.delta_f) n = N/2+1 stride = N/2 if n > len(psd): raise ValueError("PSD not compatible with requested delta_t") psd = (psd[0:n]).lal() psd.data.data[n-1] = 0 segment = TimeSeries(zeros(N), delta_t=delta_t).lal() length_generated = 0 SimNoise(segment, 0, psd, randomness) while (length_generated < length): if (length_generated + stride) < length: noise_ts.data[length_generated:length_generated+stride] = segment.data.data[0:stride] else: noise_ts.data[length_generated:length] = segment.data.data[0:length-length_generated] length_generated += stride SimNoise(segment, stride, psd, randomness) return noise_ts
def test_injection_presence(self): """Verify presence of signals at expected times""" injections = InjectionSet(self.inj_file.name) for det in self.detectors: for inj in self.injections: ts = TimeSeries(numpy.zeros(10 * self.sample_rate), delta_t=1/self.sample_rate, epoch=lal.LIGOTimeGPS(inj.end_time - 5), dtype=numpy.float64) injections.apply(ts, det.name) max_amp, max_loc = ts.abs_max_loc() # FIXME could test amplitude and time more precisely self.assertTrue(max_amp > 0 and max_amp < 1e-10) time_error = ts.sample_times.numpy()[max_loc] - inj.end_time self.assertTrue(abs(time_error) < 1.5 * self.earth_time)
def test_injection_absence(self): """Verify absence of signals outside known injection times""" clear_times = [ self.injections[0].end_time - 86400, self.injections[-1].end_time + 86400 ] injections = InjectionSet(self.inj_file.name) for det in self.detectors: for epoch in clear_times: ts = TimeSeries(numpy.zeros(10 * self.sample_rate), delta_t=1/self.sample_rate, epoch=lal.LIGOTimeGPS(epoch), dtype=numpy.float64) injections.apply(ts, det.name) max_amp, max_loc = ts.abs_max_loc() self.assertEqual(max_amp, 0)
def __init__(self, frame_src, channel_name, start_time, max_buffer=2048, force_update_cache=True, increment_update_cache=None): """ Create a rolling buffer of frame data Parameters --------- frame_src: str of list of strings Strings that indicate where to read from files from. This can be a list of frame files, a glob, etc. channel_name: str Name of the channel to read from the frame files start_time: Time to start reading from. max_buffer: {int, 2048}, Optional Length of the buffer in seconds """ self.frame_src = frame_src self.channel_name = channel_name self.read_pos = start_time self.force_update_cache = force_update_cache self.increment_update_cache = increment_update_cache self.update_cache() self.channel_type, self.raw_sample_rate = self._retrieve_metadata(self.stream, self.channel_name) raw_size = self.raw_sample_rate * max_buffer self.raw_buffer = TimeSeries(zeros(raw_size, dtype=numpy.float64), copy=False, epoch=start_time - max_buffer, delta_t=1.0/self.raw_sample_rate)
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 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 test_chirp(self): ### use a chirp as a signal sigt = TimeSeries(self.sig1, self.del_t) sig_tilde = make_frequency_series(sigt) del_f = sig_tilde.get_delta_f() psd = FrequencySeries(self.Psd, del_f) flow = self.low_frequency_cutoff with _context: hautocor, hacorfr, hnrm = matched_filter_core(self.htilde, self.htilde, psd=psd, \ low_frequency_cutoff=flow, high_frequency_cutoff=self.fmax) hautocor = hautocor * float(np.real(1./hautocor[0])) snr, cor, nrm = matched_filter_core(self.htilde, sig_tilde, psd=psd, \ low_frequency_cutoff=flow, high_frequency_cutoff=self.fmax) hacor = Array(hautocor, copy=True) indx = np.array([352250, 352256, 352260]) snr = snr*nrm with _context: dof, achisq, indices= \ autochisq_from_precomputed(snr, snr, hacor, indx, stride=3, num_points=20) obt_snr = abs(snr[indices[1]]) obt_ach = achisq[1] self.assertTrue(obt_snr > 10.0 and obt_snr < 12.0) self.assertTrue(obt_ach < 3.e-3) self.assertTrue(achisq[0] > 20.0) self.assertTrue(achisq[2] > 20.0)
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 taper_timeseries(tsdata, tapermethod=None, return_lal=False): """ Taper either or both ends of a time series using wrapped LALSimulation functions Parameters ---------- tsdata : TimeSeries Series to be tapered, dtype must be either float32 or float64 tapermethod : string Should be one of ('TAPER_NONE', 'TAPER_START', 'TAPER_END', 'TAPER_STARTEND', 'start', 'end', 'startend') - NB 'TAPER_NONE' will not change the series! return_lal : Boolean If True, return a wrapped LAL time series object, else return a PyCBC time series. """ if tapermethod is None: raise ValueError("Must specify a tapering method (function was called" "with tapermethod=None)") if tapermethod not in taper_map.keys(): raise ValueError("Unknown tapering method %s, valid methods are %s" % \ (tapermethod, ", ".join(taper_map.keys()))) if tsdata.dtype not in (float32, float64): raise TypeError("Strain dtype must be float32 or float64, not " + str(tsdata.dtype)) taper_func = taper_func_map[tsdata.dtype] # make a LAL TimeSeries to pass to the LALSim function ts_lal = tsdata.astype(tsdata.dtype).lal() if taper_map[tapermethod] is not None: taper_func(ts_lal.data, taper_map[tapermethod]) if return_lal: return ts_lal else: return TimeSeries(ts_lal.data.data[:], delta_t=ts_lal.deltaT, epoch=ts_lal.epoch)
def highpass_fir(timeseries, frequency, order, beta=5.0): """ Highpass filter the time series using an FIR filtered generated from the ideal response passed through a kaiser window (beta = 5.0) Parameters ---------- Time Series: TimeSeries The time series to be high-passed. frequency: float The frequency below which is suppressed. order: int Number of corrupted samples on each side of the time series beta: float Beta parameter of the kaiser window that sets the side lobe attenuation. """ k = frequency / float((int(1.0 / timeseries.delta_t) / 2)) coeff = scipy.signal.firwin(order * 2 + 1, k, window=('kaiser', beta), pass_zero=False) data = fir_zero_filter(coeff, timeseries) return TimeSeries(data, epoch=timeseries.start_time, delta_t=timeseries.delta_t)
class DataBuffer(object): """A linear buffer that acts as a FILO for reading in frame data """ def __init__(self, frame_src, channel_name, start_time, max_buffer=2048, force_update_cache=True, increment_update_cache=None, dtype=numpy.float64): """ Create a rolling buffer of frame data Parameters --------- frame_src: str of list of strings Strings that indicate where to read from files from. This can be a list of frame files, a glob, etc. channel_name: str Name of the channel to read from the frame files start_time: Time to start reading from. max_buffer: {int, 2048}, Optional Length of the buffer in seconds dtype: {dtype, numpy.float32}, Optional Data type to use for the interal buffer """ self.frame_src = frame_src self.channel_name = channel_name self.read_pos = start_time self.force_update_cache = force_update_cache self.increment_update_cache = increment_update_cache self.detector = channel_name.split(':')[0] self.update_cache() self.channel_type, self.raw_sample_rate = self._retrieve_metadata(self.stream, self.channel_name) raw_size = self.raw_sample_rate * max_buffer self.raw_buffer = TimeSeries(zeros(raw_size, dtype=dtype), copy=False, epoch=start_time - max_buffer, delta_t=1.0/self.raw_sample_rate) def update_cache(self): """Reset the lal cache. This can be used to update the cache if the result may change due to more files being added to the filesystem, for example. """ cache = locations_to_cache(self.frame_src) stream = lalframe.FrStreamCacheOpen(cache) self.stream = stream @staticmethod def _retrieve_metadata(stream, channel_name): """Retrieve basic metadata by reading the first file in the cache Parameters ---------- stream: lal stream object Stream containing a channel we want to learn about channel_name: str The name of the channel we want to know the dtype and sample rate of Returns ------- channel_type: lal type enum Enum value which indicates the dtype of the channel sample_rate: int The sample rate of the data within this channel """ lalframe.FrStreamGetVectorLength(channel_name, stream) channel_type = lalframe.FrStreamGetTimeSeriesType(channel_name, stream) create_series_func = _fr_type_map[channel_type][2] get_series_metadata_func = _fr_type_map[channel_type][3] series = create_series_func(channel_name, stream.epoch, 0, 0, lal.ADCCountUnit, 0) get_series_metadata_func(series, stream) return channel_type, int(1.0/series.deltaT) def _read_frame(self, blocksize): """Try to read the block of data blocksize seconds long Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data Raises ------ RuntimeError: If data cannot be read for any reason """ try: read_func = _fr_type_map[self.channel_type][0] dtype = _fr_type_map[self.channel_type][1] data = read_func(self.stream, self.channel_name, self.read_pos, int(blocksize), 0) return TimeSeries(data.data.data, delta_t=data.deltaT, epoch=self.read_pos, dtype=dtype) except Exception: raise RuntimeError('Cannot read {0} frame data'.format(self.channel_name)) def null_advance(self, blocksize): """Advance and insert zeros Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ self.raw_buffer.roll(-int(blocksize * self.raw_sample_rate)) self.read_pos += blocksize self.raw_buffer.start_time += blocksize def advance(self, blocksize): """Add blocksize seconds more to the buffer, push blocksize seconds from the beginning. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ ts = self._read_frame(blocksize) self.raw_buffer.roll(-len(ts)) self.raw_buffer[-len(ts):] = ts[:] self.read_pos += blocksize self.raw_buffer.start_time += blocksize return ts def update_cache_by_increment(self, blocksize): """Update the internal cache by starting from the first frame and incrementing. Guess the next frame file name by incrementing from the first found one. This allows a pattern to be used for the GPS folder of the file, which is indicated by `GPSX` where x is the number of digits to use. Parameters ---------- blocksize: int Number of seconds to increment the next frame file. """ start = float(self.raw_buffer.end_time) end = float(start + blocksize) if not hasattr(self, 'dur'): fname = glob.glob(self.frame_src[0])[0] fname = os.path.splitext(os.path.basename(fname))[0].split('-') self.beg = '-'.join([fname[0], fname[1]]) self.ref = int(fname[2]) self.dur = int(fname[3]) fstart = int(self.ref + numpy.floor((start - self.ref) / float(self.dur)) * self.dur) starts = numpy.arange(fstart, end, self.dur).astype(numpy.int) keys = [] for s in starts: pattern = self.increment_update_cache if 'GPS' in pattern: n = int(pattern[int(pattern.index('GPS') + 3)]) pattern = pattern.replace('GPS%s' % n, str(s)[0:n]) name = '%s/%s-%s-%s.gwf' % (pattern, self.beg, s, self.dur) # check that file actually exists, else abort now if not os.path.exists(name): logging.info("%s does not seem to exist yet" % name) raise RuntimeError keys.append(name) cache = locations_to_cache(keys) stream = lalframe.FrStreamCacheOpen(cache) self.stream = stream self.channel_type, self.raw_sample_rate = \ self._retrieve_metadata(self.stream, self.channel_name) def attempt_advance(self, blocksize, timeout=10): """ Attempt to advance the frame buffer. Retry upon failure, except if the frame file is beyond the timeout limit. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel timeout: {int, 10}, Optional Number of seconds before giving up on reading a frame Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data """ if self.force_update_cache: self.update_cache() try: if self.increment_update_cache: self.update_cache_by_increment(blocksize) return DataBuffer.advance(self, blocksize) except RuntimeError: if lal.GPSTimeNow() > timeout + self.raw_buffer.end_time: # The frame is not there and it should be by now, so we give up # and treat it as zeros DataBuffer.null_advance(self, blocksize) return None else: # I am too early to give up on this frame, so we should try again time.sleep(1) return self.attempt_advance(blocksize, timeout=timeout)
def get_td_lm_allmodes(template=None, taper=None, **kwargs): """Return time domain ringdown with all the modes specified. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. Each mode and overtone will have a different taper depending on its tau, the final taper being the superposition of all the tapers. final_mass : float Mass of the final black hole. final_spin : float Spin of the final black hole. lmns : list Desired lmn modes as strings (lm modes available: 22, 21, 33, 44, 55). The n specifies the number of overtones desired for the corresponding lm pair (maximum n=8). Example: lmns = ['223','331'] are the modes 220, 221, 222, and 330 amp220 : float Amplitude of the fundamental 220 mode. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). t_final : {None, float}, optional The ending time of the output frequency series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a ringdown with the lm modes specified and n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a ringdown with the lm modes specified and n overtones in frequency domain. """ input_params = props(template, lm_allmodes_required_args, **kwargs) # Get required args final_mass = input_params['final_mass'] final_spin = input_params['final_spin'] lmns = input_params['lmns'] for lmn in lmns: if int(lmn[2]) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = lm_deltat(final_mass, final_spin, lmns) if t_final is None: t_final = lm_tfinal(final_mass, final_spin, lmns) kmax = int(t_final / delta_t) + 1 _, tau = get_lm_f0tau_allmodes(final_mass, final_spin, lmns) # Different overtones will have different tapering window-size # Find maximum window size to create long enough output vector if taper is not None: taper_window = int(taper * max(tau.values()) / delta_t) kmax += taper_window outplus = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) outcross = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) for lmn in lmns: l, m, nmodes = int(lmn[0]), int(lmn[1]), int(lmn[2]) hplus, hcross = get_td_lm(taper=taper, l=l, m=m, nmodes=nmodes, delta_t=delta_t, t_final=t_final, **input_params) if taper is None: outplus.data += hplus.data outcross.data += hcross.data else: outplus = taper_shift(hplus, outplus) outcross = taper_shift(hcross, outcross) return outplus, outcross
def get_td_qnm(template=None, taper=None, **kwargs): """Return a time domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. phi : float The initial phase of the ringdown. amp : float The amplitude of the ringdown (constant for now). delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time domain. """ input_params = props(template, qnm_required_args, **kwargs) f_0 = input_params.pop('f_0') tau = input_params.pop('tau') amp = input_params.pop('amp') phi = input_params.pop('phi') # the following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = 1. / qnm_freq_decay(f_0, tau, 1. / 1000) if delta_t < min_dt: delta_t = min_dt if t_final is None: t_final = qnm_time_decay(tau, 1. / 1000) kmax = int(t_final / delta_t) + 1 times = numpy.arange(kmax) * delta_t hp = amp * numpy.exp(-times / tau) * numpy.cos(two_pi * f_0 * times + phi) hc = amp * numpy.exp(-times / tau) * numpy.sin(two_pi * f_0 * times + phi) # If size of tapering window is less than delta_t, do not apply taper. if taper is None or delta_t > taper * tau: hplus = TimeSeries(zeros(kmax), delta_t=delta_t) hcross = TimeSeries(zeros(kmax), delta_t=delta_t) hplus.data[:kmax] = hp hcross.data[:kmax] = hc return hplus, hcross else: taper_hp, taper_hc, taper_window, start = apply_taper( delta_t, taper, f_0, tau, amp, phi) hplus = TimeSeries(zeros(taper_window + kmax), delta_t=delta_t) hcross = TimeSeries(zeros(taper_window + kmax), delta_t=delta_t) hplus.data[:taper_window] = taper_hp hplus.data[taper_window:] = hp hplus._epoch = start hcross.data[:taper_window] = taper_hc hcross.data[taper_window:] = hc hcross._epoch = start return hplus, hcross
def get_td_lm(template=None, taper=None, **kwargs): """Return time domain lm mode with the given number of overtones. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. Each overtone will have a different taper depending on its tau, the final taper being the superposition of all the tapers. freqs : dict {lmn:f_lmn} Dictionary of the central frequencies for each overtone, as many as number of modes. taus : dict {lmn:tau_lmn} Dictionary of the damping times for each overtone, as many as number of modes. l : int l mode (lm modes available: 22, 21, 33, 44, 55). m : int m mode (lm modes available: 22, 21, 33, 44, 55). nmodes: int Number of overtones desired (maximum n=8) amp220 : float Amplitude of the fundamental 220 mode, needed for any lm. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. Should also include the information from the azimuthal angle (phi + m*Phi). inclination : {None, float}, optional Inclination of the system in radians for the spherical harmonics. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplus: TimeSeries The plus phase of a lm mode with overtones (n) in time domain. hcross: TimeSeries The cross phase of a lm mode with overtones (n) in time domain. """ input_params = props(template, lm_required_args, **kwargs) # Get required args amps, phis = lm_amps_phases(**input_params) f_0 = input_params.pop('freqs') tau = input_params.pop('taus') inc = input_params.pop('inclination', None) l, m = input_params.pop('l'), input_params.pop('m') nmodes = input_params.pop('nmodes') if int(nmodes) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if not delta_t: delta_t = lm_deltat(f_0, tau, ['%d%d%d' %(l,m,nmodes)]) if not t_final: t_final = lm_tfinal(tau, ['%d%d%d' %(l, m, nmodes)]) kmax = int(t_final / delta_t) + 1 # Different overtones will have different tapering window-size # Find maximum window size to create long enough output vector if taper: taper_window = int(taper*max(tau.values())/delta_t) kmax += taper_window outplus = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) outcross = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) if taper: start = - taper * max(tau.values()) outplus._epoch, outcross._epoch = start, start for n in range(nmodes): hplus, hcross = get_td_qnm(template=None, taper=taper, f_0=f_0['%d%d%d' %(l,m,n)], tau=tau['%d%d%d' %(l,m,n)], phi=phis['%d%d%d' %(l,m,n)], amp=amps['%d%d%d' %(l,m,n)], inclination=inc, l=l, m=m, delta_t=delta_t, t_final=t_final) if not taper: outplus.data += hplus.data outcross.data += hcross.data else: outplus = taper_shift(hplus, outplus) outcross = taper_shift(hcross, outcross) return outplus, outcross
def calc_psd_variation(strain, psd_short_segment, psd_long_segment, overlap, low_freq, high_freq): """Calculates time series of PSD variability This function first splits the segment up in to 512 second chunks. It then calculates the PSD over this 512 second period as well as in 4 second chunks throughout each 512 second period. Next the function estimates how different the 4 second PSD is to the 512 second PSD and produces a timeseries of this variability. Parameters ---------- strain : TimeSeries Input strain time series to estimate PSDs psd_short_segment : {float, 4} Duration of the short segments for PSD estimation in seconds. psd_long_segment : {float, 512} Duration of the long segments for PSD estimation in seconds. overlap : {float, 0.5} Duration in seconds to use for each sample of the PSD. low_freq : {float, 20} Minimum frequency to consider the comparison between PSDs. high_freq : {float, 512} Maximum frequency to consider the comparison between PSDs. Returns ------- psd_var : TimeSeries Time series of the variability in the PSD estimation """ # Calculate strain precision if strain.precision == 'single': fs_dtype = numpy.float32 elif strain.precision == 'double': fs_dtype = numpy.float64 # Convert start and end times immediately to floats start_time = numpy.float(strain.start_time) end_time = numpy.float(strain.end_time) # Find the times of the long segments times_long = numpy.arange(start_time, end_time, psd_long_segment) # Set up the empty time series for the PSD variation estimate psd_var = TimeSeries(zeros( int(numpy.ceil((end_time - start_time) / psd_short_segment))), delta_t=psd_short_segment, copy=False, epoch=start_time) ind = 0 for tlong in times_long: # Calculate PSD for long segment and separate the long segment in to # overlapping shorter segments if tlong + psd_long_segment <= end_time: psd_long = strain.time_slice(tlong, tlong + psd_long_segment).psd(overlap) times_short = numpy.arange(tlong, tlong + psd_long_segment, psd_short_segment) else: psd_long = strain.time_slice(end_time - psd_long_segment, end_time).psd(overlap) times_short = numpy.arange(tlong, end_time, psd_short_segment) # Calculate the PSD of the shorter segments psd_short = [] for tshort in times_short: if tshort + psd_short_segment * 2 <= end_time: pshort = strain.time_slice(tshort, tshort + psd_short_segment * 2).psd(overlap) else: pshort = strain.time_slice(tshort - psd_short_segment, end_time).psd(overlap) psd_short.append(pshort) # Estimate the range of the PSD to compare kmin = int(low_freq / psd_long.delta_f) kmax = int(high_freq / psd_long.delta_f) # Comapre the PSD of the short segment to the long segment # The weight factor gives the rough response of a cbc template across # the defined frequency range given the expected PSD (i.e. long PSD) # Then integrate the weighted ratio of the actual PSD (i.e. short PSD) # with the expected PSD (i.e. long PSD) over the specified frequency # range freqs = FrequencySeries(psd_long.sample_frequencies, delta_f=psd_long.delta_f, epoch=psd_long.epoch, dtype=fs_dtype) weight = numpy.array(freqs[kmin:kmax]**(-7. / 3.) / psd_long[kmin:kmax]) weight /= weight.sum() diff = numpy.array([ (weight * numpy.array(p_short[kmin:kmax] / psd_long[kmin:kmax])).sum() for p_short in psd_short ]) # Store variation value for i, val in enumerate(diff): psd_var[ind + i] = val ind = ind + len(diff) return psd_var
def 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
from pycbc.psd import welch, interpolate from pycbc.types import TimeSeries try: from urllib.request import urlretrieve except ImportError: # python < 3 from urllib import urlretrieve # Read data and remove low frequency content fname = 'H-H1_LOSC_4_V2-1126259446-32.gwf' url = "https://www.gw-openscience.org/GW150914data/" + fname urlretrieve(url, filename=fname) h1 = highpass_fir(read_frame(fname, 'H1:LOSC-STRAIN'), 15.0, 8) # Calculate the noise spectrum and whiten psd = interpolate(welch(h1), 1.0 / 32) white_strain = (h1.to_frequencyseries() / psd ** 0.5 * psd.delta_f).to_timeseries() # remove some of the high and low frequencies smooth = highpass_fir(white_strain, 25, 8) smooth = lowpass_fir(white_strain, 250, 8) #strech out and shift the frequency upwards to aid human hearing fdata = smooth.to_frequencyseries() fdata.roll(int(1200 / fdata.delta_f)) smooth = TimeSeries(fdata.to_timeseries(), delta_t=1.0/1024) #Take slice around signal smooth = smooth[len(smooth)/2 - 1500:len(smooth)/2 + 3000] smooth.save_to_wav('gw150914_h1_chirp.wav')
def __init__(self, filename=None, filetype='HDF5', wavetype='Auto', \ ex_order=3, group_name=None,\ modeLmin=2, modeLmax=8, skipM0=True, \ sample_rate=8192, time_length=32, rawdelta_t=-1, \ totalmass=None, inclination=0, phi=0, distance=1.e6, \ verbose=False, debug=False): """ #### Assumptions: ### 1. The nr waveform file is uniformly sampled ### 2. wavetypes passed should be : ### CCE , Extrapolated , FiniteRadius , NoGroup, Auto ### Auto : from filename figure out the file type ### 3. filetypes passed should be : ASCII , HDF5 , DataSet ###### About conventions: ### 1. Modes themselves are not amplitude-scaled. Only their time-axis is ### rescaled. ### 2. ... """ #{{{ ################################################################## # 0. Ensure inputs are correct ################################################################## if 'dataset' not in filetype: if filename is not None and not os.path.exists(filename): raise IOError("Please provide data file!") if verbose: print >>sys.stderr, "\n Reading From Filename=%s" % filename # Datafile details self.verbose = verbose self.debug = debug self.filename = filename self.filetype = filetype self.modeLmax = modeLmax self.modeLmin = modeLmin self.skipM0 = skipM0 # Extraction parameters self.ex_order = ex_order self.group_name = group_name # Data analysis parameters self.sample_rate = sample_rate self.time_length = time_length self.dt = 1./self.sample_rate self.df = 1./self.time_length self.n = int(self.sample_rate * self.time_length) if self.verbose: print >>sys.stderr, "self.sample-rate & time_len = ", \ self.sample_rate, self.time_length if self.verbose: print >>sys.stderr, "self.n = ", self.n # Binary parameters self.totalmass = None self.inclination = inclination self.phi = phi self.distance = distance if self.verbose: print >>sys.stderr, " >> Input mass, inc, phi, dist = ", totalmass,\ self.inclination, self.phi, self.distance sys.stderr.flush() ################################################################## # 1. Figure out what the data-storage type (wavetype) is ################################################################## self.wavetype = None if str(wavetype) in ['CCE', 'Extrapolated', 'FiniteRadius', 'NoGroup']: self.wavetype = wavetype elif str(wavetype) == 'Auto': # Decide from filename the wavetype fname = self.filename.split('/')[-1] if 'rhOverM_Asymptotic_GeometricUnits' in fname: self.wavetype = 'Extrapolated' elif 'Cce' in fname: self.wavetype = 'CCE' elif 'FiniteRadii' in fname: self.wavetype = 'FiniteRadius' elif 'HDF' in self.filetype: ftmp = h5py.File(self.filename, 'r') fgrps = [str(grptmp) for grptmp in fgrpstmp] if 'Y_l2_m2.dat' in fgrps: self.wavetype = 'NoGroup' # if self.wavetype == None: raise IOError("Could not figure out wavetype") if self.verbose: print >>sys.stderr, self.wavetype sys.stderr.flush() ################################################################## # 2. Read the data from the file. Read all modes. ################################################################## # if 'HDF' in self.filetype: if self.verbose: print >>sys.stderr, " > Reading NR data in HDF5 from %s" % self.filename # Read data file self.fin = h5py.File(self.filename,'r') if self.wavetype != 'NoGroup': grp = self.get_groupname() print "FOUND GROUP = ", grp, type(grp) if self.verbose: print >>sys.stderr, ("From %s out of " % grp), self.fin.keys() wavedata = self.fin[grp] else: wavedata = self.fin # Read modes self.rawtsamples, self.rawmodes_real, self.rawmodes_imag = {}, {}, {} for modeL in np.arange( 2, self.modeLmax+1 ): self.rawtsamples[modeL] = {} self.rawmodes_real[modeL], self.rawmodes_imag[modeL] = {}, {} for modeM in np.arange( modeL, -1*modeL-1, -1 ): if self.skipM0 and modeM==0: continue mdata = wavedata['Y_l%d_m%d.dat' % (modeL, modeM)].value if self.verbose: print >> sys.stderr, "Reading %d,%d mode" % (modeL, modeM) # ts = mdata[:,0] hp_int = InterpolatedUnivariateSpline(ts, mdata[:,1]) hc_int = InterpolatedUnivariateSpline(ts, mdata[:,2]) # Hard-coded to re-sample at initial dt if rawdelta_t <= 0: self.rawdelta_t = ts[1] - ts[0] else: self.rawdelta_t = rawdelta_t # self.rawtsamples[modeL][modeM] = TimeSeries(\ np.arange(ts.min(), ts.max(), self.rawdelta_t),\ delta_t=self.rawdelta_t, epoch=0) self.rawmodes_real[modeL][modeM] = TimeSeries(\ hp_int( self.rawtsamples[2][2] ),\ delta_t=self.rawdelta_t, epoch=0) self.rawmodes_imag[modeL][modeM] = TimeSeries(\ hc_int( self.rawtsamples[2][2] ),\ delta_t=self.rawdelta_t, epoch=0) # elif 'dataset' in self.filetype: raise IOError("datasets not supported yet!") data = self.filename ts = data[:,0] - data[0,0] hp_int = InterpolatedUnivariateSpline(ts, data[:,1]) hc_int = InterpolatedUnivariateSpline(ts, data[:,2]) # Hard-coded to re-sample at dt = 1M if rawdelta_t <= 0: self.rawdelta_t = 1. else: self.rawdelta_t = rawdelta_t self.rawtsamples = TimeSeries(arange(0, ts.max()),\ delta_t=self.rawdelta_t, epoch=0) self.rawhp = TimeSeries(array([hp_int(t) for t in self.rawtsamples]),\ delta_t=self.rawdelta_t, epoch=0) self.rawhc = TimeSeries(array([hc_int(t) for t in self.rawtsamples]),\ delta_t=self.rawdelta_t, epoch=0) if self.verbose: print >>sys.stderr, "times go from %f to %f" % (min(ts), max(ts)) print >>sys.stderr, "rawhp Min = %e, Max = %e" % (min(self.rawhp), max(self.rawhp)) # elif 'ASCII' in self.filetype: raise IOError("ASCII datafile not accepted yet!") if self.verbose: print >>sys.stderr, "Reading NR data in ASCII from %s" % \ self.filename # Read modes self.rawtsamples, self.rawmodes_real, self.rawmodes_imag = {}, {}, {} for modeL in np.arange( 2, self.modeLmax+1 ): self.rawtsamples[modeL] = {} self.rawmodes_real[modeL], self.rawmodes_imag[modeL] = {}, {} for modeM in np.arange( -1*modeL, modeL+1 ): if self.skipM0 and modeM==0: continue mdata = np.loadtxt(self.filename % (modeL, modeM)) if self.verbose: print >> sys.stderr, np.shape(mdata) # ts = mdata[:,0] hp_int = InterpolatedUnivariateSpline(ts, mdata[:,1]) hc_int = InterpolatedUnivariateSpline(ts, mdata[:,2]) # Hard-coded to re-sample at initial dt if rawdelta_t <= 0: self.rawdelta_t = ts[1] - ts[0] else: self.rawdelta_t = rawdelta_t # self.rawtsamples[modeL][modeM] = TimeSeries(\ np.arange(ts.min(), ts.max(), self.rawdelta_t),\ delta_t=self.rawdelta_t, epoch=0) self.rawmodes_real[modeL][modeM] = TimeSeries(\ array([hp_int(t) for t in self.rawtsamples]),\ delta_t=self.rawdelta_t, epoch=0) self.rawmodes_imag[modeL][modeM] = TimeSeries(\ array([hc_int(t) for t in self.rawtsamples]),\ delta_t=self.rawdelta_t, epoch=0) # self.rescaled_hp = None self.rescaled_hc = None if totalmass: self.rescale_to_totalmass(totalmass) self.totalmass = totalmass try: self.fin.close() except: pass
def get_td_qnm(template=None, delta_t=None, t_lower=None, t_final=None, **kwargs): """Return a time domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. t_0 : {0, float}, optional The starting time of the ringdown. phi_0 : {0, float}, optional The initial phase of the ringdown. Amp : {1, float}, optional The amplitude of the ringdown (constant for now). delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/100 of the peak amplitude. t_lower: {None, float}, optional The starting time of the output time series. If None, it will be set to delta_t. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time domain. """ input_params = props_ringdown(template,**kwargs) f_0 = input_params['f_0'] tau = input_params['tau'] t_0 = input_params['t_0'] phi_0 = input_params['phi_0'] Amp = input_params['Amp'] if delta_t is None: delta_t = 1. / qnm_freq_decay(f_0, tau, 1./100) if t_lower is None: t_lower = delta_t kmin = 0 else: kmin=int(t_lower / delta_t) if t_final is None: t_final = qnm_time_decay(tau, 1./1000) kmax = int(t_final / delta_t) n = int(t_final / delta_t) + 1 two_pi = 2 * numpy.pi times = numpy.arange(t_lower, t_final, delta_t) hp = Amp * numpy.exp(-times/tau) * numpy.cos(two_pi*f_0*times + phi_0) hc = Amp * numpy.exp(-times/tau) * numpy.sin(two_pi*f_0*times + phi_0) hplus = TimeSeries(zeros(n), delta_t=delta_t) hcross = TimeSeries(zeros(n), delta_t=delta_t) hplus.data[kmin:kmax] = hp hcross.data[kmin:kmax] = hc return hplus, hcross
def _get_waveform_from_inspiral(**p): import lalmetaio # prefix with 'Inspiral-' name = p['approximant'][9:] if name.startswith('EOB'): p['phase_order'] = -8 params = lalmetaio.SimInspiralTable() params.waveform = name + string_from_order[p['phase_order']] params.mass1= p['mass1'] params.mass2= p['mass2'] params.f_lower = p['f_lower'] params.spin1x = p['spin1x'] params.spin1y = p['spin1y'] params.spin1z = p['spin1z'] params.spin2x = p['spin2x'] params.spin2y = p['spin2y'] params.spin2z = p['spin2z'] params.inclination = p['inclination'] params.distance = p['distance'] params.coa_phase = p['coa_phase'] import lalinspiral guess_length = lalinspiral.FindChirpChirpTime(params.mass1, params.mass2, params.f_lower, 7) guess_length = max(guess_length, 3) params.geocent_end_time = guess_length * 1.5 params.taper = 'TAPER_NONE' #FIXME - either explain or don't hardcode this bufferl = guess_length * 2 dt = p['delta_t'] df = 1.0 / bufferl sample_rate = int(1.0 / dt) epoch = lal.LIGOTimeGPS(0, 0) N = bufferl * sample_rate n = N / 2 + 1 resp = FrequencySeries(zeros(n), delta_f=df, epoch=epoch, dtype=complex64) + 1 out = TimeSeries(zeros(N), delta_t=dt, epoch=epoch, dtype=float32) outl = out.lal() outl.sampleUnits = lal.ADCCountUnit out2 = TimeSeries(zeros(N), delta_t=dt, epoch=epoch, dtype=float32) outl2 = out.lal() outl2.sampleUnits = lal.ADCCountUnit respl = resp.lal() respl.sampleUnits = lal.DimensionlessUnit lalinspiral.FindChirpInjectSignals(outl, params, respl) params.coa_phase -= lal.PI / 4 lalinspiral.FindChirpInjectSignals(outl2, params, respl) seriesp = TimeSeries(outl.data.data, delta_t=dt, epoch=epoch - params.geocent_end_time) seriesc = TimeSeries(outl2.data.data, delta_t=dt, epoch=epoch - params.geocent_end_time) return seriesp, seriesc
from pycbc.frame import read_frame from pycbc.filter import highpass_fir, lowpass_fir from pycbc.psd import welch, interpolate from pycbc.types import TimeSeries import urllib # Read data and remove low frequency content fname = 'H-H1_LOSC_4_V2-1126259446-32.gwf' url = "https://losc.ligo.org/s/events/GW150914/" + fname urllib.urlretrieve(url, filename=fname) h1 = highpass_fir(read_frame(fname, 'H1:LOSC-STRAIN'), 15.0, 8) # Calculate the noise spectrum and whiten psd = interpolate(welch(h1), 1.0 / 32) white_strain = (h1.to_frequencyseries() / psd ** 0.5 * psd.delta_f).to_timeseries() # remove some of the high and low frequencies smooth = highpass_fir(white_strain, 25, 8) smooth = lowpass_fir(white_strain, 250, 8) #strech out and shift the frequency upwards to aid human hearing fdata = smooth.to_frequencyseries() fdata.roll(int(1200 / fdata.delta_f)) smooth = TimeSeries(fdata.to_timeseries(), delta_t=1.0/1024) #Take slice around signal smooth = smooth[len(smooth)/2 - 1500:len(smooth)/2 + 3000] smooth.save_to_wav('gw150914_h1_chirp.wav')
def resample_to_delta_t(timeseries, delta_t, method='butterworth'): """Resmple the time_series to delta_t Resamples the TimeSeries instance time_series to the given time step, delta_t. Only powers of two and real valued time series are supported at this time. Additional restrictions may apply to particular filter methods. Parameters ---------- time_series: TimeSeries The time series to be resampled delta_t: float The desired time step Returns ------- Time Series: TimeSeries A TimeSeries that has been resampled to delta_t. Raises ------ TypeError: time_series is not an instance of TimeSeries. TypeError: time_series is not real valued Examples -------- >>> h_plus_sampled = resample_to_delta_t(h_plus, 1.0/2048) """ if not isinstance(timeseries,TimeSeries): raise TypeError("Can only resample time series") if timeseries.kind is not 'real': raise TypeError("Time series must be real") if timeseries.delta_t == delta_t: return timeseries * 1 if method == 'butterworth': lal_data = timeseries.lal() _resample_func[timeseries.dtype](lal_data, delta_t) data = lal_data.data.data elif method == 'ldas': factor = int(delta_t / timeseries.delta_t) numtaps = factor * 20 + 1 # The kaiser window has been testing using the LDAS implementation # and is in the same configuration as used in the original lalinspiral filter_coefficients = scipy.signal.firwin(numtaps, 1.0 / factor, window=('kaiser', 5)) # apply the filter and decimate data = fir_zero_filter(filter_coefficients, timeseries)[::factor] else: raise ValueError('Invalid resampling method: %s' % method) ts = TimeSeries(data, delta_t = delta_t, dtype=timeseries.dtype, epoch=timeseries._epoch) # From the construction of the LDAS FIR filter there will be 10 corrupted samples # explanation here http://software.ligo.org/docs/lalsuite/lal/group___resample_time_series__c.html ts.corrupted_samples = 10 return ts
def get_td_qnm(template=None, taper=None, **kwargs): """Return a time domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. phi : float The initial phase of the ringdown. amp : float The amplitude of the ringdown (constant for now). delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time domain. """ input_params = props(template, qnm_required_args, **kwargs) f_0 = input_params.pop('f_0') tau = input_params.pop('tau') amp = input_params.pop('amp') phi = input_params.pop('phi') # the following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = 1. / qnm_freq_decay(f_0, tau, 1./1000) if delta_t < min_dt: delta_t = min_dt if t_final is None: t_final = qnm_time_decay(tau, 1./1000) kmax = int(t_final / delta_t) + 1 times = numpy.arange(kmax) * delta_t hp = amp * numpy.exp(-times/tau) * numpy.cos(two_pi*f_0*times + phi) hc = amp * numpy.exp(-times/tau) * numpy.sin(two_pi*f_0*times + phi) # If size of tapering window is less than delta_t, do not apply taper. if taper is None or delta_t > taper*tau: hplus = TimeSeries(zeros(kmax), delta_t=delta_t) hcross = TimeSeries(zeros(kmax), delta_t=delta_t) hplus.data[:kmax] = hp hcross.data[:kmax] = hc return hplus, hcross else: taper_hp, taper_hc, taper_window, start = apply_taper(delta_t, taper, f_0, tau, amp, phi) hplus = TimeSeries(zeros(taper_window+kmax), delta_t=delta_t) hcross = TimeSeries(zeros(taper_window+kmax), delta_t=delta_t) hplus.data[:taper_window] = taper_hp hplus.data[taper_window:] = hp hplus._epoch = start hcross.data[:taper_window] = taper_hc hcross.data[taper_window:] = hc hcross._epoch = start return hplus, hcross
def main(): parser = argparse.ArgumentParser() parser.add_argument('--start', default=0, type=int, help='The index of the first template to generate.') parser.add_argument('--number-samples', default=1000, type=int, help='The number of samples to generate.') parser.add_argument( '--workers', help= 'The number of processes to spwan. (default: number processes = number available cores)' ) parser.add_argument('--name', required=True, help='The base-name for the output file.') parser.add_argument( '--params-config-file', required=True, help='The configuration file from which the templates are generated.') parser.add_argument( '--network-config-file', required=True, help='The config-file specifying the network to use for training.') parser.add_argument( '--file-name', default='{name}-{start}-{stop}-{samples}.hdf', type=str, help= 'A formatting string from which the file names are constructed. Available keys are "name", "start", "stop", "samples".' ) parser.add_argument( '--detectors', nargs='+', default=['H1', 'L1', 'V1'], help= 'The detectors for which to generate the waveforms. (Detector names as understood by PyCBC)' ) parser.add_argument( '--verbose', default=False, type=str2bool, help='Whether or not to print a progress bar. (default: false)') parser.add_argument( '--psd-names', action=MultiDetOptionAction, help= 'The names (as understood by PyCBC) to use for whitening and noise generation. (Add as det_name1:psd_name1 det_name2:psd_name2)' ) parser.add_argument( '--time-shift-name', default='tc', help= 'The name of the variable that gives the length of the time-shift to apply to every template' ) parser.add_argument('--final-duration', default=1.0, type=float, help='The duration of the final template in seconds.') parser.add_argument( '--translation', nargs='+', action=TranslationAction, default={}, help= 'Translate the parameter names. (Input as par1:par1new par2:par2new ...)' ) parser.add_argument( '--training-data', type=str2bool, default=True, help= 'If true it uses the directory for the training data to store the results. Otherwise the testing data directory will be used.' ) parser.add_argument( '--output-vitaminb-params-files', type=str2bool, default=False, help= 'Whether or not to generate the params, bounds and fixed-vals files that are required by VItaminB.' ) parser.add_argument('--seed', help='The seed to use to draw waveform-parameters.') args = parser.parse_args() stop = args.start + args.number_samples file_name = args.file_name.format(name=args.name, start=args.start, stop=stop, samples=args.number_samples) ###################### #Convert config files# ###################### #Create .ini-file for PyCBC config_file = 'tmp_parameter_config.ini' vitaminb_params_to_pycbc_params(args.params_config_file, config_file) #Convert .ini-files to vitaminb understandable .json files. params_file = 'tmp_params.json' bounds_file = 'tmp_bounds.json' fixed_vals_file = 'tmp_fixed_vals.json' tmp_params, tmp_bounds, tmp_fixed = params_files_from_config( args.params_config_file, args.network_config_file, translation=args.translation) if args.output_vitaminb_params_files: with open(params_file, 'w') as fp: json.dump(tmp_params, fp, indent=4) with open(bounds_file, 'w') as fp: json.dump(tmp_bounds, fp, indent=4) with open(fixed_vals_file, 'w') as fp: json.dump(tmp_fixed, fp, indent=4) #################### #Generate waveforms# #################### if args.seed is None: seed = int(time.time()) else: seed = int(args.seed) param_gen = WFParamGenerator(config_file, seed=seed) params = param_gen.draw_multiple(args.number_samples) params = field_array_to_dict(params) try: params['tc'] = params.pop(args.time_shift_name) except: pass getter = WaveformGetter(variable_params=params, static_params=param_gen.static, detectors=args.detectors) wavs = getter.generate_mp(workers=args.workers, verbose=args.verbose) try: params[args.time_shift_name] = params.pop('tc') except: pass ############# #Whiten data# ############# #TODO: Make this use multiprocessing psd_names = {key: val for (key, val) in args.psd_names.items()} for key in wavs.keys(): if key not in psd_names: if key == 'H1' or key == 'L1': psd_names[key] = 'aLIGOZeroDetHighPower' elif key == 'V1': #psd_names[key] = 'AdVDesignSensitivityP1200087' psd_names[key] = 'Virgo' elif key == 'K1': psd_names[key] = 'KAGRADesignSensitivityT1600593' flow = getter.static_params['f_lower'] if args.verbose: bar = progress_tracker(len(wavs) * len(getter), name='Whitening samples') white_wavs = {} psd_cache = NamedPSDCache(list(set(psd_names.values()))) for key in wavs.keys(): white_wavs[key] = [] psd_name = psd_names.get(key) for wav in wavs[key]: tmp = TimeSeries(np.zeros(int(8 * wav.sample_rate + len(wav))), delta_t=wav.delta_t, epoch=wav.start_time - 4) tmp.data[int(4 * wav.sample_rate):int(-4 * wav.sample_rate)] = wav.data[:] psd = psd_cache.get_from_timeseries(tmp, max(flow - 2, 0), psd_name=psd_names[key]) white_wavs[key].append( whiten(tmp, low_freq_cutoff=flow, max_filter_duration=4., psd=psd)) if args.verbose: bar.iterate() ############################# #Crop data to correct length# ############################# time_shifted = {} final_len = int(args.final_duration / getter.static_params['delta_t']) if args.verbose: bar = progress_tracker(len(white_wavs) * len(getter), name='Applying time shifts') for key in white_wavs.keys(): time_shifted[key] = [] for i, wav in enumerate(white_wavs[key]): t0_idx = int((args.final_duration / 2 - float(wav.start_time)) * wav.sample_rate) #Pad too short waveforms if t0_idx < 0: wav.prepend_zeros(-t0_idx) t0_idx = 0 time_shifted[key].append(wav[t0_idx:t0_idx + final_len]) if args.verbose: bar.iterate() ################ #Store the data# ################ #Set the output file location if args.training_data: file_loc = os.path.join(tmp_params['train_set_dir'], file_name) else: file_loc = os.path.join(tmp_params['test_set_dir'], file_name) #Ensure that a translation is given for every parameter translation = args.translation keys = list(getter.variable_params) for key in keys: if key not in translation: translation[key] = key #Format the data to store it properly store_array = np.zeros((len(time_shifted), len(getter), final_len)) for i, key in enumerate(time_shifted.keys()): for j, dat in enumerate(time_shifted[key]): store_array[i][j][:] = np.array(dat)[:] store_array = store_array.transpose(1, 0, 2) #print(f"Store array shape: {store_array.shape}") x_data = np.vstack([np.array(params[key]) for key in keys]) x_data = x_data.transpose() #x_data = x_data.reshape((len(getter), 1, len(keys))) #Calculate the optimal SNR of all signals if args.verbose: bar = progress_tracker(len(time_shifted) * len(getter), name='Calculating SNRs') snrs = [] for key in time_shifted.keys(): tmp = [] for wav in time_shifted[key]: tmp.append(sigma(wav, low_frequency_cutoff=flow)) snrs.append(tmp) snrs = np.array(snrs) snrs = snrs.transpose() #Set some auxiliary data y_normscale = tmp_params['y_normscale'] tmpkeys = [translation[key] for key in keys] #Store the data if args.training_data: with h5py.File(file_loc, 'w') as fp: fp.create_dataset('x_data', data=x_data) fp.create_dataset('y_data_noisefree', data=store_array * np.sqrt(getter.static_params['delta_t'])) # fp.create_dataset('y_data_noisefree', data=store_array) fp.create_dataset('y_data_noisy', data=store_array + np.random.normal(size=store_array.shape)) # fp.create_dataset('y_data_noisy', data=store_array+np.random.normal(scale=np.sqrt(1. / getter.static_params['delta_t']), size=store_array.shape)) fp.create_dataset('rand_pars', data=np.array(tmpkeys, dtype='S')) fp.create_dataset('snrs', data=snrs) fp.create_dataset('y_normscale', data=y_normscale) for key, val in tmp_bounds.items(): fp.create_dataset(key, data=val) else: for i in range(args.number_samples): file_loc = os.path.join( tmp_params['test_set_dir'], args.file_name.format(name=args.name, start=args.start + i, stop=args.start + i + 1, samples=args.number_samples)) with h5py.File(file_loc, 'w') as fp: fp.create_dataset('x_data', data=np.expand_dims(x_data[i], axis=0)) fp.create_dataset('y_data_noisefree', data=store_array[i] * np.sqrt(getter.static_params['delta_t'])) # fp.create_dataset('y_data_noisefree', data=store_array[i]) fp.create_dataset('y_data_noisy', data=store_array[i] + np.random.normal(size=store_array[i].shape)) # fp.create_dataset('y_data_noisy', data=store_array[i]+np.random.normal(scale=np.sqrt(1. / getter.static_params['delta_t']), size=store_array[i].shape)) fp.create_dataset('rand_pars', data=np.array(tmpkeys, dtype='S')) fp.create_dataset('snrs', data=snrs[i]) fp.create_dataset('y_normscale', data=y_normscale) for key, val in tmp_bounds.items(): fp.create_dataset(key, data=val) if args.output_vitaminb_params_files: return params_file, bounds_file, fixed_vals_file else: return None
def resample_to_delta_t(timeseries, delta_t, method='butterworth'): """Resmple the time_series to delta_t Resamples the TimeSeries instance time_series to the given time step, delta_t. Only powers of two and real valued time series are supported at this time. Additional restrictions may apply to particular filter methods. Parameters ---------- time_series: TimeSeries The time series to be resampled delta_t: float The desired time step Returns ------- Time Series: TimeSeries A TimeSeries that has been resampled to delta_t. Raises ------ TypeError: time_series is not an instance of TimeSeries. TypeError: time_series is not real valued Examples -------- >>> h_plus_sampled = resample_to_delta_t(h_plus, 1.0/2048) """ if not isinstance(timeseries, TimeSeries): raise TypeError("Can only resample time series") if timeseries.kind is not 'real': raise TypeError("Time series must be real") if timeseries.delta_t == delta_t: return timeseries * 1 if method == 'butterworth': lal_data = timeseries.lal() _resample_func[timeseries.dtype](lal_data, delta_t) data = lal_data.data.data elif method == 'ldas': factor = int(delta_t / timeseries.delta_t) if factor == 8: timeseries = resample_to_delta_t(timeseries, timeseries.delta_t * 4.0, method='ldas') factor = 2 elif factor == 16: timeseries = resample_to_delta_t(timeseries, timeseries.delta_t * 4.0, method='ldas') factor = 4 elif factor == 32: timeseries = resample_to_delta_t(timeseries, timeseries.delta_t * 8.0, method='ldas') factor = 4 elif factor == 64: timeseries = resample_to_delta_t(timeseries, timeseries.delta_t * 16.0, method='ldas') factor = 4 try: filter_coefficients = LDAS_FIR_LP[factor] except: raise ValueError('Unsupported resample factor, %s, given' % factor) # apply the filter series = scipy.signal.lfilter(filter_coefficients, 1.0, timeseries.numpy()) # reverse the time shift caused by the filter corruption_length = len(filter_coefficients) data = numpy.zeros(len(timeseries)) data[:len(data) - corruption_length / 2] = series[corruption_length / 2:] # zero out corrupted region data[0:corruption_length / 2] = 0 data[len(data) - corruption_length / 2:] = 0 # Decimate the time series data = data[::factor] * 1 else: raise ValueError('Invalid resampling method: %s' % method) return TimeSeries(data, delta_t=delta_t, dtype=timeseries.dtype, epoch=timeseries._epoch)
def setUp(self): self.Msun = 4.92549095e-6 self.sample_rate = 4096 self.segment_length = 256 self.low_frequency_cutoff = 30.0 # chirp params self.m1 = 2.0 self.m2 = 2.5 self.del_t = 1.0/self.sample_rate self.Dl = 40.0 self.iota = 1.0 self.phi_c = 2.0 self.tc_indx = 86*self.sample_rate ## offset from the beginnig of a segment self.fmax = 1.0/(6.**1.5 *pi *(self.m1+self.m2)*self.Msun) self.zeta = 1.0 self.thetaS = 0.5 self.phiS = 2.781 self.Fp = 0.5*cos(2.0*self.zeta)*(1.0 + cos(self.thetaS)*cos(self.thetaS))*cos(2.0*self.phiS) - \ sin(2.*self.zeta)*cos(self.thetaS)*sin(2.*self.phiS) self.Fc = 0.5*sin(2.0*self.zeta)*(1.0 + cos(self.thetaS)*cos(self.thetaS))*cos(2.0*self.phiS) + \ cos(2.*self.zeta)*sin(self.thetaS)*sin(2.*self.phiS) # params of sin-gaussian self.Q = 1.e-1 self.om = 200.0*pi*2.0 # use flat psd self.seg_len_idx = self.segment_length * self.sample_rate self.psd_len = int(self.seg_len_idx/2+1) self.Psd = np.ones(self.psd_len)*2.0e-46 # generate waveform and chirp signal hp, hc = get_td_waveform(approximant="SpinTaylorT5", mass1=self.m1, mass2=self.m2, \ delta_t=self.del_t, f_lower=self.low_frequency_cutoff, distance=self.Dl, \ inclination=self.iota, coa_phase=self.phi_c) # signal which is a noiseless data thp = np.zeros(self.seg_len_idx) thp[self.tc_indx:len(hp)+self.tc_indx] = hp thc = np.zeros(self.seg_len_idx) thc[self.tc_indx:len(hc)+self.tc_indx] = hc fct = 10.0/15.21377 self.sig1 = fct*(self.Fp*thp + self.Fc*thc) #### template h = np.zeros(self.seg_len_idx) h[0:len(hp)] = hp hpt = TimeSeries(h, self.del_t) self.htilde = make_frequency_series(hpt) # generate sin-gaussian signal time = np.arange(0, len(hp))*self.del_t Nby2 = int(len(hp)/2) sngt = np.zeros(len(hp)) for i in range(len(hp)): sngt[i] = 9.0e-21*exp(-(time[i]-time[Nby2])**2/self.Q)*sin(self.om*time[i]) self.sig2 = np.zeros(self.seg_len_idx) self.sig2[self.tc_indx:len(sngt)+self.tc_indx] = sngt
def calc_filt_psd_variation(strain, segment, short_segment, psd_long_segment, psd_duration, psd_stride, psd_avg_method, low_freq, high_freq): """ Calculates time series of PSD variability This function first splits the segment up into 512 second chunks. It then calculates the PSD over this 512 second. The PSD is used to to create a filter that is the composition of three filters: 1. Bandpass filter between f_low and f_high. 2. Weighting filter which gives the rough response of a CBC template. 3. Whitening filter. Next it makes the convolution of this filter with the stretch of data. This new time series is given to the "mean_square" function, which computes the mean square of the timeseries within an 8 seconds window, once per second. The result, which is the variance of the S/N in that stride for the Parseval theorem, is then stored in a timeseries. Parameters ---------- strain : TimeSeries Input strain time series to estimate PSDs segment : {float, 8} Duration of the segments for the mean square estimation in seconds. short_segment : {float, 0.25} Duration of the short segments for the outliers removal. psd_long_segment : {float, 512} Duration of the long segments for PSD estimation in seconds. psd_duration : {float, 8} Duration of FFT segments for long term PSD estimation, in seconds. psd_stride : {float, 4} Separation between FFT segments for long term PSD estimation, in seconds. psd_avg_method : {string, 'median'} Method for averaging PSD estimation segments. low_freq : {float, 20} Minimum frequency to consider the comparison between PSDs. high_freq : {float, 480} Maximum frequency to consider the comparison between PSDs. Returns ------- psd_var : TimeSeries Time series of the variability in the PSD estimation """ # Calculate strain precision if strain.precision == 'single': fs_dtype = numpy.float32 elif strain.precision == 'double': fs_dtype = numpy.float64 # Convert start and end times immediately to floats start_time = numpy.float(strain.start_time) end_time = numpy.float(strain.end_time) # Resample the data strain = resample_to_delta_t(strain, 1.0 / 2048) srate = int(strain.sample_rate) # Fix the step for the PSD estimation and the time to remove at the # edge of the time series. step = 1.0 strain_crop = 8.0 # Find the times of the long segments times_long = numpy.arange( start_time, end_time, psd_long_segment - 2 * strain_crop - segment + step) # Set up the empty time series for the PSD variation estimate ts_duration = end_time - start_time - 2 * strain_crop - segment + 1 psd_var = TimeSeries(zeros(int(numpy.floor(ts_duration / step))), delta_t=step, copy=False, epoch=start_time + strain_crop + segment) # Create a bandpass filter between low_freq and high_freq filt = sig.firwin(4 * srate, [low_freq, high_freq], pass_zero=False, window='hann', nyq=srate / 2) filt.resize(int(psd_duration * srate)) # Fourier transform the filter and take the absolute value to get # rid of the phase. Save the filter as a frequency series. filt = abs(rfft(filt)) my_filter = FrequencySeries(filt, delta_f=1. / psd_duration, dtype=fs_dtype) ind = 0 for tlong in times_long: # Calculate PSD for long segment if tlong + psd_long_segment <= float(end_time): astrain = strain.time_slice(tlong, tlong + psd_long_segment) plong = pycbc.psd.welch( astrain, seg_len=int(psd_duration * strain.sample_rate), seg_stride=int(psd_stride * strain.sample_rate), avg_method=psd_avg_method) else: astrain = strain.time_slice(tlong, end_time) plong = pycbc.psd.welch( strain.time_slice(end_time - psd_long_segment, end_time), seg_len=int(psd_duration * strain.sample_rate), seg_stride=int(psd_stride * strain.sample_rate), avg_method=psd_avg_method) # Make the weighting filter - bandpass, which weight by f^-7/6, # and whiten. The normalization is chosen so that the variance # will be one if this filter is applied to white noise which # already has a variance of one. freqs = FrequencySeries(plong.sample_frequencies, delta_f=plong.delta_f, epoch=plong.epoch, dtype=fs_dtype) fweight = freqs**(-7. / 6.) * my_filter / numpy.sqrt(plong) fweight[0] = 0. norm = (sum(abs(fweight)**2) / (len(fweight) - 1.))**-0.5 fweight = norm * fweight fwhiten = numpy.sqrt(2. / srate) / numpy.sqrt(plong) fwhiten[0] = 0. full_filt = sig.hann(int(psd_duration * srate)) * numpy.roll( irfft(fwhiten * fweight), int(psd_duration / 2) * srate) # Convolve the filter with long segment of data wstrain = TimeSeries(sig.fftconvolve(astrain, full_filt, mode='same'), delta_t=strain.delta_t, epoch=astrain.start_time) wstrain = wstrain[int(strain_crop * srate):-int(strain_crop * srate)] # compute the mean square of the chunk of data delta_t = wstrain.end_time.gpsSeconds - wstrain.start_time.gpsSeconds variation = mean_square(wstrain, delta_t, short_segment, segment) # Store variation value for i, val in enumerate(variation): psd_var[ind + i] = val ind = ind + len(variation) return psd_var
def 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 matched_filter_core(template, data, psd=None, low_frequency_cutoff=None, high_frequency_cutoff=None, h_norm=None, out=None, corr_out=None): """ Return the complex snr and normalization. Return the complex snr, along with its associated normalization of the template, matched filtered against the data. Parameters ---------- template : TimeSeries or FrequencySeries The template waveform data : TimeSeries or FrequencySeries The strain data to be filtered. psd : {FrequencySeries}, optional The noise weighting of the filter. low_frequency_cutoff : {None, float}, optional The frequency to begin the filter calculation. If None, begin at the first frequency after DC. high_frequency_cutoff : {None, float}, optional The frequency to stop the filter calculation. If None, continue to the the nyquist frequency. h_norm : {None, float}, optional The template normalization. If none, this value is calculated internally. out : {None, Array}, optional An array to use as memory for snr storage. If None, memory is allocated internally. corr_out : {None, Array}, optional An array to use as memory for correlation storage. If None, memory is allocated internally. If provided, management of the vector is handled externally by the caller. No zero'ing is done internally. Returns ------- snr : TimeSeries A time series containing the complex snr. corrrelation: FrequencySeries A frequency series containing the correlation vector. norm : float The normalization of the complex snr. """ if corr_out is not None: _qtilde = corr_out else: global _qtilde_t _qtilde = _qtilde_t htilde = make_frequency_series(template) stilde = make_frequency_series(data) if len(htilde) != len(stilde): raise ValueError("Length of template and data must match") N = (len(stilde) - 1) * 2 kmin, kmax = get_cutoff_indices(low_frequency_cutoff, high_frequency_cutoff, stilde.delta_f, N) if out is None: _q = zeros(N, dtype=complex_same_precision_as(data)) elif (len(out) == N) and type(out) is Array and out.kind == 'complex': _q = out else: raise TypeError('Invalid Output Vector: wrong length or dtype') if corr_out: pass elif (_qtilde is None) or (len(_qtilde) != N) or _qtilde.dtype != data.dtype: _qtilde_t = _qtilde = zeros(N, dtype=complex_same_precision_as(data)) else: _qtilde.clear() correlate(htilde[kmin:kmax], stilde[kmin:kmax], _qtilde[kmin:kmax]) if psd is not None: if isinstance(psd, FrequencySeries): if psd.delta_f == stilde.delta_f: _qtilde[kmin:kmax] /= psd[kmin:kmax] else: raise TypeError("PSD delta_f does not match data") else: raise TypeError("PSD must be a FrequencySeries") ifft(_qtilde, _q) if h_norm is None: h_norm = sigmasq(htilde, psd, low_frequency_cutoff, high_frequency_cutoff) norm = (4.0 * stilde.delta_f) / sqrt(h_norm) delta_t = 1.0 / (N * stilde.delta_f) return (TimeSeries(_q, epoch=stilde._epoch, delta_t=delta_t, copy=False), FrequencySeries(_qtilde, epoch=stilde._epoch, delta_f=htilde.delta_f, copy=False), norm)
def do_test(self, n_ifos, n_ifos_followup): # choose a random selection of interferometers # n_ifos will be used to generate the simulated trigger # n_ifos_followup will be used as followup-only all_ifos = random.sample(self.possible_ifos, n_ifos + n_ifos_followup) trig_ifos = all_ifos[0:n_ifos] results = { 'foreground/stat': np.random.uniform(4, 20), 'foreground/ifar': np.random.uniform(0.01, 1000) } followup_data = {} for ifo in all_ifos: offset = 10000 + np.random.uniform(-0.02, 0.02) amplitude = np.random.uniform(4, 20) # generate a mock SNR time series with a peak n = 201 dt = 1. / 2048. t = np.arange(n) * dt t_peak = dt * n / 2 snr = np.exp(-(t - t_peak)**2 * 3e-3**-2) * amplitude snr_series = TimeSeries((snr + 1j * 0).astype(np.complex64), delta_t=dt, epoch=offset) # generate a mock PSD psd_samples = np.random.exponential(size=1024) psd = FrequencySeries(psd_samples, delta_f=1.) # fill in the various fields if ifo in trig_ifos: base = 'foreground/' + ifo + '/' results[base + 'end_time'] = t_peak + offset results[base + 'snr'] = amplitude results[base + 'sigmasq'] = np.random.uniform(1e6, 2e6) followup_data[ifo] = {'snr_series': snr_series, 'psd': psd} for ifo, k in itertools.product(trig_ifos, self.template): results['foreground/' + ifo + '/' + k] = self.template[k] kwargs = { 'psds': {ifo: followup_data[ifo]['psd'] for ifo in all_ifos}, 'low_frequency_cutoff': 20., 'followup_data': followup_data } coinc = SingleCoincForGraceDB(trig_ifos, results, **kwargs) tempdir = tempfile.mkdtemp() coinc_file_name = os.path.join(tempdir, 'coinc.xml.gz') if GraceDb is not None: # pretend to upload the event to GraceDB. # The upload will fail, but it should not raise an exception # and it should still leave the event file around coinc.upload(coinc_file_name, gracedb_server='localhost', testing=True) else: # no GraceDb module, so just save the coinc file coinc.save(coinc_file_name) # read back and check the coinc document read_coinc = ligolw_utils.load_filename(coinc_file_name, verbose=False, contenthandler=ContentHandler) single_table = table.get_table(read_coinc, lsctables.SnglInspiralTable.tableName) self.assertEqual(len(single_table), len(all_ifos)) coinc_table = table.get_table(read_coinc, lsctables.CoincInspiralTable.tableName) self.assertEqual(len(coinc_table), 1) # make sure lalseries can read the PSDs psd_doc = ligolw_utils.load_filename( coinc_file_name, verbose=False, contenthandler=lalseries.PSDContentHandler) psd_dict = lalseries.read_psd_xmldoc(psd_doc) self.assertEqual(set(psd_dict.keys()), set(all_ifos)) shutil.rmtree(tempdir)
def apply(self, strain, detector_name, f_lower=None, distance_scale=1): """Add injections (as seen by a particular detector) to a time series. Parameters ---------- strain : TimeSeries Time series to inject signals into, of type float32 or float64. detector_name : string Name of the detector used for projecting injections. f_lower : {None, float}, optional Low-frequency cutoff for injected signals. If None, use value provided by each injection. distance_scale: {1, foat}, optional Factor to scale the distance of an injection with. The default is no scaling. Returns ------- None Raises ------ TypeError For invalid types of `strain`. """ if not strain.dtype in (float32, float64): raise TypeError("Strain dtype must be float32 or float64, not " \ + str(strain.dtype)) lalstrain = strain.lal() detector = Detector(detector_name) earth_travel_time = lal.REARTH_SI / lal.C_SI t0 = float(strain.start_time) - earth_travel_time t1 = float(strain.end_time) + earth_travel_time # pick lalsimulation tapering function taper = taper_func_map[strain.dtype] # pick lalsimulation injection function add_injection = injection_func_map[strain.dtype] for inj in self.table: # roughly estimate if the injection may overlap with the segment end_time = inj.get_time_geocent() #CHECK: This is a hack (10.0s); replace with an accurate estimate inj_length = 10.0 eccentricity = 0.0 polarization = 0.0 start_time = end_time - 2 * inj_length if end_time < t0 or start_time > t1: continue # compute the waveform time series hp, hc = sim.SimBurstSineGaussian(float(inj.q), float(inj.frequency),float(inj.hrss),float(eccentricity), float(polarization),float(strain.delta_t)) hp = TimeSeries(hp.data.data[:], delta_t=hp.deltaT, epoch=hp.epoch) hc = TimeSeries(hc.data.data[:], delta_t=hc.deltaT, epoch=hc.epoch) hp._epoch += float(end_time) hc._epoch += float(end_time) if float(hp.start_time) > t1: continue # compute the detector response, taper it if requested # and add it to the strain #signal = detector.project_wave( # hp, hc, inj.longitude, inj.latitude, inj.polarization) signal_lal = hp.astype(strain.dtype).lal() if taper_map['TAPER_NONE'] is not None: taper(signal_lal.data, taper_map['TAPER_NONE']) add_injection(lalstrain, signal_lal, None) strain.data[:] = lalstrain.data.data[:]
def get_waveform(approximant, phase_order, amplitude_order, spin_order, template_params, start_frequency, sample_rate, length, datafile=None, verbose=False): #{{{ print("IN hERE") delta_t = 1. / sample_rate delta_f = 1. / length filter_N = int(length) filter_n = filter_N / 2 + 1 if approximant in fd_approximants() and 'Eccentric' not in approximant: print("NORMAL FD WAVEFORM for", approximant) delta_f = sample_rate / length hplus, hcross = get_fd_waveform(template_params, approximant=approximant, spin_order=spin_order, phase_order=phase_order, delta_f=delta_f, f_lower=start_frequency, amplitude_order=amplitude_order) elif approximant in td_approximants() and 'Eccentric' not in approximant: print("NORMAL TD WAVEFORM for", approximant) hplus, hcross = get_td_waveform(template_params, approximant=approximant, spin_order=spin_order, phase_order=phase_order, delta_t=1.0 / sample_rate, f_lower=start_frequency, amplitude_order=amplitude_order) elif 'EccentricIMR' in approximant: #{{{ # Legacy support import sys sys.path.append('/home/kuma/grav/kuma/src/Eccentric_IMR/Codes/Python/') import EccentricIMR as Ecc try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') rtrans = getattr(template_params, 'alpha') beta = 0 except: raise RuntimeError(\ "template_params does not have alpha{,1,2} or inclination") tol = 1.e-16 fmin = start_frequency sample_rate = sample_rate # print(" Using phase order: %d" % phase_order, file=sys.stdout) sys.stdout.flush() hplus, hcross = Ecc.generate_eccentric_waveform(mass1, mass2,\ ecc, anom, inc, beta,\ tol,\ r_transition=rtrans,\ phase_order=phase_order,\ fmin=fmin,\ sample_rate=sample_rate,\ inspiral_only=False) #}}} elif 'EccentricInspiral' in approximant: #{{{ # Legacy support import sys sys.path.append('/home/kuma/grav/kuma/src/Eccentric_IMR/Codes/Python/') import EccentricIMR as Ecc try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') beta = getattr(template_params, 'alpha') except: raise RuntimeError(\ "template_params does not have alpha{,1,2} or inclination") tol = 1.e-16 fmin = start_frequency sample_rate = sample_rate # hplus, hcross = Ecc.generate_eccentric_waveform(mass1, mass2,\ ecc, anom, inc, beta,\ tol,\ phase_order=phase_order,\ fmin=fmin,\ sample_rate=sample_rate,\ inspiral_only=True) #}}} elif 'EccentricFD' in approximant: #{{{ # Legacy support import lalsimulation as ls import lal delta_f = sample_rate / length try: mass1 = getattr(template_params, 'mass1') mass2 = getattr(template_params, 'mass2') except: raise RuntimeError("template_params does not have mass1 or mass2!") try: ecc = getattr(template_params, 'alpha1') if 'E0' in approximant: ecc = 0 anom = getattr(template_params, 'alpha2') inc = getattr(template_params, 'inclination') except: raise RuntimeError(\ "template_params does not have alpha{1,2} or inclination") eccPar = ls.SimInspiralCreateTestGRParam("inclination_azimuth", inc) ls.SimInspiralAddTestGRParam(eccPar, "e_min", ecc) fmin = start_frequency fmax = sample_rate / 2 # thp, thc = ls.SimInspiralChooseFDWaveform(0, delta_f,\ mass1*lal.MSUN_SI, mass2*lal.MSUN_SI,\ 0,0,0,0,0,0,\ fmin, fmax, 0, 1.e6 * lal.PC_SI,\ inc, 0, 0, None, eccPar, -1, 7, ls.EccentricFD) hplus = FrequencySeries(thp.data.data[:], delta_f=thp.deltaF, epoch=thp.epoch) hcross = FrequencySeries(thc.data.data[:], delta_f=thc.deltaF, epoch=thc.epoch) #}}} elif 'FromDataFile' in approximant: #{{{ # Legacy support if not os.path.exists(datafile): raise IOError("File %s not found!" % datafile) if verbose: print("Reading from data file %s" % datafile) # Figure out waveform parameters from filename #q_value, M_value, w_value, _, _ = EA.get_q_m_e_pn_o_from_filename(datafile) q_value, M_value, w_value = EA.get_q_m_e_from_filename(datafile) # Read data, down-sample (assume data file is more finely sampled than # needed, i.e. interpolation is NOT supported, nor will be) data = np.loadtxt(datafile) dt = data[1, 0] - data[0, 0] delta_t = 1. / sample_rate downsample_ratio = delta_t / dt if not approx_equal(downsample_ratio, np.int(downsample_ratio)): raise RuntimeError( "Cannot handling resampling at a fractional factor = %e" % downsample_ratio) elif verbose: print("Downsampling by a factor of %d" % int(downsample_ratio)) h_real = TimeSeries(data[::int(downsample_ratio), 1] / DYN_RANGE_FAC, delta_t=delta_t) h_imag = TimeSeries(data[::int(downsample_ratio), 2] / DYN_RANGE_FAC, delta_t=delta_t) if verbose: print("max, min,len of h_real = ", max(h_real.data), min(h_real.data), len(h_real.data)) # Compute Strain tmplt_pars = template_params wav = generate_detector_strain(tmplt_pars, h_real, h_imag) wav = extend_waveform_TimeSeries(wav, filter_N) # Return TimeSeries with (m1, m2, w_value) m1, m2 = mtotal_eta_to_mass1_mass2(M_value, q_value / (1. + q_value)**2) htilde = make_frequency_series(wav) htilde = extend_waveform_FrequencySeries(htilde, filter_n) if verbose: print("ISNAN(htilde from file) = ", np.any(np.isnan(htilde.data))) return htilde, [m1, m2, w_value, dt] #}}} else: raise IOError(".. APPROXIMANT %s not found.." % approximant) ## hvec = hplus htilde = make_frequency_series(hvec) htilde = extend_waveform_FrequencySeries(htilde, filter_n) # print("type of hplus, hcross = ", type(hplus.data), type(hcross.data)) if any(isnan(hplus.data)) or any(isnan(hcross.data)): print("..### %s hplus or hcross have NANS!!" % approximant) # if any(isinf(hplus.data)) or any(isinf(hcross.data)): print("..### %s hplus or hcross have INFS!!" % approximant) # if any(isnan(htilde.data)): print("..### %s Fourier transform htilde has NANS!!" % approximant) # if any(isinf(htilde.data)): print("..### %s Fourier transform htilde has INFS!!" % approximant) # return htilde
def blend(hin, mm, sample, time, t_opt, WinID=-1): # Only dealing with real part, don't do hc calculations # t_opt is length-5 array describing multiples of mm # Returns length-5 array of TimeSeries (1 per blending) #{{{ hp0, hc0 = hin.rescale_to_totalmass(mm) hp0._epoch = hc0._epoch = 0 amp = TimeSeries(np.sqrt(hp0**2 + hc0**2), copy=True, delta_t=hp0.delta_t) max_a, max_a_index = amp.abs_max_loc() print("\n\n In blend:\nTotal Mass = %f, len(hp0,hc0) = %d, %d = %f s" %\ (mm, len(hp0), len(hc0), hp0.sample_times[-1]-hp0.sample_times[0])) print("Waveform max = %e, located at %d" % (max_a, max_a_index)) #amp_after_peak = amp #amp_after_peak[:max_a_index] = 0 mtsun = lal.MTSUN_SI amp_after_peak = amp[max_a_index:] iA, vA = min(enumerate(amp_after_peak), key=lambda x: abs(x[1] - 0.01 * max_a)) iA += max_a_index #iA, vA = min(enumerate(amp_after_peak),key=lambda x:abs(x[1]-0.01*max_a)) iB, vB = min(enumerate(amp_after_peak), key=lambda x: abs(x[1] - 0.1 * max_a)) iB += max_a_index if iA <= max_a_index: print("iA = %d, iB = %d, vA = %e, vB = %e" % (iA, iB, vA, vB), file=sys.stdout) sys.stdout.flush() raise RuntimeError("Couldnt find amplitude threshold time iA") # do something #fout = open('hpdump.dat','w+') #for i in range( len(amp) ): # if i > max_a_index and amp[i] == 0: break # fout.write('%e\t%e\n' % (amp.sample_times[i],amp[i])) #fout.close() # Find the point the hard way target_amp = max_a * 0.01 tmp_data = amp.data for idx in range(max_a_index, len(amp)): if tmp_data[idx] < target_amp: break iA = idx print("Newfound iA = %d" % iA) # Yet another way amp_after_peak = amp[max_a_index:] iA, vA = min(enumerate(amp_after_peak), key=lambda x: abs(x[1] - 0.01 * max_a)) iA += max_a_index print("Newfound iA another way = %d" % iA) raise RuntimeError("Had to find amplitude threshold the hard way") if iB <= max_a_index: raise RuntimeError("Couldnt find amplitude threshold time iB") # this doesn't happen yet pass print("NEW: iA = %d, iB = %d, vA = %e, vB = %e" % (iA, iB, vA, vB)) t = [ [ t_opt[0] * mm, 500 * mm, hp0.sample_times.data[iA] / mtsun, hp0.sample_times.data[iA] / mtsun + t_opt[3] * mm ], # Prayush's E [ t_opt[0] * mm, t_opt[1] * mm, hp0.sample_times.data[iA] / mtsun, hp0.sample_times.data[iA] / mtsun + t_opt[3] * mm ], [ t_opt[0] * mm, t_opt[1] * mm, hp0.sample_times.data[iB] / mtsun, hp0.sample_times.data[iB] / mtsun + t_opt[4] * mm ], [ t_opt[0] * mm, t_opt[2] * mm, hp0.sample_times.data[iA] / mtsun, hp0.sample_times.data[iA] / mtsun + t_opt[3] * mm ], [ t_opt[0] * mm, t_opt[2] * mm, hp0.sample_times.data[iB] / mtsun, hp0.sample_times.data[iB] / mtsun + t_opt[4] * mm ] ] hphc = [] hphc.append(hp0) for i in range(len(t)): if (WinID >= 0 and WinID < len(t)) and i != WinID: continue print("Testing window with t = ", t[i]) hphc.append( hin.blending_function(hp0=hp0, t=t[i], sample_rate=sample, time_length=time)) print("No of blending windows being tested = %d" % (len(hphc) - 1)) return hphc
def filter_zpk(timeseries, z, p, k): """Return a new timeseries that was filtered with a zero-pole-gain filter. The transfer function in the s-domain looks like: .. math:: \\frac{H(s) = (s - s_1) * (s - s_3) * ... * (s - s_n)}{(s - s_2) * (s - s_4) * ... * (s - s_m)}, m >= n The zeroes, and poles entered in Hz are converted to angular frequency, along the imaginary axis in the s-domain s=i*omega. Then the zeroes, and poles are bilinearly transformed via: .. math:: z(s) = \\frac{(1 + s*T/2)}{(1 - s*T/2)} Where z is the z-domain value, s is the s-domain value, and T is the sampling period. After the poles and zeroes have been bilinearly transformed, then the second-order sections are found and filter the data using scipy. Parameters ---------- timeseries: TimeSeries The TimeSeries instance to be filtered. z: array Array of zeros to include in zero-pole-gain filter design. In units of Hz. p: array Array of poles to include in zero-pole-gain filter design. In units of Hz. k: float Gain to include in zero-pole-gain filter design. This gain is a constant multiplied to the transfer function. Returns ------- Time Series: TimeSeries A new TimeSeries that has been filtered. Examples -------- To apply a 5 zeroes at 100Hz, 5 poles at 1Hz, and a gain of 1e-10 filter to a TimeSeries instance, do: >>> filtered_data = zpk_filter(timeseries, [100]*5, [1]*5, 1e-10) """ # sanity check type if not isinstance(timeseries, TimeSeries): raise TypeError("Can only filter TimeSeries instances.") # sanity check casual filter degree = len(p) - len(z) if degree < 0: raise TypeError("May not have more zeroes than poles. \ Filter is not casual.") # cast zeroes and poles as arrays and gain as a float z = np.array(z) p = np.array(p) k = float(k) # put zeroes and poles in the s-domain # convert from frequency to angular frequency z *= -2 * np.pi p *= -2 * np.pi # get denominator of bilinear transform fs = 2.0 * timeseries.sample_rate # zeroes in the z-domain z_zd = (1 + z / fs) / (1 - z / fs) # any zeros that were at infinity are moved to the Nyquist frequency z_zd = z_zd[np.isfinite(z_zd)] z_zd = np.append(z_zd, -np.ones(degree)) # poles in the z-domain p_zd = (1 + p / fs) / (1 - p / fs) # gain change in z-domain k_zd = k * np.prod(fs - z) / np.prod(fs - p) # get second-order sections sos = zpk2sos(z_zd, p_zd, k_zd) # filter filtered_data = sosfilt(sos, timeseries.numpy()) return TimeSeries(filtered_data, delta_t=timeseries.delta_t, dtype=timeseries.dtype, epoch=timeseries._epoch)
def get_td_qnm(template=None, **kwargs): """Return a time domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. phi_0 : {0, float}, optional The initial phase of the ringdown. amp : {1, float}, optional The amplitude of the ringdown (constant for now). delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time domain. """ input_params = props_ringdown(template,**kwargs) # get required args try: f_0 = input_params['f_0'] except KeyError: raise ValueError('f_0 is required') try: tau = input_params['tau'] except KeyError: raise ValueError('tau is required') # get optional args # the following have defaults, and so will be populated phi_0 = input_params.pop('phi_0') amp = input_params.pop('amp') # the following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = 1. / qnm_freq_decay(f_0, tau, 1./1000) if t_final is None: t_final = qnm_time_decay(tau, 1./1000) kmax = int(t_final / delta_t) + 1 two_pi = 2 * numpy.pi times = numpy.arange(kmax)*delta_t hp = amp * numpy.exp(-times/tau) * numpy.cos(two_pi*f_0*times + phi_0) hc = amp * numpy.exp(-times/tau) * numpy.sin(two_pi*f_0*times + phi_0) hplus = TimeSeries(zeros(kmax), delta_t=delta_t) hcross = TimeSeries(zeros(kmax), delta_t=delta_t) hplus.data[:kmax] = hp hcross.data[:kmax] = hc return hplus, hcross
def get_td_qnm(template=None, taper=None, **kwargs): """Return a time domain damped sinusoid. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. f_0 : float The ringdown-frequency. tau : float The damping time of the sinusoid. amp : float The amplitude of the ringdown (constant for now). phi : float The initial phase of the ringdown. Should also include the information from the azimuthal angle (phi_0 + m*Phi) inclination : {None, float}, optional Inclination of the system in radians for the spherical harmonics. l : {2, int}, optional l mode for the spherical harmonics. Default is l=2. m : {2, int}, optional m mode for the spherical harmonics. Default is m=2. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude. t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude. Returns ------- hplus: TimeSeries The plus phase of the ringdown in time domain. hcross: TimeSeries The cross phase of the ringdown in time domain. """ input_params = props(template, qnm_required_args, **kwargs) f_0 = input_params.pop('f_0') tau = input_params.pop('tau') amp = input_params.pop('amp') phi = input_params.pop('phi') # the following may not be in input_params inc = input_params.pop('inclination', None) l = input_params.pop('l', 2) m = input_params.pop('m', 2) delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if not delta_t: delta_t = 1. / qnm_freq_decay(f_0, tau, 1./1000) if delta_t < min_dt: delta_t = min_dt if not t_final: t_final = qnm_time_decay(tau, 1./1000) kmax = int(t_final / delta_t) + 1 times = numpy.arange(kmax) * delta_t if inc is not None: Y_plus, Y_cross = spher_harms(l, m, inc) else: Y_plus, Y_cross = 1, 1 hplus = amp * Y_plus * numpy.exp(-times/tau) * \ numpy.cos(two_pi*f_0*times + phi) hcross = amp * Y_cross * numpy.exp(-times/tau) * \ numpy.sin(two_pi*f_0*times + phi) if taper and delta_t < taper*tau: taper_window = int(taper*tau/delta_t) kmax += taper_window outplus = TimeSeries(zeros(kmax), delta_t=delta_t) outcross = TimeSeries(zeros(kmax), delta_t=delta_t) # If size of tapering window is less than delta_t, do not apply taper. if not taper or delta_t > taper*tau: outplus.data[:kmax] = hplus outcross.data[:kmax] = hcross return outplus, outcross else: taper_hp, taper_hc = apply_taper(delta_t, taper, f_0, tau, amp, phi, l, m, inc) start = - taper * tau outplus.data[:taper_window] = taper_hp outplus.data[taper_window:] = hplus outcross.data[:taper_window] = taper_hc outcross.data[taper_window:] = hcross outplus._epoch, outcross._epoch = start, start return outplus, outcross
def apply(self, strain, detector_name, f_lower=None, distance_scale=1, simulation_ids=None): """Add injections (as seen by a particular detector) to a time series. Parameters ---------- strain : TimeSeries Time series to inject signals into, of type float32 or float64. detector_name : string Name of the detector used for projecting injections. f_lower : {None, float}, optional Low-frequency cutoff for injected signals. If None, use value provided by each injection. distance_scale: {1, float}, optional Factor to scale the distance of an injection with. The default is no scaling. simulation_ids: iterable, optional If given, only inject signals with the given simulation IDs. Returns ------- None Raises ------ TypeError For invalid types of `strain`. """ if not strain.dtype in (float32, float64): raise TypeError("Strain dtype must be float32 or float64, not " \ + str(strain.dtype)) lalstrain = strain.lal() detector = Detector(detector_name) earth_travel_time = lal.REARTH_SI / lal.C_SI t0 = float(strain.start_time) - earth_travel_time t1 = float(strain.end_time) + earth_travel_time # pick lalsimulation injection function add_injection = injection_func_map[strain.dtype] injections = self.table if simulation_ids: injections = [inj for inj in injections \ if inj.simulation_id in simulation_ids] for inj in injections: if f_lower is None: f_l = inj.f_lower else: f_l = f_lower if inj.numrel_data != None and inj.numrel_data != "": # performing NR waveform injection # reading Hp and Hc from the frame files swigrow = self.getswigrow(inj) import lalinspiral Hp, Hc = lalinspiral.NRInjectionFromSimInspiral(swigrow, strain.delta_t) # converting to pycbc timeseries hp = TimeSeries(Hp.data.data[:], delta_t=Hp.deltaT, epoch=Hp.epoch) hc = TimeSeries(Hc.data.data[:], delta_t=Hc.deltaT, epoch=Hc.epoch) hp /= distance_scale hc /= distance_scale end_time = float(hp.get_end_time()) start_time = float(hp.get_start_time()) if end_time < t0 or start_time > t1: continue else: # roughly estimate if the injection may overlap with the segment end_time = inj.get_time_geocent() inj_length = sim.SimInspiralTaylorLength( strain.delta_t, inj.mass1 * lal.MSUN_SI, inj.mass2 * lal.MSUN_SI, f_l, 0) start_time = end_time - 2 * inj_length if end_time < t0 or start_time > t1: continue name, phase_order = legacy_approximant_name(inj.waveform) # compute the waveform time series hp, hc = get_td_waveform( inj, approximant=name, delta_t=strain.delta_t, phase_order=phase_order, f_lower=f_l, distance=inj.distance * distance_scale, **self.extra_args) hp._epoch += float(end_time) hc._epoch += float(end_time) if float(hp.start_time) > t1: continue # compute the detector response, taper it if requested # and add it to the strain signal = detector.project_wave( hp, hc, inj.longitude, inj.latitude, inj.polarization) # the taper_timeseries function converts to a LAL TimeSeries signal = signal.astype(strain.dtype) signal_lal = wfutils.taper_timeseries(signal, inj.taper, return_lal=True) add_injection(lalstrain, signal_lal, None) strain.data[:] = lalstrain.data.data[:]
def get_td_from_freqtau(template=None, taper=None, **kwargs): """Return time domain ringdown with all the modes specified. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. Each mode and overtone will have a different taper depending on its tau, the final taper being the superposition of all the tapers. lmns : list Desired lmn modes as strings (lm modes available: 22, 21, 33, 44, 55). The n specifies the number of overtones desired for the corresponding lm pair (maximum n=8). Example: lmns = ['223','331'] are the modes 220, 221, 222, and 330 f_lmn: float Central frequency of the lmn overtone, as many as number of modes. tau_lmn: float Damping time of the lmn overtone, as many as number of modes. amp220 : float Amplitude of the fundamental 220 mode. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of modes. Should also include the information from the azimuthal angle (phi + m*Phi). inclination : {None, float}, optional Inclination of the system in radians. If None, the spherical harmonics will be set to 1. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). t_final : {None, float}, optional The ending time of the output frequency series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a ringdown with the lm modes specified and n overtones in frequency domain. hcrosstilde: FrequencySeries The cross phase of a ringdown with the lm modes specified and n overtones in frequency domain. """ input_params = props(template, freqtau_required_args, **kwargs) # Get required args f_0, tau = lm_freqs_taus(**input_params) lmns = input_params['lmns'] for lmn in lmns: if int(lmn[2]) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # following may not be in input_params inc = input_params.pop('inclination', None) delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if not delta_t: delta_t = lm_deltat(f_0, tau, lmns) if not t_final: t_final = lm_tfinal(tau, lmns) kmax = int(t_final / delta_t) + 1 # Different overtones will have different tapering window-size # Find maximum window size to create long enough output vector if taper: taper_window = int(taper*max(tau.values())/delta_t) kmax += taper_window outplus = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) outcross = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) if taper: start = - taper * max(tau.values()) outplus._epoch, outcross._epoch = start, start for lmn in lmns: l, m, nmodes = int(lmn[0]), int(lmn[1]), int(lmn[2]) hplus, hcross = get_td_lm(freqs=f_0, taus=tau, l=l, m=m, nmodes=nmodes, taper=taper, inclination=inc, delta_t=delta_t, t_final=t_final, **input_params) if not taper: outplus.data += hplus.data outcross.data += hcross.data else: outplus = taper_shift(hplus, outplus) outcross = taper_shift(hcross, outcross) return outplus, outcross
def project_wave(self, hp, hc, ra, dec, polarization, method='lal', reference_time=None): """Return the strain of a waveform as measured by the detector. Apply the time shift for the given detector relative to the assumed geocentric frame and apply the antenna patterns to the plus and cross polarizations. Parameters ---------- hp: pycbc.types.TimeSeries Plus polarization of the GW hc: pycbc.types.TimeSeries Cross polarization of the GW ra: float Right ascension of source location dec: float Declination of source location polarization: float Polarization angle of the source method: {'lal', 'constant', 'vary_polarization'} The method to use for projecting the polarizations into the detector frame. Default is 'lal'. reference_time: float, Optional The time to use as, a reference for some methods of projection. Used by 'constant' and 'vary_polarization' methods. Uses average time if not provided. """ # The robust and most fefature rich method which includes # time changing antenna patterns and doppler shifts due to the # earth rotation and orbit if method == 'lal': import lalsimulation h_lal = lalsimulation.SimDetectorStrainREAL8TimeSeries( hp.astype(np.float64).lal(), hc.astype(np.float64).lal(), ra, dec, polarization, self.lal()) ts = TimeSeries( h_lal.data.data, delta_t=h_lal.deltaT, epoch=h_lal.epoch, dtype=np.float64, copy=False) # 'constant' assume fixed orientation relative to source over the # duration of the signal, accurate for short duration signals # 'fixed_polarization' applies only time changing orientation # but no doppler corrections elif method in ['constant', 'vary_polarization']: if reference_time is not None: rtime = reference_time else: # In many cases, one should set the reference time if using # this method as we don't know where the signal is within # the given time series. If not provided, we'll choose # the midpoint time. rtime = (float(hp.end_time) + float(hp.start_time)) / 2.0 if method == 'constant': time = rtime elif method == 'vary_polarization': if (not isinstance(hp, TimeSeries) or not isinstance(hc, TimeSeries)): raise TypeError('Waveform polarizations must be given' ' as time series for this method') # this is more granular than needed, may be optimized later # assume earth rotation in ~30 ms needed for earth ceneter # to detector is completely negligible. time = hp.sample_times.numpy() fp, fc = self.antenna_pattern(ra, dec, polarization, time) dt = self.time_delay_from_earth_center(ra, dec, rtime) ts = fp * hp + fc * hc ts.start_time = float(ts.start_time) + dt # add in only the correction for the time variance in the polarization # due to the earth's rotation, no doppler correction applied else: raise ValueError("Unkown projection method {}".format(method)) return ts
def get_td_lm(template=None, taper=None, **kwargs): """Return frequency domain lm mode with the given number of overtones. Parameters ---------- template: object An object that has attached properties. This can be used to substitute for keyword arguments. A common example would be a row in an xml table. taper: {None, float}, optional Tapering at the beginning of the waveform with duration taper * tau. This option is recommended with timescales taper=1./2 or 1. for time-domain ringdown-only injections. The abrupt turn on of the ringdown can cause issues on the waveform when doing the fourier transform to the frequency domain. Setting taper will add a rapid ringup with timescale tau/10. Each overtone will have a different taper depending on its tau, the final taper being the superposition of all the tapers. final_mass : float Mass of the final black hole. final_spin : float Spin of the final black hole. l : int l mode (lm modes available: 22, 21, 33, 44, 55). m : int m mode (lm modes available: 22, 21, 33, 44, 55). nmodes: int Number of overtones desired (maximum n=8) amp220 : float Amplitude of the fundamental 220 mode, needed for any lm. amplmn : float Fraction of the amplitude of the lmn overtone relative to the fundamental mode, as many as the number of subdominant modes. philmn : float Phase of the lmn overtone, as many as the number of nmodes. delta_t : {None, float}, optional The time step used to generate the ringdown. If None, it will be set to the inverse of the frequency at which the amplitude is 1/1000 of the peak amplitude (the minimum of all modes). t_final : {None, float}, optional The ending time of the output time series. If None, it will be set to the time at which the amplitude is 1/1000 of the peak amplitude (the maximum of all modes). Returns ------- hplustilde: FrequencySeries The plus phase of a lm mode with overtones (n) in frequency domain. hcrosstilde: FrequencySeries The cross phase of a lm mode with overtones (n) in frequency domain. """ input_params = props(template, lm_required_args, **kwargs) # Get required args amps, phis = lm_amps_phases(**input_params) final_mass = input_params.pop('final_mass') final_spin = input_params.pop('final_spin') l, m = input_params.pop('l'), input_params.pop('m') nmodes = input_params.pop('nmodes') if int(nmodes) == 0: raise ValueError('Number of overtones (nmodes) must be greater ' 'than zero.') # The following may not be in input_params delta_t = input_params.pop('delta_t', None) t_final = input_params.pop('t_final', None) if delta_t is None: delta_t = lm_deltat(final_mass, final_spin, ['%d%d%d' % (l, m, nmodes)]) if t_final is None: t_final = lm_tfinal(final_mass, final_spin, ['%d%d%d' % (l, m, nmodes)]) f_0, tau = get_lm_f0tau(final_mass, final_spin, l, m, nmodes) kmax = int(t_final / delta_t) + 1 # Different overtones will have different tapering window-size # Find maximum window size to create long enough output vector if taper is not None: taper_window = int(taper * max(tau) / delta_t) kmax += taper_window outplus = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) outcross = TimeSeries(zeros(kmax, dtype=float64), delta_t=delta_t) for n in range(nmodes): hplus, hcross = get_td_qnm(template=None, taper=taper, f_0=f_0[n], tau=tau[n], phi=phis['%d%d%d' % (l, m, n)], amp=amps['%d%d%d' % (l, m, n)], delta_t=delta_t, t_final=t_final) if taper is None: outplus.data += hplus.data outcross.data += hcross.data else: outplus = taper_shift(hplus, outplus) outcross = taper_shift(hcross, outcross) return outplus, outcross
def rescale_wave(self, M=None, inclination=None, phi=None, distance=None): """ Rescale modes and polarizations to given mass, angles, distance. Note that this function re-sets the stored values of binary parameters and so all future calculations will assume new values unless otherwise specified. """ #{{{ # # If MASS has changed, rescale modes # if self.totalmass != M and M is not None: # Rescale the time-axis for all modes self.rescaledmodes_real, self.rescaledmodes_imag = {}, {} for modeL in np.arange( 2, self.modeLmax+1 ): self.rescaledmodes_real[modeL], self.rescaledmodes_imag[modeL] = {}, {} for modeM in np.arange( -1*modeL, modeL+1 ): if self.skipM0 and modeM==0: continue self.rescaledmodes_real[modeL][modeM], \ self.rescaledmodes_imag[modeL][modeM] = \ self.rescale_mode(M, modeL=modeL, modeM=modeM) self.totalmass = M elif self.totalmass == None and M == None: raise IOError("Please provide a total-mass value to rescale") # # Now rescale with distance and ANGLES # if inclination is not None: self.inclination = inclination if phi is not None: self.phi = phi if distance is not None: self.distance = distance # Mass / distance scaling pre-factor scalefac = self.totalmass * lal.MRSUN_SI / self.distance / lal.PC_SI # Orbital phase at the time of merger (time of amplitude peak) amp22 = self.get_mode_amplitude(totalmass=self.totalmass, modeL=2, modeM=2, dimensionless=False) iPeak, aPeak = self.get_peak_amplitude(amp=amp22) #phase22 = self.get_mode_phase(totalmass=self.totalmass, dimensionless=False) #phiOrbMerger = phase22[iPeak] / 2. # Combine all modes hp, hc = np.zeros(self.n, dtype=float), np.zeros(self.n, dtype=float) for modeL in np.arange( 2, self.modeLmax+1 ): for modeM in np.arange( -1*modeL, modeL+1 ): if self.skipM0 and modeM==0: continue # h+ - \ii hx = \Sum Ylm * hlm ArcTan2Merger = np.arctan2(self.rescaledmodes_imag[modeL][modeM].data[iPeak],\ self.rescaledmodes_real[modeL][modeM].data[iPeak]) if self.verbose: print "arctan2 at merger after: ", ArcTan2Merger #curr_ylm = np.exp(1 * modeM * phiOrbMerger * 1j) curr_ylm = np.exp(-1 * ArcTan2Merger * 1j) if self.debug and curr_ylm: print curr_ylm / np.abs(curr_ylm) curr_ylm *= lal.SpinWeightedSphericalHarmonic(\ self.inclination, self.phi, -2, modeL, modeM) if self.debug and curr_ylm: print curr_ylm / np.abs(curr_ylm) # if self.debug: print ( np.arctan2(curr_ylm.imag, curr_ylm.real) + (modeM*phiOrbMerger) ) / np.pi,\ ( np.arctan2(curr_ylm.imag, curr_ylm.real) + (ArcTan2Merger) ) / np.pi print ( np.arctan2(curr_ylm.imag, curr_ylm.real) - (modeM*phiOrbMerger) ) / np.pi,\ ( np.arctan2(curr_ylm.imag, curr_ylm.real) - (ArcTan2Merger) ) / np.pi ## hp += self.rescaledmodes_real[modeL][modeM].data * curr_ylm.real - \ self.rescaledmodes_imag[modeL][modeM].data * curr_ylm.imag hc -= self.rescaledmodes_real[modeL][modeM].data * curr_ylm.imag + \ self.rescaledmodes_imag[modeL][modeM].data * curr_ylm.real if self.debug: print "END\n\n" # Scale amplitude by mass and distance factors self.rescaled_hp = TimeSeries(scalefac * hp, delta_t=self.dt, epoch=0) self.rescaled_hc = TimeSeries(scalefac * hc, delta_t=self.dt, epoch=0) return [self.rescaled_hp, self.rescaled_hc]
def apply(self, strain, detector_name, f_lower=None, distance_scale=1): """Add injections (as seen by a particular detector) to a time series. Parameters ---------- strain : TimeSeries Time series to inject signals into, of type float32 or float64. detector_name : string Name of the detector used for projecting injections. f_lower : {None, float}, optional Low-frequency cutoff for injected signals. If None, use value provided by each injection. distance_scale: {1, foat}, optional Factor to scale the distance of an injection with. The default is no scaling. Returns ------- None Raises ------ TypeError For invalid types of `strain`. """ if strain.dtype not in (float32, float64): raise TypeError("Strain dtype must be float32 or float64, not " \ + str(strain.dtype)) lalstrain = strain.lal() detector = Detector(detector_name) earth_travel_time = lal.REARTH_SI / lal.C_SI t0 = float(strain.start_time) - earth_travel_time t1 = float(strain.end_time) + earth_travel_time # pick lalsimulation injection function add_injection = injection_func_map[strain.dtype] for inj in self.table: # roughly estimate if the injection may overlap with the segment end_time = inj.get_time_geocent() #CHECK: This is a hack (10.0s); replace with an accurate estimate inj_length = 10.0 eccentricity = 0.0 polarization = 0.0 start_time = end_time - 2 * inj_length if end_time < t0 or start_time > t1: continue # compute the waveform time series hp, hc = sim.SimBurstSineGaussian(float(inj.q), float(inj.frequency), float(inj.hrss), float(eccentricity), float(polarization), float(strain.delta_t)) hp = TimeSeries(hp.data.data[:], delta_t=hp.deltaT, epoch=hp.epoch) hc = TimeSeries(hc.data.data[:], delta_t=hc.deltaT, epoch=hc.epoch) hp._epoch += float(end_time) hc._epoch += float(end_time) if float(hp.start_time) > t1: continue # compute the detector response, taper it if requested # and add it to the strain strain = wfutils.taper_timeseries(strain, inj.taper) signal_lal = hp.astype(strain.dtype).lal() add_injection(lalstrain, signal_lal, None) strain.data[:] = lalstrain.data.data[:]
def convert_lalREAL8TimeSeries_to_TimeSeries( h ): return TimeSeries(h.data.data, delta_t=h.deltaT, \ copy=True, epoch=h.epoch, dtype=h.data.data.dtype)
class DataBuffer(object): """A linear buffer that acts as a FILO for reading in frame data """ def __init__(self, frame_src, channel_name, start_time, max_buffer=2048, force_update_cache=True, increment_update_cache=None): """ Create a rolling buffer of frame data Parameters --------- frame_src: str of list of strings Strings that indicate where to read from files from. This can be a list of frame files, a glob, etc. channel_name: str Name of the channel to read from the frame files start_time: Time to start reading from. max_buffer: {int, 2048}, Optional Length of the buffer in seconds """ self.frame_src = frame_src self.channel_name = channel_name self.read_pos = start_time self.force_update_cache = force_update_cache self.increment_update_cache = increment_update_cache self.update_cache() self.channel_type, self.raw_sample_rate = self._retrieve_metadata(self.stream, self.channel_name) raw_size = self.raw_sample_rate * max_buffer self.raw_buffer = TimeSeries(zeros(raw_size, dtype=numpy.float64), copy=False, epoch=start_time - max_buffer, delta_t=1.0/self.raw_sample_rate) def update_cache(self): """Reset the lal cache. This can be used to update the cache if the result may change due to more files being added to the filesystem, for example. """ cache = locations_to_cache(self.frame_src) stream = lalframe.FrStreamCacheOpen(cache) self.stream = stream def _retrieve_metadata(self, stream, channel_name): """Retrieve basic metadata by reading the first file in the cache Parameters ---------- stream: lal stream object Stream containing a channel we want to learn about channel_name: str The name of the channel we want to know the dtype and sample rate of Returns ------- channel_type: lal type enum Enum value which indicates the dtype of the channel sample_rate: int The sample rate of the data within this channel """ data_length = lalframe.FrStreamGetVectorLength(channel_name, stream) channel_type = lalframe.FrStreamGetTimeSeriesType(channel_name, stream) create_series_func = _fr_type_map[channel_type][2] get_series_metadata_func = _fr_type_map[channel_type][3] series = create_series_func(channel_name, stream.epoch, 0, 0, lal.ADCCountUnit, 0) get_series_metadata_func(series, stream) return channel_type, int(1.0/series.deltaT) def _read_frame(self, blocksize): """Try to read the block of data blocksize seconds long Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data Raises ------ RuntimeError: If data cannot be read for any reason """ try: read_func = _fr_type_map[self.channel_type][0] dtype = _fr_type_map[self.channel_type][1] data = read_func(self.stream, self.channel_name, self.read_pos, int(blocksize), 0) return TimeSeries(data.data.data, delta_t=data.deltaT, epoch=self.read_pos, dtype=dtype) except Exception as e: raise RuntimeError('Cannot read requested frame data') def null_advance(self, blocksize): """Advance and insert zeros Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ self.raw_buffer.roll(-int(blocksize * self.raw_sample_rate)) self.read_pos += blocksize self.raw_buffer.start_time += blocksize def advance(self, blocksize): """Add blocksize seconds more to the buffer, push blocksize seconds from the beginning. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ ts = self._read_frame(blocksize) self.raw_buffer.roll(-len(ts)) self.raw_buffer[-len(ts):] = ts[:] self.read_pos += blocksize self.raw_buffer.start_time += blocksize return ts def update_cache_by_increment(self, blocksize): """Update the internal cache by starting from the first frame and incrementing. Guess the next frame file name by incrementing from the first found one. This allows a pattern to be used for the GPS folder of the file, which is indicated by `GPSX` where x is the number of digits to use. Parameters ---------- blocksize: int Number of seconds to increment the next frame file. """ start = float(self.raw_buffer.end_time) end = float(start + blocksize) if not hasattr(self, 'dur'): fname = glob.glob(self.frame_src[0])[0] fname = os.path.splitext(os.path.basename(fname))[0].split('-') self.beg = '-'.join([fname[0], fname[1]]) self.ref = int(fname[2]) self.dur = int(fname[3]) fstart = int(self.ref + numpy.floor((start - self.ref) / float(self.dur)) * self.dur) starts = numpy.arange(fstart, end, self.dur).astype(numpy.int) keys = [] for s in starts: pattern = self.increment_update_cache if 'GPS' in pattern: n = int(pattern[int(pattern.index('GPS') + 3)]) pattern = pattern.replace('GPS%s' % n, str(s)[0:n]) name = '%s/%s-%s-%s.gwf' % (pattern, self.beg, s, self.dur) # check that file actually exists, else abort now if not os.path.exists(name): logging.info("%s does not seem to exist yet" % name) raise RuntimeError keys.append(name) cache = locations_to_cache(keys) stream = lalframe.FrStreamCacheOpen(cache) self.stream = stream self.channel_type, self.raw_sample_rate = self._retrieve_metadata(self.stream, self.channel_name) def attempt_advance(self, blocksize, timeout=10): """ Attempt to advance the frame buffer. Retry upon failure, except if the frame file is beyond the timeout limit. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel timeout: {int, 10}, Optional Number of seconds before giving up on reading a frame Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data """ if self.force_update_cache: self.update_cache() try: if self.increment_update_cache: self.update_cache_by_increment(blocksize) return DataBuffer.advance(self, blocksize) except RuntimeError: if lal.GPSTimeNow() > timeout + self.raw_buffer.end_time: # The frame is not there and it should be by now, so we give up # and treat it as zeros logging.info('Frame missing, giving up...') DataBuffer.null_advance(self, blocksize) return None else: # I am too early to give up on this frame, so we should try again logging.info('Frame missing, waiting a bit more...') time.sleep(1) return self.attempt_advance(blocksize, timeout=timeout)
def taper_filter_waveform( self, hpsamp=None, hcsamp=None, tapermethod='planck', \ ttaper1=100, ttaper2=1000, ftaper3=0.1, ttaper4=100., \ npad=00, f_filter=-1., \ verbose=False): """Tapers using a Plank-taper window and high-pass filters. **IMPORTANT** The domain of the window is passed as ttaper{1,2,3,4} values. ttaper1 : time (in total-mass units) from the start where the window starts ttaper2 : width of start window ftaper3 : fraction by which amplitude should fall after its peak, marking where the rolldown window starts ttaper4 : width of the rolldown window Currently supported tapermethods: 'planck' [default], 'cosine' """ #{{{ if hpsamp and hcsamp: hp0, hc0 = [hpsamp, hcsamp] elif self.rescaled_hp is not None and self.rescaled_hc is not None: totalmass = self.totalmass hp0, hc0 = [self.rescaled_hp, self.rescaled_hc] else: raise IOError("Please provide either the total mass (and strain)") # Check windowing extents if ttaper1 > ttaper2 or \ (ttaper1+ttaper2+ttaper4) > (len(hp0)*hp0.delta_t/self.totalmass/lal.MTSUN_SI): raise IOError("Invalid window configuration with [%f,%f,%f,%f]" %\ (ttaper1, ttaper2, ftaper3, ttaper4)) # hp = TimeSeries( hp0.data, dtype=hp0.dtype, delta_t=hp0.delta_t, epoch=hp0._epoch ) hc = TimeSeries( hc0.data, dtype=hc0.dtype, delta_t=hc0.delta_t, epoch=hc0._epoch ) # Get actual waveform length for idx in np.arange( len(hp)-1, 0, -1 ): if hp[idx]==0 and hc[idx]==0: break N = idx #np.where( hp.data == 0 )[0][0] # Check npad if abs(len(hp) - N) < npad: print >>sys.stdout, "Ignoring npad..\n" npad = 0 else: # Prepend some zeros to the waveform (assuming there are ''npad'' zeros at the end) hp = zero_pad_beginning( hp, steps=npad ) hc = zero_pad_beginning( hc, steps=npad ) # # ########## Construct the taper window ############# # # Get the amplitude peak amp = amplitude_from_polarizations(hp, hc) max_a, nPeak = amp.abs_max_loc() # First get the starting-half indices ttapers = np.array([ttaper1, ttaper2], dtype=np.float128) ntapers = np.int64(\ np.round(ttapers * self.totalmass * lal.MTSUN_SI * self.sample_rate)) ntaper1, ntaper2 = ntapers ntaper2 += ntaper1 if ntaper2 < ntaper1: raise RuntimeError("Could not configure starting taper-window") # Next get the next-half indices amp_after_peak = amp[nPeak:] iA, vA = min(enumerate(amp_after_peak),key=lambda x:abs(x[1] - ftaper3*max_a)) ntaper3 = nPeak + iA #iB, vB = min(enumerate(amp_after_peak),key=lambda x:abs(x[1] - 0.01*max_a)) #iB = iA + ntaper4 ntaper4 = np.int64(\ np.round(ttaper4 * self.totalmass * lal.MTSUN_SI * self.sample_rate)) ntaper4 += ntaper3 if ntaper3 <= nPeak or ntaper4 < ntaper3: tmp_data = amp_after_peak.data for idx in range( len(amp_after_peak) ): if tmp_data[idx] < ftaper3*max_a: break ntaper3 = nPeak + idx for idx in range( len(amp_after_peak) ): if tmp_data[idx] < ftaper4*max_a: break ntaper4 = nPeak + idx if ntaper3 <= nPeak or ntaper4 < ntaper3: raise RuntimeError("Could not configure ringdown tapering window") ntapers = np.array([ntaper1, ntaper2, ntaper3, ntaper4], dtype=np.int64) ttapers = np.float128(ntapers) / self.sample_rate time_array = hp.sample_times.data - np.float(hp._epoch) # # Actual windowing function # region1 = np.zeros(ntaper1) region2 = np.zeros(ntaper2 - ntaper1) region3 = np.ones(ntaper3 - ntaper2) region4 = np.zeros(ntaper4 - ntaper3) region5 = np.zeros(len(hp) - ntaper4) # if 'planck' in tapermethod: np.seterr(divide='raise',over='raise',under='ignore',invalid='raise') t1, t2, t3, t4 = ttapers i1, i2, i3, i4 = ntapers if verbose: print "window times = ", t1, t2, t3, t4, " idxs = ", i1, i2, i3, i4 # for i in range(len(region2)): if i == 0: region2[i] = 0 continue try: region2[i] = 1./(np.exp( ((t2-t1)/(time_array[i+i1]-t1)) + \ ((t2-t1)/(time_array[i+i1]-t2)) ) + 1) except: if time_array[i+i1]>0.9*t1 and time_array[i+i1]<1.1*t1: region2[i] = 0 if time_array[i+i1]>0.9*t2 and time_array[i+i1]<1.1*t2: region2[i] = 1. # for i in range(len(region4)): if i == 0: region4[i] = 1. continue try: region4[i] = 1./(np.exp( ((t3-t4)/(time_array[i+i3]-t3)) + \ ((t3-t4)/(time_array[i+i3]-t4)) ) + 1) except: if time_array[i+i3]>0.9*t3 and time_array[i+i3]<1.1*t3: region4[i] = 1. if time_array[i+i3]>0.9*t4 and time_array[i+i3]<1.1*t4: region4[i] = 0 # if verbose and False: import matplotlib.pyplot as plt plt.plot(np.arange(len(region1))*hp.delta_t, region1) offset = len(region1) plt.plot((offset+np.arange(len(region2)))*hp.delta_t, region2) offset += len(region2) plt.plot((offset+np.arange(len(region3)))*hp.delta_t, region3) offset += len(region3) plt.plot((offset+np.arange(len(region4)))*hp.delta_t, region4) plt.grid() plt.show() win = np.concatenate((region1,region2,region3,region4,region5)) elif 'cos' in tapermethod: win = region1 win12 = 0.5 + 0.5*np.array([np.cos( np.pi*(float(j-ntaper1)/float(ntaper2-\ ntaper1) - 1)) for j in np.arange(ntaper1,ntaper2)]) win = np.append(win, win12) win = np.append(win, region3) win34 = 0.5 - 0.5*np.array([np.cos( np.pi*(float(j-ntaper3)/float(ntaper4-\ ntaper3) - 1)) for j in np.arange(ntaper3,ntaper4)]) win = np.append(win, win34) win = np.append(win, region5) else: raise IOError("Please specify valid taper-method") # # ########### Taper & Filter the waveform ############## # hp.data *= win hc.data *= win # # High pass filter the waveform if f_filter > 0: hplal = convert_TimeSeries_to_lalREAL8TimeSeries( hp ) hclal = convert_TimeSeries_to_lalREAL8TimeSeries( hc ) lal.HighPassREAL8TimeSeries( hplal, f_filter, 0.9, 8 ) lal.HighPassREAL8TimeSeries( hclal, f_filter, 0.9, 8 ) hp = convert_lalREAL8TimeSeries_to_TimeSeries( hplal ) hc = convert_lalREAL8TimeSeries_to_TimeSeries( hclal ) return hp, hc
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
class DataBuffer(object): """ A linear buffer that acts as a FILO for reading in frame data """ def __init__(self, frame_src, channel_name, start_time, max_buffer=2048): """ Create a rolling buffer of frame data Parameters --------- frame_src: str of list of strings Strings that indicate where to read from files from. This can be a list of frame files, a glob, etc. channel_name: str Name of the channel to read from the frame files start_time: Time to start reading from. max_buffer: {int, 2048}, Optional Length of the buffer in seconds """ self.frame_src = frame_src self.channel_name = channel_name self.read_pos = start_time self.update_cache() self.channel_type, self.sample_rate = self._retrieve_metadata(self.stream, self.channel_name) raw_size = self.sample_rate * max_buffer self.raw_buffer = TimeSeries(zeros(raw_size, dtype=numpy.float64), copy=False, epoch=start_time - max_buffer, delta_t=1.0/self.sample_rate) def update_cache(self): """ Reset the lal cache. This can be used to update the cache if the result may change due to more files being added to the filesystem, for example. """ cache = locations_to_cache(self.frame_src) stream = lalframe.FrStreamCacheOpen(cache) self.stream = stream def _retrieve_metadata(self, stream, channel_name): """ Retrieve basic metadata by reading the first file in the cache Parameters ---------- stream: lal stream object Stream containing a channel we want to learn about channel_name: str The name of the channel we want to know the dtype and sample rate of Returns ------- channel_type: lal type enum Enum value which indicates the dtype of the channel sample_rate: int The sample rate of the data within this channel """ data_length = lalframe.FrStreamGetVectorLength(channel_name, stream) channel_type = lalframe.FrStreamGetTimeSeriesType(channel_name, stream) create_series_func = _fr_type_map[channel_type][2] get_series_metadata_func = _fr_type_map[channel_type][3] series = create_series_func(channel_name, stream.epoch, 0, 0, lal.ADCCountUnit, 0) get_series_metadata_func(series, stream) return channel_type, int(1.0/series.deltaT) def _read_frame(self, blocksize): """ Try to read the block of data blocksize seconds long Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data Raises ------ RuntimeError: If data cannot be read for any reason """ try: read_func = _fr_type_map[self.channel_type][0] dtype = _fr_type_map[self.channel_type][1] data = read_func(self.stream, self.channel_name, self.read_pos, blocksize, 0) return TimeSeries(data.data.data, delta_t=data.deltaT, epoch=self.read_pos, dtype=dtype) except: raise RuntimeError('Cannot read requested frame data') def null_advance(self, blocksize): """ Advance and insert zeros Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ self.raw_buffer.roll(-int(blocksize * self.sample_rate)) self.raw_buffer.start_time += blocksize def advance(self, blocksize): """ Add blocksize seconds more to the buffer, push blocksize seconds from the beginning. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel """ ts = self._read_frame(blocksize) self.raw_buffer.roll(-len(ts)) self.raw_buffer[-len(ts):] = ts[:] self.read_pos += blocksize self.raw_buffer.start_time += blocksize return ts def attempt_advance(self, blocksize, timeout=10): """ Attempt to advance the frame buffer. Retry upon failure, except if the frame file is beyond the timeout limit. Parameters ---------- blocksize: int The number of seconds to attempt to read from the channel timeout: {int, 10}, Optional Number of seconds before giving up on reading a frame Returns ------- data: TimeSeries TimeSeries containg 'blocksize' seconds of frame data """ self.update_cache() try: return DataBuffer.advance(self, blocksize) except ValueError: if lal.GPSTimeNow() > timeout + self.raw_buffer.end_time: # The frame is not there and it should be by now, so we give up # and treat it as zeros self.null_advance(blocksize) return None else: # I am too early to give up on this frame, so we should try again return self.attempt_advance(self, blocksize, timeout=timeout)