34 from scipy
import interpolate
43 gobject.threads_init()
49 from glue.ligolw
import utils
51 from pylal
import datatypes
as laltypes
52 from pylal
import series
as lalseries
55 from gstlal
import datasource
56 from gstlal
import pipeparts
57 from gstlal
import pipeio
58 from gstlal
import simplehandler
98 def __init__(self, *args, **kwargs):
100 simplehandler.Handler.__init__(self, *args, **kwargs)
103 if message.type == gst.MESSAGE_ELEMENT
and message.structure.get_name() ==
"spectrum":
104 self.
psd = pipeio.parse_spectrum_message(message)
137 def measure_psd(gw_data_source_info, instrument, rate, psd_fft_length = 8, verbose = False):
144 if gw_data_source_info.seg
is not None and float(abs(gw_data_source_info.seg)) < 8 * psd_fft_length:
145 raise ValueError(
"segment %s too short" % str(gw_data_source_info.seg))
152 print >>sys.stderr,
"measuring PSD in segment %s" % str(gw_data_source_info.seg)
153 print >>sys.stderr,
"building pipeline ..."
154 mainloop = gobject.MainLoop()
155 pipeline = gst.Pipeline(
"psd")
158 head = datasource.mkbasicsrc(pipeline, gw_data_source_info, instrument, verbose = verbose)
159 head = pipeparts.mkcapsfilter(pipeline, head,
"audio/x-raw-float, rate=[%d,MAX]" % rate)
160 head = pipeparts.mkresample(pipeline, head, quality = 9)
161 head = pipeparts.mkcapsfilter(pipeline, head,
"audio/x-raw-float, rate=%d" % rate)
162 head = pipeparts.mkqueue(pipeline, head, max_size_buffers = 8)
163 if gw_data_source_info.seg
is not None:
164 average_samples = int(round(float(abs(gw_data_source_info.seg)) / (psd_fft_length / 2.) - 1.))
168 head = pipeparts.mkwhiten(pipeline, head, psd_mode = 0, zero_pad = 0, fft_length = psd_fft_length, average_samples = average_samples, median_samples = 7)
169 pipeparts.mkfakesink(pipeline, head)
175 if gw_data_source_info.data_source
in (
"lvshm",
"framexmit"):
183 print >>sys.stderr,
"putting pipeline into playing state ..."
184 if pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE:
185 raise RuntimeError(
"pipeline failed to enter PLAYING state")
187 print >>sys.stderr,
"running pipeline ..."
195 print >>sys.stderr,
"PSD measurement complete"
199 def read_psd_xmldoc(xmldoc):
201 warnings.warn(
"gstlal.reference_psd.read_psd_xmldoc() is deprecated, use pylal.series.read_psd_xmldoc() instead.", DeprecationWarning)
202 return lalseries.read_psd_xmldoc(xmldoc)
205 def make_psd_xmldoc(psddict, xmldoc = None):
207 warnings.warn(
"gstlal.reference_psd.make_psd_xmldoc() is deprecated, use pylal.series.make_psd_xmldoc() instead.", DeprecationWarning)
208 return lalseries.make_psd_xmldoc(psddict, xmldoc = xmldoc)
213 Wrapper around make_psd_xmldoc() to write the XML document directly
214 to a Python file object.
216 utils.write_fileobj(lalseries.make_psd_xmldoc(psddict), fileobj, gz = gz, trap_signals = trap_signals)
219 def write_psd(filename, psddict, verbose = False, trap_signals = None):
221 Wrapper around make_psd_xmldoc() to write the XML document directly
224 utils.write_filename(lalseries.make_psd_xmldoc(psddict), filename, gz = (filename
or "stdout").endswith(
".gz"), verbose = verbose, trap_signals = trap_signals)
238 Compute horizon distance, the distance at which an optimally
239 oriented inspiral would be seen to have the given SNR. m1 and m2
240 are in solar mass units. f_min and f_max are in Hz. psd is a
241 REAL8FrequencySeries object containing the strain spectral density
242 function in the LAL normalization convention. The return value is
245 The horizon distance is determined using an integral whose upper
246 bound is the smaller of f_max (if supplied), the highest frequency
247 in the PSD, or the ISCO frequency.
249 If inspiral_spectrum is not None, it should be a two-element list.
250 The first element will be replaced with an array of frequency
251 values, and the second element will be replaced with an array of
252 spectrum values giving the amplitude of an inspiral spectrum with
253 the given SNR. The spectrum is normalized so that the SNR is
255 SNR^2 = \int (inspiral_spectrum / psd) df
257 That is, the ratio of the inspiral spectrum to the PSD gives the
268 f_max = psd.f0 + (len(Sn) - 1) * psd.deltaF
269 elif f_max > psd.f0 + (len(Sn) - 1) * psd.deltaF:
270 warnings.warn(
"f_max clipped to Nyquist frequency", UserWarning)
271 f_max = psd.f0 + (len(Sn) - 1) * psd.deltaF
277 f_isco = lal.C_SI**3 / (6**(3. / 2.) * math.pi * lal.G_SI * (m1 + m2) * lal.MSUN_SI)
278 f_max = min(f_max, f_isco)
279 assert psd.f0 <= f_isco
280 assert psd.f0 <= f_min <= f_isco
281 assert f_min <= f_max
288 k_min = int(round((f_min - psd.f0) / psd.deltaF))
289 k_max = int(round((f_max - psd.f0) / psd.deltaF))
291 f = psd.f0 + numpy.arange(k_min, k_max + 1) * psd.deltaF
292 Sn = Sn[k_min : k_max + 1]
298 mu = (m1 * m2) / (m1 + m2)
299 mchirp = mu**(3. / 5.) * (m1 + m2)**(2. / 5.)
301 inspiral = (5 * math.pi / (24 * lal.C_SI**3)) * (lal.G_SI * mchirp * lal.MSUN_SI)**(5. / 3.) * (math.pi * f)**(-7. / 3.)
308 D_at_snr_1 = math.sqrt(4 * (inspiral / Sn).sum() * psd.deltaF)
314 if inspiral_spectrum
is not None:
315 inspiral_spectrum[0] = f
316 inspiral_spectrum[1] = 4 * inspiral / (D_at_snr_1 / snr)**2
322 return D_at_snr_1 / snr / (1e6 * lal.PC_SI)
327 Returns the ratio of effective distance to physical distance for
328 compact binary mergers. Inclination is the orbital inclination of
329 the system in radians, fp and fc are the F+ and Fx antenna factors.
330 See lal.ComputeDetAMResponse() for a function to compute antenna
331 factors. The effective distance is given by
333 Deff = effective_distance_factor * D
335 See Equation (4.3) of arXiv:0705.1514.
337 cos2i = math.cos(inclination)**2
338 return 1.0 / math.sqrt(fp**2 * (1+cos2i)**2 / 4 + fc**2 * cos2i)
343 Compute an acausal finite impulse-response filter kernel from a power
344 spectral density conforming to the LAL normalization convention,
345 such that if zero-mean unit-variance Gaussian random noise is fed
346 into an FIR filter using the kernel the filter's output will
347 possess the given PSD. The PSD must be provided as a
348 REAL8FrequencySeries object (see
349 pylal.xlal.datatypes.real8frequencyseries).
351 The return value is the tuple (kernel, latency, sample rate). The
352 kernel is a numpy array containing the filter kernel, the latency
353 is the filter latency in samples and the sample rate is in Hz. The
354 kernel and latency can be used, for example, with gstreamer's stock
355 audiofirfilter element.
368 sample_rate = 2 * int(round(psd.f0 + len(data) * psd.deltaF))
381 data[0] = data[-1] = 0.0
383 kernel = scipy.fftpack.idct(numpy.sqrt(data), type = 1) / (2 * len(data) - 1)
384 kernel = numpy.hstack((kernel[::-1], kernel[1:]))
385 except AttributeError:
388 tmp = numpy.zeros((2 * len(data) - 1,), dtype = data.dtype)
393 kernel = scipy.fftpack.irfft(numpy.sqrt(data))
394 kernel = numpy.roll(kernel, (len(kernel) - 1) / 2)
401 norm_before = numpy.dot(kernel, kernel)
402 kernel *= lal.CreateTukeyREAL8Window(len(kernel), .5).data.data
403 kernel *= math.sqrt(norm_before / numpy.dot(kernel, kernel))
409 latency = (len(kernel) - 1) / 2
415 return kernel, latency, sample_rate
420 Compute an acausal finite impulse-response filter kernel from a power
421 spectral density conforming to the LAL normalization convention,
422 such that if colored Gaussian random noise with the given PSD is fed
423 into an FIR filter using the kernel the filter's output will
424 be zero-mean unit-variance Gaussian random noise. The PSD must be
425 provided as a REAL8FrequencySeries object (see
426 pylal.xlal.datatypes.real8frequencyseries).
428 The phase response of this filter is 0, just like whitening done in
429 the frequency domain.
431 The return value is the tuple (kernel, latency, sample rate). The
432 kernel is a numpy array containing the filter kernel, the latency
433 is the filter latency in samples and the sample rate is in Hz. The
434 kernel and latency can be used, for example, with gstreamer's stock
435 audiofirfilter element.
448 sample_rate = 2 * int(round(psd.f0 + len(data) * psd.deltaF))
461 data[0] = data[-1] = 0.0
462 data_nonzeros = (data != 0.)
463 data[data_nonzeros] = 1./data[data_nonzeros]
465 kernel = scipy.fftpack.idct(numpy.sqrt(data), type = 1) / (2 * len(data) - 1)
466 kernel = numpy.hstack((kernel[::-1], kernel[1:]))
467 except AttributeError:
470 tmp = numpy.zeros((2 * len(data) - 1,), dtype = data.dtype)
475 kernel = scipy.fftpack.irfft(numpy.sqrt(data))
476 kernel = numpy.roll(kernel, (len(kernel) - 1) / 2)
483 norm_before = numpy.dot(kernel, kernel)
484 kernel *= lal.CreateTukeyREAL8Window(len(kernel), .5).data.data
485 kernel *= math.sqrt(norm_before / numpy.dot(kernel, kernel))
491 latency = (len(kernel) - 1) / 2
497 return kernel, latency, sample_rate
502 Compute the minimum-phase response filter (zero latency) associated with a
503 linear-phase response filter (latency equal to half the filter length).
505 From "Design of Optimal Minimum-Phase Digital FIR Filters Using
506 Discrete Hilbert Transforms", IEEE Trans. Signal Processing, vol. 48,
507 pp. 1491-1495, May 2000.
509 The return value is the tuple (kernel, phase response). The kernel is
510 a numpy array containing the filter kernel. The kernel can be used,
511 for example, with gstreamer's stock audiofirfilter element.
517 absX = abs(scipy.fftpack.fft(linear_phase_kernel))
524 cepstrum = scipy.fftpack.ifft(scipy.log(absX))
530 sgn = scipy.ones(len(linear_phase_kernel))
532 sgn[(len(sgn)+1)/2] = 0.
533 sgn[(len(sgn)+1)/2:] *= -1.
539 theta = -1.j * scipy.fftpack.fft(sgn * cepstrum)
545 minimum_phase_kernel = scipy.real(scipy.fftpack.ifft(absX * scipy.exp(1.j * theta)))
552 minimum_phase_kernel = minimum_phase_kernel[-1::-1]
558 return minimum_phase_kernel, -theta
561 def interpolate_psd(psd, deltaF):
566 if deltaF == psd.deltaF:
568 assert deltaF < psd.deltaF
608 psd_data = numpy.where(psd_data, psd_data, 1e-300)
609 f = psd.f0 + numpy.arange(len(psd_data)) * psd.deltaF
610 interp = interpolate.splrep(f, numpy.log(psd_data), s = 0)
611 f = psd.f0 + numpy.arange(round((len(psd_data) - 1) * psd.deltaF / deltaF) + 1) * deltaF
612 psd_data = numpy.exp(interpolate.splev(f, interp, der = 0))
618 return laltypes.REAL8FrequencySeries(
623 sampleUnits = psd.sampleUnits,
630 Assumes that the underlying PSD doesn't have variance, i.e., that there
631 is no median / mean correction factor required
634 datacopy = numpy.copy(psd.data)
635 for i
in range(window_size, len(data)-window_size):
636 datacopy[i] = numpy.median(data[i-window_size:i+window_size])
637 return laltypes.REAL8FrequencySeries(
642 sampleUnits = psd.sampleUnits,
647 def polyfit(psd, minsample, maxsample, order, verbose = False):
649 f = numpy.arange(maxsample - minsample) * psd.deltaF + 1
650 data = psd.data[minsample:maxsample]
652 logf = numpy.linspace(numpy.log(f[0]), numpy.log(f[-1]), 100000)
653 interp = interpolate.interp1d(numpy.log(f), numpy.log(data))
655 p = numpy.poly1d(numpy.polyfit(logf, data, order))
657 print >> sys.stderr,
"\nFit polynomial is: \n\nlog(PSD) = \n", p,
"\n\nwhere x = f / f_min\n"
658 data = numpy.exp(p(numpy.log(f)))
660 olddata[minsample:maxsample] = data
661 return laltypes.REAL8FrequencySeries(
666 sampleUnits = psd.sampleUnits,