Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALSimulation 6.2.0.1-3a66518
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
waveform.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 gwpy.timeseries import TimeSeries
6from gwpy.frequencyseries import FrequencySeries
7
8from . import utils as ut
9from . import parameter_conventions as pc
10from . import gw as gw
11from . import waveform_conditioning as wave_cond
12from . import errors as err
13import warnings
14###################################################################################################
15
16
17class GravitationalWaveGenerator(object):
18 """
19 This is the Parent class for all gravitational wave generator classes.
20 This class implements the basic generate modes and polarizations definitions which can then be used by the
21 consequent children classes.
22 """
23
24 def __init__(self):
25 """
26 Initialize the class and set base domain to time.
27
28 Parameters
29 ----------
30
31 No parameters required for initialization.
32 """
33
34 def generate_td_modes(self, **kwargs):
35 """
36 Generate time domain modes given kwargs. The child classes will provide these routines.
37
38 Parameters
39 ----------
40
41 kwargs required for generation of waveform modes
42
43 Returns
44 -------
45
46 Waveform modes as implemented in the child class
47
48 """
49 raise NotImplementedError
50
51 def generate_fd_modes(self, **kwargs):
52 """
53 Generate frequency domain modes given kwargs. The child classes will provide these routines.
54
55 Parameters
56 ----------
57
58 kwargs required for generation of waveform modes
59
60 Returns
61 -------
62
63 Waveform modes as implemented in the child class
64
65 """
66 raise NotImplementedError
67
68 def generate_td_waveform(self, **kwargs):
69 """
70 Generate time domain polarizations given kwargs. The child classes will provide these routines.
71
72 Parameters
73 ----------
74
75 kwargs required for generation of waveform polarizations
76
77 Returns
78 -------
79
80 Waveform polarizations implemented in the child class
81
82 """
83 raise NotImplementedError
84
85 def generate_fd_waveform(self, **kwargs):
86 """
87 Generate frequency domain polarizations given kwargs. The child classes will provide these routines.
88
89 Parameters
90 ----------
91
92 kwargs required for generation of waveform polarizations
93
94 Returns
95 -------
96
97 Waveform polarizations as implemented in the child class
98
99 """
100 raise NotImplementedError
101
102 @property
103 def domain(self):
104 return self._generation_domain, self._implemented_domain
105
106 def _update_domains(self):
107 """
108 Update the generation or implementation domain for a given generator / if needed.
109
110 Parameters
111 ----------
112 None
113
114 Returns
115 -------
116 Updated domain tags for polarization generation and waveform implementation.
117
118 """
119 self._implemented_domain = self.metadata['implemented_domain']
120 return 0
121
122 @property
123 def metadata(self):
124 metadata = {
125 "type": '',
126 "f_ref_spin": None,
127 "modes": None,
128 "polarizations": None,
129 "implemented_domain": None,
130 "generation_domain": None,
131 "approximant" : None,
132 "implementation" : '',
133 "conditioning_routines" : ''
134 }
135 return metadata
136
138 """
139 This is the parent generator class for compact binary coalescence waveforms (BBH, BNS, NSBH etc.).
140
141 The main intended functionality of this class is to check the different parameters passed in the kwargs
142 and to populate the parameter dictionaries with all the other parameters from the lalsuite parameter checks.
143 """
144
145 def __init__(self):
146 """
147 Initialize class
148
149 Parameters
150 ----------
151
152 No parameters required for initilazation.
153
154 """
155 super(CompactBinaryCoalescenceGenerator, self).__init__()
156
157
158 def parameter_check(self, units_sys='S.I.', extra_parameters=dict(), **parameters):
159 """
160 Perform checks on the various parameters and populate the different parameters not passed
161 in the kwargs required for generating waveforms.
162
163 Parameters
164 ----------
165
166 Python dictionary of parameters required for waveform generation
167 of the form specified in `parameter_conventions.py`.
168
169 Returns
170 -------
171
172 Populate self.waveform_dict with python dictionary and self.lal_dict with LALSuite dictionary structure.
173 """
174 default_dict = pc.default_dict.copy()
175 # Need to add this line to take care of the extra parameters passed to ExternalPython LAL Generator
176 ExternalPythonParameters=['object', 'module']
177
178 for key, value in parameters.items():
179 if key in ExternalPythonParameters:
180 pass
181 else:
182 default_dict[key] = value
183
184 if not 'deltaF' in default_dict:
185 default_dict['deltaF'] = 1./16.*u.Hz
186 if not 'deltaT' in default_dict:
187 default_dict['deltaT'] = 1./512.*u.s
188 if not 'f_max' in default_dict:
189 default_dict['f_max'] = (0.5/default_dict['deltaT'].value)*u.Hz
190
191 # Add units if indicated
192 if units_sys is not None:
193 self.waveform_dict = ut.add_params_units(default_dict, units_sys, generic_param_dict=extra_parameters)
194 #This is a mandatory check that units are correctly used
195 ut.check_dict_parameters(default_dict, generic_param_dict=extra_parameters)
196
197 self.lal_dict = ut.to_lal_dict(default_dict)
198 return default_dict
199
200########################################################################
201# LAL CBC Generator
202########################################################################
203
204
206 """
207 Generator class for all CBC waveforms as implemented in LALSimulation.
208 """
209
210 def __init__(self, approximant, **kwargs):
211 """
212 Initialize class. Parent class is the "CompactBinaryCoalescenceGenerator" class
213
214 Parameters
215 ----------
216 approximant : type 'str'
217 Name of the LAL approximant of which waveform to be generated
218
219 Returns
220 -------
221 generator : Python GW generator class
222 Generator class for LALSimulation waveforms required to generate time and frequency domain polarizations and modes
223
224 """
225 super(LALCompactBinaryCoalescenceGenerator, self).__init__()
226 warnings.warn("This code is currently UNREVIEWED, use with caution!")
227 self.approximant = approximant
229 self._generation_domain = None
231
232 @property
233 def metadata(self):
234 metadata = {
235 "type": 'cbc_lalsimulation',
236 "f_ref_spin": True,
237 "modes": True,
238 "polarizations": True,
239 "implemented_domain": self._implemented_domain_implemented_domain,
240 "generation_domain": self._generation_domain,
241 "approximant" : self._approx_name,
242 "implementation" : "LALSimulation",
243 "conditioning_routines" : 'lalsimulation'
244 }
245 return metadata
246
247 def _update_domains(self):
248 """
249 Update the implemented domain of the LAL generator based on LALSimulations ImplementedTD/FDApproximant flag
250 """
251 # Check which is the impltemented domain for the waveform
252
253 td_flag = lalsim.SimInspiralImplementedTDApproximants(self._approx)
254 fd_flag = lalsim.SimInspiralImplementedFDApproximants(self._approx)
255 check_array = [td_flag, fd_flag]
256
257 if check_array==[1,0]:
259 elif check_array==[0,1]:
261 elif check_array==[1,1]:
263
264 def _get_approximant(self, approx):
265 """Populate the approximant name and approximant int (as used by lalsimulation) from waveform dictionary.
266
267 Parameters
268 ----------
269 approx : 'str'
270 Name of approximant.
271
272 Returns
273 -------
274
275 Populate self._approx_name (str) and self._approx (int) to be used by lalsimulation ChooseWaveformGenerator
276 function
277
278 Raises
279 ------
280 ValueError if approximant is not included in LALSimulation
281 """
282
283 if type(approx) is str:
284 self._approx_name = approx
285 self._approx = lalsim.SimInspiralGetApproximantFromString(approx)
286 elif type(approx) is int:
287 self._approx = approx
288 self._approx_name = lalsim.GetStringFromApproximant(approx)
289 else:
290 raise ValueError('approximant not of recognized type')
291
292
293 def generate_td_waveform(self, **parameters):
294 """
295 Perform parameter check, choose lalsimulation generator based on domain and conditioning subroutines.
296
297 Parameters
298 ----------
299 parameter_dict : dictionary
300 Dictionary of waveform parameters of the form specified
301 in `parameter_conventions.py`.
302
303 Returns
304 -------
305 hp, hc : LAL Time Series
306 Plus and cross polarizations of a gravitational waveform (hp,hc) as LAL Data objects.
307
308 Raises
309 ------
310 ValueError if domain ('time' or 'freq') for approximant is not specified
311 """
312 self.parameter_check(**parameters)
313 self._generation_domain = 'time'
316 self._pol_gen_function = lalsim.SimInspiralGenerateTDWaveform
317
319 if self._generation_domain=='time' and parameters['condition']==1:
320 self._pol_gen_function = lalsim.SimInspiralGenerateTDWaveform
321 elif self._generation_domain=='time' and parameters['condition']==0:
322 raise ValueError("Generator requires conditioning to be turned on to generate time domain waveform")
323
324 self._lal_generator = lalsim.SimInspiralChooseGenerator(self._approx, self.lal_dict)
325
326 @err.mapexception
327 def gen_pol_func(lal_dict, lal_generator):
328 return self._pol_gen_function(lal_dict, lal_generator)
329
330 hp, hc = gen_pol_func(self.lal_dict, self._lal_generator)
331 hp, hc = to_gwpy_Series(hp, name='hplus'), to_gwpy_Series(hc, name='hcross')
332 return hp, hc
333
334 def generate_fd_waveform(self, **parameters):
335 """
336 Perform parameter check, choose lalsimulation generator based on domain and conditioning subroutines.
337
338 Parameters
339 ----------
340 parameter_dict : dictionary
341 Dictionary of waveform parameters of the form specified
342 in `parameter_conventions.py`.
343
344 Returns
345 -------
346 hp, hc : LAL Frequency Series
347 Plus and cross polarizations of a gravitational waveform (hp,hc) as LAL Data objects.
348
349 Raises
350 ------
351 ValueError if domain ('time' or 'freq') for approximant is not specified
352 """
353 self.parameter_check(**parameters)
354 self._generation_domain = 'freq'
356
358 if self._generation_domain=='freq' and parameters['condition']==1:
359 self._pol_gen_function = lalsim.SimInspiralGenerateFDWaveform
360 elif self._generation_domain=='freq' and parameters['condition']==0:
361 raise ValueError("Generator requires conditioning to be turned on to generate frequency domain waveform")
362
364 self._pol_gen_function = lalsim.SimInspiralGenerateFDWaveform
365
366 self._lal_generator = lalsim.SimInspiralChooseGenerator(self._approx, self.lal_dict)
367
368 @err.mapexception
369 def gen_pol_func(lal_dict, lal_generator):
370 return self._pol_gen_function(lal_dict, lal_generator)
371
372 hp, hc = gen_pol_func(self.lal_dict, self._lal_generator)
373 hp, hc = to_gwpy_Series(hp, name='hplus', epoch=0.), to_gwpy_Series(hc, name='hcross', epoch=0.)
374 return hp, hc
375
376
377 def generate_td_modes(self, **parameters):
378 """
379 Perform parameter check, choose lalsimulation generator based on domain and attempt to generate
380 the waveform modes.
381
382 Parameters
383 ----------
384 parameter_dict : dictionary
385 Dictionary of waveform parameters of the form specified
386 in `parameter_conventions.py`.
387
388 Returns
389 -------
390 hlm : Python dictionary of modes
391 Modes of a gravitational waveform as python dictionary where each mode is LAL Time series
392 objects.
393 """
394
395 self.parameter_check(**parameters)
396 self._generation_domain = 'time'
398
400 self._mode_gen_function = lalsim.SimInspiralGenerateTDModes
402 raise ValueError('Approximant does not have time domain mode generator')
403 else:
404 raise ValueError('Approximant domain unspecified')
405
406
407 self._lal_generator = lalsim.SimInspiralChooseGenerator(self._approx, self.lal_dict)
408
409 @err.mapexception
410 def gen_modes(lal_dict, gen):
411 return self._mode_gen_function(lal_dict, gen)
412
413
414 hlm = gen_modes(self.lal_dict, self._lal_generator)
415
416
417 # Put hlms in a dictionary
418 # Define python dictionary and populate the modes
419 hlm_dict = {}
420
421 # Define 22 mode to get epoch and generate time data
422 mode_22 = lalsim.SphHarmTimeSeriesGetMode(hlm, 2, 2)
423 dt = self.waveform_dict['deltaT'].si.value
424 times = np.float64(mode_22.epoch) + np.arange(0, len(mode_22.data.data))*dt
425
426 hlm_dict['time_array'] = np.array(times)
427
428 lmax = hlm.l
429 m_max = hlm.m
430 for l in np.arange(2, lmax+1):
431 for m in np.arange(-l, l+1):
432 hlm_dict[l,m] = lalsim.SphHarmTimeSeriesGetMode(hlm, int(l), int(m))
433
434 hlm_out = to_gwpy_dict(hlm_dict)
435 return hlm_out
436
437
438 def generate_fd_modes(self, **parameters):
439 """
440 Perform parameter check, choose lalsimulation generator based on domain and attempt to generate
441 the waveform modes.
442
443 Parameters
444 ----------
445 parameter_dict : dictionary
446 Dictionary of waveform parameters of the form specified
447 in `parameter_conventions.py`.
448
449 Returns
450 -------
451 hlm : Python dictionary of modes
452 Modes of a gravitational waveform as python dictionary where each mode is LAL Frequency series
453 objects.
454 """
455
456 self.parameter_check(**parameters)
457 self._generation_domain = 'freq'
459
461 raise ValueError('Approximant does not have frequency domain mode generator')
463 self._mode_gen_function = lalsim.SimInspiralGenerateFDModes
464 else:
465 raise ValueError('approximant domain unspecified')
466
467
468 self._lal_generator = lalsim.SimInspiralChooseGenerator(self._approx, self.lal_dict)
469 @err.mapexception
470 def gen_modes(lal_dict, gen):
471 return self._mode_gen_function(lal_dict, gen)
472
473 hlm = gen_modes(self.lal_dict, self._lal_generator)
474
475
476 # Put hlms in a dictionary
477 # Define python dictionary and populate the modes
478 hlm_dict = {}
479
480 hlm_dict['frequency_array'] = hlm.fdata.data
481
482 lmax = hlm.l
483 m_max = hlm.m
484 for l in np.arange(2, lmax+1):
485 for m in np.arange(-l, l+1):
486 hlm_dict[l,m] = lalsim.SphHarmFrequencySeriesGetMode(hlm, int(l), int(m))
487
488 hlm_out = to_gwpy_dict(hlm_dict)
489
490 return hlm_out
491
492def conditioning_generator(generator):
493 """
494 Given a generator with contioning as 1, return function that generates conditioned waveforms
495
496 Parameters
497 ----------
498 generator : `GravitationalWaveGenerator`
499 GravitationalWaveGenerator object
500
501 Returns
502 -------
503 Conditioning function for the generator
504 """
505
506 if generator._implemented_domain=='time' and generator._generation_domain=='time':
507 generator_func = wave_cond.generate_conditioned_td_waveform_from_td
508
509 elif generator._implemented_domain=='time' and generator._generation_domain=='freq':
510 generator_func = wave_cond.generate_conditioned_fd_waveform_from_td
511
512 elif generator._implemented_domain=='freq' and generator._generation_domain=='freq':
513 generator_func = wave_cond.generate_conditioned_fd_waveform_from_fd
514
515 elif generator._implemented_domain=='freq' and generator._generation_domain=='time':
516 generator_func = wave_cond.generate_conditioned_td_waveform_from_fd
517
518 return generator_func
519
520########################################################################
521
522
523########################################################################
524
525def to_gwpy_Series(h, f0=0., **kwargs):
526 '''
527 Function to convert a lal series to a gwpy series.
528
529 Parameters
530 ----------
531 h : lal time or frequency series
532 'lal.REAL8TimeSeries' or 'lal.COMPLEX16FrequencySeries'
533
534 f0 : Starting frequency passed to gwpy.FrequencySeries
535
536 Returns
537 -------
538
539 h : GWpy Time/Freq series
540 Time/Freq domain waveform
541 '''
542
543 if isinstance(h, lal.REAL8TimeSeries) or isinstance(h, lal.COMPLEX16TimeSeries):
544 return TimeSeries(h.data.data, dt = h.deltaT, t0 = h.epoch, **kwargs)
545
546 elif isinstance(h, lal.COMPLEX16FrequencySeries):
547 return FrequencySeries(h.data.data, df = h.deltaF, f0=f0, **kwargs)
548
549 elif isinstance(h, TimeSeries) :
550 return TimeSeries(h, **kwargs)
551
552 elif isinstance(h, FrequencySeries):
553 return FrequencySeries(h, **kwargs)
554
555 else:
556 print('Input type not recognized')
557
558def to_gwpy_dict(mode_dict, **kwargs):
559 '''
560 Function to convert a mode dictionary of lal series to a dictionary of
561 gwpy series.
562
563 Parameters
564 ----------
565 mode_dict : python dictionary
566 entries of the form {(l, m): lal-series} and {`time/frequency_array` : }
567
568 Returns
569 -------
570 new_dict : python dictionary
571 entries of the form {(l, m): gwpy-series} and {`time/frequency_array` : }
572 '''
573
574 new_dict = {}
575 f0=0.
576 for k, v in mode_dict.items():
577 if k == 'time_array':
578 new_dict.update({k: v})
579 elif k == 'frequency_array':
580 new_dict.update({k: v})
581 f0 = v[0]
582 elif v is None:
583 pass
584 else:
585 new_dict.update({k: to_gwpy_Series(v, f0, name='h_%i_%i'%(k[0], k[1]),**kwargs)})
586
587 return new_dict
588
589
590########################################################################################################################
591################################ Generate Waveform Polarizations and Modes functions ##################################
592########################################################################################################################
593
594def GenerateTDWaveform(parameter_dict, generator):
595 """
596 Function to generate time domain gravitational wave polarizations.
597
598 Parameters
599 ----------
600 parameter_dict : dictionary
601 Dictionary of intrinsic / extrinsic gravitational wave parameters
602 generator : Python GW generator class
603 Generator class for the waveform approximant (either LAL or external)
604
605 Returns
606 -------
607 `GravitationalWavePolarizations` object: allows to extract the zero noise strain
608 given a detector, sky-position, polarization and time of arrival values.
609 """
610
611 generator._generation_domain = 'time'
612 generator._update_domains()
613
614 conditioning = parameter_dict['condition']
615 conditioning_routines = generator.metadata['conditioning_routines']
616
617 # First check if generator generates waveform in generation domain and
618 # if conditioning is on. If conditioning is off, raise ValueError
619 if not conditioning and generator._generation_domain!=generator._implemented_domain:
620 raise ValueError("Generator does not provide a method to generate time-domain waveforms. \n Please turn on conditioning to generate time-domain waveform")
621
622 if conditioning and conditioning_routines=='gwsignal':
623 warnings.warn("This code is currently UNREVIEWED, use with caution!")
624 generator_func = conditioning_generator(generator)
625 hp, hc = generator_func(parameter_dict, generator)
626 else:
627 hp, hc = generator.generate_td_waveform(**parameter_dict)
628
630
631def GenerateFDWaveform(parameter_dict, generator):
632 """
633 Function to generate frequency domain gravitational wave polarizations.
634
635 Parameters
636 ----------
637 parameter_dict : dictionary
638 Dictionary of intrinsic / extrinsic gravitational wave parameters
639 generator : Python GW generator class
640 Generator class for the waveform approximant (either LAL or external)
641
642 Returns
643 -------
644 `GravitationalWavePolarizations` object: allows to extract the zero noise strain
645 given a detector, sky-position, polarization and time of arrival values.
646 """
647
648 generator._generation_domain = 'freq'
649 generator._update_domains()
650
651 conditioning = parameter_dict['condition']
652 conditioning_routines = generator.metadata['conditioning_routines']
653
654 # First check if generator generates waveform in generation domain and
655 # if conditioning is on. If conditioning is off, raise ValueError
656 if not conditioning and generator._generation_domain!=generator._implemented_domain:
657 raise ValueError("Generator does not provide a method to generate frequency-domain waveforms. \n Please turn on conditioning to generate time-domain waveform")
658
659 if conditioning and conditioning_routines=='gwsignal':
660 warnings.warn("This code is currently UNREVIEWED, use with caution!")
661 generator_func = conditioning_generator(generator)
662 hp, hc = generator_func(parameter_dict, generator)
663 else:
664 hp, hc = generator.generate_fd_waveform(**parameter_dict)
665
667
668def GenerateTDModes(parameter_dict, generator):
669 """
670 Function to generate time domain gravitational wave modes.
671
672 Parameters
673 ----------
674 parameter_dict : dictionary
675 Dictionary of intrinsic / extrinsic gravitational wave parameters
676 generator : Python GW generator class
677 Generator class for the waveform approximant (either LAL or external)
678
679 Returns
680 -------
681 hlm : Python dictionary
682 Time domain modes returned as a dictionary where each mode is a GWpy Time series object
683 """
684
685 generator._generation_domain = 'time'
686 generator._update_domains()
687
688 hlm = generator.generate_td_modes(**parameter_dict)
689
690 hlm_out = to_gwpy_dict(hlm)
691 return gw.GravitationalWaveModes(hlm_out)
692
693
694def GenerateFDModes(parameter_dict, generator):
695 """
696 Function to generate frequency domain gravitational wave modes.
697
698 Parameters
699 ----------
700 parameter_dict : dictionary
701 Dictionary of intrinsic / extrinsic gravitational wave parameters
702 generator : Python GW generator class
703 Generator class for the waveform approximant (either LAL or external)
704
705 Returns
706 -------
707 hlm : Python dictionary
708 Frequency domain modes returned as a dictionary where each mode is a GWpy Frequency series object
709 """
710
711 generator._generation_domain = 'freq'
712 generator._update_domains()
713
714 hlm = generator.generate_fd_modes(**parameter_dict)
715 hlm_out = to_gwpy_dict(hlm)
716 return gw.GravitationalWaveModes(hlm_out)
717
718
719
720
721
722########################################################################################################################
723
724
725########################################################################################################################
Class for gravitational wave modes which returns the waveform recomposed with -2 spin weighted spheri...
Definition: gw.py:165
This is the parent generator class for compact binary coalescence waveforms (BBH, BNS,...
Definition: waveform.py:143
def parameter_check(self, units_sys='S.I.', extra_parameters=dict(), **parameters)
Perform checks on the various parameters and populate the different parameters not passed in the kwar...
Definition: waveform.py:173
This is the Parent class for all gravitational wave generator classes.
Definition: waveform.py:22
def generate_td_waveform(self, **kwargs)
Generate time domain polarizations given kwargs.
Definition: waveform.py:82
def generate_fd_modes(self, **kwargs)
Generate frequency domain modes given kwargs.
Definition: waveform.py:65
def __init__(self)
Initialize the class and set base domain to time.
Definition: waveform.py:32
def generate_fd_waveform(self, **kwargs)
Generate frequency domain polarizations given kwargs.
Definition: waveform.py:99
def generate_td_modes(self, **kwargs)
Generate time domain modes given kwargs.
Definition: waveform.py:48
Generator class for all CBC waveforms as implemented in LALSimulation.
Definition: waveform.py:208
def generate_td_waveform(self, **parameters)
Perform parameter check, choose lalsimulation generator based on domain and conditioning subroutines.
Definition: waveform.py:311
def generate_fd_modes(self, **parameters)
Perform parameter check, choose lalsimulation generator based on domain and attempt to generate the w...
Definition: waveform.py:454
def generate_td_modes(self, **parameters)
Perform parameter check, choose lalsimulation generator based on domain and attempt to generate the w...
Definition: waveform.py:393
def __init__(self, approximant, **kwargs)
Initialize class.
Definition: waveform.py:224
def generate_fd_waveform(self, **parameters)
Perform parameter check, choose lalsimulation generator based on domain and conditioning subroutines.
Definition: waveform.py:352
def to_gwpy_dict(mode_dict, **kwargs)
Function to convert a mode dictionary of lal series to a dictionary of gwpy series.
Definition: waveform.py:572
def GenerateFDModes(parameter_dict, generator)
Function to generate frequency domain gravitational wave modes.
Definition: waveform.py:709
def GenerateTDModes(parameter_dict, generator)
Function to generate time domain gravitational wave modes.
Definition: waveform.py:683
def conditioning_generator(generator)
Given a generator with contioning as 1, return function that generates conditioned waveforms.
Definition: waveform.py:504
def GenerateTDWaveform(parameter_dict, generator)
Generate Waveform Polarizations and Modes functions ##################################.
Definition: waveform.py:609
def to_gwpy_Series(h, f0=0., **kwargs)
Function to convert a lal series to a gwpy series.
Definition: waveform.py:541
def GenerateFDWaveform(parameter_dict, generator)
Function to generate frequency domain gravitational wave polarizations.
Definition: waveform.py:646