LALSimulation.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 J. Creighton, T. Creighton
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with with program; see the file COPYING. If not, write to the 
00016  *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
00017  *  MA  02111-1307  USA
00018  */
00019 
00020 
00021 /*
00022  * ============================================================================
00023  *
00024  *                                  Preamble
00025  *
00026  * ============================================================================
00027  */
00028 
00029 
00030 #include <math.h>
00031 #include <lal/LALSimulation.h>
00032 #include <lal/LALDetectors.h>
00033 #include <lal/LALComplex.h>
00034 #include <lal/DetResponse.h>
00035 #include <lal/Date.h>
00036 #include <lal/Units.h>
00037 #include <lal/TimeDelay.h>
00038 #include <lal/SkyCoordinates.h>
00039 #include <lal/TimeSeries.h>
00040 #include <lal/FrequencySeries.h>
00041 #include <lal/TimeFreqFFT.h>
00042 #include <lal/Window.h>
00043 #include "check_series_macros.h"
00044 
00045 
00046 #include <lal/LALRCSID.h>
00047 NRCSID(LALSIMULATIONC, "$Id:");
00048 
00049 
00050 /*
00051  * ============================================================================
00052  *
00053  *                                 Utilities
00054  *
00055  * ============================================================================
00056  */
00057 
00058 
00059 /*
00060  * Note:  this function will round really really large numbers "up" to 0.
00061  */
00062 
00063 
00064 static unsigned long round_up_to_power_of_two(unsigned long x)
00065 {
00066         unsigned n;
00067 
00068         x--;
00069         /* if x shares bits with x + 1, then x + 1 is not a power of 2 */
00070         for(n = 1; n && (x & (x + 1)); n *= 2)
00071                 x |= x >> n;
00072 
00073         return x + 1;
00074 }
00075 
00076 
00077 /**
00078  * Turn an instrument name into a LALDetector structure.  The first two
00079  * characters of the input string are used as the instrument name, which
00080  * allows channel names in the form "H1:LSC-STRAIN" to be used.  The return
00081  * value is a pointer into the lalCachedDetectors array, so modifications
00082  * to the contents are global.  Make a copy of the structure if you want to
00083  * modify it safely.
00084  */
00085 
00086 
00087 const LALDetector *XLALInstrumentNameToLALDetector(
00088         const char *string
00089 )
00090 {
00091         static const char func[] = "XLALInstrumentNameToLALDetector";
00092         int i;
00093 
00094         for(i = 0; i < LAL_NUM_DETECTORS; i++)
00095                 if(!strncmp(string, lalCachedDetectors[i].frDetector.prefix, 2))
00096                         return &lalCachedDetectors[i];
00097 
00098         XLALPrintError("%s(): error: can't identify instrument from string \"%s\"\n", func, string);
00099         XLAL_ERROR_NULL(func, XLAL_EDATA);
00100 }
00101 
00102 
00103 /*
00104  * ============================================================================
00105  *
00106  *                            Injection Machinery
00107  *
00108  * ============================================================================
00109  */
00110 
00111 
00112 #if 0
00113 REAL8TimeSeries * 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 )
00114 {
00115         REAL8 slowDeltaT = 600.0; /* 10 minutes */
00116         UINT4 slowLength;
00117 
00118         /* sanity checks */
00119 
00120         /* SET UP LOOK-UP TABLES */
00121 
00122         /* construct time-dependent time-delay look-up table */
00123         /* uses ephemeris */
00124         /* sampled slowDeltaT */
00125         /* starts 8 min before injection start, ends 8 min after injection end */
00126         slowLength = floor(0.5 + (length*deltaT + 16.0)/slowDeltaT);
00127 
00128         /* construct time-dependent polarization response look-up table */
00129         /* sampled at slowDeltaT */
00130         /* starts 8 min before injection start, ends 8 min after injection end */
00131         fplus;
00132         fcross;
00133 
00134 
00135 
00136         /* apply time-dependent time-delay */
00137         /* apply time-dependent polarization response */
00138         /* apply frequency-dependent detector response */
00139         for ( j = 0; j < injection->length; ++j ) {
00140                 REAL8 dt;
00141                 REAL8 fpl;
00142                 REAL8 fcr;
00143                 REAL8 apl;
00144                 REAL8 acr;
00145                 REAL8 freq;
00146                 REAL8 phase;
00147                 REAL8 pol;
00148 
00149                 /* interpolate to get dt, fpl, fcr */
00150 
00151                 /* interpolate to get apl, acr, freq, phase, pol */
00152                 /* but not at detector time: detector time plus dt */
00153 
00154                 /* rotate fpl and fcr using pol */
00155 
00156                 /* adjust apl, acr, and phase by detector response at freq */
00157 
00158                 injection->data->data[j] = fpl*apl*cos(phase) + fcr*acr*sin(phase); /* or something like this */
00159 
00160         }
00161 
00162 
00163 
00164 }
00165 #endif
00166 
00167 
00168 /**
00169  * Input
00170  *
00171  * - h+ and hx time series for the injection with their epochs set to the
00172  *   start of those time series at the geocentre (for simplicity the epochs
00173  *   must be the same),
00174  *
00175  * - the right ascension and declination of the source in radians.
00176  *
00177  * - the orientation of the wave co-ordinate system, psi, in radians.
00178  *
00179  * - the detector into which the injection is destined to be injected,
00180  *
00181  * Output
00182  *
00183  * The strain time series as seen in the detector, with the epoch set to
00184  * the start of the time series at that detector.
00185  *
00186  * Notes
00187  *
00188  * Antenna response factors are computed sample-by-sample, but waveform is
00189  * not Doppler corrected or otherwise re-interpolated to account for
00190  * detector motion.
00191  *
00192  * The output time series units are the same as the two input time series
00193  * (which must both have the same sample units).
00194  */
00195 
00196 
00197 REAL8TimeSeries *XLALSimDetectorStrainREAL8TimeSeries(
00198         const REAL8TimeSeries *hplus,
00199         const REAL8TimeSeries *hcross,
00200         REAL8 right_ascension,
00201         REAL8 declination,
00202         REAL8 psi,
00203         LALDetector *detector
00204 )
00205 {
00206         static const char func[] = "XLALSimDetectorStrainREAL8TimeSeries";
00207         char name[13];  /* "?? injection" + terminator */
00208         REAL8TimeSeries *h;
00209         unsigned i;
00210 
00211         LAL_CHECK_VALID_SERIES(hplus, NULL);
00212         LAL_CHECK_VALID_SERIES(hcross, NULL);
00213         LAL_CHECK_CONSISTENT_TIME_SERIES(hplus, hcross, NULL);
00214 
00215         /* generate name */
00216 
00217         sprintf(name, "%2s injection", detector->frDetector.prefix);
00218 
00219         /* allocate output time series. */
00220 
00221         h = XLALCreateREAL8TimeSeries(name, &hplus->epoch, hplus->f0, hplus->deltaT, &hplus->sampleUnits, hplus->data->length);
00222         if(!h)
00223                 XLAL_ERROR_NULL(func, XLAL_EFUNC);
00224 
00225         /* add the detector's geometric delay.  after this, epoch = the
00226          * time of the injection time series' first sample at the desired
00227          * detector */
00228 
00229         XLALGPSAdd(&h->epoch, XLALTimeDelayFromEarthCenter(detector->location, right_ascension, declination, &h->epoch));
00230 
00231         /* project + and x time series onto detector */
00232 
00233         for(i = 0; i < h->data->length; i++) {
00234                 LIGOTimeGPS t = h->epoch;
00235                 double fplus, fcross;
00236 
00237                 XLALGPSAdd(&t, i * h->deltaT);
00238                 XLALComputeDetAMResponse(&fplus, &fcross, detector->response, right_ascension, declination, psi, XLALGreenwichMeanSiderealTime(&t));
00239 
00240                 h->data->data[i] = fplus * hplus->data->data[i] + fcross * hcross->data->data[i];
00241         }
00242 
00243         /* done */
00244 
00245         return h;
00246 }
00247 
00248 
00249 /**
00250  * Essentially a wrapper for XLALAddREAL8TimeSeries(), but performs
00251  * sub-sample re-interpolation to adjust the source time series epoch to
00252  * lie on an integer sample boundary in the target time series.  This
00253  * transformation is done in the frequency domain, so it is convenient to
00254  * allow a response function to be applied at the same time.  Passing NULL
00255  * for the response function turns this feature off (i.e., uses a unit
00256  * response).
00257  *
00258  * NOTE:  the source time series is modified in place by this function!
00259  *
00260  * This function accepts source and target time series whose units are not
00261  * the same, and allows the two time series to be herterodyned (although it
00262  * currently requires them to have the same heterodyne frequency).
00263  */
00264 
00265 
00266 int XLALSimAddInjectionREAL8TimeSeries(
00267         REAL8TimeSeries *target,
00268         REAL8TimeSeries *h,
00269         const COMPLEX16FrequencySeries *response
00270 )
00271 {
00272         static const char func[] = "XLALSimAddInjectionREAL8TimeSeries";
00273         COMPLEX16FrequencySeries *tilde_h;
00274         REAL8FFTPlan *plan;
00275         REAL8Window *window;
00276         /* the source time series is padded with at least this many 0's at
00277          * the start and end before re-interpolation in an attempt to
00278          * suppress aperiodicity artifacts, and 1/2 this many samples is
00279          * clipped from the start and end afterwards */
00280         const unsigned aperiodicity_suppression_buffer = 32768;
00281         unsigned i;
00282         double start_sample_int;
00283         double start_sample_frac;
00284 
00285         /* check input */
00286 
00287         /* FIXME:  since we route the source time series through the
00288          * frequency domain, it might not be hard to adjust the heterodyne
00289          * frequency and sample rate to match the target time series
00290          * instead of making it an error if they don't match. */
00291 
00292         if(h->deltaT != target->deltaT || h->f0 != target->f0) {
00293                 XLALPrintError("%s(): error: input sample rates or heterodyne frequencies do not match\n", func);
00294                 XLAL_ERROR(func, XLAL_EINVAL);
00295         }
00296 
00297         /* extend the source time series by adding the "aperiodicity
00298          * padding" to the start and end.  for efficiency's sake, make sure
00299          * the new length is a power of two. */
00300 
00301         i = round_up_to_power_of_two(h->data->length + 2 * aperiodicity_suppression_buffer);
00302         if(i < h->data->length) {
00303                 /* integer overflow */
00304                 XLALPrintError("%s(): error: source time series too long\n", func);
00305                 XLAL_ERROR(func, XLAL_EBADLEN);
00306         }
00307         i -= h->data->length;
00308         if(!XLALResizeREAL8TimeSeries(h, -(int) (i / 2), h->data->length + i))
00309                 XLAL_ERROR(func, XLAL_EFUNC);
00310 
00311         /* compute the integer and fractional parts of the sample index in
00312          * the target time series on which the source time series begins.
00313          * modf() returns integer and fractional parts that have the same
00314          * sign, e.g. -3.9 --> -3 + -0.9.  we adjust these so that the
00315          * magnitude of the fractional part is not greater than 0.5, e.g.
00316          * -3.9 --> -4 + 0.1, so that we never do more than 1/2 a sample of
00317          * re-interpolation.  I don't know if really makes any difference,
00318          * though */
00319 
00320         start_sample_frac = modf(XLALGPSDiff(&h->epoch, &target->epoch) / target->deltaT, &start_sample_int);
00321         if(start_sample_frac < -0.5) {
00322                 start_sample_frac += 1.0;
00323                 start_sample_int -= 1.0;
00324         } else if(start_sample_frac > +0.5) {
00325                 start_sample_frac -= 1.0;
00326                 start_sample_int += 1.0;
00327         }
00328 
00329         /* transform source time series to frequency domain.  the FFT
00330          * function populates the frequency series' metadata with the
00331          * appropriate values. */
00332 
00333         tilde_h = XLALCreateCOMPLEX16FrequencySeries(NULL, &h->epoch, 0, 0, &lalDimensionlessUnit, h->data->length / 2 + 1);
00334         plan = XLALCreateForwardREAL8FFTPlan(h->data->length, 0);
00335         if(!tilde_h || !plan) {
00336                 XLALDestroyCOMPLEX16FrequencySeries(tilde_h);
00337                 XLALDestroyREAL8FFTPlan(plan);
00338                 XLAL_ERROR(func, XLAL_EFUNC);
00339         }
00340         i = XLALREAL8TimeFreqFFT(tilde_h, h, plan);
00341         XLALDestroyREAL8FFTPlan(plan);
00342         if(i) {
00343                 XLALDestroyCOMPLEX16FrequencySeries(tilde_h);
00344                 XLAL_ERROR(func, XLAL_EFUNC);
00345         }
00346 
00347         /* apply sub-sample time correction and optional response function
00348          * */
00349 
00350         for(i = 0; i < tilde_h->data->length; i++) {
00351                 const double f = tilde_h->f0 + i * tilde_h->deltaF;
00352                 COMPLEX16 fac;
00353 
00354                 /* phase for sub-sample time correction */
00355 
00356                 fac = LAL_CEXP(LAL_CMUL_REAL(LAL_COMPLEX16_I, -LAL_TWOPI * f * start_sample_frac * target->deltaT));
00357 
00358                 /* divide the source by the response function.  if a
00359                  * frequency is required that lies outside the domain of
00360                  * definition of the response function, then the response
00361                  * is assumed equal to its value at the nearest edge of the
00362                  * domain of definition.  within the domain of definition,
00363                  * frequencies are rounded to the nearest bin.  if the
00364                  * response function is zero in some bin, then the source
00365                  * data is zeroed in that bin (instead of dividing by 0).
00366                  * */
00367 
00368                 /* FIXME:  should we use GSL to construct an interpolator
00369                  * for the modulus and phase as functions of frequency, and
00370                  * use that to evaluate the response?  instead of rounding
00371                  * to nearest bin? */
00372 
00373                 if(response) {
00374                         int j = floor((f - response->f0) / response->deltaF + 0.5);
00375                         if(j < 0)
00376                                 j = 0;
00377                         else if((unsigned) j > response->data->length - 1)
00378                                 j = response->data->length - 1;
00379                         if(LAL_COMPLEX_EQ(response->data->data[j], LAL_COMPLEX16_ZERO))
00380                                 fac = LAL_COMPLEX16_ZERO;
00381                         else
00382                                 fac = LAL_CDIV(fac, response->data->data[j]);
00383                 }
00384 
00385                 /* apply factor */
00386 
00387                 tilde_h->data->data[i] = LAL_CMUL(tilde_h->data->data[i], fac);
00388         }
00389 
00390         /* adjust DC and Nyquist components.  the DC component must always
00391          * be real-valued.  because we have adjusted the source time series
00392          * to have a length that is an even integer (we've made it a power
00393          * of 2) the Nyquist component must also be real valued. */
00394 
00395         if(response) {
00396                 /* a response function has been provided.  zero the DC and
00397                  * Nyquist components */
00398                 if(tilde_h->f0 == 0.0)
00399                         tilde_h->data->data[0] = LAL_COMPLEX16_ZERO;
00400                 tilde_h->data->data[tilde_h->data->length - 1] = LAL_COMPLEX16_ZERO;
00401         } else {
00402                 /* no response has been provided.  set the phase of the DC
00403                  * component to 0, set the imaginary component of the
00404                  * Nyquist to 0 */
00405                 if(tilde_h->f0 == 0.0)
00406                         tilde_h->data->data[0] = XLALCOMPLEX16Rect(LAL_CABS(tilde_h->data->data[0]), 0.0);
00407                 tilde_h->data->data[tilde_h->data->length - 1] = XLALCOMPLEX16Rect(LAL_REAL(tilde_h->data->data[tilde_h->data->length - 1]), 0.0);
00408         }
00409 
00410         /* return to time domain */
00411 
00412         plan = XLALCreateReverseREAL8FFTPlan(h->data->length, 0);
00413         if(!plan) {
00414                 XLALDestroyCOMPLEX16FrequencySeries(tilde_h);
00415                 XLAL_ERROR(func, XLAL_EFUNC);
00416         }
00417         i = XLALREAL8FreqTimeFFT(h, tilde_h, plan);
00418         XLALDestroyREAL8FFTPlan(plan);
00419         XLALDestroyCOMPLEX16FrequencySeries(tilde_h);
00420         if(i)
00421                 XLAL_ERROR(func, XLAL_EFUNC);
00422 
00423         /* the deltaT can get "corrupted" by floating point round-off
00424          * during its trip through the frequency domain.  since this
00425          * function starts by confirming that the sample rate of the source
00426          * matches that of the target time series, we can use the target
00427          * series' sample rate to reset the source's sample rate to its
00428          * original value.  but we do a check to make sure we're not
00429          * masking a real bug */
00430 
00431         if(fabs(h->deltaT - target->deltaT) / target->deltaT > 1e-12) {
00432                 XLALPrintError("%s(): error: oops, internal sample rate mismatch\n", func);
00433                 XLAL_ERROR(func, XLAL_EERR);
00434         }
00435         h->deltaT = target->deltaT;
00436 
00437         /* set source epoch from target epoch and integer sample offset */
00438 
00439         h->epoch = target->epoch;
00440         XLALGPSAdd(&h->epoch, start_sample_int * target->deltaT);
00441 
00442         /* clip half of the "aperiodicity padding" from the start and end
00443          * of the source time series in a continuing effort to suppress
00444          * aperiodicity artifacts. */
00445 
00446         if(!XLALResizeREAL8TimeSeries(h, aperiodicity_suppression_buffer / 2, h->data->length - aperiodicity_suppression_buffer))
00447                 XLAL_ERROR(func, XLAL_EFUNC);
00448 
00449         /* apply a Tukey window whose tapers lie within the remaining
00450          * aperiodicity padding. leaving one sample of the aperiodicty
00451          * padding untouched on each side of the original time series
00452          * because the data might have been shifted into it */
00453 
00454         window = XLALCreateTukeyREAL8Window(h->data->length, (double) (aperiodicity_suppression_buffer - 2) / h->data->length);
00455         if(!window)
00456                 XLAL_ERROR(func, XLAL_EFUNC);
00457         for(i = 0; i < h->data->length; i++)
00458                 h->data->data[i] *= window->data->data[i];
00459         XLALDestroyREAL8Window(window);
00460 
00461         /* add source time series to target time series */
00462 
00463         if(!XLALAddREAL8TimeSeries(target, h))
00464                 XLAL_ERROR(func, XLAL_EFUNC);
00465 
00466         /* done */
00467 
00468         return 0;
00469 }

Generated on Tue Oct 7 02:40:04 2008 for LAL by  doxygen 1.5.2