/** Compute the semi-coherent statistics
 *
 * This function computes the semi-coherent s.
 *
 */
int XLALComputeSemiCoherentStat(FILE *fp,                                /**< [in] the output file pointer */
                                REAL4DemodulatedPowerVector *power,      /**< [in] the input data in the form of power */
                                ParameterSpace *pspace,                  /**< [in] the parameter space */
                                GridParametersVector *fgrid,		/**< UNDOCUMENTED */
                                GridParameters *bingrid,                 /**< [in] the grid parameters */
                                INT4 ntoplist,		/**< UNDOCUMENTED */
                                BOOLEAN with_xbins                       /**< enable fast summing of extra bins */
                                )
{

  toplist TL;                                         /* the results toplist */
  Template *bintemp = NULL;                           /* the binary parameter space template */
  Template *fdots = NULL;                             /* the freq derivitive template for each segment */
  UINT4 i,j;                                          /* counters */
  UINT4 percent = 0;                                  /* counter for status update */
  REAL8 mean = 0.0;
  REAL8 var = 0.0;
  UINT4 Ntemp = 0;

  /* validate input parameters */
 /*  if ((*SemiCo) != NULL) { */
/*     LogPrintf(LOG_CRITICAL,"%s: Invalid input, output BayesianProducts structure != NULL.\n",__func__); */
/*     XLAL_ERROR(XLAL_EINVAL); */
/*   } */
  if (power == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input REAL4DemodulatedPowerVector structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (pspace == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParameters structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (fgrid == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParametersVector structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (bingrid == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParameters structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }

  /* check for same frequency spacing */
  const REAL8 dfreq = fgrid->segment[0]->grid[0].delta;
  for (i=0;i<power->length;i++) {
    if ( dfreq != fgrid->segment[i]->grid[0].delta ) {
      LogPrintf(LOG_CRITICAL,"%s : inconsistent frequency spacing in segment %u, %.10g != %.10g\n",__func__,i,fgrid->segment[i]->grid[0].delta,dfreq);
      XLAL_ERROR(XLAL_EINVAL);
    }
  }

  /* check that coherent grids step by 1 in frequency */
  for (i=0;i<power->length;i++) {
    GridParameters *fdotgrid = fgrid->segment[i];
    if ( fdotgrid->prod[0] != 1 ) {
      LogPrintf(LOG_CRITICAL,"%s : coherent grid step in segment %u does not equal 1\n",__func__,i);
      XLAL_ERROR(XLAL_EINVAL);
    }
  }

  /* allocate memory for the results toplist */
  TL.n = ntoplist;
  if ((TL.data = (REAL4 *)XLALCalloc(TL.n,sizeof(REAL4))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  if ((TL.params = (REAL8 **)XLALCalloc(TL.n,sizeof(REAL8 *))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  for (i=0;i<(UINT4)TL.n;i++) {
    TL.params[i] = NULL;
    if ((TL.params[i] = (REAL8 *)XLALCalloc(bingrid->ndim,sizeof(REAL8))) == NULL) {
      LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
      XLAL_ERROR(XLAL_ENOMEM);
    }
  }
  TL.idx = 0;

  /* allocate memory for the fdots */
  if ((fdots = XLALCalloc(power->length, sizeof(*fdots))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  for (i=0;i<power->length;i++) {
    if ((fdots[i].x = XLALCalloc(fgrid->segment[0]->ndim,sizeof(REAL8))) == NULL) {
      LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
      XLAL_ERROR(XLAL_ENOMEM);
    }
    if ((fdots[i].idx = XLALCalloc(fgrid->segment[0]->ndim,sizeof(INT4))) == NULL) {
      LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
      XLAL_ERROR(XLAL_ENOMEM);
    }
    fdots[i].ndim = fgrid->segment[0]->ndim;
  }

  /* define the chi-squared threshold */
  /* REAL8 thr = gsl_cdf_chisq_Qinv(frac,2*power->length);
  LogPrintf(LOG_DEBUG,"%s : computed the threshold as %f\n",__func__,thr); */

  int (*getnext)(Template **temp,GridParameters *gridparams, ParameterSpace *space,void *);
  UINT8 newmax = bingrid->max;
  ParameterSpace *temppspace = NULL;
  if (bingrid->coverage>0) {
    getnext = &XLALGetNextRandomBinaryTemplate;
    newmax = bingrid->Nr;
    temppspace = pspace;
  }
  else getnext = &XLALGetNextTemplate;

  REAL4 *logLratiosumvec = NULL;

  /* single loop over binary templates */
  double fine_deriv_t = 0;
  UINT8 num_fine_deriv = 0;
  double fine_sum_t = 0;
  UINT8 num_fine_sum = 0;
  INT4 max_tot_xbins = 0;
  while (getnext(&bintemp,bingrid,temppspace,r)) {

    const REAL8 asini = bintemp->x[1];           /* define asini */
    const REAL8 omega = bintemp->x[3];           /* define omega */

    /* maximum extra bins to sum in frequency at this template,
       while keeping correct derivative bins (minus 10% for safety) */
    const INT4 xbins = !with_xbins ? 0 :
      (INT4) floor( 0.45 * dfreq / ( asini * omega ) );
    XLAL_CHECK( xbins >= 0, XLAL_EDOM, "Invalid xbins=%d\n", xbins );

    INT4 left_xbins = xbins, right_xbins = xbins;

    /** loop over segments **********************************************************************************/
    const double fine_deriv_t0 = XLALGetCPUTime();
    for (i=0;i<power->length;i++) {

      REAL8 tmid = XLALGPSGetREAL8(&(power->segment[i]->epoch)) + 0.5*pspace->tseg;
      GridParameters *fdotgrid = fgrid->segment[i];
      /* REAL8 norm = (REAL8)background->data[i]; */

      /* compute instantaneous frequency derivitives corresponding to the current template for this segment */
      XLALComputeBinaryFreqDerivitives(&fdots[i],bintemp,tmid);

      /* find ndim-D indices corresponding to the spin derivitive values for the segment power */
      for (j=0;j<fdots[i].ndim;j++) {
        fdots[i].idx[j] = lround( (fdots[i].x[j] - fdotgrid->grid[j].min)*fdotgrid->grid[j].oneoverdelta );

        /* check that index is in range */
        if ( fdots[i].idx[j] < 0 || fdots[i].idx[j] >= (INT4)fdotgrid->grid[j].length ) {
          LogPrintf(LOG_CRITICAL,"%s: stepped outside grid in segment %d, nu=%g, asini=%g, tasc=%g, omega=%g, fdots[%d] = [%g <= %g <= %g, 0 <= %d < %d]\n", __func__, i, bintemp->x[0], bintemp->x[1], bintemp->x[2], bintemp->x[3], j, fdotgrid->grid[j].min, fdots[i].x[j], fdotgrid->grid[j].min + fdotgrid->grid[j].delta*fdotgrid->grid[j].length, fdots[i].idx[j], fdotgrid->grid[j].length);
          XLAL_ERROR(XLAL_EDOM);
        }

      }

      /* ensure number of extra frequency bins do not go out of range of coherent grids */
      left_xbins = GSL_MIN( left_xbins, (INT4)( fdots[i].idx[0] ) );
      right_xbins = GSL_MIN( right_xbins, (INT4)( fdotgrid->grid[0].length - fdots[i].idx[0] - 1 ) );
      XLAL_CHECK( left_xbins >= 0, XLAL_EDOM, "Invalid left_xbins=%d\n", left_xbins );
      XLAL_CHECK( right_xbins >= 0, XLAL_EDOM, "Invalid right_xbins=%d\n", right_xbins );

    } /* end loop over segments */
    fine_deriv_t += XLALGetCPUTime() - fine_deriv_t0;
    num_fine_deriv += power->length;
    /*************************************************************************************/

    const INT4 tot_xbins = 1 + left_xbins + right_xbins;
    if ( max_tot_xbins < tot_xbins ) {
      max_tot_xbins = tot_xbins;

      /* reallocate logLratiosumvec */
      logLratiosumvec = XLALRealloc( logLratiosumvec, max_tot_xbins * sizeof( *logLratiosumvec ) );
      XLAL_CHECK( logLratiosumvec != NULL, XLAL_ENOMEM );

    }

    /** loop over segments **********************************************************************************/
    const double fine_sum_t0 = XLALGetCPUTime();
    for (i=0;i<power->length;i++) {

      REAL4DemodulatedPower *currentpower = power->segment[i];
      GridParameters *fdotgrid = fgrid->segment[i];

      /* find starting 1-D index corresponding to the spin derivitive values for the segment power */
      INT4 idx0 = fdots[i].idx[0] - left_xbins;
      for (j=1;j<fdots[i].ndim;j++) {
        idx0 += fdots[i].idx[j] * fdotgrid->prod[j];
      }

      /* define the power at this location in this segment */
      if (i==0) {
        memcpy( logLratiosumvec, &currentpower->data->data[idx0], tot_xbins * sizeof( *logLratiosumvec ) );
      } else {
        XLAL_CHECK( XLALVectorAddREAL4( logLratiosumvec, logLratiosumvec, &currentpower->data->data[idx0], tot_xbins ) == XLAL_SUCCESS, XLAL_EFUNC );
      }

    } /* end loop over segments */
    fine_sum_t += XLALGetCPUTime() - fine_sum_t0;
    num_fine_sum += ( power->length - 1 ) * tot_xbins;
    /*************************************************************************************/

    /* make it a true chi-squared variable */
    XLAL_CHECK( XLALVectorScaleREAL4( logLratiosumvec, 2.0, logLratiosumvec, tot_xbins ) == XLAL_SUCCESS, XLAL_EFUNC );

    /* save central binary template frequency */
    const REAL8 nu0 = bintemp->x[0];

    for (INT4 x = -left_xbins; x <= right_xbins; ++x) {

      /* set binary template frequency */
      bintemp->x[0] = nu0 + dfreq*x;

      /* output semi-coherent statistic for this template if it exceeds the threshold*/
      const REAL4 logLratiosum = logLratiosumvec[x + left_xbins];
      mean += logLratiosum;
      var += logLratiosum*logLratiosum;
      ++Ntemp;
      XLALtoplist(logLratiosum,bintemp,&TL);

    }

    /* if (logLratiosum>thr) {
      for (j=0;j<bingrid->ndim;j++) fprintf(fp,"%6.12f\t",bintemp->x[j]);
      fprintf(fp,"%6.12e\n",logLratiosum);
    } */

    /* output status to screen */
    if ( (bintemp->currentidx == 0) || (floor(100.0*(REAL8)bintemp->currentidx/(REAL8)newmax) > (REAL8)percent) ) {
      percent = (UINT4)floor(100*(REAL8)bintemp->currentidx/(REAL8)newmax);
      LogPrintf(LOG_NORMAL,"%s : completed %d%% (%"LAL_UINT8_FORMAT"/%"LAL_UINT8_FORMAT")\n",__func__,percent,bintemp->currentidx,newmax);
    }

    /* we've computed this number of extra templates */
    bintemp->currentidx += left_xbins + right_xbins;

  } /* end loop over templates */
  LogPrintf(LOG_NORMAL,"%s : time to compute fine grid derivatives = %0.12e\n",__func__, fine_deriv_t);
  LogPrintf(LOG_NORMAL,"%s : number of fine grid derivatives = %"LAL_UINT8_FORMAT"\n",__func__, num_fine_deriv);
  LogPrintf(LOG_NORMAL,"%s : time to compute fine summations = %0.12e\n",__func__, fine_sum_t);
  LogPrintf(LOG_NORMAL,"%s : number of fine grid summations = %"LAL_UINT8_FORMAT"\n",__func__, num_fine_sum);
  LogPrintf(LOG_NORMAL,"%s : summed up to %d frequency bins at a time\n", __func__, max_tot_xbins);
  /*************************************************************************************/

  /* compute mean and variance of results */
  mean = mean/(REAL8)Ntemp;
  var = var/(REAL8)(Ntemp-1);
  var = (var - mean*mean);

  /* modify toplist to have correct mean and variance */
  for (i=0;i<(UINT4)TL.n;i++) TL.data[i] = (TL.data[i] - mean)*sqrt(4.0*power->length)/sqrt(var) + 2.0*power->length;

  /* output toplist to file */
  for (i=0;i<(UINT4)TL.n;i++) {
    for (j=0;j<bingrid->ndim;j++) fprintf(fp,"%6.12f\t",TL.params[i][j]);
    fprintf(fp,"%6.12e\n",TL.data[i]);
  }

  /* free template memory */
  for (i=0;i<power->length;i++) {
    XLALFree(fdots[i].x);
    XLALFree(fdots[i].idx);
  }
  XLALFree(fdots);
  XLALFree(TL.data);
  for (i=0;i<(UINT4)TL.n;i++) XLALFree(TL.params[i]);
  XLALFree(TL.params);

  XLALFree(logLratiosumvec);

  LogPrintf(LOG_DEBUG,"%s : leaving.\n",__func__);
  return XLAL_SUCCESS;

}
/** Compute the semi-coherent statistics
 *
 * This function computes the semi-coherent s.
 *
 */
int XLALComputeSemiCoherentStat(FILE *fp,                                /**< [in] the output file pointer */
				REAL4DemodulatedPowerVector *power,      /**< [in] the input data in the form of power */
				ParameterSpace *pspace,                  /**< [in] the parameter space */
				GridParametersVector *fgrid,		/**< UNDOCUMENTED */
				GridParameters *bingrid,                 /**< [in] the grid parameters */
				INT4 ntoplist		/**< UNDOCUMENTED */
				)
{

  toplist TL;					      /* the results toplist */
  Template *bintemp = NULL;                           /* the binary parameter space template */
  Template fdots;                                     /* the freq derivitive template for each segment */
  UINT4 i,j;                                          /* counters */
  UINT4 percent = 0;                                  /* counter for status update */
  REAL8 mean = 0.0;
  REAL8 var = 0.0;

  /* validate input parameters */
 /*  if ((*SemiCo) != NULL) { */
/*     LogPrintf(LOG_CRITICAL,"%s: Invalid input, output BayesianProducts structure != NULL.\n",__func__); */
/*     XLAL_ERROR(XLAL_EINVAL); */
/*   } */
  if (power == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input REAL4DemodulatedPowerVector structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (pspace == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParameters structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (fgrid == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParametersVector structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }
  if (bingrid == NULL) {
    LogPrintf(LOG_CRITICAL,"%s: Invalid input, input GridParameters structure = NULL.\n",__func__);
    XLAL_ERROR(XLAL_EINVAL);
  }

  /* allocate memory for the results toplist */
  TL.n = ntoplist;
  if ((TL.data = (REAL8 *)XLALCalloc(TL.n,sizeof(REAL8))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  if ((TL.params = (REAL8 **)XLALCalloc(TL.n,sizeof(REAL8 *))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  for (i=0;i<(UINT4)TL.n;i++) {
    TL.params[i] = NULL;
    if ((TL.params[i] = (REAL8 *)XLALCalloc(bingrid->ndim,sizeof(REAL8))) == NULL) {
      LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
      XLAL_ERROR(XLAL_ENOMEM);
    }
  }
  TL.idx = 0;

  /* allocate memory for the fdots */
  if ((fdots.x = XLALCalloc(fgrid->segment[0]->ndim,sizeof(REAL8))) == NULL) {
    LogPrintf(LOG_CRITICAL,"%s : XLALCalloc() failed with error = %d\n",__func__,xlalErrno);
    XLAL_ERROR(XLAL_ENOMEM);
  }
  fdots.ndim = fgrid->segment[0]->ndim;

  /* define the chi-squared threshold */
  /* REAL8 thr = gsl_cdf_chisq_Qinv(frac,2*power->length);
  LogPrintf(LOG_DEBUG,"%s : computed the threshold as %f\n",__func__,thr); */

  int (*getnext)(Template **temp,GridParameters *gridparams, ParameterSpace *space,void *);
  INT4 newmax = bingrid->max;
  ParameterSpace *temppspace = NULL;
  if (bingrid->Nr>0) {
    getnext = &XLALGetNextRandomBinaryTemplate;
    newmax = bingrid->Nr;
    temppspace = pspace;
  }
  else getnext = &XLALGetNextTemplate;

  /* single loop over binary templates */
  while (getnext(&bintemp,bingrid,temppspace,r)) {

    REAL8 logLratiosum = 0.0;                       /* initialise likelihood ratio */

    /** loop over segments **********************************************************************************/
    for (i=0;i<power->length;i++) {

      REAL4DemodulatedPower *currentpower = power->segment[i];
      GridParameters *fdotgrid = fgrid->segment[i];
      REAL8 tmid = XLALGPSGetREAL8(&(power->segment[i]->epoch)) + 0.5*pspace->tseg;
      /* REAL8 norm = (REAL8)background->data[i]; */
      INT4 idx = 0;

      /* compute instantaneous frequency derivitives corresponding to the current template for this segment */
      XLALComputeBinaryFreqDerivitives(&fdots,bintemp,tmid);

      /* find indices corresponding to the spin derivitive values for the segment power */
      for (j=0;j<fdots.ndim;j++) {
	UINT4 tempidx = 0.5 + (fdots.x[j] - fdotgrid->grid[j].min)*fdotgrid->grid[j].oneoverdelta;
	idx += tempidx*fdotgrid->prod[j];
      }

      /* define the power at this location in this segment */
      logLratiosum += currentpower->data->data[idx]; /* /norm; */

    } /* end loop over segments */
    /*************************************************************************************/

    /* output semi-coherent statistic for this template if it exceeds the threshold*/
    logLratiosum *= 2.0;     /* make it a true chi-squared variable */
    mean += logLratiosum;
    var += logLratiosum*logLratiosum;
    XLALtoplist(logLratiosum,bintemp,&TL);

    /* if (logLratiosum>thr) {
      for (j=0;j<bingrid->ndim;j++) fprintf(fp,"%6.12f\t",bintemp->x[j]);
      fprintf(fp,"%6.12e\n",logLratiosum);
    } */

    /* output status to screen */
    if (floor(100.0*(REAL8)bintemp->currentidx/(REAL8)newmax) > (REAL8)percent) {
      percent = (UINT4)floor(100*(REAL8)bintemp->currentidx/(REAL8)newmax);
      LogPrintf(LOG_DEBUG,"%s : completed %d%% (%d/%d)\n",__func__,percent,bintemp->currentidx,newmax);
    }

  } /* end loop over templates */
  /*************************************************************************************/

  /* compute mean and variance of results */
  mean = mean/(REAL8)newmax;
  var = var/(REAL8)(newmax-1);
  var = (var - mean*mean);

  /* modify toplist to have correct mean and variance */
  for (i=0;i<(UINT4)TL.n;i++) TL.data[i] = (TL.data[i] - mean)*sqrt(4.0*power->length)/sqrt(var) + 2.0*power->length;

  /* output toplist to file */
  for (i=0;i<(UINT4)TL.n;i++) {
    for (j=0;j<bingrid->ndim;j++) fprintf(fp,"%6.12f\t",TL.params[i][j]);
    fprintf(fp,"%6.12e\n",TL.data[i]);
  }

  /* free template memory */
  XLALFree(fdots.x);
  XLALFree(TL.data);
  for (i=0;i<(UINT4)TL.n;i++) XLALFree(TL.params[i]);
  XLALFree(TL.params);

  LogPrintf(LOG_DEBUG,"%s : leaving.\n",__func__);
  return XLAL_SUCCESS;

}