/**
 * Freq-shift the given COMPLEX8Timeseries by an amount of 'shift' Hz,
 * using the time-domain expression y(t) = x(t) * e^(-i 2pi df t),
 * which shifts x(f) into y(f) = x(f + df)
 *
 * NOTE: this <b>modifies</b> the COMPLEX8TimeSeries in place
 */
int
XLALFrequencyShiftCOMPLEX8TimeSeries ( COMPLEX8TimeSeries *x,	        /**< [in/out] timeseries to time-shift */
				       const REAL8 shift	        /**< [in] freq-shift in Hz */
                                       )
{
  XLAL_CHECK ( (x != NULL) && ( x->data != NULL ), XLAL_EINVAL );

  if ( shift == 0 ) {
    return XLAL_SUCCESS;
  }

  /* get timeseries time-step */
  REAL8 deltat = x->deltaT;

  /* loop over COMPLEX8TimeSeries elements */
  for ( UINT4 k=0; k < x->data->length; k++)
    {
      REAL8 tk = k * deltat;	/* time of k-th bin */
      REAL8 shiftCycles = shift * tk;
      REAL4 fact_re, fact_im;			/* complex phase-shift factor e^(-2pi f tau) */

      /* use a sin/cos look-up-table for speed */
      XLAL_CHECK( XLALSinCos2PiLUT ( &fact_im, &fact_re, shiftCycles ) == XLAL_SUCCESS, XLAL_EFUNC );
      COMPLEX8 fact = crectf(fact_re, fact_im);

      x->data->data[k] *= fact;

    } /* for k < numBins */

  /* adjust timeseries heterodyne frequency to the shift */
  x->f0 -= shift;

  return XLAL_SUCCESS;

} /* XLALFrequencyShiftCOMPLEX8TimeSeries() */
/**
 * Time-shift the given SFT by an amount of 'shift' seconds,
 * using the frequency-domain expression
 * \f$\widetilde{y}(f) = \widetilde{x}(f) \, e^{i 2\pi\,f\,\tau}\f$,
 * which shifts \f$x(t)\f$ into \f$y(t) = x(t + \tau)\f$
 *
 * NOTE: this <b>modifies</b> the SFT in place
 */
int
XLALTimeShiftSFT ( SFTtype *sft,	/**< [in/out] SFT to time-shift */
		   REAL8 shift		/**< time-shift \f$\tau\f$ in seconds */
                   )
{
  XLAL_CHECK ( (sft != NULL) && (sft->data != NULL), XLAL_EINVAL );

  if ( shift == 0 ) {
    return XLAL_SUCCESS;
  }

  for ( UINT4 k=0; k < sft->data->length; k++ )
    {
      REAL8 fk = sft->f0 + k * sft->deltaF;	/* frequency of k-th bin */
      REAL8 shiftCyles = shift * fk;
      REAL4 fact_re, fact_im;			/* complex phase-shift factor e^(-2pi f tau) */
      XLAL_CHECK ( XLALSinCos2PiLUT ( &fact_im, &fact_re, shiftCyles ) == XLAL_SUCCESS, XLAL_EFUNC );

      COMPLEX8 fact = crectf(fact_re, fact_im);
      sft->data->data[k] *= fact;

    } /* for k < numBins */

  /* adjust SFTs epoch to the shift */
  XLALGPSAdd ( &sft->epoch, shift );

  return XLAL_SUCCESS;

} // XLALTimeShiftSFT()
/**
 * Apply a spin-down correction to the complex8 timeseries
 * using the time-domain expression y(t) = x(t) * e^(-i 2pi sum f_k * (t-tref)^(k+1)),
 *
 * NOTE: this <b>modifies</b> the input COMPLEX8TimeSeries in place
 */
int
XLALSpinDownCorrectionMultiTS ( MultiCOMPLEX8TimeSeries *multiTimeSeries,       /**< [in/out] timeseries to time-shift */
                                const PulsarDopplerParams *doppler		/**< parameter-space point to correct for */
                                )
{
  // check input sanity
  XLAL_CHECK ( (multiTimeSeries != NULL) &&  (multiTimeSeries->data != NULL) && (multiTimeSeries->length > 0), XLAL_EINVAL );
  XLAL_CHECK ( doppler != NULL, XLAL_EINVAL );

  UINT4 numDetectors = multiTimeSeries->length;

  LIGOTimeGPS *epoch = &(multiTimeSeries->data[0]->epoch);
  UINT4 numSamples = multiTimeSeries->data[0]->data->length;

  REAL8 dt = multiTimeSeries->data[0]->deltaT;

  /* determine number of spin down's and check if sensible */
  UINT4 nspins = PULSAR_MAX_SPINS - 1;
  while ( (nspins > 0) && (doppler->fkdot[nspins] == 0) ) {
    nspins--;
  }

  /* apply spin derivitive correction to resampled timeseries */
  REAL8 tk = XLALGPSDiff ( epoch, &(doppler->refTime) );
  for ( UINT4 k=0; k < numSamples; k++ )
    {
      REAL8 tk_pow_jp1 = tk;
      REAL8 cycles_k = 0;
      for ( UINT4 j=1; j <= nspins; j++ )
        {
          tk_pow_jp1 *= tk;
          /* compute fractional number of cycles the spin-derivitive has added since the reftime */
          cycles_k += LAL_FACT_INV[j+1] * doppler->fkdot[j] * tk_pow_jp1;
        } // for j < nspins

      REAL4 cosphase, sinphase;
      XLAL_CHECK( XLALSinCos2PiLUT ( &sinphase, &cosphase, -cycles_k ) == XLAL_SUCCESS, XLAL_EFUNC );
      COMPLEX8 em2piphase = crectf ( cosphase, sinphase );

      /* loop over detectors */
      for ( UINT4 X=0; X < numDetectors; X++ )
        {
          multiTimeSeries->data[X]->data->data[k] *= em2piphase;
        } // for X < numDetectors

      tk += dt;

    } // for k < numSamples

  return XLAL_SUCCESS;

} // XLALSpinDownCorrectionMultiTS()
/**
 * Turn the given SFTvector into one long time-series, properly dealing with gaps.
 */
COMPLEX8TimeSeries *
XLALSFTVectorToCOMPLEX8TimeSeries ( const SFTVector *sftsIn         /**< [in] SFT vector */
                                    )
{
  // check input sanity
  XLAL_CHECK_NULL ( (sftsIn !=NULL) && (sftsIn->length > 0), XLAL_EINVAL );

  // create a local copy of the input SFTs, as they will be locally modified!
  SFTVector *sfts;
  XLAL_CHECK_NULL ( (sfts = XLALDuplicateSFTVector ( sftsIn )) != NULL, XLAL_EFUNC );

  /* define some useful shorthands */
  UINT4 numSFTs = sfts->length;
  SFTtype *firstSFT = &(sfts->data[0]);
  SFTtype *lastSFT = &(sfts->data[numSFTs-1]);
  UINT4 numFreqBinsSFT = firstSFT->data->length;
  REAL8 dfSFT = firstSFT->deltaF;
  REAL8 Tsft = 1.0 / dfSFT;
  REAL8 deltaT = Tsft / numFreqBinsSFT;	// complex FFT: numSamplesSFT = numFreqBinsSFT
  REAL8 f0SFT = firstSFT->f0;

  /* if the start and end input pointers are NOT NULL then determine start and time-span of the final long time-series */
  LIGOTimeGPS start = firstSFT->epoch;
  LIGOTimeGPS end = lastSFT->epoch;
  XLALGPSAdd ( &end, Tsft );

  /* determine output time span */
  REAL8 Tspan;
  XLAL_CHECK_NULL ( (Tspan = XLALGPSDiff ( &end, &start ) ) > 0, XLAL_EINVAL );

  UINT4 numSamples = lround ( Tspan / deltaT );

  /* determine the heterodyning frequency */
  /* fHet = DC of our internal DFTs */
  UINT4 NnegSFT = NhalfNeg ( numFreqBinsSFT );
  REAL8 fHet = f0SFT + 1.0 * NnegSFT * dfSFT;

  /* ----- Prepare invFFT of SFTs: compute plan for FFTW */
  COMPLEX8FFTPlan *SFTplan;
  XLAL_CHECK_NULL ( (SFTplan = XLALCreateReverseCOMPLEX8FFTPlan( numFreqBinsSFT, 0 )) != NULL, XLAL_EFUNC );

  /* ----- Prepare short time-series holding ONE invFFT of a single SFT */
  LIGOTimeGPS XLAL_INIT_DECL(epoch);
  COMPLEX8TimeSeries *sTS;
  XLAL_CHECK_NULL ( (sTS = XLALCreateCOMPLEX8TimeSeries ( "short timeseries", &epoch, 0, deltaT, &emptyLALUnit, numFreqBinsSFT )) != NULL, XLAL_EFUNC );

  /* ----- prepare long TimeSeries container ---------- */
  COMPLEX8TimeSeries *lTS;
  XLAL_CHECK_NULL ( (lTS = XLALCreateCOMPLEX8TimeSeries ( firstSFT->name, &start, fHet, deltaT, &emptyLALUnit, numSamples )) != NULL, XLAL_EFUNC );
  memset ( lTS->data->data, 0, numSamples * sizeof(*lTS->data->data)); 	/* set all time-samples to zero (in case there are gaps) */

  /* ---------- loop over all SFTs and inverse-FFT them ---------- */
  for ( UINT4 n = 0; n < numSFTs; n ++ )
    {
      SFTtype *thisSFT = &(sfts->data[n]);

      /* find bin in long timeseries corresponding to starttime of *this* SFT */
      REAL8 offset_n = XLALGPSDiff ( &(thisSFT->epoch), &start );
      UINT4 bin0_n = lround ( offset_n / deltaT );	/* round to closest bin */

      REAL8 nudge_n = bin0_n * deltaT - offset_n;		/* rounding error */
      nudge_n = 1e-9 * round ( nudge_n * 1e9 );	/* round to closest nanosecond */
      /* nudge SFT into integer timestep bin if necessary */
      XLAL_CHECK_NULL ( XLALTimeShiftSFT ( thisSFT, nudge_n ) == XLAL_SUCCESS, XLAL_EFUNC  );

      /* determine heterodyning phase-correction for this SFT */
      REAL8 offset = XLALGPSDiff ( &thisSFT->epoch, &start );	// updated value after time-shift
      // fHet * Tsft is an integer by construction, because fHet was chosen as a frequency-bin of the input SFTs
      // therefore we only need the remainder (offset % Tsft)
      REAL8 offsetEff = fmod ( offset, Tsft );
      REAL8 hetCycles = fmod ( fHet * offsetEff, 1); // heterodyning phase-correction for this SFT

      if ( nudge_n != 0 ){
        XLALPrintInfo("n = %d, offset_n = %g, nudge_n = %g, offset = %g, offsetEff = %g, hetCycles = %g\n",
                      n, offset_n, nudge_n, offset, offsetEff, hetCycles );
      }

      REAL4 hetCorrection_re, hetCorrection_im;
      XLAL_CHECK_NULL ( XLALSinCos2PiLUT ( &hetCorrection_im, &hetCorrection_re, -hetCycles ) == XLAL_SUCCESS, XLAL_EFUNC );
      COMPLEX8 hetCorrection = crectf( hetCorrection_re, hetCorrection_im );

      /* Note: we also bundle the overall normalization of 'df' into the het-correction.
       * This ensures that the resulting timeseries will have the correct normalization, according to
       * x_l = invFT[sft]_l = df * sum_{k=0}^{N-1} xt_k * e^(i 2pi k l / N )
       * where x_l is the l-th timestamp, and xt_k is the k-th frequency bin of the SFT.
       * See the LAL-conventions on FFTs:  http://www.ligo.caltech.edu/docs/T/T010095-00.pdf
       * (the FFTw convention does not contain the factor of 'df', which is why we need to
       * apply it ourselves)
       *
       */
      hetCorrection *= dfSFT;

      XLAL_CHECK_NULL ( XLALReorderSFTtoFFTW (thisSFT->data) == XLAL_SUCCESS, XLAL_EFUNC );
      XLAL_CHECK_NULL ( XLALCOMPLEX8VectorFFT( sTS->data, thisSFT->data, SFTplan ) == XLAL_SUCCESS, XLAL_EFUNC );

      for ( UINT4 j=0; j < sTS->data->length; j++) {
        sTS->data->data[j] *= hetCorrection;
      } // for j < numFreqBinsSFT

      // copy the short (shifted) heterodyned timeseries into correct location within long timeseries
      UINT4 binsLeft = numSamples - bin0_n;
      UINT4 copyLen = MYMIN ( numFreqBinsSFT, binsLeft );		/* make sure not to write past the end of the long TS */
      memcpy ( &lTS->data->data[bin0_n], sTS->data->data, copyLen * sizeof(lTS->data->data[0]) );

    } /* for n < numSFTs */

  // cleanup memory
  XLALDestroySFTVector ( sfts );
  XLALDestroyCOMPLEX8TimeSeries ( sTS );
  XLALDestroyCOMPLEX8FFTPlan ( SFTplan );

  return lTS;

} // XLALSFTVectorToCOMPLEX8TimeSeries()
示例#5
0
/* Revamped version of LALDemod() (based on TestLALDemod() in CFS).
 * Compute JKS's Fa and Fb, which are ingredients for calculating the F-statistic.
 */
static int
XLALComputeFaFb ( Fcomponents *FaFb,                    /* [out] Fa,Fb (and possibly atoms) returned */
                  const SFTVector *sfts,                /* [in] input SFTs */
                  const PulsarSpins fkdot,              /* [in] frequency and derivatives fkdot = d^kf/dt^k */
                  const SSBtimes *tSSB,                 /* [in] SSB timing series for particular sky-direction */
                  const AMCoeffs *amcoe,                /* [in] antenna-pattern coefficients for this sky-direction */
                  const ComputeFParams *params )        /* addition computational params */
{
  UINT4 alpha;                  /* loop index over SFTs */
  UINT4 spdnOrder;              /* maximal spindown-orders */
  UINT4 numSFTs;                /* number of SFTs (M in the Notes) */
  COMPLEX16 Fa, Fb;
  REAL8 Tsft;                   /* length of SFTs in seconds */
  INT4 freqIndex0;              /* index of first frequency-bin in SFTs */
  INT4 freqIndex1;              /* index of last frequency-bin in SFTs */

  REAL4 *a_al, *b_al;           /* pointer to alpha-arrays over a and b */
  REAL8 *DeltaT_al, *Tdot_al;   /* pointer to alpha-arrays of SSB-timings */
  SFTtype *SFT_al;              /* SFT alpha  */
  UINT4 Dterms = params->Dterms;

  REAL8 norm = OOTWOPI;

  /* ----- check validity of input */
#ifndef LAL_NDEBUG
  if ( !FaFb ) {
    XLALPrintError ("\nOutput-pointer is NULL !\n\n");
    XLAL_ERROR ( XLAL_EINVAL);
  }

  if ( !sfts || !sfts->data ) {
    XLALPrintError ("\nInput SFTs are NULL!\n\n");
    XLAL_ERROR ( XLAL_EINVAL);
  }

  if ( !tSSB || !tSSB->DeltaT || !tSSB->Tdot || !amcoe || !amcoe->a || !amcoe->b || !params)
    {
      XLALPrintError ("\nIllegal NULL in input !\n\n");
      XLAL_ERROR ( XLAL_EINVAL);
    }

  if ( PULSAR_MAX_SPINS > LAL_FACT_MAX )
    {
      XLALPrintError ("\nInverse factorials table only up to order s=%d, can't handle %d spin-order\n\n",
                     LAL_FACT_MAX, PULSAR_MAX_SPINS - 1 );
      XLAL_ERROR ( XLAL_EINVAL);
    }
#endif

  /* ----- prepare convenience variables */
  numSFTs = sfts->length;
  Tsft = 1.0 / sfts->data[0].deltaF;
  {
    REAL8 dFreq = sfts->data[0].deltaF;
    freqIndex0 = lround ( sfts->data[0].f0 / dFreq ); /* lowest freqency-index */
    freqIndex1 = freqIndex0 + sfts->data[0].data->length;
  }

  /* ----- prepare return of 'FstatAtoms' if requested */
  if ( params->returnAtoms )
    {
      if ( (FaFb->multiFstatAtoms = LALMalloc ( sizeof(*FaFb->multiFstatAtoms) )) == NULL ){
        XLAL_ERROR ( XLAL_ENOMEM );
      }
      FaFb->multiFstatAtoms->length = 1;        /* in this function: single-detector only */
      if ( (FaFb->multiFstatAtoms->data = LALMalloc ( 1 * sizeof( *FaFb->multiFstatAtoms->data) )) == NULL ){
        LALFree (FaFb->multiFstatAtoms);
        XLAL_ERROR ( XLAL_ENOMEM );
      }
      if ( (FaFb->multiFstatAtoms->data[0] = XLALCreateFstatAtomVector ( numSFTs )) == NULL ) {
        LALFree ( FaFb->multiFstatAtoms->data );
        LALFree ( FaFb->multiFstatAtoms );
        XLAL_ERROR( XLAL_ENOMEM );
      }

      FaFb->multiFstatAtoms->data[0]->TAtom = Tsft;     /* time-baseline of returned atoms is Tsft */

    } /* if returnAtoms */

  /* ----- find highest non-zero spindown-entry */
  for ( spdnOrder = PULSAR_MAX_SPINS - 1;  spdnOrder > 0 ; spdnOrder --  )
    if ( fkdot[spdnOrder] )
      break;

  Fa = 0.0f;
  Fb = 0.0f;

  a_al = amcoe->a->data;        /* point to beginning of alpha-arrays */
  b_al = amcoe->b->data;
  DeltaT_al = tSSB->DeltaT->data;
  Tdot_al = tSSB->Tdot->data;
  SFT_al = sfts->data;

  /* Loop over all SFTs  */
  for ( alpha = 0; alpha < numSFTs; alpha++ )
    {
      REAL4 a_alpha, b_alpha;

      INT4 kstar;               /* central frequency-bin k* = round(xhat_alpha) */
      INT4 k0, k1;

      COMPLEX8 *Xalpha = SFT_al->data->data; /* pointer to current SFT-data */
      COMPLEX8 *Xalpha_l;       /* pointer to frequency-bin k in current SFT */
      REAL4 s_alpha=0, c_alpha=0;/* sin(2pi kappa_alpha) and (cos(2pi kappa_alpha)-1) */
      REAL4 realQ, imagQ;       /* Re and Im of Q = e^{-i 2 pi lambda_alpha} */
      REAL4 realXP, imagXP;     /* Re/Im of sum_k X_ak * P_ak */
      REAL4 realQXP, imagQXP;   /* Re/Im of Q_alpha R_alpha */

      REAL8 lambda_alpha, kappa_max, kappa_star;
      COMPLEX8 Fa_alpha, Fb_alpha;

      /* ----- calculate kappa_max and lambda_alpha */
      {
        UINT4 s;                /* loop-index over spindown-order */
        REAL8 phi_alpha, Dphi_alpha, DT_al;
        REAL8 Tas;      /* temporary variable to calculate (DeltaT_alpha)^s */

        /* init for s=0 */
        phi_alpha = 0.0;
        Dphi_alpha = 0.0;
        DT_al = (*DeltaT_al);
        Tas = 1.0;              /* DeltaT_alpha ^ 0 */

        for (s=0; s <= spdnOrder; s++)
          {
            REAL8 fsdot = fkdot[s];
            Dphi_alpha += fsdot * Tas * LAL_FACT_INV[s];        /* here: DT^s/s! */
            Tas *= DT_al;                               /* now: DT^(s+1) */
            phi_alpha += fsdot * Tas * LAL_FACT_INV[s+1];
          } /* for s <= spdnOrder */

        /* Step 3: apply global factors to complete Dphi_alpha */
        Dphi_alpha *= Tsft * (*Tdot_al);                /* guaranteed > 0 ! */

        lambda_alpha = phi_alpha - 0.5 * Dphi_alpha;

        /* real- and imaginary part of e^{-i 2 pi lambda_alpha } */
        if ( XLALSinCos2PiLUT ( &imagQ, &realQ, - lambda_alpha ) ) {
          XLAL_ERROR ( XLAL_EFUNC);
        }

        kstar = (INT4) (Dphi_alpha);    /* k* = floor(Dphi_alpha) for positive Dphi */
        kappa_star = Dphi_alpha - 1.0 * kstar;  /* remainder of Dphi_alpha: >= 0 ! */
        kappa_max = kappa_star + 1.0 * Dterms - 1.0;

        /* ----- check that required frequency-bins are found in the SFTs ----- */
        k0 = kstar - Dterms + 1;
        k1 = k0 + 2 * Dterms - 1;
        if ( (k0 < freqIndex0) || (k1 > freqIndex1) )
          {
            XLALPrintError ("Required frequency-bins [%d, %d] not covered by SFT-interval [%d, %d]\n\n",
                           k0, k1, freqIndex0, freqIndex1 );
            XLAL_ERROR(XLAL_EDOM);
          }

      } /* compute kappa_star, lambda_alpha */

      /* NOTE: sin[ 2pi (Dphi_alpha - k) ] = sin [ 2pi Dphi_alpha ], therefore
       * the trig-functions need to be calculated only once!
       * We choose the value sin[ 2pi(Dphi_alpha - kstar) ] because it is the
       * closest to zero and will pose no numerical difficulties !
       */
      XLAL_CHECK( XLALSinCos2PiLUT ( &s_alpha, &c_alpha, kappa_star ) == XLAL_SUCCESS, XLAL_EFUNC );
      c_alpha -= 1.0f;

      /* ---------- calculate the (truncated to Dterms) sum over k ---------- */

      /* ---------- ATTENTION: this the "hot-loop", which will be
       * executed many millions of times, so anything in here
       * has a HUGE impact on the whole performance of the code.
       *
       * DON'T touch *anything* in here unless you really know
       * what you're doing !!
       *------------------------------------------------------------
       */

      Xalpha_l = Xalpha + k0 - freqIndex0;  /* first frequency-bin in sum */

      realXP = 0;
      imagXP = 0;

      /* if no danger of denominator -> 0 */
      if ( ( kappa_star > LD_SMALL4 ) && (kappa_star < 1.0 - LD_SMALL4) )
        {
          /* improved hotloop algorithm by Fekete Akos:
           * take out repeated divisions into a single common denominator,
           * plus use extra cleverness to compute the nominator efficiently...
           */
          REAL4 Sn = crealf(*Xalpha_l);
          REAL4 Tn = cimagf(*Xalpha_l);
          REAL4 pn = kappa_max;
          REAL4 qn = pn;
          REAL4 U_alpha, V_alpha;

          /* recursion with 2*Dterms steps */
          UINT4 l;
          for ( l = 1; l < 2*Dterms; l ++ )
            {
              Xalpha_l ++;

              pn = pn - 1.0f;                   /* p_(n+1) */
              Sn = pn * Sn + qn * crealf(*Xalpha_l);    /* S_(n+1) */
              Tn = pn * Tn + qn * cimagf(*Xalpha_l);    /* T_(n+1) */
              qn *= pn;                         /* q_(n+1) */
            } /* for l <= 2*Dterms */

          U_alpha = Sn / qn;
          V_alpha = Tn / qn;

#ifndef LAL_NDEBUG
          if ( !isfinite(U_alpha) || !isfinite(V_alpha) || !isfinite(pn) || !isfinite(qn) || !isfinite(Sn) || !isfinite(Tn) ) {
            XLALPrintError("XLALComputeFaFb() returned non-finite: U_alpha=%f, V_alpha=%f, pn=%f, qn=%f, Sn=%f, Tn=%f\n",
                           U_alpha, V_alpha, pn, qn, Sn, Tn);
            XLAL_ERROR (XLAL_EFPINVAL);
          }
#endif

          realXP = s_alpha * U_alpha - c_alpha * V_alpha;
          imagXP = c_alpha * U_alpha + s_alpha * V_alpha;

        } /* if |remainder| > LD_SMALL4 */
      else
        { /* otherwise: lim_{rem->0}P_alpha,k  = 2pi delta_{k,kstar} */
          UINT4 ind0;
          if ( kappa_star <= LD_SMALL4 ) ind0 = Dterms - 1;
          else ind0 = Dterms;
          realXP = TWOPI_FLOAT * crealf(Xalpha_l[ind0]);
          imagXP = TWOPI_FLOAT * cimagf(Xalpha_l[ind0]);
        } /* if |remainder| <= LD_SMALL4 */

      realQXP = realQ * realXP - imagQ * imagXP;
      imagQXP = realQ * imagXP + imagQ * realXP;

      /* we're done: ==> combine these into Fa and Fb */
      a_alpha = (*a_al);
      b_alpha = (*b_al);

      Fa_alpha = crectf( a_alpha * realQXP, a_alpha * imagQXP );
      Fa += Fa_alpha;

      Fb_alpha = crectf( b_alpha * realQXP, b_alpha * imagQXP );
      Fb += Fb_alpha;

      /* store per-SFT F-stat 'atoms' for transient-CW search */
      if ( params->returnAtoms )
        {
          FaFb->multiFstatAtoms->data[0]->data[alpha].timestamp = (UINT4)XLALGPSGetREAL8( &SFT_al->epoch );
          FaFb->multiFstatAtoms->data[0]->data[alpha].a2_alpha   = a_alpha * a_alpha;
          FaFb->multiFstatAtoms->data[0]->data[alpha].b2_alpha   = b_alpha * b_alpha;
          FaFb->multiFstatAtoms->data[0]->data[alpha].ab_alpha   = a_alpha * b_alpha;
          FaFb->multiFstatAtoms->data[0]->data[alpha].Fa_alpha   = norm * Fa_alpha;
          FaFb->multiFstatAtoms->data[0]->data[alpha].Fb_alpha   = norm * Fb_alpha;
        }

      /* advance pointers over alpha */
      a_al ++;
      b_al ++;
      DeltaT_al ++;
      Tdot_al ++;
      SFT_al ++;

    } /* for alpha < numSFTs */

  /* return result */
  FaFb->Fa = norm * Fa;
  FaFb->Fb = norm * Fb;

  return XLAL_SUCCESS;

} // XLALComputeFaFb()