Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALSimulation 6.2.0.1-b246709
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
waveform_conditioning.py
Go to the documentation of this file.
1import lal
2import lalsimulation as lalsim
3import numpy as np
4from astropy import units as u
5from . import waveform as wave
6from . import conditioning_subroutines as cond
7import warnings
8import math
9
10# This is basically the same conditioning as done in LALSimulation.
11# Specifically, what is done in "LALSimInspiralGeneratorConditioning.c".
12
13def fix_ref_frequency(parameter_dict, generator):
14 """
15 Function to fix the reference frequency to f22_start or generator specific reference frequency for conditioning routines.
16
17 Parameters
18 ----------
19 parameter_dict : `dictionary`
20 Dictionary of waveform parameters
21 generator : `GravitationalWaveGenerator`
22 Generator object of the GravitationalWaveGenerator class
23 """
24 if generator.metadata['implementation']=='LALSimulation':
25 return 0
26 else:
27 cases_by_approximant = {
28 'NRSurr' : parameter_dict['f22_start']
29 }
30 return cases_by_approximant[generator.metadata['approximant']]
31
32########################################################################################
33
34def generate_conditioned_td_waveform_from_td(parameter_dict, generator):
35 """
36 Function to generate conditioned time-domain waveform from time domain waveform model.
37
38 Parameters
39 ----------
40 parameter_dict : `dictionary`
41 Dictionary of intrinsic / extrinsic gravitational wave parameters
42 generator : `GravitationalWaveGenerator`
43 Generator object of the GravitationalWaveGenerator class
44
45 Returns
46 -------
47 hp, hc : GWpy Time series
48 Conditioned time domain polarizations
49 """
50
51 extra_time_fraction = 0.1 # fraction of waveform duration to add as extra time for tapering
52 extra_cycles = 3.0 # more extra time measured in cycles at the starting frequency
53
54 # Get some of the required parameters and get their values (so as not to have issues with units and LAL)
55 f_min = parameter_dict['f22_start'].value
56 f_ref = parameter_dict['f22_ref'].value
57 s1z = parameter_dict['spin1z'].value
58 s2z = parameter_dict['spin2z'].value
59
60 m1 = parameter_dict['mass1'].si.value
61 m2 = parameter_dict['mass2'].si.value
62
63 # Fix reference frequency to fmin/ case by case for Python generators. It is set to zero for lalsim generators as LALSim has its
64 # own checks.
65 if np.isclose(f_ref, 0):
66 f_ref = fix_ref_frequency(parameter_dict, generator)
67
68
69 # If loweset freq. is below fisco, set fmin to fisco.
70 fisco = 1.0 / (np.power(9.0, 1.5) * np.pi * (m1 + m2) * lal.MTSUN_SI / lal.MSUN_SI)
71 if (f_min > fisco):
72 f_min = fisco
73
74
75 # Upper chrip time bound
76 tchirp = lalsim.SimInspiralChirpTimeBound(f_min, m1, m2, s1z, s2z)
77
78 # Upper bound on the final black hole spin
79 s = lalsim.SimInspiralFinalBlackHoleSpinBound(s1z, s2z)
80
81 # Upper bound on the final plunge, merger, and ringdown time
82 tmerge = lalsim.SimInspiralMergeTimeBound(m1, m2) + lalsim.SimInspiralRingdownTimeBound(m1 + m2, s)
83
84 # extra time to include for all waveforms to take care of situations
85 # where the frequency is close to merger (and is sweeping rapidly):
86 # this is a few cycles at the low frequency
87 textra = extra_cycles / f_min
88
89 # For conditioning, start waveform at a lower frequency than f_min and then apply tapers between new low freq and f_min.
90 fstart = lalsim.SimInspiralChirpStartFrequencyBound((1.0 + extra_time_fraction) * tchirp + tmerge + textra, m1, m2)
91
92 # generate the waveform in the time domain starting at fstart. Add astropy units
93 new_parameters = parameter_dict.copy()
94 new_parameters['f22_ref'] = f_ref*parameter_dict['f22_start'].unit
95 new_parameters['f22_start'] = fstart*parameter_dict['f22_start'].unit
96
97
98 # Generate the new waveform
99 new_parameters['condition']=0
100 hp, hc = wave.GenerateTDWaveform(new_parameters, generator)
101
102 times = hp.times
103 dt = hp.dt.value
104 # Condition the time domain waveform by tapering in the extra time at the beginning
105 # And perform the high-pass filtering
106 hp, hc = cond.time_array_condition_stage1(hp, hc, dt, extra_time_fraction * tchirp + textra, parameter_dict['f22_start'].value)
107
108 fisco = 1.0 / (np.power(6.0, 1.5) * np.pi * (m1 + m2) * lal.MTSUN_SI / lal.MSUN_SI)
109 hp, hc = cond.time_array_condition_stage2(hp, hc, dt, f_min, fisco)
110
111 return hp, hc
112
113########################################################################################
114
115def generate_conditioned_td_waveform_from_fd(parameter_dict, generator):
116 """
117 Function to generate conditioned time-domain waveform from frequency domain waveform model.
118
119 Parameters
120 ----------
121 parameter_dict : dictionary
122 Dictionary of intrinsic / extrinsic gravitational wave parameters
123 generator : `GravitationalWaveGenerator`
124 Generator object of the GravitationalWaveGenerator class
125
126 Returns
127 -------
128 hp, hc : GWpy Time series
129 Conditioned time domain polarizations
130 """
131
132 extra_time_fraction = 0.1 # fraction of waveform duration to add as extra time for tapering
133 extra_cycles = 3.0 # more extra time measured in cycles at the starting frequency
134
135 dt = parameter_dict['deltaT'].value
136 original_f_min = f_min = parameter_dict['f22_start'].value
137 f_ref = parameter_dict['f22_ref'].value
138 f_max = 0.5/dt
139 s1z = parameter_dict['spin1z'].value
140 s2z = parameter_dict['spin2z'].value
141
142 m1 = parameter_dict['mass1'].si.value
143 m2 = parameter_dict['mass2'].si.value
144
145 # Fix reference frequency to fmin/ case by case for Python generators. It is set to zero for lalsim generators as LALSim has its
146 # own checks.
147 if np.isclose(f_ref, 0):
148 f_ref = fix_ref_frequency(parameter_dict, generator)
149
150 # If loweset freq. is below fisco, set fmin to fisco.
151 fisco = 1.0 / (np.power(9.0, 1.5) * np.pi * (m1 + m2) * lal.MTSUN_SI / lal.MSUN_SI)
152 if (f_min > fisco):
153 f_min = fisco
154
155 # Upper chrip time bound
156 tchirp = lalsim.SimInspiralChirpTimeBound(f_min, m1, m2, s1z, s2z)
157
158 # Upper bound on the final black hole spin
159 s = lalsim.SimInspiralFinalBlackHoleSpinBound(s1z, s2z)
160
161 # Upper bound on the final plunge, merger, and ringdown time
162 tmerge = lalsim.SimInspiralMergeTimeBound(m1, m2) + lalsim.SimInspiralRingdownTimeBound(m1 + m2, s)
163
164 # extra time to include for all waveforms to take care of situations
165 # where the frequency is close to merger (and is sweeping rapidly):
166 # this is a few cycles at the low frequency
167 textra = extra_cycles / f_min
168
169 # Generate frequency domain conditioned waveform with deltaF = 0 to get
170 # enough frequency resolution.
171
172 new_parameters = parameter_dict.copy()
173 new_parameters['f22_start'] = f_min*u.Hz
174 new_parameters['f22_ref'] = f_ref*u.Hz
175 new_parameters['f_max'] = f_max*u.Hz
176 new_parameters['deltaF'] = 0.*u.Hz
177
178 # Generate the new waveform
179 new_parameters['condition']=1
180 hp, hc = wave.GenerateFDWaveform(new_parameters, generator)
181
182
183 # We want to make sure that this waveform will give something
184 # sensible if it is later transformed into the time domain:
185 # to avoid the end of the waveform wrapping around to the beginning,
186 # we shift waveform backwards in time and compensate for this
187 # shift by adjusting the epoch -- note that the conditioned
188 # generate_fd_waveform method guarantees that there is the
189 # extra padding to do this
190
191 tshift = np.round(textra / dt) * dt # integer number of samples
192
193 kvals = np.arange(0, len(hp))
194 phase_shift = np.exp(2*np.pi*1j*hp.df.value*tshift*kvals)
195 hp *= phase_shift
196 hc *= phase_shift
197
198 if hp.epoch==None or hc.epoch==None:
199 hp.epoch=tshift*u.s
200 hc.epoch=tshift*u.s
201 else:
202 hp.epoch+=tshift*u.s
203 hc.epoch+=tshift*u.s
204
205 # transform the waveform into the time domain
206 hpt = hp.ifft()*2*hp.df
207 hct = hc.ifft()*2*hp.df
208
209 # High pass timeseries
210 hpt = cond.high_pass_time_series(hpt, dt, original_f_min, 0.99, 8.)
211 hct = cond.high_pass_time_series(hct, dt, original_f_min, 0.99, 8.)
212
213 # Resize time series
214 fstart = lalsim.SimInspiralChirpStartFrequencyBound((1.0 + extra_time_fraction) * tchirp, m1, m2);
215 tchirp = lalsim.SimInspiralChirpTimeBound(fstart, m1, m2, s1z, s2z);
216
217 #total expected chirp length includes merger
218
219 chirplen = round((tchirp + tmerge) /dt)
220 end = len(hpt) - np.round(tshift/dt)
221
222 # snip off extra time at beginning and at the end
223 hpt = cond.resize_gwpy_timeseries(hpt, end - chirplen, chirplen)
224 hct = cond.resize_gwpy_timeseries(hct, end - chirplen, chirplen)
225
226 # final tapering at the beginning and at the end to remove filter transients
227 fisco = 1.0 / (np.power(6.0, 1.5) * np.pi * (m1 + m2) * lal.MTSUN_SI / lal.MSUN_SI)
228 hpt, hct = cond.time_array_condition_stage2(hpt, hct, dt, f_min, fisco)
229
230 return hpt, hct
231
232
233########################################################################################
234
235def check_pow_of_2(n):
236 """
237 Return the next highest power of 2 given number n.
238 For eg: if n = 7; exponent will be 3 (2**3=8)
239 """
240 nv = int(n)
241 out=False
242 if (nv & (nv-1) == 0) and n!=0:
243 out = True
244 mant, exponent = math.frexp(n)
245
246 return out, exponent
247
248def generate_conditioned_fd_waveform_from_fd(parameter_dict, generator):
249 """
250 Function to generate conditioned frequency-domain waveform from frequency domain waveform model.
251
252 Parameters
253 ----------
254 parameter_dict : dictionary
255 Dictionary of intrinsic / extrinsic gravitational wave parameters
256 generator : `GravitationalWaveGenerator`
257 Generator object of the GravitationalWaveGenerator class
258
259 Returns
260 -------
261 hp, hc : GWpy Time series
262 Conditioned frequency domain polarizations
263 """
264
265 extra_time_fraction = 0.1 # fraction of waveform duration to add as extra time for tapering
266 extra_cycles = 3.0 # more extra time measured in cycles at the starting frequency
267
268 df = parameter_dict['deltaF'].value
269 f_min = parameter_dict['f22_start'].value
270 f_ref = parameter_dict['f22_ref'].value
271 f_max = parameter_dict['f_max'].value
272 s1z = parameter_dict['spin1z'].value
273 s2z = parameter_dict['spin2z'].value
274
275 m1 = parameter_dict['mass1'].si.value
276 m2 = parameter_dict['mass2'].si.value
277
278 # Fix reference frequency to fmin/ case by case for Python generators. It is set to zero for lalsim generators as LALSim has its
279 # own checks.
280 if np.isclose(f_ref, 0):
281 f_ref = fix_ref_frequency(parameter_dict, generator)
282
283 # Apply condition that f_max rounds to the next power-of-two multiple of deltaF.
284 # Round f_max / deltaF to next power of two.
285 # Set f_max to the new Nyquist frequency.
286 # The length of the chirp signal is then 2 * f_nyquist / deltaF.
287 # The time spacing is 1 / (2 * f_nyquist)
288 f_nyquist = f_max
289
290
291 # Check if n is power of 2
292 if df!=0:
293 n = np.round(f_max/df)
294 truth, exponent = check_pow_of_2(n)
295 if not truth:
296 f_nyquist = 2**(exponent)*df
297 deltaT = 0.5/f_nyquist
298
299 # If loweset freq. is below fisco, set fmin to fisco.
300 fisco = 1.0 / (np.power(9.0, 1.5) * np.pi * (m1 + m2) * lal.MTSUN_SI / lal.MSUN_SI)
301 if (f_min > fisco):
302 f_min = fisco
303
304 # Upper chirp-time bound
305 tchirp = lalsim.SimInspiralChirpTimeBound(f_min, m1, m2, s1z, s2z)
306
307 # Upper bound on the final black hole spin
308 s = lalsim.SimInspiralFinalBlackHoleSpinBound(s1z, s2z)
309
310 # Upper bound on the final plunge, merger, and ringdown time
311 tmerge = lalsim.SimInspiralMergeTimeBound(m1, m2) + lalsim.SimInspiralRingdownTimeBound(m1 + m2, s)
312
313 # To condition waveform at lower freq, add some extra early part by changing f_min.
314 # New f_min determined by a fixed fraction of the chirp time.
315 textra = extra_cycles / f_min
316 fstart = lalsim.SimInspiralChirpStartFrequencyBound((1.0 + extra_time_fraction) * tchirp, m1, m2)
317
318 # Revise chirp estimate from fstart
319 tchirp = lalsim.SimInspiralChirpTimeBound(fstart, m1, m2, s1z, s2z)
320
321 # Get length required for full waveform with padding upto power of 2
322 chirplen = round((tchirp + tmerge + 2.0 * textra) / deltaT);
323 truth, exponent = check_pow_of_2(chirplen)
324 chirplen = 2**exponent
325
326 if df==0:
327 df = 1./(chirplen*deltaT)
328 elif df>1./(chirplen*deltaT):
329 warnings.warn("Specified frequency interval of %.2f Hz is too large for a chirp of duration %.2f s"%(df, chirplen*deltaT))
330
331 # generate the waveform in the time domain starting at fstart. Add astropy units
332 new_parameters = parameter_dict.copy()
333 new_parameters['f22_ref'] = f_ref*u.Hz
334 new_parameters['f22_start'] = fstart*u.Hz
335 new_parameters['deltaF'] = df*u.Hz
336
337 # Generate the new waveform
338 new_parameters['condition']=0
339 hp, hc = wave.GenerateFDWaveform(new_parameters, generator)
340
341 # Tapering the FD data.
342 # Get indices at fstart and fmin
343 k0 = int(np.round(fstart/df))
344 k1 = int(np.round(f_min/df))
345
346 # Below fstart, ensure all elements are 0
347 hp[:k0]=0.
348 hc[:k0]=0.
349
350 # From fstart to fmin, apply the taper
351 kvals = np.arange(k0, k1)
352 w = 0.5 - 0.5*np.cos(np.pi*(kvals-k0)/(k1-k0))
353 hp[k0:k1] = w*hp[k0:k1]
354 hc[k0:k1] = w*hc[k0:k1]
355
356 # Ensure nyquist frequency elemnt is zero
357 hp[len(hp)-1]=0.
358 hc[len(hc)-1]=0.
359
360 # Time shift the waveform to avoid waveform wrapping back over itself when converted to time-domain.
361 tshift = np.round(tmerge / deltaT) * deltaT
362 kvals = np.arange(0, len(hp))
363 phase_shift = np.exp(1j*2*np.pi*df*tshift*kvals)
364
365 hp *= phase_shift
366 hc *= phase_shift
367
368 # Add tshift to epoch
369 hp.epoch = tshift*u.s
370 hc.epoch = tshift*u.s
371
372 return hp, hc
373
374########################################################################################
375
376def generate_conditioned_fd_waveform_from_td(parameter_dict, generator):
377 """
378 Function to generate conditioned frequency-domain waveform from time domain waveform model.
379
380 Parameters
381 ----------
382 parameter_dict : dictionary
383 Dictionary of intrinsic / extrinsic gravitational wave parameters
384 generator : `GravitationalWaveGenerator`
385 Generator object of the GravitationalWaveGenerator class
386
387 Returns
388 -------
389 hp, hc : GWpy Time series
390 Conditioned frequency domain polarizations
391 """
392
393 extra_time_fraction = 0.1 # fraction of waveform duration to add as extra time for tapering
394 extra_cycles = 3.0 # more extra time measured in cycles at the starting frequency
395
396 df = parameter_dict['deltaF'].value
397 f_min = parameter_dict['f22_start'].value
398 f_ref = parameter_dict['f22_ref'].value
399 f_max = parameter_dict['f_max'].value
400
401 s1z = parameter_dict['spin1z'].value
402 s2z = parameter_dict['spin2z'].value
403
404 m1 = parameter_dict['mass1'].si.value
405 m2 = parameter_dict['mass2'].si.value
406
407 # Fix reference frequency to fmin/ case by case for Python generators. It is set to zero for lalsim generators as LALSim has its
408 # own checks.
409 if np.isclose(f_ref, 0):
410 f_ref = fix_ref_frequency(parameter_dict, generator)
411
412 # Apply condition that f_max rounds to the next power-of-two multiple of deltaF.
413 # Round f_max / deltaF to next power of two.
414 # Set f_max to the new Nyquist frequency.
415 # The length of the chirp signal is then 2 * f_nyquist / deltaF.
416 # The time spacing is 1 / (2 * f_nyquist)
417 f_nyquist = f_max
418
419 # Check if n is power of 2
420 if df!=0:
421 n = np.round(f_max/df)
422 truth, exponent = check_pow_of_2(n)
423 if not truth:
424 f_nyquist = 2**(exponent)*df
425
426 deltaT = 0.5/f_nyquist
427
428 # generate the waveform in the time domain starting at fstart. Add astropy units
429 new_parameters = parameter_dict.copy()
430 new_parameters['f22_ref'] = f_ref*u.Hz
431 new_parameters['deltaT'] = deltaT*u.s
432
433 # Generate the new waveform
434 new_parameters['condition']=1
435 hp, hc = wave.GenerateTDWaveform(new_parameters, generator)
436
437 if df==0:
438 chirplen = len(hp)
439 tt, chirplen_exp = check_pow_of_2(chirplen)
440 chirplen = 2**(chirplen_exp)
441 df = 1./(chirplen*hp.dt)
442 else:
443 chirplen=2*f_nyquist/df
444
445
446 hp = cond.resize_gwpy_timeseries(hp, len(hp)-chirplen,chirplen)
447 hc = cond.resize_gwpy_timeseries(hc,len(hc)-chirplen,chirplen)
448
449 hpf = hp.fft()
450 hcf = hc.fft()
451
452 # Normalize to match lalsuite
453 hpf = hpf/(2*hpf.df)
454 hcf = hcf/(2*hpf.df)
455 return hpf, hcf
def generate_conditioned_fd_waveform_from_fd(parameter_dict, generator)
Function to generate conditioned frequency-domain waveform from frequency domain waveform model.
def fix_ref_frequency(parameter_dict, generator)
Function to fix the reference frequency to f22_start or generator specific reference frequency for co...
def generate_conditioned_td_waveform_from_td(parameter_dict, generator)
Function to generate conditioned time-domain waveform from time domain waveform model.
def check_pow_of_2(n)
Return the next highest power of 2 given number n.
def generate_conditioned_fd_waveform_from_td(parameter_dict, generator)
Function to generate conditioned frequency-domain waveform from time domain waveform model.
def generate_conditioned_td_waveform_from_fd(parameter_dict, generator)
Function to generate conditioned time-domain waveform from frequency domain waveform model.