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
LALSimulation.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2008 J. Creighton, T. Creighton
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with with program; see the file COPYING. If not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA
18 */
19
20/*
21 * ============================================================================
22 *
23 * Preamble
24 *
25 * ============================================================================
26 */
27
28
29#include <math.h>
30#include <gsl/gsl_sf_expint.h>
31#include <lal/LALSimulation.h>
32#include <lal/LALDetectors.h>
33#include <lal/DetResponse.h>
34#include <lal/Date.h>
35#include <lal/Units.h>
36#include <lal/TimeDelay.h>
37#include <lal/SkyCoordinates.h>
38#include <lal/TimeSeries.h>
39#include <lal/TimeSeriesInterp.h>
40#include <lal/FrequencySeries.h>
41#include <lal/TimeFreqFFT.h>
42#include <lal/Window.h>
43#include "check_series_macros.h"
44
45/*
46 * ============================================================================
47 *
48 * Utilities
49 *
50 * ============================================================================
51 */
52
53
54/*
55 * Note: this function will round really really large numbers "up" to 0.
56 */
57
58
59static unsigned long round_up_to_power_of_two(unsigned long x)
60{
61 unsigned n;
62
63 x--;
64 /* if x shares bits with x + 1, then x + 1 is not a power of 2 */
65 for(n = 1; n && (x & (x + 1)); n *= 2)
66 x |= x >> n;
67
68 return x + 1;
69}
70
71
72/**
73 * @brief Turn a detector prefix string into a LALDetector structure.
74 * @details
75 * The first two characters of the input string are used as the instrument
76 * name, which allows channel names in the form `H1:LSC-STRAIN` to be used.
77 * The return value is a pointer into the lalCachedDetectors array, so
78 * modifications to the contents are global. Make a copy of the structure if
79 * you want to modify it safely.
80 * @param[in] string The detector prefix string.
81 * @returns
82 * A cached LALDetector structure corresponding to the supplied prefix.
83 * @retval NULL Unrecognized prefix string.
84 */
86 const char *string
87)
88{
89 int i;
90
91 for(i = 0; i < LAL_NUM_DETECTORS; i++)
92 if(!strncmp(string, lalCachedDetectors[i].frDetector.prefix, 2))
93 return &lalCachedDetectors[i];
94
95 XLALPrintError("%s(): error: can't identify instrument from string \"%s\"\n", __func__, string);
97}
98
99
100/*
101 * ============================================================================
102 *
103 * Injection Machinery
104 *
105 * ============================================================================
106 */
107
108
109#if 0
110REAL8TimeSeries * XLALSimQuasiPeriodicInjectionREAL8TimeSeries( REAL8TimeSeries *aplus, REAL8TimeSeries *across, REAL8TimeSeries *frequency, REAL8TimeSeries *phi, REAL8TimeSeries *psi, SkyPosition *position, LALDetector *detector, EphemerisData *ephemerides, LIGOTimeGPS *start, REAL8 deltaT, UINT4 length, COMPLEX16FrequencySeries *response )
111{
112 REAL8 slowDeltaT = 600.0; /* 10 minutes */
113 UINT4 slowLength;
114
115 /* sanity checks */
116
117 /* SET UP LOOK-UP TABLES */
118
119 /* construct time-dependent time-delay look-up table */
120 /* uses ephemeris */
121 /* sampled slowDeltaT */
122 /* starts 8 min before injection start, ends 8 min after injection end */
123 slowLength = floor(0.5 + (length*deltaT + 16.0)/slowDeltaT);
124
125 /* construct time-dependent polarization response look-up table */
126 /* sampled at slowDeltaT */
127 /* starts 8 min before injection start, ends 8 min after injection end */
128 fplus;
129 fcross;
130
131
132
133 /* apply time-dependent time-delay */
134 /* apply time-dependent polarization response */
135 /* apply frequency-dependent detector response */
136 for ( j = 0; j < injection->length; ++j ) {
137 REAL8 dt;
138 REAL8 fpl;
139 REAL8 fcr;
140 REAL8 apl;
141 REAL8 acr;
142 REAL8 freq;
143 REAL8 phase;
144 REAL8 pol;
145
146 /* interpolate to get dt, fpl, fcr */
147
148 /* interpolate to get apl, acr, freq, phase, pol */
149 /* but not at detector time: detector time plus dt */
150
151 /* rotate fpl and fcr using pol */
152
153 /* adjust apl, acr, and phase by detector response at freq */
154
155 injection->data->data[j] = fpl*apl*cos(phase) + fcr*acr*sin(phase); /* or something like this */
156
157 }
158
159
160
161}
162#endif
163
164
167 double T; /*armlen/(c*deltaT)*/
168 double armcos;
169};
170
171
172/**
173 * This kernel function incorporates beyond-long-wavelength effects.
174 * The derivation of the formula is summarized in
175 * Soichiro Morisaki, "Kernel function for injection of signals with short
176 * wavelength", LIGO-T1800394.
177 */
178static void highfreq_kernel(double *cached_kernel, int kernel_length, double residual, void *data)
179{
180 struct highfreq_kernel_data *kernel_data = data;
181 double welch_factor = kernel_data->welch_factor;
182 double T = kernel_data->T;
183 double armcos = kernel_data->armcos;
184 double *stop = cached_kernel + kernel_length;
185 int i;
186
187 for(i = -(kernel_length - 1) / 2; cached_kernel < stop; i++) {
188 double x = i + residual;
189 double y = welch_factor * x;
190 if(fabs(y) < 1.) {
191 double Si1 = gsl_sf_Si(LAL_PI * (x + T * armcos));
192 double Si2 = gsl_sf_Si(LAL_PI * (x + T));
193 double Si3 = gsl_sf_Si(LAL_PI * (x - T));
194 *cached_kernel++ = ((Si2 - Si1) / (T * (1. - armcos)) + (Si1 - Si3) / (T * (1. + armcos))) * (1. - y * y) / LAL_TWOPI;
195 } else
196 *cached_kernel++ = 0.;
197 }
198};
199
200
201/**
202 * @brief Transforms the waveform polarizations into a detector strain
203 * @details
204 * This routine takes the plus and cross waveform polarizations, along
205 * with the sky position, polarization angle, and detector structure,
206 * and computes the external strain on the detector.
207 *
208 * The input time series should have their epochs set to the start of
209 * those time series at the geocetre (for simplicity the epochs must be
210 * the same, and they must have the same length and sample rates)
211 *
212 * @param[in] hplus Pointer to a REAL8TimeSeries containing the plus polarization waveform
213 * @param[in] hcross Pointer to a REAL8TimeSeries containing the cross polarization waveform
214 * @param[in] right_ascension The right ascension of the source in radians
215 * @param[in] declination The declination of the source in radians
216 * @param[in] psi The polarization angle giving the orientation of the wave co-ordinate system in radians
217 * @param[in] detector Pointer to a LALDetector structure for the detector into which the injection is destined to be injected
218 *
219 * @returns
220 * The strain time series as seen in the detector, with the epoch set to
221 * the start of the time series at that detector. The output time series
222 * units are the same as the two input time series (which must both have
223 * the same sample units).
224 *
225 * @retval NULL Failure
226 *
227 * @note
228 * A 19-sample Welch-windowed sinc kernel is used for sub-sample
229 * interpolation. See XLALREAL8TimeSeriesInterpEval() for more
230 * information, and consider the frequency response of this kernel when
231 * using this function with injections whose frequency content approaches
232 * the Nyquist frequency.
233 * @n@n
234 * The geometric delay and antenna response are only recalculated every 250
235 * ms --- the Earth's rotation is modelled as discontinuous jumps occurring
236 * at a rate of 4 Hz. The Earth rotates at 7e-5 rad/s, therefore given a
237 * radius of 6e6 m and c=3e8 m/s, the maximum geometric speed for points on
238 * the surface is about 1.5 us/s. Updating the detector response and
239 * geometric delay every 250 ms means the antenna response is accurate to
240 * about +/- 20 urad and the geometric delay to about +/- 300 ns (about
241 * 0.01 sample at 32 kHz). Because we use UTC (instead of UT1) sidereal
242 * time is only accurate to +/- 900 ms, so assuming the Earth's orientation
243 * to be fixed for 250 ms at a time is not the dominant source of Earth
244 * orientation error in these calculations, but one should be aware of the
245 * periodic nature of the updates if extreme phase stability is required.
246 * @n@n
247 * The output time series is padded to capture the interpolation kernel
248 * structure resulting from possible sharp edges at the start or end of the
249 * input time series data. Neglecting the padding for the interpolation
250 * kernel's impulse response, the output time series is, in general, not
251 * the same duration as the input time series due to Doppler compression or
252 * resulting from Earth rotation.
253 */
255 const REAL8TimeSeries *hplus,
256 const REAL8TimeSeries *hcross,
257 REAL8 right_ascension,
258 REAL8 declination,
259 REAL8 psi,
260 const LALDetector *detector
261)
262{
263 /* mean arm length in samples */
264 const double arm_length_samples = (detector->frDetector.xArmMidpoint + detector->frDetector.yArmMidpoint) / (LAL_C_SI * hplus->deltaT);
265 /* kernel length in samples. increase by 28 times the arm length
266 * to accomodate the additional signal delay. */
267 const int kernel_length = 67 + 48 * lround(2.0 * arm_length_samples);
268 /* 0.25 s or 1 sample whichever is larger */
269 const unsigned det_resp_interval = round(0.25 / hplus->deltaT) < 1 ? 1 : round(0.25 / hplus->deltaT);
270 REAL8TimeSeries *xsignal = NULL;
271 REAL8TimeSeries *ysignal = NULL;
272 LALREAL8TimeSeriesInterp *xinterp = NULL;
273 LALREAL8TimeSeriesInterp *yinterp = NULL;
274 struct highfreq_kernel_data xdata;
275 struct highfreq_kernel_data ydata;
276 double fxplus = XLAL_REAL8_FAIL_NAN;
277 double fxcross = XLAL_REAL8_FAIL_NAN;
278 double fyplus = XLAL_REAL8_FAIL_NAN;
279 double fycross = XLAL_REAL8_FAIL_NAN;
280 double geometric_delay = XLAL_REAL8_FAIL_NAN;
281 LIGOTimeGPS t; /* a time */
282 double dt; /* an offset */
283 char *name;
284 REAL8TimeSeries *h = NULL;
285 unsigned i;
286
287 /* check input */
288
289 LAL_CHECK_VALID_SERIES(hplus, NULL);
290 LAL_CHECK_VALID_SERIES(hcross, NULL);
291 LAL_CHECK_CONSISTENT_TIME_SERIES(hplus, hcross, NULL);
292 /* test that the input's length can be treated as a signed valued
293 * without overflow, and that adding the kernel length plus an
294 * Earth diameter's worth of samples won't overflow */
295 if((int) hplus->data->length < 0 || (int) (hplus->data->length + kernel_length + 2.0 * LAL_REARTH_SI / LAL_C_SI / hplus->deltaT) < 0) {
296 XLALPrintError("%s(): error: input series too long\n", __func__);
298 }
299
300 /* generate name */
301
302 name = XLALMalloc(strlen(detector->frDetector.prefix) + 11);
303 if(!name)
304 goto error;
305 sprintf(name, "%s injection", detector->frDetector.prefix);
306
307 /* allocate output time series. the time series' duration is
308 * adjusted to account for Doppler-induced dilation of the
309 * waveform, and is padded to accomodate ringing of the
310 * interpolation kernel. the sign of dt follows from the
311 * observation that time stamps in the output time series are
312 * mapped to time stamps in the input time series by adding the
313 * output of XLALTimeDelayFromEarthCenter(), so if that number is
314 * larger at the start of the waveform than at the end then the
315 * output time series must be longer than the input. (the Earth's
316 * rotation is not super-luminal so we don't have to account for
317 * time reversals in the mapping) */
318
319 /* time (at geocentre) of end of waveform */
320 t = hplus->epoch;
321 if(!XLALGPSAdd(&t, hplus->data->length * hplus->deltaT)) {
322 XLALFree(name);
323 goto error;
324 }
325 /* change in geometric delay from start to end */
326 dt = XLALTimeDelayFromEarthCenter(detector->location, right_ascension, declination, &hplus->epoch) - XLALTimeDelayFromEarthCenter(detector->location, right_ascension, declination, &t);
327 /* allocate, lengthen sequence to incorporate time delay caused by
328 * beyond-long-wavelength effect */
329 h = XLALCreateREAL8TimeSeries(name, &hplus->epoch, hplus->f0, hplus->deltaT, &hplus->sampleUnits, (int) hplus->data->length + kernel_length - 1 + ceil(dt / hplus->deltaT) + lround(4.0 * arm_length_samples));
330 XLALFree(name);
331 if(!h)
332 goto error;
333
334 /* shift the epoch so that the start of the input time series
335 * passes through this detector at the time of the sample at offset
336 * (kernel_length-1)/2 we assume the kernel is sufficiently short
337 * that it doesn't matter whether we compute the geometric delay at
338 * the start or middle of the kernel. */
339
340 geometric_delay = XLALTimeDelayFromEarthCenter(detector->location, right_ascension, declination, &h->epoch);
341 if(XLAL_IS_REAL8_FAIL_NAN(geometric_delay))
342 goto error;
343 if(!XLALGPSAdd(&h->epoch, geometric_delay - (kernel_length - 1) / 2 * h->deltaT))
344 goto error;
345
346 /* round epoch to an integer sample boundary so that
347 * XLALSimAddInjectionREAL8TimeSeries() can use no-op code path.
348 * note: we assume a sample boundary occurs on the integer second.
349 * if this isn't the case (e.g, time-shifted injections or some GEO
350 * data) that's OK, but we might end up paying for a second
351 * sub-sample time shift when adding the the time series into the
352 * target data stream in XLALSimAddInjectionREAL8TimeSeries().
353 * don't bother checking for errors, this is changing the timestamp
354 * by less than 1 sample, if we're that close to overflowing it'll
355 * be caught in the loop below. */
356
357 dt = XLALGPSModf(&dt, &h->epoch);
358 XLALGPSAdd(&h->epoch, round(dt / h->deltaT) * h->deltaT - dt);
359
360 /* Compute signals at the times of samples in hplus in advance.
361 * It reduces the computational cost for interpolation */
362
363 xsignal = XLALCreateREAL8TimeSeries("xsignal", &hplus->epoch, hplus->f0, hplus->deltaT, &hplus->sampleUnits, (int) hplus->data->length);
364 ysignal = XLALCreateREAL8TimeSeries("ysignal", &hplus->epoch, hplus->f0, hplus->deltaT, &hplus->sampleUnits, (int) hplus->data->length);
365 for(i = 0; i < hplus->data->length; i++) {
366 t = hplus->epoch;
367 if(!XLALGPSAdd(&t, i * hplus->deltaT))
368 goto error;
369 /* Compute detector's response. Here the geometric delay
370 * from geocenter is neglected since it is small compared
371 * to the rotational period of the Earth */
372 if(!(i % det_resp_interval)) {
373 double armlen = XLAL_REAL8_FAIL_NAN;
374 double xcos = XLAL_REAL8_FAIL_NAN;
375 double ycos = XLAL_REAL8_FAIL_NAN;
376 XLALComputeDetAMResponseParts(&armlen, &xcos, &ycos, &fxplus, &fyplus, &fxcross, &fycross, detector, right_ascension, declination, psi, XLALGreenwichMeanSiderealTime(&t));
377 }
378 xsignal->data->data[i] = fxplus * hplus->data->data[i] + fxcross * hcross->data->data[i];
379 ysignal->data->data[i] = fyplus * hplus->data->data[i] + fycross * hcross->data->data[i];
380 if(XLAL_IS_REAL8_FAIL_NAN(xsignal->data->data[i]) || XLAL_IS_REAL8_FAIL_NAN(ysignal->data->data[i]))
381 goto error;
382 }
383
384 /* initialize interpolators. */
385
386 /* use filtering interpolators. see TimeSeriesInterp.c for
387 * meaning of welch_factor */
388 xdata.welch_factor = ydata.welch_factor = 1.0 / ((kernel_length - 1.) / 2. + 1.);
389 xinterp = XLALREAL8TimeSeriesInterpCreate(xsignal, kernel_length, highfreq_kernel, &xdata);
390 yinterp = XLALREAL8TimeSeriesInterpCreate(ysignal, kernel_length, highfreq_kernel, &ydata);
391 if(!xinterp || !yinterp)
392 goto error;
393
394 /* compute output sample by sample */
395 /* FIXME: Now xdata and ydata are not renewed until geometric delay
396 * changes significantly. This can cause systematic errors. For
397 * example, if the detector is on the North pole, xdata and ydata
398 * are never renewed although armcos can be changing. */
399
400 for(i = 0; i < h->data->length; i++) {
401 /* time of sample in detector */
402 t = h->epoch;
403 if(!XLALGPSAdd(&t, i * h->deltaT))
404 goto error;
405
406 /* geometric delay from geocentre and highfreq_kernel_data */
407 if(!(i % det_resp_interval)) {
408 geometric_delay = -XLALTimeDelayFromEarthCenter(detector->location, right_ascension, declination, &t);
409 /* Compute highfreq_kernel_data */
410 double armlen = XLAL_REAL8_FAIL_NAN;
411 XLALComputeDetAMResponseParts(&armlen, &xdata.armcos, &ydata.armcos, &fxplus, &fyplus, &fxcross, &fycross, detector, right_ascension, declination, psi, XLALGreenwichMeanSiderealTime(&t));
412 armlen /= LAL_C_SI * h->deltaT;
413 xdata.T = armlen;
414 ydata.T = armlen;
415 }
416 if(XLAL_IS_REAL8_FAIL_NAN(geometric_delay))
417 goto error;
419 goto error;
420
421 /* time of sample at geocentre */
422 if(!XLALGPSAdd(&t, geometric_delay))
423 goto error;
424
425 /* evaluate linear combination of interpolators */
426 h->data->data[i] = XLALREAL8TimeSeriesInterpEval(xinterp, &t, 0) + XLALREAL8TimeSeriesInterpEval(yinterp, &t, 0);
427
429 goto error;
430 }
431
432 /* done */
437 return h;
438
439error:
446}
447
448
449/**
450 * @brief Adds a detector strain time series to detector data.
451 * @details
452 * Essentially a wrapper for XLALAddREAL8TimeSeries(), but performs
453 * sub-sample re-interpolation to adjust the source time series epoch to
454 * lie on an integer sample boundary in the target time series. This
455 * transformation is done in the frequency domain, so it is convenient to
456 * allow a response function to be applied at the same time. Passing NULL
457 * for the response function turns this feature off (i.e., uses a unit
458 * response).
459 *
460 * This function accepts source and target time series whose units are not
461 * the same, and allows the two time series to be herterodyned (although it
462 * currently requires them to have the same heterodyne frequency).
463 *
464 * @param[in,out] target Pointer to the time series into which the strain will
465 * be added
466 *
467 * @param[in,out] h Pointer to the time series containing the detector strain
468 * (the strain data is modified by this routine)
469 *
470 * @param[in] response Pointer to the response function transforming strain to
471 * detector output units, or NULL for unit response.
472 *
473 * @retval 0 Success
474 * @retval <0 Failure
475 *
476 * @attention
477 * The source time series is modified in place by this function!
478 */
480 REAL8TimeSeries *target,
482 const COMPLEX16FrequencySeries *response
483)
484{
485 /* 1 ns is about 10^-5 samples at 16384 Hz */
486 const double noop_threshold = 1e-4; /* samples */
487 /* the source time series is padded with at least this many 0's at
488 * the start and end before re-interpolation in an attempt to
489 * suppress aperiodicity artifacts, and 1/2 this many samples is
490 * clipped from the start and end afterwards */
491 const unsigned aperiodicity_suppression_buffer = 32768;
492 double start_sample_int;
493 double start_sample_frac;
494
495 /* check input */
496
497 /* FIXME: since we route the source time series through the
498 * frequency domain, it might not be hard to adjust the heterodyne
499 * frequency and sample rate to match the target time series
500 * instead of making it an error if they don't match. */
501
502 if(h->deltaT != target->deltaT || h->f0 != target->f0) {
503 XLALPrintError("%s(): error: input sample rates or heterodyne frequencies do not match\n", __func__);
505 }
506
507 /* compute the integer and fractional parts of the sample index in
508 * the target time series on which the source time series begins.
509 * modf() returns integer and fractional parts that have the same
510 * sign, e.g. -3.9 --> -3 + -0.9. we adjust these so that the
511 * magnitude of the fractional part is not greater than 0.5, e.g.
512 * -3.9 --> -4 + 0.1, so that we never do more than 1/2 a sample of
513 * re-interpolation. I don't know if really makes any difference,
514 * though */
515
516 start_sample_frac = modf(XLALGPSDiff(&h->epoch, &target->epoch) / target->deltaT, &start_sample_int);
517 if(start_sample_frac < -0.5) {
518 start_sample_frac += 1.0;
519 start_sample_int -= 1.0;
520 } else if(start_sample_frac > +0.5) {
521 start_sample_frac -= 1.0;
522 start_sample_int += 1.0;
523 }
524
525 /* perform sub-sample interpolation if needed */
526
527 if(fabs(start_sample_frac) > noop_threshold || response) {
529 REAL8FFTPlan *plan;
530 REAL8Window *window;
531 unsigned i;
532
533 /* extend the source time series by adding the
534 * "aperiodicity padding" to the start and end. for
535 * efficiency's sake, make sure the new length is a power
536 * of two, and don't forget to adjust the start index in
537 * the target time series. */
538
539 i = round_up_to_power_of_two(h->data->length + 2 * aperiodicity_suppression_buffer);
540 if(i < h->data->length) {
541 /* integer overflow */
542 XLALPrintError("%s(): error: source time series too long\n", __func__);
544 }
545 start_sample_int -= (i - h->data->length) / 2;
546 if(!XLALResizeREAL8TimeSeries(h, -(int) (i - h->data->length) / 2, i))
548
549 /* transform source time series to frequency domain. the
550 * FFT function populates the frequency series' metadata
551 * with the appropriate values. */
552
553 tilde_h = XLALCreateCOMPLEX16FrequencySeries(NULL, &h->epoch, 0, 0, &lalDimensionlessUnit, h->data->length / 2 + 1);
555 if(!tilde_h || !plan) {
559 }
560 i = XLALREAL8TimeFreqFFT(tilde_h, h, plan);
562 if(i) {
565 }
566
567 /* apply sub-sample time correction and optional response
568 * function */
569
570 for(i = 0; i < tilde_h->data->length; i++) {
571 const double f = tilde_h->f0 + i * tilde_h->deltaF;
572 COMPLEX16 fac;
573
574 /* phase for sub-sample time correction */
575
576 fac = cexp(-I * LAL_TWOPI * f * start_sample_frac * target->deltaT);
577
578 /* divide the source by the response function. if
579 * a frequency is required that lies outside the
580 * domain of definition of the response function,
581 * then the response is assumed equal to its value
582 * at the nearest edge of the domain of definition.
583 * within the domain of definition, frequencies are
584 * rounded to the nearest bin. if the response
585 * function is zero in some bin, then the source
586 * data is zeroed in that bin (instead of dividing
587 * by 0). */
588
589 /* FIXME: should we use GSL to construct an
590 * interpolator for the modulus and phase as
591 * functions of frequency, and use that to evaluate
592 * the response? instead of rounding to nearest
593 * bin? */
594
595 if(response) {
596 int j = floor((f - response->f0) / response->deltaF + 0.5);
597 if(j < 0)
598 j = 0;
599 else if((unsigned) j > response->data->length - 1)
600 j = response->data->length - 1;
601 if(response->data->data[j] == 0.0)
602 fac = 0.0;
603 else
604 fac /= response->data->data[j];
605 }
606
607 /* apply factor */
608
609 tilde_h->data->data[i] *= fac;
610 }
611
612 /* adjust DC and Nyquist components. the DC component must
613 * always be real-valued. because we have adjusted the
614 * source time series to have a length that is an even
615 * integer (we've made it a power of 2) the Nyquist
616 * component must also be real valued. */
617
618 if(response) {
619 /* a response function has been provided. zero the
620 * DC and Nyquist components */
621 if(tilde_h->f0 == 0.0)
622 tilde_h->data->data[0] = 0.0;
623 tilde_h->data->data[tilde_h->data->length - 1] = 0.0;
624 } else {
625 /* no response has been provided. set the phase of
626 * the DC component to 0, set the imaginary
627 * component of the Nyquist to 0 */
628 if(tilde_h->f0 == 0.0)
629 tilde_h->data->data[0] = cabs(tilde_h->data->data[0]);
630 tilde_h->data->data[tilde_h->data->length - 1] = creal(tilde_h->data->data[tilde_h->data->length - 1]);
631 }
632
633 /* return to time domain */
634
636 if(!plan) {
639 }
640 i = XLALREAL8FreqTimeFFT(h, tilde_h, plan);
643 if(i)
645
646 /* the deltaT can get "corrupted" by floating point
647 * round-off during its trip through the frequency domain.
648 * since this function starts by confirming that the sample
649 * rate of the source matches that of the target time
650 * series, we can use the target series' sample rate to
651 * reset the source's sample rate to its original value.
652 * but we do a check to make sure we're not masking a real
653 * bug */
654
655 if(fabs(h->deltaT - target->deltaT) / target->deltaT > 1e-12) {
656 XLALPrintError("%s(): error: oops, internal sample rate mismatch\n", __func__);
658 }
659 h->deltaT = target->deltaT;
660
661 /* set source epoch from target epoch and integer sample
662 * offset */
663
664 h->epoch = target->epoch;
665 XLALGPSAdd(&h->epoch, start_sample_int * target->deltaT);
666
667 /* clip half of the "aperiodicity padding" from the start
668 * and end of the source time series in a continuing effort
669 * to suppress aperiodicity artifacts. */
670
671 if(!XLALResizeREAL8TimeSeries(h, aperiodicity_suppression_buffer / 2, h->data->length - aperiodicity_suppression_buffer))
673
674 /* apply a Tukey window whose tapers lie within the
675 * remaining aperiodicity padding. leaving one sample of
676 * the aperiodicty padding untouched on each side of the
677 * original time series because the data might have been
678 * shifted into it */
679
680 window = XLALCreateTukeyREAL8Window(h->data->length, (double) (aperiodicity_suppression_buffer - 2) / h->data->length);
681 if(!window)
683 for(i = 0; i < h->data->length; i++)
684 h->data->data[i] *= window->data->data[i];
686 }
687
688 /* add source time series to target time series */
689
690 if(!XLALAddREAL8TimeSeries(target, h))
692
693 /* done */
694
695 return 0;
696}
697
698
699/**
700 * @brief Adds a detector strain time series to detector data.
701 * @details
702 * Essentially a wrapper for XLALAddREAL4TimeSeries(), but performs
703 * sub-sample re-interpolation to adjust the source time series epoch to
704 * lie on an integer sample boundary in the target time series. This
705 * transformation is done in the frequency domain, so it is convenient to
706 * allow a response function to be applied at the same time. Passing NULL
707 * for the response function turns this feature off (i.e., uses a unit
708 * response).
709 *
710 * This function accepts source and target time series whose units are not
711 * the same, and allows the two time series to be herterodyned (although it
712 * currently requires them to have the same heterodyne frequency).
713 *
714 * @param[in,out] target Pointer to the time series into which the strain will
715 * be added
716 *
717 * @param[in,out] h Pointer to the time series containing the detector strain
718 * (the strain data is modified by this routine)
719 *
720 * @param[in] response Pointer to the response function transforming strain to
721 * detector output units, or NULL for unit response.
722 *
723 * @retval 0 Success
724 * @retval <0 Failure
725 *
726 * @attention
727 * The source time series is modified in place by this function!
728 */
730 REAL4TimeSeries *target,
732 const COMPLEX8FrequencySeries *response
733)
734{
735 /* 1 ns is about 10^-5 samples at 16384 Hz */
736 const double noop_threshold = 1e-4; /* samples */
737 /* the source time series is padded with at least this many 0's at
738 * the start and end before re-interpolation in an attempt to
739 * suppress aperiodicity artifacts, and 1/2 this many samples is
740 * clipped from the start and end afterwards */
741 const unsigned aperiodicity_suppression_buffer = 32768;
742 double start_sample_int;
743 double start_sample_frac;
744
745 /* check input */
746
747 /* FIXME: since we route the source time series through the
748 * frequency domain, it might not be hard to adjust the heterodyne
749 * frequency and sample rate to match the target time series
750 * instead of making it an error if they don't match. */
751
752 if(h->deltaT != target->deltaT || h->f0 != target->f0) {
753 XLALPrintError("%s(): error: input sample rates or heterodyne frequencies do not match\n", __func__);
755 }
756
757 /* compute the integer and fractional parts of the sample index in
758 * the target time series on which the source time series begins.
759 * modf() returns integer and fractional parts that have the same
760 * sign, e.g. -3.9 --> -3 + -0.9. we adjust these so that the
761 * magnitude of the fractional part is not greater than 0.5, e.g.
762 * -3.9 --> -4 + 0.1, so that we never do more than 1/2 a sample of
763 * re-interpolation. I don't know if really makes any difference,
764 * though */
765
766 start_sample_frac = modf(XLALGPSDiff(&h->epoch, &target->epoch) / target->deltaT, &start_sample_int);
767 if(start_sample_frac < -0.5) {
768 start_sample_frac += 1.0;
769 start_sample_int -= 1.0;
770 } else if(start_sample_frac > +0.5) {
771 start_sample_frac -= 1.0;
772 start_sample_int += 1.0;
773 }
774
775 /* perform sub-sample interpolation if needed */
776
777 if(fabs(start_sample_frac) > noop_threshold || response) {
779 REAL4FFTPlan *plan;
780 REAL4Window *window;
781 unsigned i;
782
783 /* extend the source time series by adding the "aperiodicity
784 * padding" to the start and end. for efficiency's sake, make sure
785 * the new length is a power of two, and don't forget to adjust the
786 * start index in the target time series. */
787
788 i = round_up_to_power_of_two(h->data->length + 2 * aperiodicity_suppression_buffer);
789 if(i < h->data->length) {
790 /* integer overflow */
791 XLALPrintError("%s(): error: source time series too long\n", __func__);
793 }
794 start_sample_int -= (i - h->data->length) / 2;
795 if(!XLALResizeREAL4TimeSeries(h, -(int) (i - h->data->length) / 2, i))
797
798 /* transform source time series to frequency domain. the FFT
799 * function populates the frequency series' metadata with the
800 * appropriate values. */
801
802 tilde_h = XLALCreateCOMPLEX8FrequencySeries(NULL, &h->epoch, 0, 0, &lalDimensionlessUnit, h->data->length / 2 + 1);
804 if(!tilde_h || !plan) {
808 }
809 i = XLALREAL4TimeFreqFFT(tilde_h, h, plan);
811 if(i) {
814 }
815
816 /* apply sub-sample time correction and optional response function
817 * */
818
819 for(i = 0; i < tilde_h->data->length; i++) {
820 const double f = tilde_h->f0 + i * tilde_h->deltaF;
821 COMPLEX8 fac;
822
823 /* phase for sub-sample time correction */
824
825 fac = cexp(-I * LAL_TWOPI * f * start_sample_frac * target->deltaT);
826
827 /* divide the source by the response function. if a
828 * frequency is required that lies outside the domain of
829 * definition of the response function, then the response
830 * is assumed equal to its value at the nearest edge of the
831 * domain of definition. within the domain of definition,
832 * frequencies are rounded to the nearest bin. if the
833 * response function is zero in some bin, then the source
834 * data is zeroed in that bin (instead of dividing by 0).
835 * */
836
837 /* FIXME: should we use GSL to construct an interpolator
838 * for the modulus and phase as functions of frequency, and
839 * use that to evaluate the response? instead of rounding
840 * to nearest bin? */
841
842 if(response) {
843 int j = floor((f - response->f0) / response->deltaF + 0.5);
844 if(j < 0)
845 j = 0;
846 else if((unsigned) j > response->data->length - 1)
847 j = response->data->length - 1;
848 if(response->data->data[j] == 0.0)
849 fac = 0.0;
850 else
851 fac /= response->data->data[j];
852 }
853
854 /* apply factor */
855
856 tilde_h->data->data[i] *= fac;
857 }
858
859 /* adjust DC and Nyquist components. the DC component must always
860 * be real-valued. because we have adjusted the source time series
861 * to have a length that is an even integer (we've made it a power
862 * of 2) the Nyquist component must also be real valued. */
863
864 if(response) {
865 /* a response function has been provided. zero the DC and
866 * Nyquist components */
867 if(tilde_h->f0 == 0.0)
868 tilde_h->data->data[0] = 0.0;
869 tilde_h->data->data[tilde_h->data->length - 1] = 0.0;
870 } else {
871 /* no response has been provided. set the phase of the DC
872 * component to 0, set the imaginary component of the
873 * Nyquist to 0 */
874 if(tilde_h->f0 == 0.0)
875 tilde_h->data->data[0] = cabsf(tilde_h->data->data[0]);
876 tilde_h->data->data[tilde_h->data->length - 1] = crealf(tilde_h->data->data[tilde_h->data->length - 1]);
877 }
878
879 /* return to time domain */
880
882 if(!plan) {
885 }
886 i = XLALREAL4FreqTimeFFT(h, tilde_h, plan);
889 if(i)
891
892 /* the deltaT can get "corrupted" by floating point round-off
893 * during its trip through the frequency domain. since this
894 * function starts by confirming that the sample rate of the source
895 * matches that of the target time series, we can use the target
896 * series' sample rate to reset the source's sample rate to its
897 * original value. but we do a check to make sure we're not
898 * masking a real bug */
899
900 if(fabs(h->deltaT - target->deltaT) / target->deltaT > 1e-12) {
901 XLALPrintError("%s(): error: oops, internal sample rate mismatch\n", __func__);
903 }
904 h->deltaT = target->deltaT;
905
906 /* set source epoch from target epoch and integer sample offset */
907
908 h->epoch = target->epoch;
909 XLALGPSAdd(&h->epoch, start_sample_int * target->deltaT);
910
911 /* clip half of the "aperiodicity padding" from the start and end
912 * of the source time series in a continuing effort to suppress
913 * aperiodicity artifacts. */
914
915 if(!XLALResizeREAL4TimeSeries(h, aperiodicity_suppression_buffer / 2, h->data->length - aperiodicity_suppression_buffer))
917
918 /* apply a Tukey window whose tapers lie within the remaining
919 * aperiodicity padding. leaving one sample of the aperiodicty
920 * padding untouched on each side of the original time series
921 * because the data might have been shifted into it */
922
923 window = XLALCreateTukeyREAL4Window(h->data->length, (double) (aperiodicity_suppression_buffer - 2) / h->data->length);
924 if(!window)
926 for(i = 0; i < h->data->length; i++)
927 h->data->data[i] *= window->data->data[i];
929 }
930
931 /* add source time series to target time series */
932
933 if(!XLALAddREAL4TimeSeries(target, h))
935
936 /* done */
937
938 return 0;
939}
940
941
942
943
944/* Helper routine that computes a segment of strain data with a single
945 * time delay and beam pattern applied to the whole segment. The duration
946 * of the segment must therefore be reasonably short or else the movement
947 * of the earth will invalidate the use of a single time shift and beam
948 * pattern for the entire segment. */
950 REAL8TimeSeries *segment,
951 const REAL8TimeSeries *hplus,
952 const REAL8TimeSeries *hcross,
955 REAL8FFTPlan *fwdplan,
956 REAL8FFTPlan *revplan,
957 REAL8Window *window,
958 double ra,
959 double dec,
960 double psi,
962 const COMPLEX16FrequencySeries *response
963)
964{
965 LIGOTimeGPS t;
966 double gmst;
967 double xcos;
968 double ycos;
969 double fxplus;
970 double fyplus;
971 double fxcross;
972 double fycross;
973 double armlen;
974 double deltaT;
975 double offint;
976 double offrac;
977 int offset;
978 int j;
979 size_t k;
980
981 /* this routine assumes the segment has a length of N points where N is
982 * a power of two and that the workspace frequency series and the FFT
983 * plans are compatible with the size N; these assumptions are not
984 * checked: the calling routine must ensure that they are true */
985
986 /* compute fplus, fcross, and time delay from earth's center at the
987 * time corresponding to the middle of the segment */
988
989 t = segment->epoch;
990 XLALGPSAdd(&t, 0.5 * segment->data->length * segment->deltaT);
992 XLALComputeDetAMResponseParts(&armlen, &xcos, &ycos, &fxplus, &fyplus,
993 &fxcross, &fycross, detector, ra, dec, psi, gmst);
994 deltaT = XLALTimeDelayFromEarthCenter(detector->location, ra, dec, &t);
995
996 /* add to the geometric delay the difference in time between the
997 * beginning of the injection timeseries and the beginning of the
998 * segment */
999
1000 deltaT += XLALGPSDiff(&hplus->epoch, &segment->epoch);
1001
1002 /* compute the integer and fractional parts of the sample index in the
1003 * segment on which the hplus and hcross time series begins: modf()
1004 * returns integer and fractional parts that have the same sign, e.g.,
1005 * -3.9 --> -3 + -0.9, and we adjust these so that magnitude of the
1006 * fractional part is not greater than 0.5, e.g., -3.9 --> -4.0 + 0.1,
1007 * so that we never do more than 1/2 a sample of re-interpolation */
1008
1009 offrac = modf(deltaT / segment->deltaT, &offint);
1010 if (offrac < -0.5) {
1011 offrac += 1.0;
1012 offint -= 1.0;
1013 } else if (offrac > 0.5) {
1014 offrac -= 1.0;
1015 offint += 1.0;
1016 }
1017 offset = offint;
1018
1019 /* now compute the sub-sample time shift that must be applied to the
1020 * segment data */
1021
1022 deltaT = offrac * segment->deltaT;
1023
1024 /* window the date and put it in frequency domain */
1025
1026 for (j = 0; j < (int)segment->data->length; ++j)
1027 if (j >= offset && j < (int)hplus->data->length + offset) {
1028 segment->data->data[j] = window->data->data[j]
1029 * hplus->data->data[j - offset];
1030 } else
1031 segment->data->data[j] = 0.0;
1032
1033 if (XLALREAL8TimeFreqFFT(work1, segment, fwdplan) < 0)
1035
1036 for (j = 0; j < (int)segment->data->length; ++j)
1037 if (j >= offset && j < (int)hcross->data->length + offset) {
1038 segment->data->data[j] = window->data->data[j]
1039 * hcross->data->data[j - offset];
1040 } else
1041 segment->data->data[j] = 0.0;
1042
1043 if (XLALREAL8TimeFreqFFT(work2, segment, fwdplan) < 0)
1045
1046 /* apply sub-sample time shift in frequency domain */
1047
1048 for (k = 0; k < work1->data->length; ++k) {
1049 double f = work1->f0 + k * work1->deltaF;
1050 double beta = f * armlen / LAL_C_SI;
1051 COMPLEX16 Tx, Ty; /* x- and y-arm transfer functions */
1052 COMPLEX16 gplus, gcross;
1053 COMPLEX16 fac;
1054
1055 /* phase for sub-sample time correction */
1056 fac = cexp(-I * LAL_TWOPI * f * deltaT);
1057 if (response)
1058 fac /= response->data->data[k];
1059
1062 gplus = Tx * fxplus + Ty * fyplus;
1063 gcross = Tx * fxcross + Ty * fycross;
1064
1065 work1->data->data[k] *= gplus;
1066 work1->data->data[k] += gcross * work2->data->data[k];
1067 work1->data->data[k] *= fac;
1068 }
1069
1070 /* adjust DC and Nyquist components: the DC component must always be
1071 * real-valued; because the calling routine has made the time series
1072 * have an even length, the Nyquist component must also be real-valued;
1073 * also this routine makes the assumption that both the DC and the
1074 * Nyquist components are zero */
1075
1076 work1->data->data[0] = cabs(work1->data->data[0]);
1077 work1->data->data[work1->data->length - 1] =
1078 creal(work1->data->data[work1->data->length - 1]);
1079
1080 /* return data to time domain */
1081
1082 if (XLALREAL8FreqTimeFFT(segment, work1, revplan) < 0)
1084
1085 return 0;
1086}
1087
1088/* Helper routine that computes a segment of strain data with a single
1089 * time delay and beam pattern applied to the whole segment. The duration
1090 * of the segment must therefore be reasonably short or else the movement
1091 * of the earth will invalidate the use of a single time shift and beam
1092 * pattern for the entire segment. */
1094 REAL4TimeSeries *segment,
1095 const REAL4TimeSeries *hplus,
1096 const REAL4TimeSeries *hcross,
1099 REAL4FFTPlan *fwdplan,
1100 REAL4FFTPlan *revplan,
1101 REAL4Window *window,
1102 double ra,
1103 double dec,
1104 double psi,
1106 const COMPLEX8FrequencySeries *response
1107)
1108{
1109 LIGOTimeGPS t;
1110 double gmst;
1111 double xcos;
1112 double ycos;
1113 double fxplus;
1114 double fyplus;
1115 double fxcross;
1116 double fycross;
1117 double armlen;
1118 double deltaT;
1119 double offint;
1120 double offrac;
1121 int offset;
1122 int j;
1123 size_t k;
1124
1125 /* this routine assumes the segment has a length of N points where N is
1126 * a power of two and that the workspace frequency series and the FFT
1127 * plans are compatible with the size N; these assumptions are not
1128 * checked: the calling routine must ensure that they are true */
1129
1130 /* compute fplus, fcross, and time delay from earth's center at the
1131 * time corresponding to the middle of the segment */
1132
1133 t = segment->epoch;
1134 XLALGPSAdd(&t, 0.5 * segment->data->length * segment->deltaT);
1136 XLALComputeDetAMResponseParts(&armlen, &xcos, &ycos, &fxplus, &fyplus,
1137 &fxcross, &fycross, detector, ra, dec, psi, gmst);
1138 deltaT = XLALTimeDelayFromEarthCenter(detector->location, ra, dec, &t);
1139
1140 /* add to the geometric delay the difference in time between the
1141 * beginning of the injection timeseries and the beginning of the
1142 * segment */
1143
1144 deltaT += XLALGPSDiff(&hplus->epoch, &segment->epoch);
1145
1146 /* compute the integer and fractional parts of the sample index in the
1147 * segment on which the hplus and hcross time series begins: modf()
1148 * returns integer and fractional parts that have the same sign, e.g.,
1149 * -3.9 --> -3 + -0.9, and we adjust these so that magnitude of the
1150 * fractional part is not greater than 0.5, e.g., -3.9 --> -4.0 + 0.1,
1151 * so that we never do more than 1/2 a sample of re-interpolation */
1152
1153 offrac = modf(deltaT / segment->deltaT, &offint);
1154 if (offrac < -0.5) {
1155 offrac += 1.0;
1156 offint -= 1.0;
1157 } else if (offrac > 0.5) {
1158 offrac -= 1.0;
1159 offint += 1.0;
1160 }
1161 offset = offint;
1162
1163 /* now compute the sub-sample time shift that must be applied to the
1164 * segment data */
1165
1166 deltaT = offrac * segment->deltaT;
1167
1168 /* window the date and put it in frequency domain */
1169
1170 for (j = 0; j < (int)segment->data->length; ++j)
1171 if (j >= offset && j < (int)hplus->data->length + offset) {
1172 segment->data->data[j] = window->data->data[j]
1173 * hplus->data->data[j - offset];
1174 } else
1175 segment->data->data[j] = 0.0;
1176
1177 if (XLALREAL4TimeFreqFFT(work1, segment, fwdplan) < 0)
1179
1180 for (j = 0; j < (int)segment->data->length; ++j)
1181 if (j >= offset && j < (int)hcross->data->length + offset) {
1182 segment->data->data[j] = window->data->data[j]
1183 * hcross->data->data[j - offset];
1184 } else
1185 segment->data->data[j] = 0.0;
1186
1187 if (XLALREAL4TimeFreqFFT(work2, segment, fwdplan) < 0)
1189
1190 /* apply sub-sample time shift in frequency domain */
1191
1192 for (k = 0; k < work1->data->length; ++k) {
1193 double f = work1->f0 + k * work1->deltaF;
1194 double beta = f * armlen / LAL_C_SI;
1195 COMPLEX16 Tx, Ty; /* x- and y-arm transfer functions */
1196 COMPLEX16 gplus, gcross;
1197 COMPLEX16 fac;
1198
1199 /* phase for sub-sample time correction */
1200 fac = cexp(-I * LAL_TWOPI * f * deltaT);
1201 if (response)
1202 fac /= response->data->data[k];
1203
1206 gplus = Tx * fxplus + Ty * fyplus;
1207 gcross = Tx * fxcross + Ty * fycross;
1208
1209 work1->data->data[k] *= gplus;
1210 work1->data->data[k] += gcross * work2->data->data[k];
1211 work1->data->data[k] *= fac;
1212 }
1213
1214 /* adjust DC and Nyquist components: the DC component must always be
1215 * real-valued; because the calling routine has made the time series
1216 * have an even length, the Nyquist component must also be real-valued;
1217 * also this routine makes the assumption that both the DC and the
1218 * Nyquist components are zero */
1219
1220 work1->data->data[0] = cabs(work1->data->data[0]);
1221 work1->data->data[work1->data->length - 1] =
1222 creal(work1->data->data[work1->data->length - 1]);
1223
1224 /* return data to time domain */
1225
1226 if (XLALREAL4FreqTimeFFT(segment, work1, revplan) < 0)
1228
1229 return 0;
1230}
1231
1232/**
1233 * @brief Computes strain for a detector and injects into target time series.
1234 * @details This routine takes care of the time-changing time delay from
1235 * the Earth's center and the time-changing antenna response pattern; it
1236 * also accounts for deviations from the long-wavelength limit at high
1237 * frequencies. An optional calibration response function can be provided
1238 * if the output time series is not in strain units.
1239 * @param[in,out] target Time series to inject strain into.
1240 * @param[in] hplus Time series with plus-polarization gravitational waveform.
1241 * @param[in] hcross Time series with cross-polarization gravitational waveform.
1242 * @param[in] ra Right ascension of the source (radians).
1243 * @param[in] dec Declination of the source (radians).
1244 * @param[in] psi Polarization angle of the source (radians).
1245 * @param[in] detector Detector to use when computing strain.
1246 * @param[in] response Response function to use, or NULL if none.
1247 * @retval 0 Success.
1248 * @retval <0 Failure.
1249 */
1251 REAL8TimeSeries *target,
1252 const REAL8TimeSeries *hplus,
1253 const REAL8TimeSeries *hcross,
1254 double ra,
1255 double dec,
1256 double psi,
1258 const COMPLEX16FrequencySeries *response
1259)
1260{
1261 const double nominal_segdur = 2.0; /* nominal segment duration = 2s */
1262 const double max_time_delay = 0.1; /* generous allowed time delay */
1263 const size_t strides_per_segment = 2; /* 2 strides in one segment */
1264 LIGOTimeGPS t0;
1265 LIGOTimeGPS t1;
1266 size_t length; /* length in samples of interval t0 - t1 */
1267 size_t seglen; /* length of segment in samples */
1268 size_t padlen; /* padding at beginning and end of segment */
1269 size_t ovrlap; /* overlapping data length */
1270 size_t stride; /* stride of each step */
1271 size_t nsteps; /* number of steps to take */
1272 REAL8TimeSeries *h = NULL; /* strain timeseries to inject into target */
1273 REAL8TimeSeries *segment = NULL;
1274 COMPLEX16FrequencySeries *work1 = NULL;
1275 COMPLEX16FrequencySeries *work2 = NULL;
1276 REAL8FFTPlan *fwdplan = NULL;
1277 REAL8FFTPlan *revplan = NULL;
1278 REAL8Window *window = NULL;
1279 size_t step;
1280 size_t j;
1281 int errnum = 0;
1282
1283 /* check validity and compatibility of time series */
1284
1289 if (response == NULL) {
1291 } else {
1292 /* units do no need to agree, but sample interval and
1293 * start frequency do */
1294 if (fabs(target->deltaT - hplus->deltaT ) > LAL_REAL8_EPS)
1296 if (fabs(target->f0 - hplus->f0) > LAL_REAL8_EPS)
1298 }
1299
1300
1301 /* constants describing the data segmentation: the length of the
1302 * segment must be a power of two and the segment duration is at least
1303 * nominal_segdur */
1304
1305 seglen = round_up_to_power_of_two(nominal_segdur / target->deltaT);
1306 stride = seglen / strides_per_segment;
1307 padlen = max_time_delay / target->deltaT;
1308 ovrlap = seglen;
1309 ovrlap -= 2 * padlen;
1310 ovrlap -= stride;
1311
1312 /* determine start and end time: the start time is the later of the
1313 * start of the hplus/hcross time series and the target time series;
1314 * the end time is the earlier of the end of the hplus/hcross time
1315 * series and the target time series */
1316
1317 t0 = hplus->epoch;
1318 t1 = target->epoch;
1319 XLALGPSAdd(&t0, hplus->data->length * hplus->deltaT);
1320 XLALGPSAdd(&t1, target->data->length * target->deltaT);
1321 t1 = XLALGPSCmp(&t1, &t0) < 0 ? t1 : t0;
1322 t0 = hplus->epoch;
1323 t0 = XLALGPSCmp(&t0, &target->epoch) > 0 ? t0 : target->epoch;
1324
1325 /* add padding of 1 stride before and after these start and end times */
1326
1327 XLALGPSAdd(&t0, -1.0 * stride * target->deltaT);
1328 XLALGPSAdd(&t1, stride * target->deltaT);
1329
1330 /* determine if this is a disjoint set: if so, there is nothing to do */
1331
1332 if (XLALGPSCmp(&t1, &t0) <= 0)
1333 return 0;
1334
1335 /* create a segment that is seglen samples long */
1336
1337 segment = XLALCreateREAL8TimeSeries(NULL, &t0, target->f0,
1338 target->deltaT, &target->sampleUnits, seglen);
1339 if (!segment) {
1340 errnum = XLAL_EFUNC;
1341 goto freereturn;
1342 }
1343
1344 /* create a time series to hold the strain to inject into the target */
1345
1346 length = XLALGPSDiff(&t1, &t0) / target->deltaT;
1347 h = XLALCreateREAL8TimeSeries(NULL, &t0, target->f0, target->deltaT,
1348 &target->sampleUnits, length);
1349 if (!h) {
1350 errnum = XLAL_EFUNC;
1351 goto freereturn;
1352 }
1353 memset(h->data->data, 0, h->data->length * sizeof(*h->data->data));
1354
1355 /* determine number of steps it takes to go from t0 to t1 */
1356
1357 nsteps = ((length%stride) ? (1 + length/stride) : (length/stride));
1358
1359
1360 /* create frequency-domain workspace; note that the FFT function
1361 * populates the frequency series' metadata with the appropriate
1362 * values */
1363
1364 work1 = XLALCreateCOMPLEX16FrequencySeries(NULL, &t0, 0, 0,
1365 &lalDimensionlessUnit, seglen / 2 + 1);
1366 work2 = XLALCreateCOMPLEX16FrequencySeries(NULL, &t0, 0, 0,
1367 &lalDimensionlessUnit, seglen / 2 + 1);
1368 if (!work1 || !work2) {
1369 errnum = XLAL_EFUNC;
1370 goto freereturn;
1371 }
1372
1373 /* create forward and reverse FFT plans */
1374
1375 fwdplan = XLALCreateForwardREAL8FFTPlan(seglen, 0);
1376 revplan = XLALCreateReverseREAL8FFTPlan(seglen, 0);
1377 if (!fwdplan || !revplan) {
1378 errnum = XLAL_EFUNC;
1379 goto freereturn;
1380 }
1381
1382 /* create a Tukey window with tapers entirely within the padding */
1383
1384 window = XLALCreateTukeyREAL8Window(seglen, (double)padlen / seglen);
1385
1386
1387 /* loop over steps, adding data from the current step to the strain */
1388
1389 for (step = 0; step < nsteps; ++step) {
1390
1391 int status;
1392 size_t offset;
1393
1394 /* compute one segment of strain with time appropriate beam
1395 * pattern functions and time delays from earth's center */
1396
1397 status = XLALSimComputeStrainSegmentREAL8TimeSeries(segment, hplus, hcross, work1, work2, fwdplan, revplan, window,
1398 ra, dec, psi, detector, response);
1399 if (status < 0) {
1400 errnum = XLAL_EFUNC;
1401 goto freereturn;
1402 }
1403
1404 /* compute the offset of this segment relative to the strain series */
1405
1406 offset = XLALGPSDiff(&segment->epoch, &h->epoch) / h->deltaT;
1407 for (j = padlen; j < seglen - padlen; ++j)
1408 if ((j + offset) < h->data->length) {
1409 if (step && j - padlen < ovrlap) {
1410 /* feather overlapping data */
1411 double x = (double)(j - padlen) / ovrlap;
1412 h->data->data[j + offset] = x * segment->data->data[j]
1413 + (1.0 - x) * h->data->data[j + offset];
1414 } else /* no feathering of remaining data */
1415 h->data->data[j + offset] = segment->data->data[j];
1416 }
1417
1418 /* advance segment start time the next step */
1419
1420 XLALGPSAdd(&segment->epoch, stride * segment->deltaT);
1421 }
1422
1423 /* apply window to beginning and end of time series to reduce ringing */
1424
1425 for (j = 0; j < stride - padlen; ++j)
1426 h->data->data[j] = h->data->data[h->data->length - 1 - j] = 0.0;
1427 for ( ; j < stride; ++j) {
1428 double fac = window->data->data[j - (stride - padlen)];
1429 h->data->data[j] *= fac;
1430 h->data->data[h->data->length - 1 - j] *= fac;
1431 }
1432
1433
1434 /* add computed strain to target time series */
1435
1436 XLALAddREAL8TimeSeries(target, h);
1437
1438freereturn:
1439
1440 /* free all memory and return */
1441
1442 XLALDestroyREAL8Window(window);
1443 XLALDestroyREAL8FFTPlan(revplan);
1444 XLALDestroyREAL8FFTPlan(fwdplan);
1449
1450 if (errnum)
1451 XLAL_ERROR(errnum);
1452 return 0;
1453}
1454
1455/**
1456 * @brief Computes strain for a detector and injects into target time series.
1457 * @details This routine takes care of the time-changing time delay from
1458 * the Earth's center and the time-changing antenna response pattern; it
1459 * also accounts for deviations from the long-wavelength limit at high
1460 * frequencies. An optional calibration response function can be provided
1461 * if the output time series is not in strain units.
1462 * @param[in,out] target Time series to inject strain into.
1463 * @param[in] hplus Time series with plus-polarization gravitational waveform.
1464 * @param[in] hcross Time series with cross-polarization gravitational waveform.
1465 * @param[in] ra Right ascension of the source (radians).
1466 * @param[in] dec Declination of the source (radians).
1467 * @param[in] psi Polarization angle of the source (radians).
1468 * @param[in] detector Detector to use when computing strain.
1469 * @param[in] response Response function to use, or NULL if none.
1470 * @retval 0 Success.
1471 * @retval <0 Failure.
1472 */
1474 REAL4TimeSeries *target,
1475 const REAL4TimeSeries *hplus,
1476 const REAL4TimeSeries *hcross,
1477 double ra,
1478 double dec,
1479 double psi,
1481 const COMPLEX8FrequencySeries *response
1482)
1483{
1484 const double nominal_segdur = 2.0; /* nominal segment duration = 2s */
1485 const double max_time_delay = 0.1; /* generous allowed time delay */
1486 const size_t strides_per_segment = 2; /* 2 strides in one segment */
1487 LIGOTimeGPS t0;
1488 LIGOTimeGPS t1;
1489 size_t length; /* length in samples of interval t0 - t1 */
1490 size_t seglen; /* length of segment in samples */
1491 size_t padlen; /* padding at beginning and end of segment */
1492 size_t ovrlap; /* overlapping data length */
1493 size_t stride; /* stride of each step */
1494 size_t nsteps; /* number of steps to take */
1495 REAL4TimeSeries *h = NULL; /* strain timeseries to inject into target */
1496 REAL4TimeSeries *segment = NULL;
1497 COMPLEX8FrequencySeries *work1 = NULL;
1498 COMPLEX8FrequencySeries *work2 = NULL;
1499 REAL4FFTPlan *fwdplan = NULL;
1500 REAL4FFTPlan *revplan = NULL;
1501 REAL4Window *window = NULL;
1502 size_t step;
1503 size_t j;
1504 int errnum = 0;
1505
1506 /* check validity and compatibility of time series */
1507
1512 if (response == NULL) {
1514 } else {
1515 /* units do no need to agree, but sample interval and
1516 * start frequency do */
1517 if (fabs(target->deltaT - hplus->deltaT ) > LAL_REAL8_EPS)
1519 if (fabs(target->f0 - hplus->f0) > LAL_REAL8_EPS)
1521 }
1522
1523
1524 /* constants describing the data segmentation: the length of the
1525 * segment must be a power of two and the segment duration is at least
1526 * nominal_segdur */
1527
1528 seglen = round_up_to_power_of_two(nominal_segdur / target->deltaT);
1529 stride = seglen / strides_per_segment;
1530 padlen = max_time_delay / target->deltaT;
1531 ovrlap = seglen;
1532 ovrlap -= 2 * padlen;
1533 ovrlap -= stride;
1534
1535 /* determine start and end time: the start time is the later of the
1536 * start of the hplus/hcross time series and the target time series;
1537 * the end time is the earlier of the end of the hplus/hcross time
1538 * series and the target time series */
1539
1540 t0 = hplus->epoch;
1541 t1 = target->epoch;
1542 XLALGPSAdd(&t0, hplus->data->length * hplus->deltaT);
1543 XLALGPSAdd(&t1, target->data->length * target->deltaT);
1544 t1 = XLALGPSCmp(&t1, &t0) < 0 ? t1 : t0;
1545 t0 = hplus->epoch;
1546 t0 = XLALGPSCmp(&t0, &target->epoch) > 0 ? t0 : target->epoch;
1547
1548 /* add padding of 1 stride before and after these start and end times */
1549
1550 XLALGPSAdd(&t0, -1.0 * stride * target->deltaT);
1551 XLALGPSAdd(&t1, stride * target->deltaT);
1552
1553 /* determine if this is a disjoint set: if so, there is nothing to do */
1554
1555 if (XLALGPSCmp(&t1, &t0) <= 0)
1556 return 0;
1557
1558 /* create a segment that is seglen samples long */
1559
1560 segment = XLALCreateREAL4TimeSeries(NULL, &t0, target->f0,
1561 target->deltaT, &target->sampleUnits, seglen);
1562 if (!segment) {
1563 errnum = XLAL_EFUNC;
1564 goto freereturn;
1565 }
1566
1567 /* create a time series to hold the strain to inject into the target */
1568
1569 length = XLALGPSDiff(&t1, &t0) / target->deltaT;
1570 h = XLALCreateREAL4TimeSeries(NULL, &t0, target->f0, target->deltaT,
1571 &target->sampleUnits, length);
1572 if (!h) {
1573 errnum = XLAL_EFUNC;
1574 goto freereturn;
1575 }
1576 memset(h->data->data, 0, h->data->length * sizeof(*h->data->data));
1577
1578 /* determine number of steps it takes to go from t0 to t1 */
1579
1580 nsteps = ((length%stride) ? (1 + length/stride) : (length/stride));
1581
1582
1583 /* create frequency-domain workspace; note that the FFT function
1584 * populates the frequency series' metadata with the appropriate
1585 * values */
1586
1587 work1 = XLALCreateCOMPLEX8FrequencySeries(NULL, &t0, 0, 0,
1588 &lalDimensionlessUnit, seglen / 2 + 1);
1589 work2 = XLALCreateCOMPLEX8FrequencySeries(NULL, &t0, 0, 0,
1590 &lalDimensionlessUnit, seglen / 2 + 1);
1591 if (!work1 || !work2) {
1592 errnum = XLAL_EFUNC;
1593 goto freereturn;
1594 }
1595
1596 /* create forward and reverse FFT plans */
1597
1598 fwdplan = XLALCreateForwardREAL4FFTPlan(seglen, 0);
1599 revplan = XLALCreateReverseREAL4FFTPlan(seglen, 0);
1600 if (!fwdplan || !revplan) {
1601 errnum = XLAL_EFUNC;
1602 goto freereturn;
1603 }
1604
1605 /* create a Tukey window with tapers entirely within the padding */
1606
1607 window = XLALCreateTukeyREAL4Window(seglen, (double)padlen / seglen);
1608
1609
1610 /* loop over steps, adding data from the current step to the strain */
1611
1612 for (step = 0; step < nsteps; ++step) {
1613
1614 int status;
1615 size_t offset;
1616
1617 /* compute one segment of strain with time appropriate beam
1618 * pattern functions and time delays from earth's center */
1619
1620 status = XLALSimComputeStrainSegmentREAL4TimeSeries(segment, hplus, hcross, work1, work2, fwdplan, revplan, window,
1621 ra, dec, psi, detector, response);
1622 if (status < 0) {
1623 errnum = XLAL_EFUNC;
1624 goto freereturn;
1625 }
1626
1627 /* compute the offset of this segment relative to the strain series */
1628
1629 offset = XLALGPSDiff(&segment->epoch, &h->epoch) / h->deltaT;
1630 for (j = padlen; j < seglen - padlen; ++j)
1631 if ((j + offset) < h->data->length) {
1632 if (step && j - padlen < ovrlap) {
1633 /* feather overlapping data */
1634 double x = (double)(j - padlen) / ovrlap;
1635 h->data->data[j + offset] = x * segment->data->data[j]
1636 + (1.0 - x) * h->data->data[j + offset];
1637 } else /* no feathering of remaining data */
1638 h->data->data[j + offset] = segment->data->data[j];
1639 }
1640
1641 /* advance segment start time the next step */
1642
1643 XLALGPSAdd(&segment->epoch, stride * segment->deltaT);
1644 }
1645
1646 /* apply window to beginning and end of time series to reduce ringing */
1647
1648 for (j = 0; j < stride - padlen; ++j)
1649 h->data->data[j] = h->data->data[h->data->length - 1 - j] = 0.0;
1650 for ( ; j < stride; ++j) {
1651 double fac = window->data->data[j - (stride - padlen)];
1652 h->data->data[j] *= fac;
1653 h->data->data[h->data->length - 1 - j] *= fac;
1654 }
1655
1656
1657 /* add computed strain to target time series */
1658
1659 XLALAddREAL4TimeSeries(target, h);
1660
1661freereturn:
1662
1663 /* free all memory and return */
1664
1665 XLALDestroyREAL4Window(window);
1666 XLALDestroyREAL4FFTPlan(revplan);
1667 XLALDestroyREAL4FFTPlan(fwdplan);
1672
1673 if (errnum)
1674 XLAL_ERROR(errnum);
1675 return 0;
1676}
1677
1678
1679/*
1680 * The following routines are more computationally efficient but they
1681 * assume the long-wavelength limit is valid.
1682 */
1683
1684
1685/* Helper routine that computes a segment of strain data with a single
1686 * time delay and beam pattern applied to the whole segment. The duration
1687 * of the segment must therefore be reasonably short or else the movement
1688 * of the earth will invalidate the use of a single time shift and beam
1689 * pattern for the entire segment. */
1691 REAL8TimeSeries *segment,
1692 const REAL8TimeSeries *hplus,
1693 const REAL8TimeSeries *hcross,
1695 REAL8FFTPlan *fwdplan,
1696 REAL8FFTPlan *revplan,
1697 REAL8Window *window,
1698 double ra,
1699 double dec,
1700 double psi,
1702 const COMPLEX16FrequencySeries *response
1703)
1704{
1705 LIGOTimeGPS t;
1706 double gmst;
1707 double fplus;
1708 double fcross;
1709 double deltaT;
1710 double offint;
1711 double offrac;
1712 int offset;
1713 int j;
1714 size_t k;
1715
1716 /* this routine assumes the segment has a length of N points where N is
1717 * a power of two and that the workspace frequency series and the FFT
1718 * plans are compatible with the size N; these assumptions are not
1719 * checked: the calling routine must ensure that they are true */
1720
1721 /* compute fplus, fcross, and time delay from earth's center at the
1722 * time corresponding to the middle of the segment */
1723
1724 t = segment->epoch;
1725 XLALGPSAdd(&t, 0.5 * segment->data->length * segment->deltaT);
1727 XLALComputeDetAMResponse(&fplus, &fcross,
1728 (const REAL4(*)[3])(uintptr_t)detector->response, ra, dec, psi, gmst);
1729 deltaT = XLALTimeDelayFromEarthCenter(detector->location, ra, dec, &t);
1730
1731 /* add to the geometric delay the difference in time between the
1732 * beginning of the injection timeseries and the beginning of the
1733 * segment */
1734
1735 deltaT += XLALGPSDiff(&hplus->epoch, &segment->epoch);
1736
1737 /* compute the integer and fractional parts of the sample index in the
1738 * segment on which the hplus and hcross time series begins: modf()
1739 * returns integer and fractional parts that have the same sign, e.g.,
1740 * -3.9 --> -3 + -0.9, and we adjust these so that magnitude of the
1741 * fractional part is not greater than 0.5, e.g., -3.9 --> -4.0 + 0.1,
1742 * so that we never do more than 1/2 a sample of re-interpolation */
1743
1744 offrac = modf(deltaT / segment->deltaT, &offint);
1745 if (offrac < -0.5) {
1746 offrac += 1.0;
1747 offint -= 1.0;
1748 } else if (offrac > 0.5) {
1749 offrac -= 1.0;
1750 offint += 1.0;
1751 }
1752 offset = offint;
1753
1754 /* now compute the sub-sample time shift that must be applied to the
1755 * segment data */
1756
1757 deltaT = offrac * segment->deltaT;
1758
1759 /* compute the strain from hplus and hcross and populate the segment
1760 * with this windowed data; apply appropriate integer offset */
1761
1762 for (j = 0; j < (int)segment->data->length; ++j)
1763 if (j >= offset && j < (int)hplus->data->length + offset)
1764 segment->data->data[j] = window->data->data[j]
1765 * ( fplus * hplus->data->data[j - offset]
1766 + fcross * hcross->data->data[j - offset] );
1767 else
1768 segment->data->data[j] = 0.0;
1769
1770
1771 /* put segment in frequency domain */
1772
1773 if (XLALREAL8TimeFreqFFT(work, segment, fwdplan) < 0)
1775
1776 /* apply sub-sample time shift in frequency domain */
1777
1778 for (k = 0; k < work->data->length; ++k) {
1779 double f = work->f0 + k * work->deltaF;
1780 COMPLEX16 fac;
1781
1782 /* phase for sub-sample time correction */
1783 fac = cexp(-I * LAL_TWOPI * f * deltaT);
1784 if (response)
1785 fac /= response->data->data[k];
1786
1787 work->data->data[k] *= fac;
1788 }
1789
1790 /* adjust DC and Nyquist components: the DC component must always be
1791 * real-valued; because the calling routine has made the time series
1792 * have an even length, the Nyquist component must also be real-valued;
1793 * also this routine makes the assumption that both the DC and the
1794 * Nyquist components are zero */
1795
1796 work->data->data[0] = cabs(work->data->data[0]);
1797 work->data->data[work->data->length - 1] =
1798 creal(work->data->data[work->data->length - 1]);
1799
1800 /* return data to time domain */
1801
1802 if (XLALREAL8FreqTimeFFT(segment, work, revplan) < 0)
1804
1805 return 0;
1806}
1807
1808
1809/* Helper routine that computes a segment of strain data with a single
1810 * time delay and beam pattern applied to the whole segment. The duration
1811 * of the segment must therefore be reasonably short or else the movement
1812 * of the earth will invalidate the use of a single time shift and beam
1813 * pattern for the entire segment. */
1815 REAL4TimeSeries *segment,
1816 const REAL4TimeSeries *hplus,
1817 const REAL4TimeSeries *hcross,
1819 REAL4FFTPlan *fwdplan,
1820 REAL4FFTPlan *revplan,
1821 REAL4Window *window,
1822 double ra,
1823 double dec,
1824 double psi,
1826 const COMPLEX8FrequencySeries *response
1827)
1828{
1829 LIGOTimeGPS t;
1830 double gmst;
1831 double fplus;
1832 double fcross;
1833 double deltaT;
1834 double offint;
1835 double offrac;
1836 int offset;
1837 int j;
1838 size_t k;
1839
1840 /* this routine assumes the segment has a length of N points where N is
1841 * a power of two and that the workspace frequency series and the FFT
1842 * plans are compatible with the size N; these assumptions are not
1843 * checked: the calling routine must ensure that they are true */
1844
1845 /* compute fplus, fcross, and time delay from earth's center at the
1846 * time corresponding to the middle of the segment */
1847
1848 t = segment->epoch;
1849 XLALGPSAdd(&t, 0.5 * segment->data->length * segment->deltaT);
1851 XLALComputeDetAMResponse(&fplus, &fcross,
1852 (const REAL4(*)[3])(uintptr_t)detector->response, ra, dec, psi, gmst);
1853 deltaT = XLALTimeDelayFromEarthCenter(detector->location, ra, dec, &t);
1854
1855 /* add to the geometric delay the difference in time between the
1856 * beginning of the injection timeseries and the beginning of the
1857 * segment */
1858
1859 deltaT += XLALGPSDiff(&hplus->epoch, &segment->epoch);
1860
1861 /* compute the integer and fractional parts of the sample index in the
1862 * segment on which the hplus and hcross time series begins: modf()
1863 * returns integer and fractional parts that have the same sign, e.g.,
1864 * -3.9 --> -3 + -0.9, and we adjust these so that magnitude of the
1865 * fractional part is not greater than 0.5, e.g., -3.9 --> -4.0 + 0.1,
1866 * so that we never do more than 1/2 a sample of re-interpolation */
1867
1868 offrac = modf(deltaT / segment->deltaT, &offint);
1869 if (offrac < -0.5) {
1870 offrac += 1.0;
1871 offint -= 1.0;
1872 } else if (offrac > 0.5) {
1873 offrac -= 1.0;
1874 offint += 1.0;
1875 }
1876 offset = offint;
1877
1878 /* now compute the sub-sample time shift that must be applied to the
1879 * segment data */
1880
1881 deltaT = offrac * segment->deltaT;
1882
1883 /* compute the strain from hplus and hcross and populate the segment
1884 * with this windowed data; apply appropriate integer offset */
1885
1886 for (j = 0; j < (int)segment->data->length; ++j)
1887 if (j >= offset && j < (int)hplus->data->length + offset)
1888 segment->data->data[j] = window->data->data[j]
1889 * ( fplus * hplus->data->data[j - offset]
1890 + fcross * hcross->data->data[j - offset] );
1891 else
1892 segment->data->data[j] = 0.0;
1893
1894
1895 /* put segment in frequency domain */
1896
1897 if (XLALREAL4TimeFreqFFT(work, segment, fwdplan) < 0)
1899
1900 /* apply sub-sample time shift in frequency domain */
1901
1902 for (k = 0; k < work->data->length; ++k) {
1903 double f = work->f0 + k * work->deltaF;
1904 COMPLEX8 fac;
1905
1906 /* phase for sub-sample time correction */
1907 fac = cexp(-I * LAL_TWOPI * f * deltaT);
1908 if (response)
1909 fac /= response->data->data[k];
1910
1911 work->data->data[k] *= fac;
1912 }
1913
1914 /* adjust DC and Nyquist components: the DC component must always be
1915 * real-valued; because the calling routine has made the time series
1916 * have an even length, the Nyquist component must also be real-valued;
1917 * also this routine makes the assumption that both the DC and the
1918 * Nyquist components are zero */
1919
1920 work->data->data[0] = cabs(work->data->data[0]);
1921 work->data->data[work->data->length - 1] =
1922 creal(work->data->data[work->data->length - 1]);
1923
1924 /* return data to time domain */
1925
1926 if (XLALREAL4FreqTimeFFT(segment, work, revplan) < 0)
1928
1929 return 0;
1930}
1931
1932
1933/**
1934 * @brief Computes strain for a detector and injects into target time series.
1935 * @details This routine takes care of the time-changing time delay from
1936 * the Earth's center and the time-changing antenna response pattern; it
1937 * does NOT account for deviations from the long-wavelength limit at high
1938 * frequencies. An optional calibration response function can be provided
1939 * if the output time series is not in strain units.
1940 * @param[in,out] target Time series to inject strain into.
1941 * @param[in] hplus Time series with plus-polarization gravitational waveform.
1942 * @param[in] hcross Time series with cross-polarization gravitational waveform.
1943 * @param[in] ra Right ascension of the source (radians).
1944 * @param[in] dec Declination of the source (radians).
1945 * @param[in] psi Polarization angle of the source (radians).
1946 * @param[in] detector Detector to use when computing strain.
1947 * @param[in] response Response function to use, or NULL if none.
1948 * @retval 0 Success.
1949 * @retval <0 Failure.
1950 * @warning This routine assumes the long-wavelength limit (LWL) is valid
1951 * when computing the detector strain; at high frequencies near the free
1952 * spectral range of an interferometric detector this approximation becomes
1953 * invalid.
1954 */
1956 REAL8TimeSeries *target,
1957 const REAL8TimeSeries *hplus,
1958 const REAL8TimeSeries *hcross,
1959 double ra,
1960 double dec,
1961 double psi,
1963 const COMPLEX16FrequencySeries *response
1964)
1965{
1966 const double nominal_segdur = 2.0; /* nominal segment duration = 2s */
1967 const double max_time_delay = 0.1; /* generous allowed time delay */
1968 const size_t strides_per_segment = 2; /* 2 strides in one segment */
1969 LIGOTimeGPS t0;
1970 LIGOTimeGPS t1;
1971 size_t length; /* length in samples of interval t0 - t1 */
1972 size_t seglen; /* length of segment in samples */
1973 size_t padlen; /* padding at beginning and end of segment */
1974 size_t ovrlap; /* overlapping data length */
1975 size_t stride; /* stride of each step */
1976 size_t nsteps; /* number of steps to take */
1977 REAL8TimeSeries *h = NULL; /* strain timeseries to inject into target */
1978 REAL8TimeSeries *segment = NULL;
1979 COMPLEX16FrequencySeries *work = NULL;
1980 REAL8FFTPlan *fwdplan = NULL;
1981 REAL8FFTPlan *revplan = NULL;
1982 REAL8Window *window = NULL;
1983 size_t step;
1984 size_t j;
1985 int errnum = 0;
1986
1987 /* check validity and compatibility of time series */
1988
1993 if (response == NULL) {
1995 } else {
1996 /* units do no need to agree, but sample interval and
1997 * start frequency do */
1998 if (fabs(target->deltaT - hplus->deltaT ) > LAL_REAL8_EPS)
2000 if (fabs(target->f0 - hplus->f0) > LAL_REAL8_EPS)
2002 }
2003
2004
2005 /* constants describing the data segmentation: the length of the
2006 * segment must be a power of two and the segment duration is at least
2007 * nominal_segdur */
2008
2009 seglen = round_up_to_power_of_two(nominal_segdur / target->deltaT);
2010 stride = seglen / strides_per_segment;
2011 padlen = max_time_delay / target->deltaT;
2012 ovrlap = seglen;
2013 ovrlap -= 2 * padlen;
2014 ovrlap -= stride;
2015
2016 /* determine start and end time: the start time is the later of the
2017 * start of the hplus/hcross time series and the target time series;
2018 * the end time is the earlier of the end of the hplus/hcross time
2019 * series and the target time series */
2020
2021 t0 = hplus->epoch;
2022 t1 = target->epoch;
2023 XLALGPSAdd(&t0, hplus->data->length * hplus->deltaT);
2024 XLALGPSAdd(&t1, target->data->length * target->deltaT);
2025 t1 = XLALGPSCmp(&t1, &t0) < 0 ? t1 : t0;
2026 t0 = hplus->epoch;
2027 t0 = XLALGPSCmp(&t0, &target->epoch) > 0 ? t0 : target->epoch;
2028
2029 /* add padding of 1 stride before and after these start and end times */
2030
2031 XLALGPSAdd(&t0, -1.0 * stride * target->deltaT);
2032 XLALGPSAdd(&t1, stride * target->deltaT);
2033
2034 /* determine if this is a disjoint set: if so, there is nothing to do */
2035
2036 if (XLALGPSCmp(&t1, &t0) <= 0)
2037 return 0;
2038
2039 /* create a segment that is seglen samples long */
2040
2041 segment = XLALCreateREAL8TimeSeries(NULL, &t0, target->f0,
2042 target->deltaT, &target->sampleUnits, seglen);
2043 if (!segment) {
2044 errnum = XLAL_EFUNC;
2045 goto freereturn;
2046 }
2047
2048 /* create a time series to hold the strain to inject into the target */
2049
2050 length = XLALGPSDiff(&t1, &t0) / target->deltaT;
2051 h = XLALCreateREAL8TimeSeries(NULL, &t0, target->f0, target->deltaT,
2052 &target->sampleUnits, length);
2053 if (!h) {
2054 errnum = XLAL_EFUNC;
2055 goto freereturn;
2056 }
2057 memset(h->data->data, 0, h->data->length * sizeof(*h->data->data));
2058
2059 /* determine number of steps it takes to go from t0 to t1 */
2060
2061 nsteps = ((length%stride) ? (1 + length/stride) : (length/stride));
2062
2063
2064 /* create frequency-domain workspace; note that the FFT function
2065 * populates the frequency series' metadata with the appropriate
2066 * values */
2067
2068 work = XLALCreateCOMPLEX16FrequencySeries(NULL, &t0, 0, 0,
2069 &lalDimensionlessUnit, seglen / 2 + 1);
2070 if (!work) {
2071 errnum = XLAL_EFUNC;
2072 goto freereturn;
2073 }
2074
2075 /* create forward and reverse FFT plans */
2076
2077 fwdplan = XLALCreateForwardREAL8FFTPlan(seglen, 0);
2078 revplan = XLALCreateReverseREAL8FFTPlan(seglen, 0);
2079 if (!fwdplan || !revplan) {
2080 errnum = XLAL_EFUNC;
2081 goto freereturn;
2082 }
2083
2084 /* create a Tukey window with tapers entirely within the padding */
2085
2086 window = XLALCreateTukeyREAL8Window(seglen, (double)padlen / seglen);
2087
2088
2089 /* loop over steps, adding data from the current step to the strain */
2090
2091 for (step = 0; step < nsteps; ++step) {
2092
2093 int status;
2094 size_t offset;
2095
2096 /* compute one segment of strain with time appropriate beam
2097 * pattern functions and time delays from earth's center */
2098
2100 hplus, hcross, work, fwdplan, revplan, window, ra, dec,
2101 psi, detector, response);
2102 if (status < 0) {
2103 errnum = XLAL_EFUNC;
2104 goto freereturn;
2105 }
2106
2107 /* compute the offset of this segment relative to the strain series */
2108
2109 offset = XLALGPSDiff(&segment->epoch, &h->epoch) / h->deltaT;
2110 for (j = padlen; j < seglen - padlen; ++j)
2111 if ((j + offset) < h->data->length) {
2112 if (step && j - padlen < ovrlap) {
2113 /* feather overlapping data */
2114 double x = (double)(j - padlen) / ovrlap;
2115 h->data->data[j + offset] = x * segment->data->data[j]
2116 + (1.0 - x) * h->data->data[j + offset];
2117 } else /* no feathering of remaining data */
2118 h->data->data[j + offset] = segment->data->data[j];
2119 }
2120
2121 /* advance segment start time the next step */
2122
2123 XLALGPSAdd(&segment->epoch, stride * segment->deltaT);
2124 }
2125
2126 /* apply window to beginning and end of time series to reduce ringing */
2127
2128 for (j = 0; j < stride - padlen; ++j)
2129 h->data->data[j] = h->data->data[h->data->length - 1 - j] = 0.0;
2130 for ( ; j < stride; ++j) {
2131 double fac = window->data->data[j - (stride - padlen)];
2132 h->data->data[j] *= fac;
2133 h->data->data[h->data->length - 1 - j] *= fac;
2134 }
2135
2136
2137 /* add computed strain to target time series */
2138
2139 XLALAddREAL8TimeSeries(target, h);
2140
2141freereturn:
2142
2143 /* free all memory and return */
2144
2145 XLALDestroyREAL8Window(window);
2146 XLALDestroyREAL8FFTPlan(revplan);
2147 XLALDestroyREAL8FFTPlan(fwdplan);
2151
2152 if (errnum)
2153 XLAL_ERROR(errnum);
2154 return 0;
2155}
2156
2157
2158/**
2159 * @brief Computes strain for a detector and injects into target time series.
2160 * @details This routine takes care of the time-changing time delay from
2161 * the Earth's center and the time-changing antenna response pattern; it
2162 * does NOT account for deviations from the long-wavelength limit at high
2163 * frequencies. An optional calibration response function can be provided
2164 * if the output time series is not in strain units.
2165 * @param[in,out] target Time series to inject strain into.
2166 * @param[in] hplus Time series with plus-polarization gravitational waveform.
2167 * @param[in] hcross Time series with cross-polarization gravitational waveform.
2168 * @param[in] ra Right ascension of the source (radians).
2169 * @param[in] dec Declination of the source (radians).
2170 * @param[in] psi Polarization angle of the source (radians).
2171 * @param[in] detector Detector to use when computing strain.
2172 * @param[in] response Response function to use, or NULL if none.
2173 * @retval 0 Success.
2174 * @retval <0 Failure.
2175 * @warning This routine assumes the long-wavelength limit (LWL) is valid
2176 * when computing the detector strain; at high frequencies near the free
2177 * spectral range of an interferometric detector this approximation becomes
2178 * invalid.
2179 */
2181 REAL4TimeSeries *target,
2182 const REAL4TimeSeries *hplus,
2183 const REAL4TimeSeries *hcross,
2184 double ra,
2185 double dec,
2186 double psi,
2188 const COMPLEX8FrequencySeries *response
2189)
2190{
2191 const double nominal_segdur = 2.0; /* nominal segment duration = 2s */
2192 const double max_time_delay = 0.1; /* generous allowed time delay */
2193 const size_t strides_per_segment = 2; /* 2 strides in one segment */
2194 LIGOTimeGPS t0;
2195 LIGOTimeGPS t1;
2196 size_t length; /* length in samples of interval t0 - t1 */
2197 size_t seglen; /* length of segment in samples */
2198 size_t padlen; /* padding at beginning and end of segment */
2199 size_t ovrlap; /* overlapping data length */
2200 size_t stride; /* stride of each step */
2201 size_t nsteps; /* number of steps to take */
2202 REAL4TimeSeries *h = NULL; /* strain timeseries to inject into target */
2203 REAL4TimeSeries *segment = NULL;
2204 COMPLEX8FrequencySeries *work = NULL;
2205 REAL4FFTPlan *fwdplan = NULL;
2206 REAL4FFTPlan *revplan = NULL;
2207 REAL4Window *window = NULL;
2208 size_t step;
2209 size_t j;
2210 int errnum = 0;
2211
2212 /* check validity and compatibility of time series */
2213
2218 if (response == NULL) {
2220 } else {
2221 /* units do no need to agree, but sample interval and
2222 * start frequency do */
2223 if (fabs(target->deltaT - hplus->deltaT ) > LAL_REAL8_EPS)
2225 if (fabs(target->f0 - hplus->f0) > LAL_REAL8_EPS)
2227 }
2228
2229
2230 /* constants describing the data segmentation: the length of the
2231 * segment must be a power of two and the segment duration is at least
2232 * nominal_segdur */
2233
2234 seglen = round_up_to_power_of_two(nominal_segdur / target->deltaT);
2235 stride = seglen / strides_per_segment;
2236 padlen = max_time_delay / target->deltaT;
2237 ovrlap = seglen;
2238 ovrlap -= 2 * padlen;
2239 ovrlap -= stride;
2240
2241 /* determine start and end time: the start time is the later of the
2242 * start of the hplus/hcross time series and the target time series;
2243 * the end time is the earlier of the end of the hplus/hcross time
2244 * series and the target time series */
2245
2246 t0 = hplus->epoch;
2247 t1 = target->epoch;
2248 XLALGPSAdd(&t0, hplus->data->length * hplus->deltaT);
2249 XLALGPSAdd(&t1, target->data->length * target->deltaT);
2250 t1 = XLALGPSCmp(&t1, &t0) < 0 ? t1 : t0;
2251 t0 = hplus->epoch;
2252 t0 = XLALGPSCmp(&t0, &target->epoch) > 0 ? t0 : target->epoch;
2253
2254 /* add padding of 1 stride before and after these start and end times */
2255
2256 XLALGPSAdd(&t0, -1.0 * stride * target->deltaT);
2257 XLALGPSAdd(&t1, stride * target->deltaT);
2258
2259 /* determine if this is a disjoint set: if so, there is nothing to do */
2260
2261 if (XLALGPSCmp(&t1, &t0) <= 0)
2262 return 0;
2263
2264 /* create a segment that is seglen samples long */
2265
2266 segment = XLALCreateREAL4TimeSeries(NULL, &t0, target->f0,
2267 target->deltaT, &target->sampleUnits, seglen);
2268 if (!segment) {
2269 errnum = XLAL_EFUNC;
2270 goto freereturn;
2271 }
2272
2273 /* create a time series to hold the strain to inject into the target */
2274
2275 length = XLALGPSDiff(&t1, &t0) / target->deltaT;
2276 h = XLALCreateREAL4TimeSeries(NULL, &t0, target->f0, target->deltaT,
2277 &target->sampleUnits, length);
2278 if (!h) {
2279 errnum = XLAL_EFUNC;
2280 goto freereturn;
2281 }
2282 memset(h->data->data, 0, h->data->length * sizeof(*h->data->data));
2283
2284 /* determine number of steps it takes to go from t0 to t1 */
2285
2286 nsteps = ((length%stride) ? (1 + length/stride) : (length/stride));
2287
2288
2289 /* create frequency-domain workspace; note that the FFT function
2290 * populates the frequency series' metadata with the appropriate
2291 * values */
2292
2293 work = XLALCreateCOMPLEX8FrequencySeries(NULL, &t0, 0, 0,
2294 &lalDimensionlessUnit, seglen / 2 + 1);
2295 if (!work) {
2296 errnum = XLAL_EFUNC;
2297 goto freereturn;
2298 }
2299
2300 /* create forward and reverse FFT plans */
2301
2302 fwdplan = XLALCreateForwardREAL4FFTPlan(seglen, 0);
2303 revplan = XLALCreateReverseREAL4FFTPlan(seglen, 0);
2304 if (!fwdplan || !revplan) {
2305 errnum = XLAL_EFUNC;
2306 goto freereturn;
2307 }
2308
2309 /* create a Tukey window with tapers entirely within the padding */
2310
2311 window = XLALCreateTukeyREAL4Window(seglen, (double)padlen / seglen);
2312
2313
2314 /* loop over steps, adding data from the current step to the strain */
2315
2316 for (step = 0; step < nsteps; ++step) {
2317
2318 int status;
2319 size_t offset;
2320
2321 /* compute one segment of strain with time appropriate beam
2322 * pattern functions and time delays from earth's center */
2323
2325 hplus, hcross, work, fwdplan, revplan, window, ra, dec,
2326 psi, detector, response);
2327 if (status < 0) {
2328 errnum = XLAL_EFUNC;
2329 goto freereturn;
2330 }
2331
2332 /* compute the offset of this segment relative to the strain series */
2333
2334 offset = XLALGPSDiff(&segment->epoch, &h->epoch) / h->deltaT;
2335 for (j = padlen; j < seglen - padlen; ++j)
2336 if ((j + offset) < h->data->length) {
2337 if (step && j - padlen < ovrlap) {
2338 /* feather overlapping data */
2339 double x = (double)(j - padlen) / ovrlap;
2340 h->data->data[j + offset] = x * segment->data->data[j]
2341 + (1.0 - x) * h->data->data[j + offset];
2342 } else /* no feathering of remaining data */
2343 h->data->data[j + offset] = segment->data->data[j];
2344 }
2345
2346 /* advance segment start time the next step */
2347
2348 XLALGPSAdd(&segment->epoch, stride * segment->deltaT);
2349 }
2350
2351 /* apply window to beginning and end of time series to reduce ringing */
2352
2353 for (j = 0; j < stride - padlen; ++j)
2354 h->data->data[j] = h->data->data[h->data->length - 1 - j] = 0.0;
2355 for ( ; j < stride; ++j) {
2356 double fac = window->data->data[j - (stride - padlen)];
2357 h->data->data[j] *= fac;
2358 h->data->data[h->data->length - 1 - j] *= fac;
2359 }
2360
2361
2362 /* add computed strain to target time series */
2363
2364 XLALAddREAL4TimeSeries(target, h);
2365
2366freereturn:
2367
2368 /* free all memory and return */
2369
2370 XLALDestroyREAL4Window(window);
2371 XLALDestroyREAL4FFTPlan(revplan);
2372 XLALDestroyREAL4FFTPlan(fwdplan);
2376
2377 if (errnum)
2378 XLAL_ERROR(errnum);
2379 return 0;
2380}
static double beta(const double a, const double b, const sysq *system)
Internal function that computes the spin-orbit couplings.
const char * name
static int XLALSimComputeStrainSegmentREAL8TimeSeries(REAL8TimeSeries *segment, const REAL8TimeSeries *hplus, const REAL8TimeSeries *hcross, COMPLEX16FrequencySeries *work1, COMPLEX16FrequencySeries *work2, REAL8FFTPlan *fwdplan, REAL8FFTPlan *revplan, REAL8Window *window, double ra, double dec, double psi, LALDetector *detector, const COMPLEX16FrequencySeries *response)
static unsigned long round_up_to_power_of_two(unsigned long x)
Definition: LALSimulation.c:59
static int XLALSimComputeLWLStrainSegmentREAL8TimeSeries(REAL8TimeSeries *segment, const REAL8TimeSeries *hplus, const REAL8TimeSeries *hcross, COMPLEX16FrequencySeries *work, REAL8FFTPlan *fwdplan, REAL8FFTPlan *revplan, REAL8Window *window, double ra, double dec, double psi, LALDetector *detector, const COMPLEX16FrequencySeries *response)
static int XLALSimComputeStrainSegmentREAL4TimeSeries(REAL4TimeSeries *segment, const REAL4TimeSeries *hplus, const REAL4TimeSeries *hcross, COMPLEX8FrequencySeries *work1, COMPLEX8FrequencySeries *work2, REAL4FFTPlan *fwdplan, REAL4FFTPlan *revplan, REAL4Window *window, double ra, double dec, double psi, LALDetector *detector, const COMPLEX8FrequencySeries *response)
static int XLALSimComputeLWLStrainSegmentREAL4TimeSeries(REAL4TimeSeries *segment, const REAL4TimeSeries *hplus, const REAL4TimeSeries *hcross, COMPLEX8FrequencySeries *work, REAL4FFTPlan *fwdplan, REAL4FFTPlan *revplan, REAL4Window *window, double ra, double dec, double psi, LALDetector *detector, const COMPLEX8FrequencySeries *response)
static void highfreq_kernel(double *cached_kernel, int kernel_length, double residual, void *data)
This kernel function incorporates beyond-long-wavelength effects.
static REAL8TimeSeries * error(const REAL8TimeSeries *s1, const REAL8TimeSeries *s0)
LALREAL8TimeSeriesInterp * XLALREAL8TimeSeriesInterpCreate(const REAL8TimeSeries *series, int kernel_length, void(*kernel)(double *, int, double, void *), void *kernel_data)
void XLALREAL8TimeSeriesInterpDestroy(LALREAL8TimeSeriesInterp *interp)
REAL8 XLALREAL8TimeSeriesInterpEval(LALREAL8TimeSeriesInterp *interp, const LIGOTimeGPS *t, int bounds_check)
double dt
Definition: bh_ringdown.c:113
double i
Definition: bh_ringdown.c:118
double e
Definition: bh_ringdown.c:117
#define LAL_CHECK_VALID_SERIES(s, val)
#define LAL_CHECK_CONSISTENT_TIME_SERIES(s1, s2, val)
#define LAL_CHECK_COMPATIBLE_TIME_SERIES(s1, s2, val)
const char * detector
sigmaKerr data[0]
const LALDetector lalCachedDetectors[LAL_NUM_DETECTORS]
void XLALComputeDetAMResponseParts(double *armlen, double *xcos, double *ycos, double *fxplus, double *fyplus, double *fxcross, double *fycross, const LALDetector *detector, double ra, double dec, double psi, double gmst)
void XLALComputeDetAMResponse(double *fplus, double *fcross, const REAL4 D[3][3], const double ra, const double dec, const double psi, const double gmst)
COMPLEX16 XLALComputeDetArmTransferFunction(double beta, double mu)
COMPLEX16FrequencySeries * XLALCreateCOMPLEX16FrequencySeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaF, const LALUnit *sampleUnits, size_t length)
void XLALDestroyCOMPLEX8FrequencySeries(COMPLEX8FrequencySeries *series)
COMPLEX8FrequencySeries * XLALCreateCOMPLEX8FrequencySeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaF, const LALUnit *sampleUnits, size_t length)
void XLALDestroyCOMPLEX16FrequencySeries(COMPLEX16FrequencySeries *series)
#define LAL_C_SI
#define LAL_REARTH_SI
#define LAL_REAL8_EPS
#define LAL_PI
#define LAL_TWOPI
double complex COMPLEX16
double REAL8
uint32_t UINT4
float complex COMPLEX8
float REAL4
LAL_NUM_DETECTORS
void * XLALMalloc(size_t n)
void XLALFree(void *p)
const LALDetector * XLALDetectorPrefixToLALDetector(const char *string)
Turn a detector prefix string into a LALDetector structure.
Definition: LALSimulation.c:85
int XLALSimInjectLWLDetectorStrainREAL4TimeSeries(REAL4TimeSeries *target, const REAL4TimeSeries *hplus, const REAL4TimeSeries *hcross, double ra, double dec, double psi, LALDetector *detector, const COMPLEX8FrequencySeries *response)
Computes strain for a detector and injects into target time series.
int XLALSimInjectDetectorStrainREAL8TimeSeries(REAL8TimeSeries *target, const REAL8TimeSeries *hplus, const REAL8TimeSeries *hcross, double ra, double dec, double psi, LALDetector *detector, const COMPLEX16FrequencySeries *response)
Computes strain for a detector and injects into target time series.
int XLALSimInjectLWLDetectorStrainREAL8TimeSeries(REAL8TimeSeries *target, const REAL8TimeSeries *hplus, const REAL8TimeSeries *hcross, double ra, double dec, double psi, LALDetector *detector, const COMPLEX16FrequencySeries *response)
Computes strain for a detector and injects into target time series.
int XLALSimInjectDetectorStrainREAL4TimeSeries(REAL4TimeSeries *target, const REAL4TimeSeries *hplus, const REAL4TimeSeries *hcross, double ra, double dec, double psi, LALDetector *detector, const COMPLEX8FrequencySeries *response)
Computes strain for a detector and injects into target time series.
int XLALSimAddInjectionREAL4TimeSeries(REAL4TimeSeries *target, REAL4TimeSeries *h, const COMPLEX8FrequencySeries *response)
Adds a detector strain time series to detector data.
REAL8TimeSeries * XLALSimDetectorStrainREAL8TimeSeries(const REAL8TimeSeries *hplus, const REAL8TimeSeries *hcross, REAL8 right_ascension, REAL8 declination, REAL8 psi, const LALDetector *detector)
Transforms the waveform polarizations into a detector strain.
int XLALSimAddInjectionREAL8TimeSeries(REAL8TimeSeries *target, REAL8TimeSeries *h, const COMPLEX16FrequencySeries *response)
Adds a detector strain time series to detector data.
void XLALDestroyREAL8FFTPlan(REAL8FFTPlan *plan)
REAL4FFTPlan * XLALCreateReverseREAL4FFTPlan(UINT4 size, int measurelvl)
REAL4FFTPlan * XLALCreateForwardREAL4FFTPlan(UINT4 size, int measurelvl)
void XLALDestroyREAL4FFTPlan(REAL4FFTPlan *plan)
REAL8FFTPlan * XLALCreateReverseREAL8FFTPlan(UINT4 size, int measurelvl)
REAL8FFTPlan * XLALCreateForwardREAL8FFTPlan(UINT4 size, int measurelvl)
REAL8 XLALTimeDelayFromEarthCenter(const double detector_earthfixed_xyz_metres[3], double source_right_ascension_radians, double source_declination_radians, const LIGOTimeGPS *gpstime)
int XLALREAL8TimeFreqFFT(COMPLEX16FrequencySeries *freq, const REAL8TimeSeries *tser, const REAL8FFTPlan *plan)
int XLALREAL4FreqTimeFFT(REAL4TimeSeries *tser, const COMPLEX8FrequencySeries *freq, const REAL4FFTPlan *plan)
int XLALREAL8FreqTimeFFT(REAL8TimeSeries *tser, const COMPLEX16FrequencySeries *freq, const REAL8FFTPlan *plan)
int XLALREAL4TimeFreqFFT(COMPLEX8FrequencySeries *freq, const REAL4TimeSeries *tser, const REAL4FFTPlan *plan)
REAL4TimeSeries * XLALCreateREAL4TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
REAL4TimeSeries * XLALAddREAL4TimeSeries(REAL4TimeSeries *arg1, const REAL4TimeSeries *arg2)
void XLALDestroyREAL8TimeSeries(REAL8TimeSeries *series)
REAL8TimeSeries * XLALResizeREAL8TimeSeries(REAL8TimeSeries *series, int first, size_t length)
REAL8TimeSeries * XLALAddREAL8TimeSeries(REAL8TimeSeries *arg1, const REAL8TimeSeries *arg2)
void XLALDestroyREAL4TimeSeries(REAL4TimeSeries *series)
REAL8TimeSeries * XLALCreateREAL8TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
REAL4TimeSeries * XLALResizeREAL4TimeSeries(REAL4TimeSeries *series, int first, size_t length)
const LALUnit lalDimensionlessUnit
REAL4Window * XLALCreateTukeyREAL4Window(UINT4 length, REAL4 beta)
REAL8Window * XLALCreateTukeyREAL8Window(UINT4 length, REAL8 beta)
void XLALDestroyREAL8Window(REAL8Window *window)
void XLALDestroyREAL4Window(REAL4Window *window)
#define XLAL_ERROR_NULL(...)
#define XLAL_ERROR(...)
#define XLAL_REAL8_FAIL_NAN
int XLALPrintError(const char *fmt,...) _LAL_GCC_PRINTF_FORMAT_(1
#define XLAL_IS_REAL8_FAIL_NAN(val)
XLAL_EBADLEN
XLAL_EFREQ
XLAL_EDATA
XLAL_EFUNC
XLAL_EERR
XLAL_EINVAL
XLAL_FAILURE
XLAL_ETIME
REAL8 XLALGreenwichMeanSiderealTime(const LIGOTimeGPS *gpstime)
REAL8 XLALGPSModf(REAL8 *iptr, const LIGOTimeGPS *epoch)
LIGOTimeGPS * XLALGPSAdd(LIGOTimeGPS *epoch, REAL8 dt)
int XLALGPSCmp(const LIGOTimeGPS *t0, const LIGOTimeGPS *t1)
REAL8 XLALGPSDiff(const LIGOTimeGPS *t1, const LIGOTimeGPS *t0)
list y
string status
x
COMPLEX16Sequence * data
COMPLEX16 * data
COMPLEX8Sequence * data
COMPLEX8 * data
REAL4Sequence * data
LALUnit sampleUnits
LIGOTimeGPS epoch
REAL4 * data
REAL4Sequence * data
REAL8Sequence * data
LALUnit sampleUnits
LIGOTimeGPS epoch
REAL8 * data
REAL8Sequence * data
double deltaT
Definition: unicorn.c:24