int main ( void )
{
  const char *fn = __func__;

  //LALStatus status = empty_status;

  SFTtype *mySFT;
  LIGOTimeGPS epoch = { 731210229, 0 };
  REAL8 dFreq = 1.0 / 1800.0;
  REAL8 f0 = 150.0 - 2.0 * dFreq;

  /* init data array */
  COMPLEX8 vals[] = {
    crectf( -1.249241e-21,   1.194085e-21 ),
    crectf(  2.207420e-21,   2.472366e-22 ),
    crectf(  1.497939e-21,   6.593609e-22 ),
    crectf(  3.544089e-20,  -9.365807e-21 ),
    crectf(  1.292773e-21,  -1.402466e-21 )
  };
  UINT4 numBins = sizeof ( vals ) / sizeof(vals[0] );

  if ( (mySFT = XLALCreateSFT ( numBins )) == NULL ) {
    XLALPrintError ("%s: Failed to create test-SFT using XLALCreateSFT(), xlalErrno = %d\n", fn, xlalErrno );
    return XLAL_EFAILED;
  }
  /* init header */
  strcpy ( mySFT->name, "H1;testSFTRngmed" );
  mySFT->epoch = epoch;
  mySFT->f0 = f0;
  mySFT->deltaF = dFreq;

  /* we simply copy over these data-values into the SFT */
  UINT4 iBin;
  for ( iBin = 0; iBin < numBins; iBin ++ )
    mySFT->data->data[iBin] = vals[iBin];

  /* get memory for running-median vector */
  REAL8FrequencySeries rngmed;
  INIT_MEM ( rngmed );
  XLAL_CHECK ( (rngmed.data = XLALCreateREAL8Vector ( numBins )) != NULL, XLAL_EFUNC, "Failed  XLALCreateREAL8Vector ( %d )", numBins );

  // ---------- Test running-median PSD estimation in simple blocksize cases
  // ------------------------------------------------------------
  // TEST 1: odd blocksize = 3
  // ------------------------------------------------------------
  UINT4 blockSize3 = 3;

  /* reference result for 3-bin block running-median computed in octave:
octave> sft = [ \
        -1.249241e-21 +  1.194085e-21i, \
         2.207420e-21 +  2.472366e-22i, \
         1.497939e-21 +  6.593609e-22i, \
         3.544089e-20 -  9.365807e-21i, \
         1.292773e-21 -  1.402466e-21i  \
         ];
octave> periodo = abs(sft).^2;
octave> m1 = median ( periodo(1:3) ); m2 = median ( periodo(2:4) ); m3 = median ( periodo (3:5 ) );
octave> rngmed = [ m1, m1, m2, m3, m3 ];
octave> printf ("rngmedREF3 = { %.16g, %.16g, %.16g, %.16g, %.16g };\n", rngmed );
        rngmedREF3[] = { 2.986442063306e-42, 2.986442063306e-42, 4.933828992779561e-42, 3.638172910684999e-42, 3.638172910684999e-42 };
  */
  REAL8 rngmedREF3[] = { 2.986442063306e-42, 2.986442063306e-42, 4.933828992779561e-42, 3.638172910684999e-42, 3.638172910684999e-42 };

  /* compute running median */
  XLAL_CHECK ( XLALSFTtoRngmed ( &rngmed, mySFT, blockSize3 ) == XLAL_SUCCESS, XLAL_EFUNC, "XLALSFTtoRngmed() failed.");

  /* get median->mean bias correction, needed for octave-reference results, to make
   * them comparable to the bias-corrected results from LALSFTtoRngmed()
   */
  REAL8 medianBias3 = XLALRngMedBias ( blockSize3 );
  XLAL_CHECK ( xlalErrno == 0, XLAL_EFUNC, "XLALRngMedBias() failed.");

  BOOLEAN pass = 1;
  const CHAR *passStr;
  printf ("%4s %22s %22s %8s    <%g\n", "Bin", "rngmed(LAL)", "rngmed(Octave)", "relError", tol);
  for (iBin=0; iBin < numBins; iBin ++ )
    {
      REAL8 rngmedVAL = rngmed.data->data[iBin];
      REAL8 rngmedREF = rngmedREF3[iBin] / medianBias3;	// apply median-bias correction
      REAL8 relErr = REL_ERR ( rngmedREF, rngmedVAL );
      if ( relErr > tol ) {
        pass = 0;
        passStr = "fail";
      } else {
        passStr = "OK.";
      }

      printf ("%4d %22.16g %22.16g %8.1g    %s\n", iBin, rngmedVAL, rngmedREF, relErr, passStr );

    } /* for iBin < numBins */

  // ------------------------------------------------------------
  // TEST 2: even blocksize = 4
  // ------------------------------------------------------------
  UINT4 blockSize4 = 4;

  /* reference result for 4-bin block running-median computed in octave:
octave> m1 = median ( periodo(1:4) ); m2 = median ( periodo(2:5) );
octave> rngmed = [ m1, m1, m1, m2, m2 ];
octave> printf ("rngmedREF4[] = { %.16g, %.16g, %.16g, %.16g, %.16g };\n", rngmed );
rngmedREF4[] = { 3.96013552804278e-42, 3.96013552804278e-42, 3.96013552804278e-42, 4.28600095173228e-42, 4.28600095173228e-42 };
  */
  REAL8 rngmedREF4[] = { 3.96013552804278e-42, 3.96013552804278e-42, 3.96013552804278e-42, 4.28600095173228e-42, 4.28600095173228e-42 };

  /* compute running median */
  XLAL_CHECK ( XLALSFTtoRngmed ( &rngmed, mySFT, blockSize4 ) == XLAL_SUCCESS, XLAL_EFUNC, "XLALSFTtoRngmed() failed.");

  /* get median->mean bias correction, needed for octave-reference results, to make
   * them comparable to the bias-corrected results from LALSFTtoRngmed()
   */
  REAL8 medianBias4 = XLALRngMedBias ( blockSize4 );
  XLAL_CHECK ( xlalErrno == 0, XLAL_EFUNC, "XLALRngMedBias() failed.");

  printf ("%4s %22s %22s %8s    <%g\n", "Bin", "rngmed(LAL)", "rngmed(Octave)", "relError", tol);
  for (iBin=0; iBin < numBins; iBin ++ )
    {
      REAL8 rngmedVAL = rngmed.data->data[iBin];
      REAL8 rngmedREF = rngmedREF4[iBin] / medianBias4;	// apply median-bias correction
      REAL8 relErr = REL_ERR ( rngmedREF, rngmedVAL );
      if ( relErr > tol ) {
        pass = 0;
        passStr = "fail";
      } else {
        passStr = "OK.";
      }

      printf ("%4d %22.16g %22.16g %8.1g    %s\n", iBin, rngmedVAL, rngmedREF, relErr, passStr );

    } /* for iBin < numBins */

  /* free memory */
  XLALDestroyREAL8Vector ( rngmed.data );
  XLALDestroySFT ( mySFT );

  LALCheckMemoryLeaks();

  if ( !pass )
    {
      printf ("Test failed! Difference exceeded tolerance.\n");
      return XLAL_EFAILED;
    }
  else
    {
      printf ("Test passed.\n");
      return XLAL_SUCCESS;
    }

} /* main() */
/**
 * Unit-Test for function XLALSFTVectorToLFT().
 * Generates random data (timeseries + corresponding SFTs),
 * then feeds the SFTs into XLALSFTVectorToLFT()
 * and checks correctness of output Fourier transform by
 * comparing to FT of original real-valued timeseries.
 *
 * \note This indirectly also checks XLALSFTVectorToCOMPLEX8TimeSeries()
 * which is used by XLALSFTVectorToLFT().
 *
 * returns XLAL_SUCCESS on success, XLAL-error otherwise
 */
int
test_XLALSFTVectorToLFT ( void )
{
  // ----- generate real-valued random timeseries and corresponding SFTs
  REAL4TimeSeries *tsR4 = NULL;
  SFTVector *sfts0 = NULL;
  XLAL_CHECK ( XLALgenerateRandomData ( &tsR4, &sfts0 ) == XLAL_SUCCESS, XLAL_EFUNC );
  UINT4 numSamplesR4 = tsR4->data->length;
  REAL8 dt_R4 = tsR4->deltaT;
  REAL8 TspanR4 = numSamplesR4 * dt_R4;
  // ----- consider only the frequency band [3Hz, 4Hz]
  REAL8 out_fMin = 3;
  REAL8 out_Band = 1;
  SFTVector *sftsBand;
  XLAL_CHECK ( (sftsBand = XLALExtractBandFromSFTVector ( sfts0, out_fMin, out_Band )) != NULL, XLAL_EFUNC );
  XLALDestroySFTVector ( sfts0 );

  // ----- 1) compute FFT on original REAL4 timeseries -----
  REAL4FFTPlan *planR4;
  XLAL_CHECK ( (planR4 = XLALCreateForwardREAL4FFTPlan ( numSamplesR4, 0 )) != NULL, XLAL_EFUNC );

  SFTtype *lft0;
  XLAL_CHECK ( (lft0 = XLALCreateSFT ( numSamplesR4/2+1 )) != NULL, XLAL_EFUNC );
  XLAL_CHECK ( XLALREAL4ForwardFFT ( lft0->data, tsR4->data, planR4 ) == XLAL_SUCCESS, XLAL_EFUNC );

  strcpy ( lft0->name, tsR4->name );
  lft0->f0 = 0;
  lft0->epoch = tsR4->epoch;
  REAL8 dfLFT = 1.0 / TspanR4;
  lft0->deltaF = dfLFT;

  // ----- extract frequency band of interest
  SFTtype *lftR4 = NULL;
  XLAL_CHECK ( XLALExtractBandFromSFT ( &lftR4, lft0, out_fMin, out_Band ) == XLAL_SUCCESS, XLAL_EFUNC );
  for ( UINT4 k=0; k < lftR4->data->length; k ++ ) {
    lftR4->data->data[k] *= dt_R4;
  }

  // ----- 2) compute LFT directly from SFTs ----------
  SFTtype *lftSFTs0;
  XLAL_CHECK ( (lftSFTs0 = XLALSFTVectorToLFT ( sftsBand, 1 )) != NULL, XLAL_EFUNC );
  XLALDestroySFTVector ( sftsBand );

  // ----- re-extract frequency band of interest
  SFTtype *lftSFTs = NULL;
  XLAL_CHECK ( XLALExtractBandFromSFT ( &lftSFTs, lftSFTs0, out_fMin, out_Band ) == XLAL_SUCCESS, XLAL_EFUNC );

  if ( lalDebugLevel & LALINFO )
  {   // ----- write debug output
    XLAL_CHECK ( XLALdumpREAL4TimeSeries ( "TS_R4.dat", tsR4 ) == XLAL_SUCCESS, XLAL_EFUNC );

    XLAL_CHECK ( write_SFTdata ("LFT_R4T.dat", lftR4 ) == XLAL_SUCCESS, XLAL_EFUNC );
    XLAL_CHECK ( write_SFTdata ("LFT_SFTs.dat", lftSFTs ) == XLAL_SUCCESS, XLAL_EFUNC );

  } // end: debug output

  // ========== compare resulting LFTs ==========
  VectorComparison XLAL_INIT_DECL(tol0);
  XLALPrintInfo ("Comparing LFT with itself: should give 0 for all measures\n");
  XLAL_CHECK ( XLALCompareSFTs ( lftR4, lftR4, &tol0 ) == XLAL_SUCCESS, XLAL_EFUNC );
  XLAL_CHECK ( XLALCompareSFTs ( lftSFTs, lftSFTs, &tol0 ) == XLAL_SUCCESS, XLAL_EFUNC );

  VectorComparison XLAL_INIT_DECL(tol);
  tol.relErr_L1 	= 4e-2;
  tol.relErr_L2		= 5e-2;
  tol.angleV 		= 5e-2;	// rad
  tol.relErr_atMaxAbsx 	= 1.2e-2;
  tol.relErr_atMaxAbsy  = 1.2e-2;

  XLALPrintInfo ("Comparing LFT from REAL4-timeseries, to LFT from heterodyned COMPLEX8-timeseries:\n");
  XLAL_CHECK ( XLALCompareSFTs ( lftR4, lftSFTs, &tol ) == XLAL_SUCCESS, XLAL_EFUNC );

  // ---------- free memory ----------
  XLALDestroyREAL4TimeSeries ( tsR4 );
  XLALDestroyREAL4FFTPlan ( planR4 );

  XLALDestroySFT ( lft0 );
  XLALDestroySFT ( lftR4 );
  XLALDestroySFT ( lftSFTs );
  XLALDestroySFT ( lftSFTs0 );

  return XLAL_SUCCESS;

} // test_XLALSFTVectorToLFT()
/**
 * Turn the given multi-IFO SFTvectors into one long Fourier transform (LFT) over the total observation time
 */
SFTtype *
XLALSFTVectorToLFT ( SFTVector *sfts,		/**< input SFT vector (gets modified!) */
		     REAL8 upsampling		/**< upsampling factor >= 1 */
                     )
{
  XLAL_CHECK_NULL ( (sfts != NULL) && (sfts->length > 0), XLAL_EINVAL );
  XLAL_CHECK_NULL ( upsampling >= 1, XLAL_EDOM, "Upsampling factor (%f) must be >= 1 \n", upsampling );

  // ----- some useful SFT infos
  SFTtype *firstSFT = &(sfts->data[0]);
  UINT4 numBinsSFT = firstSFT->data->length;
  REAL8 dfSFT = firstSFT->deltaF;
  REAL8 Tsft = 1.0 / dfSFT;
  REAL8 f0SFT = firstSFT->f0;

  // ----- turn input SFTs into a complex (heterodyned) timeseries
  COMPLEX8TimeSeries *lTS;
  XLAL_CHECK_NULL ( (lTS = XLALSFTVectorToCOMPLEX8TimeSeries ( sfts )) != NULL, XLAL_EFUNC );
  REAL8 dt = lTS->deltaT;
  UINT4 numSamples0 = lTS->data->length;
  REAL8 Tspan0 = numSamples0 * dt;

  // ---------- determine time-span of upsampled time-series
  /* NOTE: Tspan MUST be an integer multiple of Tsft,
   * in order for the frequency bins of the final FFT
   * to be commensurate with the SFT bins.
   * This is required so that fHet is an exact
   * frequency-bin in both cases
   */
  UINT4 numSFTsFit = lround ( (Tspan0 * upsampling) / Tsft );
  REAL8 Tspan = numSFTsFit * Tsft;
  UINT4 numSamples = lround ( Tspan / dt );

  // ----- enlarge TimeSeries container for zero-padding if neccessary
  if ( numSamples > numSamples0 )
    {
      XLAL_CHECK_NULL ( (lTS->data->data = XLALRealloc ( lTS->data->data, numSamples * sizeof(lTS->data->data[0]) )) != NULL, XLAL_ENOMEM );
      lTS->data->length = numSamples;
      memset ( lTS->data->data + numSamples0, 0, (numSamples - numSamples0) * sizeof(lTS->data->data[0])); /* set all new time-samples to zero */
    }

  /* translate this back into fmin for the LFT (counting down from DC==fHet) */
  /* fHet = DC of our internal DFTs */
  UINT4 NnegSFT = NhalfNeg ( numBinsSFT );
  REAL8 fHet = f0SFT + 1.0 * NnegSFT * dfSFT;

  UINT4 NnegLFT = NhalfNeg ( numSamples );
  REAL8 f0LFT = fHet - NnegLFT / Tspan;

  // ----- prepare output LFT ----------
  SFTtype *outputLFT;
  XLAL_CHECK_NULL ( (outputLFT = XLALCreateSFT ( numSamples )) != NULL, XLAL_EFUNC );

  // prepare LFT header
  strcpy ( outputLFT->name, firstSFT->name );
  strncat ( outputLFT->name, ":long Fourier transform", sizeof(outputLFT->name) - 1 - strlen(outputLFT->name));
  outputLFT->epoch  = firstSFT->epoch;
  outputLFT->f0     = f0LFT;
  outputLFT->deltaF = 1.0 / Tspan;
  outputLFT->sampleUnits = firstSFT->sampleUnits;

  // ---------- FFT the long timeseries ----------
  COMPLEX8FFTPlan *LFTplan;
  XLAL_CHECK_NULL ( (LFTplan = XLALCreateForwardCOMPLEX8FFTPlan( numSamples, 0 )) != NULL, XLAL_EFUNC );
  XLAL_CHECK_NULL ( XLALCOMPLEX8VectorFFT( outputLFT->data, lTS->data, LFTplan ) == XLAL_SUCCESS, XLAL_EFUNC );
  XLAL_CHECK_NULL ( XLALReorderFFTWtoSFT (outputLFT->data) == XLAL_SUCCESS, XLAL_EFUNC );
  // apply proper normalization 'dt'
  for ( UINT4 k = 0; k < outputLFT->data->length; k ++ ) {
    outputLFT->data->data[k] *= dt;
  }

  /* cleanup memory */
  XLALDestroyCOMPLEX8TimeSeries ( lTS );
  XLALDestroyCOMPLEX8FFTPlan ( LFTplan );

  return outputLFT;

} // XLALSFTVectorToLFT()