33 from pyfftw.interfaces
import scipy_fftpack
as fftpack
35 from scipy
import fftpack
36 from scipy
import interpolate
43 gi.require_version(
'Gst',
'1.0')
44 from gi.repository
import GObject
45 from gi.repository
import Gst
46 GObject.threads_init()
50 from glue.ligolw
import utils
as ligolw_utils
53 from lal
import LIGOTimeGPS
57 from gstlal
import datasource
58 from gstlal
import pipeparts
59 from gstlal
import pipeio
60 from gstlal
import simplehandler
66 +-------------------------------------------------+------------------------------------------+------------+ 67 | Names | Hash | Date | 68 +=================================================+==========================================+============+ 69 | Florent, Sathya, Duncan Me., Jolien, Kipp, Chad | b3ef077fe87b597578000f140e4aa780f3a227aa | 2014-05-01 | 70 +-------------------------------------------------+------------------------------------------+------------+ 92 simplehandler.Handler.__init__(self, *args, **kwargs)
94 def do_on_message(self, bus, message):
95 if message.type == Gst.MessageType.ELEMENT
and message.get_structure().get_name() ==
"spectrum":
96 self.
psd = pipeio.parse_spectrum_message(message)
106 def measure_psd(gw_data_source_info, instrument, rate, psd_fft_length = 8, verbose = False):
117 node [shape=record fontsize=10 fontname="Verdana"]; 118 edge [fontsize=8 fontname="Verdana"]; 130 "mkbasicsrc()" -> capsfilter1 -> resample -> capsfilter2 -> queue -> whiten -> fakesink; 140 if gw_data_source_info.seg
is not None and float(abs(gw_data_source_info.seg)) < 8 * psd_fft_length:
141 raise ValueError(
"segment %s too short" % str(gw_data_source_info.seg))
148 print >>sys.stderr,
"measuring PSD in segment %s" % str(gw_data_source_info.seg)
149 print >>sys.stderr,
"building pipeline ..." 150 mainloop = GObject.MainLoop()
151 pipeline = Gst.Pipeline(name=
"psd")
154 head, _, _ = datasource.mkbasicsrc(pipeline, gw_data_source_info, instrument, verbose = verbose)
155 head = pipeparts.mkcapsfilter(pipeline, head,
"audio/x-raw, rate=[%d,MAX]" % rate)
156 head = pipeparts.mkresample(pipeline, head, quality = 9)
157 head = pipeparts.mkcapsfilter(pipeline, head,
"audio/x-raw, rate=%d" % rate)
158 head = pipeparts.mkqueue(pipeline, head, max_size_buffers = 8)
159 if gw_data_source_info.seg
is not None:
160 average_samples = int(round(float(abs(gw_data_source_info.seg)) / (psd_fft_length / 2.) - 1.))
164 head = pipeparts.mkwhiten(pipeline, head, psd_mode = 0, zero_pad = 0, fft_length = psd_fft_length, average_samples = average_samples, median_samples = 7)
165 pipeparts.mkfakesink(pipeline, head)
171 if gw_data_source_info.data_source
in (
"lvshm",
"framexmit"):
179 print >>sys.stderr,
"putting pipeline into READY state ..." 180 if pipeline.set_state(Gst.State.READY) == Gst.StateChangeReturn.FAILURE:
181 raise RuntimeError(
"pipeline failed to enter READY state")
182 if gw_data_source_info.data_source
not in (
"lvshm",
"framexmit"):
183 datasource.pipeline_seek_for_gps(pipeline, *gw_data_source_info.seg)
185 print >>sys.stderr,
"putting pipeline into PLAYING state ..." 186 if pipeline.set_state(Gst.State.PLAYING) == Gst.StateChangeReturn.FAILURE:
187 raise RuntimeError(
"pipeline failed to enter PLAYING state")
189 print >>sys.stderr,
"running pipeline ..." 197 print >>sys.stderr,
"PSD measurement complete" 201 def write_psd(filename, psddict, verbose = False, trap_signals = None):
203 Wrapper around make_psd_xmldoc() to write the XML document directly 206 ligolw_utils.write_filename(lal.series.make_psd_xmldoc(psddict), filename, gz = (filename
or "stdout").endswith(
".gz"), verbose = verbose, trap_signals = trap_signals)
219 def __init__(self, f_min, f_max, delta_f, m1, m2, spin1 = (0., 0., 0.), spin2 = (0., 0., 0.), eccentricity = 0., inclination = 0.):
221 Configures the horizon distance calculation for a specific 222 waveform model. The waveform is pre-computed and stored, 223 so this initialization step can be time-consuming but 224 computing horizon distances from measured PSDs will be 227 The waveform model's spectrum parameters, for example its 228 Nyquist and frequency resolution, need not match the 229 parameters for the PSDs that will ultimately be supplied 230 but there are some advantages to be had in getting them to 231 match. For example, computing the waveform with a smaller 232 delta_f than will be needed will require additional storage 233 and consume additional CPU time for the initialization, 234 while computing it with too low an f_max or too large a 235 delta_f might lead to inaccurate horizon distance 238 f_min (Hertz) sets the frequency at which the waveform 241 f_max (Hertz) sets the frequency upto which the waveform's 244 delta_f (Hertz) sets the frequency resolution of the 245 desired waveform model. 247 m1, m2 (solar masses) set the component masses of the 250 spin1, spin2 (3-component vectors, geometric units) set the 251 spins of the component masses. 253 eccentricity [0, 1) sets the eccentricity of the system. 255 inclination (radians) sets the orbital inclination of the 260 >>> # configure for non-spinning, circular, 1.4+1.4 BNS 261 >>> horizon_distance = HorizonDistance(10., 1024., 1./32., 1.4, 1.4) 262 >>> # populate a PSD for testing 263 >>> import lal, lalsimulation 264 >>> psd = lal.CreateREAL8FrequencySeries("psd", lal.LIGOTimeGPS(0), 0., 1./32., lal.Unit("strain^2 s"), horizon_distance.model.data.length) 265 >>> lalsimulation.SimNoisePSDaLIGODesignSensitivityP1200087(psd, 0.) 267 >>> # compute horizon distance 268 >>> D, (f, model) = horizon_distance(psd) 269 >>> print "%.4g Mpc" % D 271 >>> # compute distance and spectrum for SNR = 25 272 >>> D, (f, model) = horizon_distance(psd, 25.) 276 array([ 10. , 10.03125, 10.0625 , ..., 1023.9375 , 279 array([ 8.00942750e-45, 7.95221458e-45, 7.89520620e-45, ..., 280 1.11473675e-49, 1.11465176e-49, 1.11456678e-49]) 284 - Currently the SEOBNRv4_ROM waveform model is used, so its 285 limitations with respect to masses, spins, etc., apply. 286 The choice of waveform model is subject to change. 292 self.
spin1 = numpy.array(spin1)
293 self.
spin2 = numpy.array(spin2)
300 hp, hc = lalsimulation.SimInspiralFD(
301 m1 * lal.MSUN_SI, m2 * lal.MSUN_SI,
302 spin1[0], spin1[1], spin1[2],
303 spin2[0], spin2[1], spin2[2],
315 lalsimulation.GetApproximantFromString(
"SEOBNRv4_ROM")
317 assert hp.data.length > 0,
"huh!? h+ has zero length!" 324 self.
model = lal.CreateREAL8FrequencySeries(
325 name =
"signal spectrum",
326 epoch = LIGOTimeGPS(0),
329 sampleUnits = hp.sampleUnits * hp.sampleUnits,
330 length = hp.data.length
332 self.
model.data.data[:] = numpy.abs(hp.data.data)**2.
337 Compute the horizon distance for the configured waveform 338 model given the PSD and the SNR at which the horizon is 339 defined (default = 8). Equivalently, from a PSD and an 340 observed SNR compute and return the amplitude of the 341 configured waveform's spectrum required to achieve that 344 The return value is a two-element tuple. The first element 345 is the horizon distance in Mpc. The second element is, 346 itself, a two-element tuple containing two vectors giving 347 the frequencies and amplitudes of the waveform model's 348 spectrum scaled so as to have the given SNR. The vectors 349 are clipped to the range of frequencies that were used for 352 The parameters of the PSD, for example its Nyquist and 353 frequency resolution, need not match the parameters of the 354 configured waveform model. In the event of a mismatch, the 355 waveform model is resampled to the frequencies at which the 356 PSD has been measured. 358 The inspiral spectrum returned has the same units as the 359 PSD and is normalized so that the SNR is 361 SNR^2 = \int (inspiral_spectrum / psd) df 363 That is, the ratio of the inspiral spectrum to the PSD 364 gives the spectral density of SNR^2. 370 f = psd.f0 + numpy.arange(psd.data.length) * psd.deltaF
377 indexes = ((f - self.
model.f0) / self.
model.deltaF).round().astype(
"int").clip(0, self.
model.data.length - 1)
378 model = self.
model.data.data[indexes]
384 kmin = (max(psd.f0, self.
model.f0, self.
f_min) - psd.f0) / psd.deltaF
385 kmin = int(round(kmin))
386 kmax = (min(psd.f0 + psd.data.length * psd.deltaF, self.
model.f0 + self.
model.data.length * self.
model.deltaF, self.
f_max) - psd.f0) / psd.deltaF
387 kmax = int(round(kmax)) + 1
388 assert kmin < kmax,
"PSD and waveform model do not intersect" 396 model = model[kmin:kmax]
397 D = math.sqrt(4. * (model / psd.data.data[kmin:kmax]).sum() * psd.deltaF)
416 return D / (1e6 * lal.PC_SI), (f, model)
419 def effective_distance_factor(inclination, fp, fc):
421 Returns the ratio of effective distance to physical distance for 422 compact binary mergers. Inclination is the orbital inclination of 423 the system in radians, fp and fc are the F+ and Fx antenna factors. 424 See lal.ComputeDetAMResponse() for a function to compute antenna 425 factors. The effective distance is given by 427 Deff = effective_distance_factor * D 429 See Equation (4.3) of arXiv:0705.1514. 431 cos2i = math.cos(inclination)**2
432 return 1.0 / math.sqrt(fp**2 * (1+cos2i)**2 / 4 + fc**2 * cos2i)
442 def set_phase(self, psd, f_low = 10.0, m1 = 1.4, m2 = 1.4):
449 f_psd = psd.f0 + numpy.arange(len(psd.data.data)) * psd.deltaF
450 horizon_distance =
HorizonDistance(f_low, f_psd[-1], psd.deltaF, m1, m2)
451 f_model, model= horizon_distance(psd, 1.)[1]
455 kmin, kmax = f_psd.searchsorted(f_model[0]), f_psd.searchsorted(f_model[-1]) + 1
458 unit_snr2_density = numpy.zeros_like(phase)
459 unit_snr2_density[kmin:kmax] = model / psd.data.data[kmin:kmax]
469 unit_snr2_density = unit_snr2_density**(1./16)
470 unit_snr2_density /= unit_snr2_density.max()
478 Compute an acausal finite impulse-response filter kernel 479 from a power spectral density conforming to the LAL 480 normalization convention, such that if colored Gaussian 481 random noise with the given PSD is fed into an FIR filter 482 using the kernel the filter's output will be zero-mean 483 unit-variance Gaussian random noise. The PSD must be 484 provided as a lal.REAL8FrequencySeries object. 486 The phase response of this filter is 0, just like whitening 487 done in the frequency domain. 489 The return value is the tuple (kernel, latency, sample 490 rate). The kernel is a numpy array containing the filter 491 kernel, the latency is the filter latency in samples and 492 the sample rate is in Hz. The kernel and latency can be 493 used, for example, with gstreamer's stock audiofirfilter 506 data = psd.data.data / 2
507 sample_rate = 2 * int(round(psd.f0 + (len(data) - 1) * psd.deltaF))
520 if nyquist
is not None:
521 i = int(round((nyquist * 2. - psd.f0) / psd.deltaF))
524 sample_rate = 2 * int(round(psd.f0 + (len(data) - 1) * psd.deltaF))
531 data[0] = data[-1] = 0.0
533 data_nonzeros = (data != 0.)
534 data[data_nonzeros] = 1./data[data_nonzeros]
536 tmp = numpy.zeros((2 * len(data) - 1,), dtype = data.dtype)
537 tmp[len(data)-1:] = data
541 kernel_fseries = lal.CreateCOMPLEX16FrequencySeries(
542 name =
"double sided psd",
543 epoch = LIGOTimeGPS(0),
547 sampleUnits = lal.Unit(
"strain s")
550 kernel_tseries = lal.CreateCOMPLEX16TimeSeries(
551 name =
"timeseries of whitening kernel",
552 epoch = LIGOTimeGPS(0.),
554 deltaT = 1.0 / sample_rate,
556 sampleUnits = lal.Unit(
"strain")
561 self.
revplan = lal.CreateReverseCOMPLEX16FFTPlan(len(data), 1)
563 kernel_fseries.data.data = numpy.sqrt(data) + 0.j
564 lal.COMPLEX16FreqTimeFFT(kernel_tseries, kernel_fseries, self.
revplan)
565 kernel = kernel_tseries.data.data.real
566 kernel = numpy.roll(kernel, (len(data) - 1) / 2) / sample_rate * 2
573 norm_before = numpy.dot(kernel, kernel)
574 kernel *= lal.CreateTukeyREAL8Window(len(data), .5).data.data
575 kernel *= math.sqrt(norm_before / numpy.dot(kernel, kernel))
581 latency = (len(data) - 1) / 2
587 return kernel, latency, sample_rate
592 Compute the minimum-phase response filter (zero latency) 593 associated with a linear-phase response filter (latency 594 equal to half the filter length). 596 From "Design of Optimal Minimum-Phase Digital FIR Filters 597 Using Discrete Hilbert Transforms", IEEE Trans. Signal 598 Processing, vol. 48, pp. 1491-1495, May 2000. 600 The return value is the tuple (kernel, phase response). 601 The kernel is a numpy array containing the filter kernel. 602 The kernel can be used, for example, with gstreamer's stock 603 audiofirfilter element. 611 self.
fwdplan = lal.CreateForwardCOMPLEX16FFTPlan(len(linear_phase_kernel), 1)
613 self.
revplan = lal.CreateReverseCOMPLEX16FFTPlan(len(linear_phase_kernel), 1)
615 deltaT = 1. / sample_rate
616 deltaF = 1. / (len(linear_phase_kernel) * deltaT)
617 working_length = len(linear_phase_kernel)
619 kernel_tseries = lal.CreateCOMPLEX16TimeSeries(
620 name =
"timeseries of whitening kernel",
621 epoch = LIGOTimeGPS(0.),
624 length = working_length,
625 sampleUnits = lal.Unit(
"strain")
627 kernel_tseries.data.data = linear_phase_kernel
629 absX = lal.CreateCOMPLEX16FrequencySeries(
631 epoch = LIGOTimeGPS(0),
634 length = working_length,
635 sampleUnits = lal.Unit(
"strain s")
638 logabsX = lal.CreateCOMPLEX16FrequencySeries(
640 epoch = LIGOTimeGPS(0),
643 length = working_length,
644 sampleUnits = lal.Unit(
"strain s")
647 cepstrum = lal.CreateCOMPLEX16TimeSeries(
649 epoch = LIGOTimeGPS(0.),
652 length = working_length,
653 sampleUnits = lal.Unit(
"strain")
656 theta = lal.CreateCOMPLEX16FrequencySeries(
658 epoch = LIGOTimeGPS(0),
661 length = working_length,
662 sampleUnits = lal.Unit(
"strain s")
665 min_phase_kernel = lal.CreateCOMPLEX16TimeSeries(
666 name =
"min phase kernel",
667 epoch = LIGOTimeGPS(0.),
670 length = working_length,
671 sampleUnits = lal.Unit(
"strain")
674 lal.COMPLEX16TimeFreqFFT(absX, kernel_tseries, self.
fwdplan)
675 absX.data.data[:] = abs(absX.data.data)
682 logabsX.data.data[:] = numpy.log(absX.data.data)
683 lal.COMPLEX16FreqTimeFFT(cepstrum, logabsX, self.
revplan)
689 cepstrum.data.data[0] = 0.
690 cepstrum.data.data[working_length // 2] = 0.
691 cepstrum.data.data[working_length // 2 + 1:] = -cepstrum.data.data[working_length // 2 + 1:]
697 lal.COMPLEX16TimeFreqFFT(theta, cepstrum, self.
fwdplan)
705 theta_data = theta.data.data[working_length // 2:]
707 phase = -theta_data.imag
718 phase_adjustment = numpy.concatenate((phase_adjustment[1:][-1::-1].conj(), phase_adjustment))
725 theta.data.data += -1.j * phase_adjustment
734 absX.data.data *= numpy.exp(theta.data.data)
735 lal.COMPLEX16FreqTimeFFT(min_phase_kernel, absX, self.
revplan)
737 kernel = min_phase_kernel.data.data.real
744 kernel = kernel[-1::-1]
753 def interpolate_psd(psd, deltaF):
758 if deltaF == psd.deltaF:
788 psd_data = psd.data.data
789 psd_data = numpy.where(psd_data, psd_data, 1e-300)
790 f = psd.f0 + numpy.arange(len(psd_data)) * psd.deltaF
791 interp = interpolate.splrep(f, numpy.log(psd_data), s = 0)
792 f = psd.f0 + numpy.arange(round((len(psd_data) - 1) * psd.deltaF / deltaF) + 1) * deltaF
793 psd_data = numpy.exp(interpolate.splev(f, interp, der = 0))
799 psd = lal.CreateREAL8FrequencySeries(
804 sampleUnits = psd.sampleUnits,
805 length = len(psd_data)
807 psd.data.data = psd_data
812 def movingmedian(psd, window_size):
814 Assumes that the underlying PSD doesn't have variance, i.e., that there 815 is no median / mean correction factor required 818 datacopy = numpy.copy(data)
819 for i
in range(window_size, len(data)-window_size):
820 datacopy[i] = numpy.median(data[i-window_size:i+window_size])
821 psd = lal.CreateREAL8FrequencySeries(
826 sampleUnits = psd.sampleUnits,
827 length = len(datacopy)
829 psd.data.data = datacopy
833 def polyfit(psd, minsample, maxsample, order, verbose = False):
835 f = numpy.arange(maxsample - minsample) * psd.deltaF + 1
836 data = psd.data.data[minsample:maxsample]
838 logf = numpy.linspace(numpy.log(f[0]), numpy.log(f[-1]), 100000)
839 interp = interpolate.interp1d(numpy.log(f), numpy.log(data))
841 p = numpy.poly1d(numpy.polyfit(logf, data, order))
843 print >> sys.stderr,
"\nFit polynomial is: \n\nlog(PSD) = \n", p,
"\n\nwhere x = f / f_min\n" 844 data = numpy.exp(p(numpy.log(f)))
845 olddata = psd.data.data
846 olddata[minsample:maxsample] = data
847 psd = lal.CreateREAL8FrequencySeries(
852 sampleUnits = psd.sampleUnits,
853 length = len(olddata)
855 psd.data.data = olddata
858 def one_second_highpass_kernel(rate, cutoff = 12):
859 highpass_filter_fd = numpy.ones(rate, dtype=complex)
860 highpass_filter_fd[:int(cutoff)] = 0.
861 highpass_filter_fd[-int(cutoff):] = 0.
862 highpass_filter_fd[rate/2-1:rate/2+1] = 0.
863 highpass_filter_td = fftpack.ifft(highpass_filter_fd)
864 highpass_filter_td = numpy.roll(highpass_filter_td.real, rate/2)
865 highpass_filter_kernel = numpy.zeros(len(highpass_filter_td)+1)
866 highpass_filter_kernel[:-1] = highpass_filter_td[:]
867 x = numpy.arange(len(highpass_filter_kernel))
869 highpass_filter_kernel *= 1. - (x-mid)**2 / mid**2
870 return highpass_filter_kernel
872 def fixed_duration_bandpass_kernel(rate, flow = 0, fhigh = float(
"inf"), duration = 1.0):
873 deltaF = 1. / duration
874 nsamps = int(rate * duration) + 1
875 f = numpy.arange(nsamps) * deltaF - rate / 2.
876 filt = numpy.ones(len(f))
877 ix1 = numpy.logical_and(f <= -flow, f >= -fhigh)
878 ix2 = numpy.logical_and(f >= flow, f <= fhigh)
879 filt[numpy.logical_not(numpy.logical_or(ix1, ix2))] = 0.
880 filt = numpy.real(scipy.ifft(scipy.fftpack.ifftshift(filt))) / nsamps
881 window = numpy.sinc(2 * f / rate)
882 out = numpy.roll(filt, nsamps / 2) * window
883 out /= (out**2).sum()**.5
def __init__(self, f_min, f_max, delta_f, m1, m2, spin1=(0., 0., 0.), spin2=(0., 0., 0.), eccentricity=0., inclination=0.)
def linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(self, linear_phase_kernel, sample_rate)
def psd_to_linear_phase_whitening_fir_kernel(self, psd, invert=True, nyquist=None)
def __call__(self, psd, snr=8.)