gstlal-inspiral  0.4.2
 All Classes Namespaces Files Functions Variables Pages
templates.py
Go to the documentation of this file.
1 # Copyright (C) 2009--2013 LIGO Scientific Collaboration
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation; either version 2 of the License, or (at your
6 # option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 # Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 
17 ## @file
18 # A module to implement template slicing and other operations as part of the LLOID aglorithm
19 #
20 # ### Review Status
21 #
22 # | Names | Hash | Date |
23 # | ------------------------------------------- | ------------------------------------------- | ---------- |
24 # | Florent, Sathya, Duncan Me, Jolien, Kipp, Chad | 7536db9d496be9a014559f4e273e1e856047bf71 | 2014-04-29 |
25 #
26 # #### Actions
27 #
28 # - Performance of time slices could be improved by e.g., not using powers of 2 for time slice sample size. This might also simplify the code
29 #
30 
31 ## @package templates
32 
33 #
34 # =============================================================================
35 #
36 # Preamble
37 #
38 # =============================================================================
39 #
40 
41 
42 import numpy
43 import math
44 
45 import sys
46 from pylal import datatypes as laltypes
47 from pylal import lalfft
48 from pylal import spawaveform
49 import lalsimulation as lalsim
50 from gstlal import chirptime
51 from lal import MSUN_SI
52 
53 
54 __author__ = "Kipp Cannon <kipp.cannon@ligo.org>, Chad Hanna <chad.hanna@ligo.org>, Drew Keppel <drew.keppel@ligo.org>"
55 __version__ = "FIXME"
56 __date__ = "FIXME"
57 
58 
59 #
60 # =============================================================================
61 #
62 # Stuff
63 #
64 # =============================================================================
65 #
66 
67 gstlal_FD_approximants = set(('IMRPhenomC', 'SEOBNRv2_ROM_DoubleSpin', 'TaylorF2', 'TaylorF2RedSpin', 'TaylorF2RedSpinTidal'))
68 gstlal_TD_approximants = set(('TaylorT1', 'TaylorT2', 'TaylorT3', 'TaylorT4', 'EOBNRv2'))
69 gstlal_approximants = gstlal_FD_approximants | gstlal_TD_approximants
70 gstlal_IMR_approximants = set(('IMRPhenomC', 'SEOBNRv2_ROM_DoubleSpin', 'EOBNRv2'))
71 
72 def gstlal_valid_approximant(appx_str):
73  try:
74  lalsim.GetApproximantFromString(str(appx_str))
75  except RuntimeError:
76  raise ValueError("Approximant not supported by lalsimulation: %s" % appx_str)
77  if appx_str not in gstlal_approximants:
78  raise ValueError("Approximant not currently supported by gstlal, but is supported by lalsimulation: %s. Please consider preparing a patch" % appx_str)
79 
80 
81 def add_quadrature_phase(fseries, n):
82  """
83  From the Fourier transform of a real-valued function of time,
84  compute and return the Fourier transform of the complex-valued
85  function of time whose real component is the original time series
86  and whose imaginary component is the quadrature phase of the real
87  part. fseries is a LAL COMPLEX16FrequencySeries and n is the
88  number of samples in the original time series.
89  """
90  #
91  # prepare output frequency series
92  #
93 
94  out_fseries = laltypes.COMPLEX16FrequencySeries(
95  name = fseries.name,
96  epoch = fseries.epoch,
97  f0 = fseries.f0, # caution: only 0 is supported
98  deltaF = fseries.deltaF,
99  sampleUnits = fseries.sampleUnits
100  )
101 
102  #
103  # positive frequencies include Nyquist if n is even
104  #
105 
106  have_nyquist = not (n % 2)
107 
108  #
109  # shuffle frequency bins
110  #
111 
112  positive_frequencies = fseries.data
113  positive_frequencies[0] = 0 # set DC to zero
114  if have_nyquist:
115  positive_frequencies[-1] = 0 # set Nyquist to 0
116  zeros = numpy.zeros((len(positive_frequencies),), dtype = "cdouble")
117  if have_nyquist:
118  # complex transform never includes positive Nyquist
119  positive_frequencies = positive_frequencies[:-1]
120 
121  out_fseries.data = numpy.concatenate((zeros, 2 * positive_frequencies[1:]))
122 
123  return out_fseries
124 
125 
126 class QuadraturePhase(object):
127  """
128  A tool for generating the quadrature phase of a real-valued
129  template.
130 
131  Example:
132 
133  >>> import numpy
134  >>> from pylal.datatypes import REAL8TimeSeries
135  >>> q = QuadraturePhase(128) # initialize for 128-sample templates
136  >>> inseries = REAL8TimeSeries(deltaT = 1.0 / 128, data = numpy.cos(numpy.arange(128, dtype = "double") * 2 * numpy.pi / 128)) # one cycle of cos(t)
137  >>> outseries = q(inseries) # output has cos(t) in real part, sin(t) in imaginary part
138  """
139 
140  def __init__(self, n):
141  """
142  Initialize. n is the size, in samples, of the templates to
143  be processed. This is used to pre-allocate work space.
144  """
145  self.n = n
146  self.fwdplan = lalfft.XLALCreateForwardREAL8FFTPlan(n, 1)
147  self.revplan = lalfft.XLALCreateReverseCOMPLEX16FFTPlan(n, 1)
148  self.in_fseries = lalfft.prepare_fseries_for_real8tseries(laltypes.REAL8TimeSeries(deltaT = 1.0, data = numpy.zeros((n,), dtype = "double")))
149 
150  def __call__(self, tseries):
151  """
152  Transform the real-valued time series stored in tseries
153  into a complex-valued time series. The return value is a
154  newly-allocated complex time series. The input time series
155  is stored in the real part of the output time series, and
156  the complex part stores the quadrature phase.
157  """
158  #
159  # transform to frequency series
160  #
161 
162  lalfft.XLALREAL8TimeFreqFFT(self.in_fseries, tseries, self.fwdplan)
163 
164  #
165  # transform to complex time series
166  #
167 
168  tseries = laltypes.COMPLEX16TimeSeries(data = numpy.zeros((self.n,), dtype = "cdouble"))
169  lalfft.XLALCOMPLEX16FreqTimeFFT(tseries, add_quadrature_phase(self.in_fseries, self.n), self.revplan)
170 
171  #
172  # done
173  #
174 
175  return tseries
176 
177 
178 def normalized_autocorrelation(fseries, revplan):
179  data = fseries.data
180  fseries = laltypes.COMPLEX16FrequencySeries(
181  name = fseries.name,
182  epoch = fseries.epoch,
183  f0 = fseries.f0,
184  deltaF = fseries.deltaF,
185  sampleUnits = fseries.sampleUnits,
186  data = data * numpy.conj(data)
187  )
188  tseries = laltypes.COMPLEX16TimeSeries(
189  data = numpy.empty((len(data),), dtype = "cdouble")
190  )
191  lalfft.XLALCOMPLEX16FreqTimeFFT(tseries, fseries, revplan)
192  data = tseries.data
193  tseries.data = data / data[0]
194  return tseries
195 
196 
197 # Round a number up to the nearest power of 2
198 def ceil_pow_2(x):
199  x = int(math.ceil(x))
200  x -= 1
201  n = 1
202  while n and (x & (x + 1)):
203  x |= x >> n
204  n *= 2
205  return x + 1
206 
207 
208 def time_slices(
209  sngl_inspiral_rows,
210  flow = 40,
211  fhigh = 900,
212  padding = 1.5,
213  samples_min = 1024,
214  samples_max_256 = 1024,
215  samples_max_64 = 2048,
216  samples_max = 4096,
217  verbose = False
218 ):
219  """
220  The function time_frequency_boundaries splits a template bank up by
221  times for which different sampling rates are appropriate.
222 
223  The function returns a list of 3-tuples of the form (rate,begin,end)
224  where rate is the sampling rate and begin/end mark the boundaries
225  during which the given rate is guaranteed to be appropriate (no
226  template exceeds a frequency of Nyquist/padding during these times).
227 
228  @param sngl_inspiral_rows The snigle inspiral rows for this sub bank
229  @param flow The low frequency to generate the waveform from
230  @param fhigh The maximum frequency for generating the waveform
231  @param padding Allow a template to be generated up to Nyquist / padding for any slice
232  @param samples_min The minimum number of samples to use in any time slice
233  @param samples_max_256 The maximum number of samples to have in any time slice greater than or equal to 256 Hz
234  @param samples_max_64 The maximum number of samples to have in any time slice greater than or equal to 64 Hz
235  @param samples_max The maximum number of samples in any time slice below 64 Hz
236  @param verbose Be verbose
237  """
238 
239  #
240  # DETERMINE A SET OF ALLOWED SAMPLE RATES
241  #
242 
243  # We only allow sample rates that are powers of two. The upper limit
244  # is set by the sampling rate for h(t), which is 16384Hz. The lower
245  # limit is set arbitrarily to be 32Hz.
246  allowed_rates = [16384,8192,4096,2048,1024,512,256,128,64,32]
247 
248  # Remove too-small and too-big sample rates base on input params.
249  sample_rate_min = ceil_pow_2( 2 * padding * flow )
250  sample_rate_max = ceil_pow_2( 2 * fhigh )
251  while allowed_rates[-1] < sample_rate_min:
252  allowed_rates.pop(-1)
253  while allowed_rates[0] > sample_rate_max:
254  allowed_rates.pop(0)
255 
256  #
257  # FIND TIMES WHEN THESE SAMPLE RATES ARE OK TO USE
258  #
259 
260  # How many sample points should be included in a chunk?
261  # We need to balance the need to have few chunks with the
262  # need to have small chunks.
263  # We choose the min size such that the template matrix
264  # has its time dimension at least as large as its template dimension.
265  # The max size is chosen based on experience, which shows that
266  # SVDs of matrices bigger than m x 8192 are very slow.
267  segment_samples_min = max(ceil_pow_2( 2*len(sngl_inspiral_rows) ),samples_min)
268 
269  # For each allowed sampling rate with associated Nyquist frequency fN,
270  # determine the greatest amount of time any template in the bank spends
271  # between fN/2 and fhigh.
272  # fN/2 is given by sampling_rate/4 and is the next lowest Nyquist frequency
273  # We pad this so that we only start a new lower frequency sampling rate
274  # when all the waveforms are a fraction (padding-1) below the fN/2.
275  time_freq_boundaries = []
276  accum_time = 0
277 
278  # Get the low frequency staring points for the chirptimes at a given
279  # rate. This is always a factor of 2*padding smaller than the rate
280  # below to gaurantee that the waveform doesn't cross the Nyquist rate
281  # in the next time slice. The last element should always be the
282  # requested flow. The allowed rates won't go below that by
283  # construction.
284  rates_below = [r/(2.*padding) for r in allowed_rates[1:]] + [flow]
285 
286  for this_flow, rate in zip(rates_below, allowed_rates):
287  if rate >= 256:
288  segment_samples_max = samples_max_256
289  elif rate >= 64:
290  segment_samples_max = samples_max_64
291  else:
292  segment_samples_max = samples_max
293 
294  if segment_samples_min > segment_samples_max:
295  raise ValueError("The input template bank must have fewer than %d templates, but had %d." % (segment_samples_max, 2 * len(sngl_inspiral_rows)))
296 
297  longest_chirp = max(chirptime.imr_time(this_flow, row.mass1*MSUN_SI ,row.mass2*MSUN_SI, row.spin1z, row.spin2z) for row in sngl_inspiral_rows)
298 
299  # Do any of the templates go beyond the accumulated time?
300  # If so, we need to add some blocks at this sampling rate.
301  # If not, we can skip this sampling rate and move on to the next lower one.
302  if longest_chirp > accum_time:
303  # How many small/large blocks are needed to cover this band?
304  number_large_blocks = int(numpy.floor( rate*(longest_chirp-accum_time) / segment_samples_max ))
305  number_small_blocks = int(numpy.ceil( rate*(longest_chirp-accum_time) / segment_samples_min )
306  - number_large_blocks*segment_samples_max/segment_samples_min)
307 
308  # Add small blocks first, since the templates change more rapidly
309  # near the end of the template and therefore have more significant
310  # components in the SVD.
311  exponent = 0
312  while number_small_blocks > 0:
313  if number_small_blocks % 2 == 1:
314  time_freq_boundaries.append((rate,
315  accum_time,
316  accum_time+float(2**exponent)*segment_samples_min/rate))
317  accum_time += float(2**exponent)*segment_samples_min/rate
318  exponent += 1
319  number_small_blocks = number_small_blocks >> 1 # bit shift to right, dropping LSB
320 
321 
322  # Now add the big blocks
323  for idx in range(number_large_blocks):
324  time_freq_boundaries.append((rate,
325  accum_time,
326  accum_time+(1./rate)*segment_samples_max))
327  accum_time += (1./rate)*segment_samples_max
328 
329  if verbose:
330  print>> sys.stderr, "Time freq boundaries: "
331  print>> sys.stderr, time_freq_boundaries
332 
333  return numpy.array(time_freq_boundaries,dtype=[('rate','int'),('begin','float'),('end','float')])