Esempio n. 1
0
REAL8 calculate_ligo_snr_from_strain_real8(  REAL8TimeSeries *strain,
                                       const CHAR            ifo[3])
{

  REAL8 ret = -1, snrSq, freq, psdValue;
  REAL8 deltaF;
  REAL8FFTPlan *pfwd;
  COMPLEX16FrequencySeries *fftData;
  UINT4 k;

  /* create the time series */
  deltaF  = strain->deltaT * strain->data->length;
  fftData = XLALCreateCOMPLEX16FrequencySeries( strain->name,  &(strain->epoch),
                                               0, deltaF, &lalDimensionlessUnit,
                                               strain->data->length/2 + 1 );

  /* perform the fft */
  pfwd = XLALCreateForwardREAL8FFTPlan( strain->data->length, 0 );
  XLALREAL8TimeFreqFFT( fftData, strain, pfwd );

  /* compute the SNR for initial LIGO at design */
  for ( snrSq = 0, k = 0; k < fftData->data->length; k++ )
    {
      freq = fftData->deltaF * k;

      if ( ifo[0] == 'V' )
        {
          if (freq < 35)
            continue;

          LALVIRGOPsd( NULL, &psdValue, freq );
          psdValue /= 9e-46;
        }
      else
        {
          if (freq < 40)
            continue;

          LALLIGOIPsd( NULL, &psdValue, freq );
        }

      fftData->data->data[k] /= 3e-23;
      snrSq += creal(fftData->data->data[k]) * creal(fftData->data->data[k]) / psdValue;
      snrSq += cimag(fftData->data->data[k]) * cimag(fftData->data->data[k]) / psdValue;
    }
  snrSq *= 4*fftData->deltaF;

  XLALDestroyREAL8FFTPlan( pfwd );
  XLALDestroyCOMPLEX16FrequencySeries( fftData );

  ret = sqrt(snrSq);
  return ret;
}
static PyObject *__new__(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	pylal_COMPLEX16FrequencySeries *obj;
	LIGOTimeGPS zero = {0, 0};

	obj = (pylal_COMPLEX16FrequencySeries *) PyType_GenericNew(type, args, kwds);
	if(!obj)
		return NULL;
	obj->series = XLALCreateCOMPLEX16FrequencySeries(NULL, &zero, 0.0, 0.0, &lalDimensionlessUnit, 0);
	obj->owner = NULL;
	return (PyObject *) obj;
}
Esempio n. 3
0
REAL8 calculate_lalsim_snr(SimInspiralTable *inj, char *IFOname, REAL8FrequencySeries *psd, REAL8 start_freq)
{
  /* Calculate and return the single IFO SNR
   *
   * Required options:
   *
   * inj:     SimInspiralTable entry for which the SNR has to be calculated
   * IFOname: The canonical name (e.g. H1, L1, V1) name of the IFO for which the SNR must be calculated
   * PSD:     PSD curve to be used for the overlap integrap
   * start_freq: lower cutoff of the overlap integral
   *
   * */

  int ret=0;
  INT4 errnum=0;
  UINT4 j=0;
  /* Fill detector site info */
  LALDetector*  detector=NULL;
  detector=calloc(1,sizeof(LALDetector));
  if(!strcmp(IFOname,"H1"))
    memcpy(detector,&lalCachedDetectors[LALDetectorIndexLHODIFF],sizeof(LALDetector));
  if(!strcmp(IFOname,"H2"))
    memcpy(detector,&lalCachedDetectors[LALDetectorIndexLHODIFF],sizeof(LALDetector));
  if(!strcmp(IFOname,"LLO")||!strcmp(IFOname,"L1"))
    memcpy(detector,&lalCachedDetectors[LALDetectorIndexLLODIFF],sizeof(LALDetector));
  if(!strcmp(IFOname,"V1")||!strcmp(IFOname,"VIRGO"))
    memcpy(detector,&lalCachedDetectors[LALDetectorIndexVIRGODIFF],sizeof(LALDetector));

  Approximant approx=TaylorF2;
  approx=XLALGetApproximantFromString(inj->waveform);
  LALSimulationDomain modelDomain;

  if(XLALSimInspiralImplementedFDApproximants(approx)) modelDomain = LAL_SIM_DOMAIN_FREQUENCY;
  else if(XLALSimInspiralImplementedTDApproximants(approx)) modelDomain = LAL_SIM_DOMAIN_TIME;
  else
  {
      fprintf(stderr,"ERROR. Unknown approximant number %i. Unable to choose time or frequency domain model.",approx);
      exit(1);
  }

  REAL8 m1,m2, s1x,s1y,s1z,s2x,s2y,s2z,phi0,f_min,f_max,iota,polarization;

  /* No tidal PN terms until injtable is able to get them */

  LALDict *LALpars= XLALCreateDict();

  /* Spin and tidal interactions at the highest level (default) until injtable stores them.
   * When spinO and tideO are added to injtable we can un-comment those lines and should be ok
   *
  int spinO = inj->spinO;
  int tideO = inj->tideO;
  XLALSimInspiralSetSpinOrder(waveFlags, *(LALSimInspiralSpinOrder*) spinO);
  XLALSimInspiralSetTidalOrder(waveFlags, *(LALSimInspiralTidalOrder*) tideO);
  */

  XLALSimInspiralWaveformParamsInsertPNPhaseOrder(LALpars,XLALGetOrderFromString(inj->waveform));
  XLALSimInspiralWaveformParamsInsertPNAmplitudeOrder(LALpars,inj->amp_order);
  /* Read parameters */
  m1=inj->mass1*LAL_MSUN_SI;
  m2=inj->mass2*LAL_MSUN_SI;
  s1x=inj->spin1x;
  s1y=inj->spin1y;
  s1z=inj->spin1z;
  s2x=inj->spin2x;
  s2y=inj->spin2y;
  s2z=inj->spin2z;
  iota=inj->inclination;
  f_min=XLALSimInspiralfLow2fStart(inj->f_lower,XLALSimInspiralWaveformParamsLookupPNAmplitudeOrder(LALpars),XLALGetApproximantFromString(inj->waveform));
  phi0=inj->coa_phase;
  polarization=inj->polarization;
  REAL8 latitude=inj->latitude;
  REAL8 longitude=inj->longitude;

  LIGOTimeGPS epoch;
  memcpy(&epoch,&(inj->geocent_end_time),sizeof(LIGOTimeGPS));

  /* Hardcoded values of srate and segment length. If changed here they must also be changed in inspinj.c */
  REAL8 srate=4096.0;
  const CHAR *WF=inj->waveform;
  /* Increase srate for EOB WFs */
  if (strstr(WF,"EOB"))
    srate=8192.0;
  REAL8 segment=64.0;

  f_max=(srate/2.0-(1.0/segment));
  size_t seglen=(size_t) segment*srate;
  REAL8 deltaF=1.0/segment;
  REAL8 deltaT=1.0/srate;

  /* Frequency domain h+ and hx. They are going to be filled either by a FD WF or by the FFT of a TD WF*/
  COMPLEX16FrequencySeries *freqHplus;
  COMPLEX16FrequencySeries *freqHcross;
  freqHplus=  XLALCreateCOMPLEX16FrequencySeries("fhplus",
    &epoch,
    0.0,
    deltaF,
    &lalDimensionlessUnit,
    seglen/2+1
  );

  freqHcross=XLALCreateCOMPLEX16FrequencySeries("fhcross",
    &epoch,
    0.0,
    deltaF,
    &lalDimensionlessUnit,
    seglen/2+1
  );

  /* If the approximant is on the FD call XLALSimInspiralChooseFDWaveform */
  if (modelDomain == LAL_SIM_DOMAIN_FREQUENCY)
  {

    COMPLEX16FrequencySeries *hptilde=NULL;
    COMPLEX16FrequencySeries *hctilde=NULL;
    //We do not pass the polarization here, we assume it is taken into account when projecting h+,x onto the detector.
    XLAL_TRY(ret=XLALSimInspiralChooseFDWaveform(&hptilde,&hctilde, m1, m2,
						 s1x, s1y, s1z, s2x, s2y, s2z,
						 LAL_PC_SI * 1.0e6, iota, phi0, 0., 0., 0.,
						 deltaF, f_min, 0.0, 0.0,
						 LALpars,approx),errnum);
    XLALDestroyDict(LALpars);

    if(!hptilde|| hptilde->data==NULL || hptilde->data->data==NULL ||!hctilde|| hctilde->data==NULL || hctilde->data->data==NULL)
    {
      XLALPrintError(" ERROR in XLALSimInspiralChooseFDWaveform(): error generating waveform. errnum=%d. Exiting...\n",errnum );
      exit(1);
    }

    COMPLEX16 *dataPtr = hptilde->data->data;
    for (j=0; j<(UINT4) freqHplus->data->length; ++j)
    {
      if(j < hptilde->data->length)
      {
        freqHplus->data->data[j] = dataPtr[j];
      }
      else
      {
        freqHplus->data->data[j]=0.0 + I*0.0;
      }
    }
    dataPtr = hctilde->data->data;
    for (j=0; j<(UINT4) freqHplus->data->length; ++j)
    {
      if(j < hctilde->data->length)
      {
        freqHcross->data->data[j] = dataPtr[j];
      }
      else
      {
        freqHcross->data->data[j]=0.0+0.0*I;
      }
    }
    /* Clean */
    if(hptilde) XLALDestroyCOMPLEX16FrequencySeries(hptilde);
    if(hctilde) XLALDestroyCOMPLEX16FrequencySeries(hctilde);

  }
  else
  {

    /* Otherwise use XLALSimInspiralChooseTDWaveform */
    REAL8FFTPlan *timeToFreqFFTPlan = XLALCreateForwardREAL8FFTPlan((UINT4) seglen, 0 );
    REAL8TimeSeries *hplus=NULL;
    REAL8TimeSeries *hcross=NULL;
    REAL8TimeSeries *timeHplus=NULL;
    REAL8TimeSeries *timeHcross=NULL;
    REAL8 padding =0.4;//seconds
    REAL8Window *window=XLALCreateTukeyREAL8Window(seglen,(REAL8)2.0*padding*srate/(REAL8)seglen);
    REAL4 WinNorm = sqrt(window->sumofsquares/window->data->length);
    timeHcross=XLALCreateREAL8TimeSeries("timeModelhCross",
      &epoch,
      0.0,
      deltaT,
      &lalStrainUnit,
      seglen
    );
    timeHplus=XLALCreateREAL8TimeSeries("timeModelhplus",
      &epoch,
      0.0,
      deltaT,
      &lalStrainUnit,
      seglen
    );
    for (j=0;j<(UINT4) timeHcross->data->length;++j)
      timeHcross->data->data[j]=0.0;
    for (j=0;j<(UINT4) timeHplus->data->length;++j)
      timeHplus->data->data[j]=0.0;
    XLAL_TRY(ret=XLALSimInspiralChooseTDWaveform(&hplus, &hcross, m1, m2,
						 s1x, s1y, s1z, s2x, s2y, s2z,
						 LAL_PC_SI*1.0e6, iota,
						 phi0, 0., 0., 0., deltaT, f_min, 0.,
						 LALpars, approx),
        errnum);

    if (ret == XLAL_FAILURE || hplus == NULL || hcross == NULL)
    {
      XLALPrintError(" ERROR in XLALSimInspiralChooseTDWaveform(): error generating waveform. errnum=%d. Exiting...\n",errnum );
      exit(1);
    }

    hplus->epoch  = timeHplus->epoch;
    hcross->epoch = timeHcross->epoch;

    XLALSimAddInjectionREAL8TimeSeries(timeHplus, hplus, NULL);
    XLALSimAddInjectionREAL8TimeSeries(timeHcross, hcross, NULL);
    for (j=0; j<(UINT4) timeHplus->data->length; ++j)
      timeHplus->data->data[j]*=window->data->data[j];
    for (j=0; j<(UINT4) timeHcross->data->length; ++j)
      timeHcross->data->data[j]*=window->data->data[j];
      
    for (j=0; j<(UINT4) freqHplus->data->length; ++j)
    {
      freqHplus->data->data[j]=0.0+I*0.0;
      freqHcross->data->data[j]=0.0+I*0.0;
    }

    /* FFT into freqHplus and freqHcross */
    XLALREAL8TimeFreqFFT(freqHplus,timeHplus,timeToFreqFFTPlan);
    XLALREAL8TimeFreqFFT(freqHcross,timeHcross,timeToFreqFFTPlan);
    for (j=0; j<(UINT4) freqHplus->data->length; ++j)
    {
      freqHplus->data->data[j]/=WinNorm;
      freqHcross->data->data[j]/=WinNorm;
    }
    /* Clean... */
    if ( hplus ) XLALDestroyREAL8TimeSeries(hplus);
    if ( hcross ) XLALDestroyREAL8TimeSeries(hcross);
    if ( timeHplus ) XLALDestroyREAL8TimeSeries(timeHplus);
    if ( timeHcross ) XLALDestroyREAL8TimeSeries(timeHcross);
    if (timeToFreqFFTPlan) LALFree(timeToFreqFFTPlan);
    if (window) XLALDestroyREAL8Window(window);
  }

  /* The WF has been generated and is in freqHplus/cross. Now project into the IFO frame */
  double Fplus, Fcross;
  double FplusScaled, FcrossScaled;
  double HSquared;
  double GPSdouble=(REAL8) inj->geocent_end_time.gpsSeconds+ (REAL8) inj->geocent_end_time.gpsNanoSeconds*1.0e-9;
  double gmst;
  LIGOTimeGPS GPSlal;
  XLALGPSSetREAL8(&GPSlal, GPSdouble);
  gmst=XLALGreenwichMeanSiderealTime(&GPSlal);

  /* Fill Fplus and Fcross*/
  XLALComputeDetAMResponse(&Fplus, &Fcross, (const REAL4 (*)[3])detector->response,longitude, latitude, polarization, gmst);
  /* And take the distance into account */
  FplusScaled  = Fplus  / (inj->distance);
  FcrossScaled = Fcross / (inj->distance);

  REAL8 timedelay = XLALTimeDelayFromEarthCenter(detector->location,longitude, latitude, &GPSlal);
  REAL8 timeshift =  timedelay;
  REAL8 twopit    = LAL_TWOPI * timeshift;

  UINT4 lower = (UINT4)ceil(start_freq / deltaF);
  UINT4 upper = (UINT4)floor(f_max / deltaF);
  REAL8 re = cos(twopit*deltaF*lower);
  REAL8 im = -sin(twopit*deltaF*lower);

  /* Incremental values, using cos(theta) - 1 = -2*sin(theta/2)^2 */
  REAL8 dim = -sin(twopit*deltaF);
  REAL8 dre = -2.0*sin(0.5*twopit*deltaF)*sin(0.5*twopit*deltaF);
  REAL8 TwoDeltaToverN = 2.0 *deltaT / ((double) seglen);

  REAL8 plainTemplateReal,  plainTemplateImag,templateReal,templateImag;
  REAL8 newRe, newIm,temp;
  REAL8 this_snr=0.0;
  if ( psd )
  {
    psd = XLALInterpolatePSD(psd,  deltaF);
  } 
  for (j=lower; j<=(UINT4) upper; ++j)
  {
    /* derive template (involving location/orientation parameters) from given plus/cross waveforms: */
    plainTemplateReal = FplusScaled * creal(freqHplus->data->data[j])
                        +  FcrossScaled *creal(freqHcross->data->data[j]);
    plainTemplateImag = FplusScaled * cimag(freqHplus->data->data[j])
                        +  FcrossScaled * cimag(freqHcross->data->data[j]);

    /* do time-shifting...             */
    /* (also un-do 1/deltaT scaling): */
    templateReal = (plainTemplateReal*re - plainTemplateImag*im) / deltaT;
    templateImag = (plainTemplateReal*im + plainTemplateImag*re) / deltaT;
    HSquared  = templateReal*templateReal + templateImag*templateImag ;
    temp = ((TwoDeltaToverN * HSquared) / psd->data->data[j]);
    this_snr  += temp;
    /* Now update re and im for the next iteration. */
    newRe = re + re*dre - im*dim;
    newIm = im + re*dim + im*dre;

    re = newRe;
    im = newIm;
  }

  /* Clean */
  if (freqHcross) XLALDestroyCOMPLEX16FrequencySeries(freqHcross);
  if (freqHplus) XLALDestroyCOMPLEX16FrequencySeries(freqHplus);
  if (detector) free(detector);

  return sqrt(this_snr*2.0);

}
Esempio n. 4
0
static int IMRPhenomCGenerateFDForTD(
    COMPLEX16FrequencySeries **htilde, /**< FD waveform */
    const REAL8 t0,					   /**< time of coalescence */
    const REAL8 phi0,                  /**< phase at peak */
    const REAL8 deltaF,                /**< frequency resolution */
    const REAL8 m1,                    /**< mass of companion 1 [solar masses] */
    const REAL8 m2,                    /**< mass of companion 2 [solar masses] */
    //const REAL8 chi,                   /**< mass-weighted aligned-spin parameter */
    const REAL8 f_min,                 /**< start frequency */
    const REAL8 f_max,                 /**< end frequency */
    const REAL8 distance,              /**< distance to source (m) */
    const BBHPhenomCParams *params,    /**< from ComputeIMRPhenomCParams */
    const size_t nf                    /**< Length of frequency vector required */
) {
  static LIGOTimeGPS ligotimegps_zero = {0, 0};
  size_t i;
  INT4 errcode;

  const REAL8 M = m1 + m2;
  const REAL8 eta = m1 * m2 / (M * M);

  /* Memory to temporarily store components of amplitude and phase */
  /*
  REAL8 phSPA, phPM, phRD, aPM, aRD;
  REAL8 wPlusf1, wPlusf2, wMinusf1, wMinusf2, wPlusf0, wMinusf0;
  */
  REAL8 phPhenomC = 0.0;
  REAL8 aPhenomC = 0.0;

  /* compute the amplitude pre-factor */
  REAL8 amp0 = 2. * sqrt(5. / (64.*LAL_PI)) * M * LAL_MRSUN_SI * M * LAL_MTSUN_SI / distance;

  /* allocate htilde */
  size_t n = NextPow2(f_max / deltaF) + 1;
  if ( n > nf )
    XLAL_ERROR(XLAL_EDOM, "The required length passed as input wont fit the FD waveform\n");
  else
    n = nf;
  *htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &ligotimegps_zero, 0.0,
      deltaF, &lalStrainUnit, n);
  memset((*htilde)->data->data, 0, n * sizeof(COMPLEX16));
  XLALUnitMultiply(&((*htilde)->sampleUnits), &((*htilde)->sampleUnits), &lalSecondUnit);
  if (!(*htilde)) XLAL_ERROR(XLAL_EFUNC);

  /* now generate the waveform */
  size_t ind_max = (size_t) (f_max / deltaF);
  for (i = (size_t) (f_min / deltaF); i < ind_max; i++)
  {
    REAL8 f = i * deltaF;

    errcode = IMRPhenomCGenerateAmpPhase( &aPhenomC, &phPhenomC, f, eta, params );
    if( errcode != XLAL_SUCCESS )
      XLAL_ERROR(XLAL_EFUNC);
    phPhenomC -= 2.*phi0; // factor of 2 b/c phi0 is orbital phase
    phPhenomC += 2.*LAL_PI*f*t0; //shifting the coalescence to t=t0

    /* generate the waveform */
    ((*htilde)->data->data)[i] = amp0 * aPhenomC * cos(phPhenomC);
    ((*htilde)->data->data)[i] += -I * amp0 * aPhenomC * sin(phPhenomC);
  }

  return XLAL_SUCCESS;
}
Esempio n. 5
0
static int IMRPhenomCGenerateFD(
    COMPLEX16FrequencySeries **htilde, /**< FD waveform */
    const REAL8 phi0,                  /**< phase at peak */
    const REAL8 deltaF,                /**< frequency resolution */
    const REAL8 m1,                    /**< mass of companion 1 [solar masses] */
    const REAL8 m2,                    /**< mass of companion 2 [solar masses] */
    //const REAL8 chi,                   /**< mass-weighted aligned-spin parameter */
    const REAL8 f_min,                 /**< start frequency */
    const REAL8 f_max,                 /**< end frequency */
    const REAL8 distance,              /**< distance to source (m) */
    const BBHPhenomCParams *params      /**< from ComputeIMRPhenomCParams */
) {
  LIGOTimeGPS ligotimegps_zero = LIGOTIMEGPSZERO; // = {0, 0}

  int errcode = XLAL_SUCCESS;
  /*
   We can't call XLAL_ERROR() directly with OpenMP on.
   Keep track of return codes for each thread and in addition use flush to get out of
   the parallel for loop as soon as possible if something went wrong in any thread.
  */

  const REAL8 M = m1 + m2;
  const REAL8 eta = m1 * m2 / (M * M);

  /* Memory to temporarily store components of amplitude and phase */
  /*
  REAL8 phSPA, phPM, phRD, aPM, aRD;
  REAL8 wPlusf1, wPlusf2, wMinusf1, wMinusf2, wPlusf0, wMinusf0;
  */

  /* compute the amplitude pre-factor */
  REAL8 amp0 = 2. * sqrt(5. / (64.*LAL_PI)) * M * LAL_MRSUN_SI * M * LAL_MTSUN_SI / distance;

  /* allocate htilde */
  size_t n = NextPow2(f_max / deltaF) + 1;
  /* coalesce at t=0 */
  XLALGPSAdd(&ligotimegps_zero, -1. / deltaF); // shift by overall length in time
  *htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &ligotimegps_zero, 0.0,
      deltaF, &lalStrainUnit, n);
  memset((*htilde)->data->data, 0, n * sizeof(COMPLEX16));
  XLALUnitMultiply(&((*htilde)->sampleUnits), &((*htilde)->sampleUnits), &lalSecondUnit);
  if (!(*htilde)) XLAL_ERROR(XLAL_EFUNC);

  size_t ind_min = (size_t) (f_min / deltaF);
  size_t ind_max = (size_t) (f_max / deltaF);

  /* Set up spline for phase */
  gsl_interp_accel *acc = gsl_interp_accel_alloc();
  size_t L =  ind_max - ind_min;
  gsl_spline *phiI = gsl_spline_alloc(gsl_interp_cspline, L);
  REAL8 *freqs = XLALMalloc(L*sizeof(REAL8));
  REAL8 *phis = XLALMalloc(L*sizeof(REAL8));

  /* now generate the waveform */
  #pragma omp parallel for
  for (size_t i = ind_min; i < ind_max; i++)
  {

    REAL8 phPhenomC = 0.0;
    REAL8 aPhenomC = 0.0;
    REAL8 f = i * deltaF;

    int per_thread_errcode;
    #pragma omp flush(errcode)
    if (errcode != XLAL_SUCCESS)
      goto skip;

    per_thread_errcode = IMRPhenomCGenerateAmpPhase( &aPhenomC, &phPhenomC, f, eta, params );
    if (per_thread_errcode != XLAL_SUCCESS) {
      errcode = per_thread_errcode;
      #pragma omp flush(errcode)
    }

    phPhenomC -= 2.*phi0; // factor of 2 b/c phi0 is orbital phase

    freqs[i-ind_min] = f;
    phis[i-ind_min] = -phPhenomC; // PhenomP uses cexp(-I*phPhenomC); want to use same phase adjustment code, so we will flip the sign of the phase

    /* generate the waveform */
    ((*htilde)->data->data)[i] = amp0 * aPhenomC * cos(phPhenomC);
    ((*htilde)->data->data)[i] += -I * amp0 * aPhenomC * sin(phPhenomC);

  skip: /* this statement intentionally left blank */;

  }

  if( errcode != XLAL_SUCCESS )
    XLAL_ERROR(errcode);

  /* Correct phasing so we coalesce at t=0 (with the definition of the epoch=-1/deltaF above) */
  gsl_spline_init(phiI, freqs, phis, L);

  REAL8 f_final = params->fRingDown;
  XLAL_PRINT_INFO("f_ringdown = %g\n", f_final);

  // Prevent gsl interpolation errors
  if (f_final > freqs[L-1])
    f_final = freqs[L-1];
  if (f_final < freqs[0])
    XLAL_ERROR(XLAL_EDOM, "f_ringdown <= f_min\n");

  /* Time correction is t(f_final) = 1/(2pi) dphi/df (f_final) */
  REAL8 t_corr = gsl_spline_eval_deriv(phiI, f_final, acc) / (2*LAL_PI);
  XLAL_PRINT_INFO("t_corr = %g\n", t_corr);
  /* Now correct phase */
  for (size_t i = ind_min; i < ind_max; i++) {
    REAL8 f = i * deltaF;
    ((*htilde)->data->data)[i] *= cexp(-2*LAL_PI * I * f * t_corr);
  }

  gsl_spline_free(phiI);
  gsl_interp_accel_free(acc);
  XLALFree(freqs);
  XLALFree(phis);

  return XLAL_SUCCESS;
}
/**
 * Generate the "reduced-spin templates" proposed in http://arxiv.org/abs/1107.1267
 * Add the tidal phase terms from http://arxiv.org/abs/1101.1673 (Eqs. 3.9, 3.10)
 * The chi parameter should be determined from XLALSimInspiralTaylorF2ReducedSpinComputeChi.
 */
int XLALSimInspiralTaylorF2ReducedSpinTidal(
    COMPLEX16FrequencySeries **htilde,   /**< FD waveform */
    const REAL8 phic,                /**< orbital coalescence phase (rad) */
    const REAL8 deltaF,              /**< frequency resolution (Hz) */
    const REAL8 m1_SI,               /**< mass of companion 1 (kg) */
    const REAL8 m2_SI,               /**< mass of companion 2 (kg) */
    const REAL8 chi,                 /**< dimensionless aligned-spin param */
    const REAL8 lam1,                /**< (tidal deformability of mass 1) / (mass of body 1)^5 (dimensionless) */
    const REAL8 lam2,                /**< (tidal deformability of mass 2) / (mass of body 2)^5 (dimensionless) */
    const REAL8 fStart,              /**< start GW frequency (Hz) */
    const REAL8 fEnd,                /**< highest GW frequency (Hz) of waveform generation - if 0, end at Schwarzschild ISCO */
    const REAL8 r,                   /**< distance of source (m) */
    const INT4 phaseO,               /**< twice PN phase order */
    const INT4 ampO                  /**< twice PN amplitude order */
    ) {
    /* external: SI; internal: solar masses */
    const REAL8 m1 = m1_SI / LAL_MSUN_SI;
    const REAL8 m2 = m2_SI / LAL_MSUN_SI;
    const REAL8 m = m1 + m2;
    const REAL8 m_sec = m * LAL_MTSUN_SI;  /* total mass in seconds */
    const REAL8 eta = m1 * m2 / (m * m);
    const REAL8 xi1 = m1 / m;
    const REAL8 xi2 = m2 / m;
    const REAL8 piM = LAL_PI * m_sec;
    const REAL8 mSevenBySix = -7./6.;
    const REAL8 vISCO = 1. / sqrt(6.);
    const REAL8 fISCO = vISCO * vISCO * vISCO / piM;
    REAL8 v0 = 1.0; /* v0=c */
    REAL8 shft, amp0, f_max;
    REAL8 psiNewt, psi2, psi3, psi4, psi5, psi6, psi6L, psi7, psi3S, psi4S, psi5S, psi10T1, psi10T2, psi10, psi12T1, psi12T2, psi12;
    REAL8 alpha2, alpha3, alpha4, alpha5, alpha6, alpha6L, alpha7, alpha3S, alpha4S, alpha5S;
    size_t i, n, iStart;
    COMPLEX16 *data = NULL;
    LIGOTimeGPS tStart = {0, 0};

    /* check inputs for sanity */
    if (*htilde) XLAL_ERROR(XLAL_EFAULT);
    if (m1_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (m2_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (fabs(chi) > 1) XLAL_ERROR(XLAL_EDOM);
    if (fStart <= 0) XLAL_ERROR(XLAL_EDOM);
    if (r <= 0) XLAL_ERROR(XLAL_EDOM);
    if (ampO > 7) XLAL_ERROR(XLAL_EDOM); /* only implemented to pN 3.5 */
    if (phaseO > 7) XLAL_ERROR(XLAL_EDOM); /* only implemented to pN 3.5 */

    /* allocate htilde */
    if ( fEnd == 0. ) // End at ISCO
        f_max = fISCO;
    else // End at user-specified freq.
        f_max = fEnd;
    n = (size_t) (f_max / deltaF + 1);
    XLALGPSAdd(&tStart, -1 / deltaF);  /* coalesce at t=0 */
    *htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &tStart, 0.0, deltaF, &lalStrainUnit, n);
    if (!(*htilde)) XLAL_ERROR(XLAL_EFUNC);
    memset((*htilde)->data->data, 0, n * sizeof(COMPLEX16));
    XLALUnitMultiply(&((*htilde)->sampleUnits), &((*htilde)->sampleUnits), &lalSecondUnit);

    /* extrinsic parameters */
    amp0 = -pow(m_sec, 5./6.) * sqrt(5. * eta / 24.) / (cbrt(LAL_PI * LAL_PI) * r / LAL_C_SI);
    shft = LAL_TWOPI * (tStart.gpsSeconds + 1e-9 * tStart.gpsNanoSeconds);

    /* spin terms in the amplitude and phase (in terms of the reduced
     * spin parameter */
    psi3S = 113.*chi/3.;
    psi4S = 63845.*(-81. + 4.*eta)*chi*chi/(8.*pow(-113. + 76.*eta, 2.));
    psi5S = -565.*(-146597. + 135856.*eta + 17136.*eta*eta)*chi/(2268.*(-113. + 76.*eta));

    alpha3S = (113.*chi)/24.;
    alpha4S = (12769.*chi*chi*(-81. + 4.*eta))/(32.*pow(-113. + 76.*eta,2));
    alpha5S = (-113.*chi*(502429. - 591368.*eta + 1680*eta*eta))/(16128.*(-113 + 76*eta));

    /* tidal terms in the phase */
    psi10T2 = -24./xi2 * (1. + 11. * xi1) * lam2 * xi2*xi2*xi2*xi2*xi2;
    psi10T1 = -24./xi1 * (1. + 11. * xi2) * lam1 * xi1*xi1*xi1*xi1*xi1;
    psi12T2 = -5./28./xi2 * (3179. - 919.* xi2 - 2286.* xi2*xi2 + 260.* xi2*xi2*xi2)* lam2 * xi2*xi2*xi2*xi2*xi2;
    psi12T1 = -5./28./xi1 * (3179. - 919.* xi1 - 2286.* xi1*xi1 + 260.* xi1*xi1*xi1)* lam1 * xi1*xi1*xi1*xi1*xi1;

    /* coefficients of the phase at PN orders from 0 to 3.5PN */
    psiNewt = 3./(128.*eta);
    psi2 = 3715./756. + 55.*eta/9.;
    psi3 = psi3S - 16.*LAL_PI;
    psi4 = 15293365./508032. + 27145.*eta/504. + 3085.*eta*eta/72. + psi4S;
    psi5 = (38645.*LAL_PI/756. - 65.*LAL_PI*eta/9. + psi5S);
    psi6 = 11583231236531./4694215680. - (640.*LAL_PI*LAL_PI)/3. - (6848.*LAL_GAMMA)/21.
             + (-5162.983708047263 + 2255.*LAL_PI*LAL_PI/12.)*eta
             + (76055.*eta*eta)/1728. - (127825.*eta*eta*eta)/1296.;
    psi6L = -6848./21.;
    psi7 = (77096675.*LAL_PI)/254016. + (378515.*LAL_PI*eta)/1512.
             - (74045.*LAL_PI*eta*eta)/756.;
    psi10 = psi10T1+psi10T2;
    psi12 = psi12T1+psi12T2;

    /* amplitude coefficients */
    alpha2 = 1.1056547619047619 + (11*eta)/8.;
    alpha3 = -LAL_TWOPI + alpha3S;
    alpha4 = 0.8939214212884228 + (18913*eta)/16128. + (1379*eta*eta)/1152. + alpha4S;
    alpha5 = (-4757*LAL_PI)/1344. + (57*eta*LAL_PI)/16. + alpha5S;
    alpha6 = -58.601030974347324 + (3526813753*eta)/2.7869184e7 -
                (1041557*eta*eta)/258048. + (67999*eta*eta*eta)/82944. +
                (10*LAL_PI*LAL_PI)/3. - (451*eta*LAL_PI*LAL_PI)/96.;
    alpha6L = 856/105.;
    alpha7 = (-5111593*LAL_PI)/2.709504e6 - (72221*eta*LAL_PI)/24192. -
                (1349*eta*eta*LAL_PI)/24192.;

    /* select the terms according to the PN order chosen */
    switch (ampO) {
        case 0:
        case 1:
            alpha2 = 0.;
        case 2:
            alpha3 = 0.;
        case 3:
            alpha4 = 0.;
        case 4:
            alpha5 = 0.;
        case 5:
            alpha6 = 0.;
            alpha6L = 0.;
        case 6:
            alpha7 = 0.;
        default:
            break;
    }

    switch (phaseO) {
        case 0:
        case 1:
            psi2 = 0.;
        case 2:
            psi3 = 0.;
        case 3:
            psi4 = 0.;
        case 4:
            psi5 = 0.;
        case 5:
            psi6 = 0.;
            psi6L = 0.;
        case 6:
            psi7 = 0.;
        default:
            break;
    }

    /* Fill with non-zero vals from fStart to lesser of fEnd, fISCO */
    iStart = (size_t) ceil(fStart / deltaF);
    data = (*htilde)->data->data;
    const REAL8 logv0=log(v0);
    const REAL8 log4=log(4.0);
    
    for (i = iStart; i < n; i++) {
        /* fourier frequency corresponding to this bin */
        const REAL8 f = i * deltaF;
        const REAL8 v3 = piM*f;

        /* PN expansion parameter */
        REAL8 Psi, amp;
        const REAL8 v = cbrt(v3);
	const REAL8 logv=log(v);
        const REAL8 v2 = v*v;
	const REAL8 v4 = v3*v;
	const REAL8 v5 = v4*v;
	const REAL8 v6 = v3*v3;
	const REAL8 v7 = v6*v;
        const REAL8 v10 = v5*v5;
	const REAL8 v12 = v6*v6;

        /* compute the phase and amplitude */
        Psi = psiNewt / v5 * (1.
            + psi2 * v2 + psi3 * v3 + psi4 * v4
            + psi5 * v5 * (1. + 3. * (logv - logv0))
            + (psi6 + psi6L * (log4 + logv)) * v6 + psi7 * v7)
            + psi10 * v10 + psi12 * v12;

        amp = amp0 * pow(f, mSevenBySix) * (1.
            + alpha2 * v2 + alpha3 * v3 + alpha4 * v4 + alpha5 * v5
            + (alpha6 + alpha6L * (LAL_GAMMA + (log4 + logv))) * v6
            + alpha7 * v7);

        data[i] = amp * cos(Psi + shft * f - 2.*phic - LAL_PI_4) - I * (amp * sin(Psi + shft * f - 2.*phic - LAL_PI_4));
    }

    return XLAL_SUCCESS;
}
int XLALSimInspiralTaylorF2Core(
        COMPLEX16FrequencySeries **htilde_out, /**< FD waveform */
	const REAL8Sequence *freqs,            /**< frequency points at which to evaluate the waveform (Hz) */
        const REAL8 phi_ref,                   /**< reference orbital phase (rad) */
        const REAL8 m1_SI,                     /**< mass of companion 1 (kg) */
        const REAL8 m2_SI,                     /**< mass of companion 2 (kg) */
        const REAL8 S1z,                       /**<  z component of the spin of companion 1 */
        const REAL8 S2z,                       /**<  z component of the spin of companion 2  */
        const REAL8 f_ref,                     /**< Reference GW frequency (Hz) - if 0 reference point is coalescence */
	const REAL8 shft,		       /**< time shift to be applied to frequency-domain phase (sec)*/
        const REAL8 r,                         /**< distance of source (m) */
        LALDict *p /**< Linked list containing the extra testing GR parameters >**/
        )
{

    if (!htilde_out) XLAL_ERROR(XLAL_EFAULT);
    if (!freqs) XLAL_ERROR(XLAL_EFAULT);
    /* external: SI; internal: solar masses */
    const REAL8 m1 = m1_SI / LAL_MSUN_SI;
    const REAL8 m2 = m2_SI / LAL_MSUN_SI;
    const REAL8 m = m1 + m2;
    const REAL8 m_sec = m * LAL_MTSUN_SI;  /* total mass in seconds */
    const REAL8 eta = m1 * m2 / (m * m);
    const REAL8 piM = LAL_PI * m_sec;
    const REAL8 m1OverM = m1 / m;
    const REAL8 m2OverM = m2 / m;
    REAL8 amp0;
    size_t i;
    COMPLEX16 *data = NULL;
    LIGOTimeGPS tC = {0, 0};
    INT4 iStart = 0;

    COMPLEX16FrequencySeries *htilde = NULL;

    if (*htilde_out) { //case when htilde_out has been allocated in XLALSimInspiralTaylorF2
	    htilde = *htilde_out;
	    iStart = htilde->data->length - freqs->length; //index shift to fill pre-allocated data
	    if(iStart < 0) XLAL_ERROR(XLAL_EFAULT);
    }
    else { //otherwise allocate memory here
	    htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &tC, freqs->data[0], 0., &lalStrainUnit, freqs->length);
	    if (!htilde) XLAL_ERROR(XLAL_EFUNC);
	    XLALUnitMultiply(&htilde->sampleUnits, &htilde->sampleUnits, &lalSecondUnit);
    }

    /* phasing coefficients */
    PNPhasingSeries pfa;
    XLALSimInspiralPNPhasing_F2(&pfa, m1, m2, S1z, S2z, S1z*S1z, S2z*S2z, S1z*S2z, p);

    REAL8 pfaN = 0.; REAL8 pfa1 = 0.;
    REAL8 pfa2 = 0.; REAL8 pfa3 = 0.; REAL8 pfa4 = 0.;
    REAL8 pfa5 = 0.; REAL8 pfl5 = 0.;
    REAL8 pfa6 = 0.; REAL8 pfl6 = 0.;
    REAL8 pfa7 = 0.;

    INT4 phaseO=XLALSimInspiralWaveformParamsLookupPNPhaseOrder(p);
    switch (phaseO)
    {
        case -1:
        case 7:
            pfa7 = pfa.v[7];
        case 6:
            pfa6 = pfa.v[6];
            pfl6 = pfa.vlogv[6];
        case 5:
            pfa5 = pfa.v[5];
            pfl5 = pfa.vlogv[5];
        case 4:
            pfa4 = pfa.v[4];
        case 3:
            pfa3 = pfa.v[3];
        case 2:
            pfa2 = pfa.v[2];
        case 1:
            pfa1 = pfa.v[1];
        case 0:
            pfaN = pfa.v[0];
            break;
        default:
            XLAL_ERROR(XLAL_ETYPE, "Invalid phase PN order %d", phaseO);
    }

    /* Validate expansion order arguments.
     * This must be done here instead of in the OpenMP parallel loop
     * because when OpenMP parallelization is turned on, early exits
     * from loops (via return or break statements) are not permitted.
     */

    /* Validate amplitude PN order. */
    INT4 amplitudeO=XLALSimInspiralWaveformParamsLookupPNAmplitudeOrder(p);
    switch (amplitudeO)
    {
        case -1:
        case 7:
        case 6:
        case 5:
        case 4:
        case 3:
        case 2:
        case 0:
            break;
        default:
            XLAL_ERROR(XLAL_ETYPE, "Invalid amplitude PN order %d", amplitudeO);
    }

    /* Generate tidal terms separately.
     * Enums specifying tidal order are in LALSimInspiralWaveformFlags.h
     */
    REAL8 pft10 = 0.;
    REAL8 pft12 = 0.;
    REAL8 lambda1=XLALSimInspiralWaveformParamsLookupTidalLambda1(p);
    REAL8 lambda2=XLALSimInspiralWaveformParamsLookupTidalLambda2(p);
    switch( XLALSimInspiralWaveformParamsLookupPNTidalOrder(p) )
    {
        case LAL_SIM_INSPIRAL_TIDAL_ORDER_ALL:
        case LAL_SIM_INSPIRAL_TIDAL_ORDER_6PN:
	    pft12 = pfaN * (lambda1*XLALSimInspiralTaylorF2Phasing_12PNTidalCoeff(m1OverM) + lambda2*XLALSimInspiralTaylorF2Phasing_12PNTidalCoeff(m2OverM) );
        case LAL_SIM_INSPIRAL_TIDAL_ORDER_5PN:
            pft10 = pfaN * ( lambda1*XLALSimInspiralTaylorF2Phasing_10PNTidalCoeff(m1OverM) + lambda2*XLALSimInspiralTaylorF2Phasing_10PNTidalCoeff(m2OverM) );
        case LAL_SIM_INSPIRAL_TIDAL_ORDER_0PN:
            break;
        default:
	    XLAL_ERROR(XLAL_EINVAL, "Invalid tidal PN order %d", XLALSimInspiralWaveformParamsLookupPNTidalOrder(p) );
    }

    /* The flux and energy coefficients below are used to compute SPA amplitude corrections */

    /* flux coefficients */
    const REAL8 FTaN = XLALSimInspiralPNFlux_0PNCoeff(eta);
    const REAL8 FTa2 = XLALSimInspiralPNFlux_2PNCoeff(eta);
    const REAL8 FTa3 = XLALSimInspiralPNFlux_3PNCoeff(eta);
    const REAL8 FTa4 = XLALSimInspiralPNFlux_4PNCoeff(eta);
    const REAL8 FTa5 = XLALSimInspiralPNFlux_5PNCoeff(eta);
    const REAL8 FTl6 = XLALSimInspiralPNFlux_6PNLogCoeff(eta);
    const REAL8 FTa6 = XLALSimInspiralPNFlux_6PNCoeff(eta);
    const REAL8 FTa7 = XLALSimInspiralPNFlux_7PNCoeff(eta);

    /* energy coefficients */
    const REAL8 dETaN = 2. * XLALSimInspiralPNEnergy_0PNCoeff(eta);
    const REAL8 dETa1 = 2. * XLALSimInspiralPNEnergy_2PNCoeff(eta);
    const REAL8 dETa2 = 3. * XLALSimInspiralPNEnergy_4PNCoeff(eta);
    const REAL8 dETa3 = 4. * XLALSimInspiralPNEnergy_6PNCoeff(eta);


    /* Perform some initial checks */
    if (m1_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (m2_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (f_ref < 0) XLAL_ERROR(XLAL_EDOM);
    if (r <= 0) XLAL_ERROR(XLAL_EDOM);

    /* extrinsic parameters */
    amp0 = -4. * m1 * m2 / r * LAL_MRSUN_SI * LAL_MTSUN_SI * sqrt(LAL_PI/12.L);

    data = htilde->data->data;

    /* Compute the SPA phase at the reference point
     * N.B. f_ref == 0 means we define the reference time/phase at "coalescence"
     * when the frequency approaches infinity. In that case,
     * the integrals Eq. 3.15 of arXiv:0907.0700 vanish when evaluated at
     * f_ref == infinity. If f_ref is finite, we must compute the SPA phase
     * evaluated at f_ref, store it as ref_phasing and subtract it off.
     */
    REAL8 ref_phasing = 0.;
    if( f_ref != 0. ) {
        const REAL8 vref = cbrt(piM*f_ref);
        const REAL8 logvref = log(vref);
        const REAL8 v2ref = vref * vref;
        const REAL8 v3ref = vref * v2ref;
        const REAL8 v4ref = vref * v3ref;
        const REAL8 v5ref = vref * v4ref;
        const REAL8 v6ref = vref * v5ref;
        const REAL8 v7ref = vref * v6ref;
        const REAL8 v8ref = vref * v7ref;
        const REAL8 v9ref = vref * v8ref;
        const REAL8 v10ref = vref * v9ref;
        const REAL8 v12ref = v2ref * v10ref;
        ref_phasing += pfa7 * v7ref;
        ref_phasing += (pfa6 + pfl6 * logvref) * v6ref;
        ref_phasing += (pfa5 + pfl5 * logvref) * v5ref;
        ref_phasing += pfa4 * v4ref;
        ref_phasing += pfa3 * v3ref;
        ref_phasing += pfa2 * v2ref;
        ref_phasing += pfa1 * vref;
        ref_phasing += pfaN;

        /* Tidal terms in reference phasing */
        ref_phasing += pft12 * v12ref;
        ref_phasing += pft10 * v10ref;

        ref_phasing /= v5ref;
    } /* End of if(f_ref != 0) block */

    #pragma omp parallel for
    for (i = 0; i < freqs->length; i++) {
        const REAL8 f = freqs->data[i];
        const REAL8 v = cbrt(piM*f);
        const REAL8 logv = log(v);
        const REAL8 v2 = v * v;
        const REAL8 v3 = v * v2;
        const REAL8 v4 = v * v3;
        const REAL8 v5 = v * v4;
        const REAL8 v6 = v * v5;
        const REAL8 v7 = v * v6;
        const REAL8 v8 = v * v7;
        const REAL8 v9 = v * v8;
        const REAL8 v10 = v * v9;
        const REAL8 v12 = v2 * v10;
        REAL8 phasing = 0.;
        REAL8 dEnergy = 0.;
        REAL8 flux = 0.;
        REAL8 amp;

        phasing += pfa7 * v7;
        phasing += (pfa6 + pfl6 * logv) * v6;
        phasing += (pfa5 + pfl5 * logv) * v5;
        phasing += pfa4 * v4;
        phasing += pfa3 * v3;
        phasing += pfa2 * v2;
        phasing += pfa1 * v;
        phasing += pfaN;

        /* Tidal terms in phasing */
        phasing += pft12 * v12;
        phasing += pft10 * v10;

    /* WARNING! Amplitude orders beyond 0 have NOT been reviewed!
     * Use at your own risk. The default is to turn them off.
     * These do not currently include spin corrections.
     * Note that these are not higher PN corrections to the amplitude.
     * They are the corrections to the leading-order amplitude arising
     * from the stationary phase approximation. See for instance
     * Eq 6.9 of arXiv:0810.5336
     */
	switch (amplitudeO)
        {
            case 7:
                flux += FTa7 * v7;
            case 6:
                flux += (FTa6 + FTl6*logv) * v6;
                dEnergy += dETa3 * v6;
            case 5:
                flux += FTa5 * v5;
            case 4:
                flux += FTa4 * v4;
                dEnergy += dETa2 * v4;
            case 3:
                flux += FTa3 * v3;
            case 2:
                flux += FTa2 * v2;
                dEnergy += dETa1 * v2;
            case -1: /* Default to no SPA amplitude corrections */
            case 0:
                flux += 1.;
                dEnergy += 1.;
        }

        phasing /= v5;
        flux *= FTaN * v10;
        dEnergy *= dETaN * v;
        // Note the factor of 2 b/c phi_ref is orbital phase
        phasing += shft * f - 2.*phi_ref - ref_phasing;
        amp = amp0 * sqrt(-dEnergy/flux) * v;
        data[i+iStart] = amp * cos(phasing - LAL_PI_4)
                - amp * sin(phasing - LAL_PI_4) * 1.0j;
    }

    *htilde_out = htilde;
    return XLAL_SUCCESS;
}
Esempio n. 8
0
static int IMRPhenomDGenerateFD(
    COMPLEX16FrequencySeries **htilde, /**< FD waveform */
    const REAL8 phi0,                  /**< phase at peak */
    const REAL8 deltaF,                /**< frequency resolution */
    const REAL8 m1,                    /**< mass of companion 1 [solar masses] */
    const REAL8 m2,                    /**< mass of companion 2 [solar masses] */
    const REAL8 chi1_in,               /**< aligned-spin of companion 1 */
    const REAL8 chi2_in,               /**< aligned-spin of companion 2 */
    const REAL8 f_min,                 /**< start frequency */
    const REAL8 f_max,                 /**< end frequency */
    const REAL8 distance               /**< distance to source (m) */
) {
  LIGOTimeGPS ligotimegps_zero = LIGOTIMEGPSZERO; // = {0, 0}

  const REAL8 M = m1 + m2;
  REAL8 eta = m1 * m2 / (M * M);
  const REAL8 M_sec = M * LAL_MTSUN_SI;

  REAL8 chi1, chi2;
  if (m1>m2) { // swap spins
    chi1 = chi1_in;
    chi2 = chi2_in;
  } else {
    chi1 = chi2_in;
    chi2 = chi1_in;
  }

  /* Compute the amplitude pre-factor */
  REAL8 amp0 = 2. * sqrt(5. / (64.*LAL_PI)) * M * LAL_MRSUN_SI * M * LAL_MTSUN_SI / distance;

  /* Allocate htilde */
  size_t n = NextPow2(f_max / deltaF) + 1;
  /* Coalesce at t=0 */
  XLALGPSAdd(&ligotimegps_zero, -1. / deltaF); // shift by overall length in time
  *htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &ligotimegps_zero, 0.0,
      deltaF, &lalStrainUnit, n);
  memset((*htilde)->data->data, 0, n * sizeof(COMPLEX16));
  XLALUnitMultiply(&((*htilde)->sampleUnits), &((*htilde)->sampleUnits), &lalSecondUnit);
  if (!(*htilde)) XLAL_ERROR(XLAL_EFUNC);

  size_t ind_min = (size_t) (f_min / deltaF);
  size_t ind_max = (size_t) (f_max / deltaF);

  // Calculate phenomenological parameters
  IMRPhenomDAmplitudeCoefficients *pAmp = ComputeIMRPhenomDAmplitudeCoefficients(eta, chi1, chi2);
  IMRPhenomDPhaseCoefficients *pPhi = ComputeIMRPhenomDPhaseCoefficients(eta, chi1, chi2);
  if (!pAmp || !pPhi) XLAL_ERROR(XLAL_EFUNC);

  // Compute coefficients to make phase C^1
  ComputeIMRPhenDPhaseConnectionCoefficients(pPhi);

  /* Now generate the waveform */
  #pragma omp parallel for
  for (size_t i = ind_min; i < ind_max; i++) {

    REAL8 Mf = M_sec * i * deltaF; // geometric frequency

    REAL8 amp = IMRPhenDAmplitude(Mf, pAmp);
    REAL8 phi = IMRPhenDPhase(Mf, pPhi);

    phi -= 2.*phi0; // factor of 2 b/c phi0 is orbital phase

    ((*htilde)->data->data)[i] = amp0 * amp * cexp(-I * phi);
  }

  LALFree(pAmp);
  LALFree(pPhi);

  return XLAL_SUCCESS;
}
/**
 * Core function for computing the ROM waveform.
 * Interpolate projection coefficient data and evaluate coefficients at desired (q, chi).
 * Construct 1D splines for amplitude and phase.
 * Compute strain waveform from amplitude and phase.
*/
static int SEOBNRv1ROMEffectiveSpinCore(
  COMPLEX16FrequencySeries **hptilde,
  COMPLEX16FrequencySeries **hctilde,
  double phiRef,
  double fRef,
  double distance,
  double inclination,
  double Mtot_sec,
  double q,
  double chi,
  const REAL8Sequence *freqs_in, /* Frequency points at which to evaluate the waveform (Hz) */
  double deltaF
  /* If deltaF > 0, the frequency points given in freqs are uniformly spaced with
   * spacing deltaF. Otherwise, the frequency points are spaced non-uniformly.
   * Then we will use deltaF = 0 to create the frequency series we return. */
  )
{
  /* Check output arrays */
  if(!hptilde || !hctilde)
    XLAL_ERROR(XLAL_EFAULT);
  SEOBNRROMdata *romdata=&__lalsim_SEOBNRv1ROMSS_data;
  if(*hptilde || *hctilde) {
    XLALPrintError("(*hptilde) and (*hctilde) are supposed to be NULL, but got %p and %p",(*hptilde),(*hctilde));
    XLAL_ERROR(XLAL_EFAULT);
  }
  int retcode=0;

  // 'Nudge' parameter values to allowed boundary values if close by
  if (q < 1.0)    nudge(&q, 1.0, 1e-6);
  if (q > 100.0)  nudge(&q, 100.0, 1e-6);
  if (chi < -1.0) nudge(&chi, -1.0, 1e-6);
  if (chi > 0.6)  nudge(&chi, 0.6, 1e-6);

  /* If either spin > 0.6, model not available, exit */
  if ( chi < -1.0 || chi > 0.6 ) {
    XLALPrintError( "XLAL Error - %s: chi smaller than -1 or larger than 0.6!\nSEOBNRv1ROMEffectiveSpin is only available for spins in the range -1 <= a/M <= 0.6.\n", __func__);
    XLAL_ERROR( XLAL_EDOM );
  }

  if (q > 100) {
    XLALPrintError( "XLAL Error - %s: q=%lf larger than 100!\nSEOBNRv1ROMEffectiveSpin is only available for q in the range 1 <= q <= 100.\n", __func__,q);
    XLAL_ERROR( XLAL_EDOM );
  }

  if (q >= 20 && q <= 40 && chi < -0.75 && chi > -0.9) {
    XLALPrintWarning( "XLAL Warning - %s: q in [20,40] and chi in [-0.8]. The SEOBNRv1 model is not trustworthy in this region!\nSee Fig 15 in CQG 31 195010, 2014 for details.", __func__);
    XLAL_ERROR( XLAL_EDOM );
  }

  /* Find frequency bounds */
  if (!freqs_in) XLAL_ERROR(XLAL_EFAULT);
  double fLow  = freqs_in->data[0];
  double fHigh = freqs_in->data[freqs_in->length - 1];

  if(fRef==0.0)
    fRef=fLow;

  /* Convert to geometric units for frequency */
  double Mf_ROM_min = fmax(gA[0], gPhi[0]);               // lowest allowed geometric frequency for ROM
  double Mf_ROM_max = fmin(gA[nk_amp-1], gPhi[nk_phi-1]); // highest allowed geometric frequency for ROM
  double fLow_geom = fLow * Mtot_sec;
  double fHigh_geom = fHigh * Mtot_sec;
  double fRef_geom = fRef * Mtot_sec;
  double deltaF_geom = deltaF * Mtot_sec;

  // Enforce allowed geometric frequency range
  if (fLow_geom < Mf_ROM_min)
    XLAL_ERROR(XLAL_EDOM, "Starting frequency Mflow=%g is smaller than lowest frequency in ROM Mf=%g. Starting at lowest frequency in ROM.\n", fLow_geom, Mf_ROM_min);
  if (fHigh_geom == 0)
    fHigh_geom = Mf_ROM_max;
  else if (fHigh_geom > Mf_ROM_max) {
	  XLALPrintWarning("Maximal frequency Mf_high=%g is greater than highest ROM frequency Mf_ROM_Max=%g. Using Mf_high=Mf_ROM_Max.", fHigh_geom, Mf_ROM_max);
	  fHigh_geom = Mf_ROM_max;
  }
  else if (fHigh_geom < Mf_ROM_min)
    XLAL_ERROR(XLAL_EDOM, "End frequency %g is smaller than starting frequency %g!\n", fHigh_geom, fLow_geom);
  if (fRef_geom > Mf_ROM_max) {
	  XLALPrintWarning("Reference frequency Mf_ref=%g is greater than maximal frequency in ROM Mf=%g. Starting at maximal frequency in ROM.\n", fRef_geom, Mf_ROM_max);
    fRef_geom = Mf_ROM_max; // If fref > fhigh we reset fref to default value of cutoff frequency.
  }
  if (fRef_geom < Mf_ROM_min) {
    XLALPrintWarning("Reference frequency Mf_ref=%g is smaller than lowest frequency in ROM Mf=%g. Starting at lowest frequency in ROM.\n", fLow_geom, Mf_ROM_min);
    fRef_geom = Mf_ROM_min;
  }

  /* Internal storage for w.f. coefficiencts */
  SEOBNRROMdata_coeff *romdata_coeff=NULL;
  SEOBNRROMdata_coeff_Init(&romdata_coeff);
  REAL8 amp_pre;

  /* Interpolate projection coefficients and evaluate them at (q,chi) */
  retcode=TP_Spline_interpolation_2d(
    q,                         // Input: q-value for which projection coefficients should be evaluated
    chi,                       // Input: chi-value for which projection coefficients should be evaluated
    romdata->cvec_amp,         // Input: data for spline coefficients for amplitude
    romdata->cvec_phi,         // Input: data for spline coefficients for phase
    romdata->cvec_amp_pre,     // Input: data for spline coefficients for amplitude prefactor
    romdata_coeff->c_amp,      // Output: interpolated projection coefficients for amplitude
    romdata_coeff->c_phi,      // Output: interpolated projection coefficients for phase
    &amp_pre                   // Output: interpolated amplitude prefactor
  );

  if(retcode!=0) {
    SEOBNRROMdata_coeff_Cleanup(romdata_coeff);
    XLAL_ERROR(retcode, "Parameter-space interpolation failed.");
  }

  // Compute function values of amplitude an phase on sparse frequency points by evaluating matrix vector products
  // amp_pts = B_A^T . c_A
  // phi_pts = B_phi^T . c_phi
  gsl_vector* amp_f = gsl_vector_alloc(nk_amp);
  gsl_vector* phi_f = gsl_vector_alloc(nk_phi);
  gsl_blas_dgemv(CblasTrans, 1.0, romdata->Bamp, romdata_coeff->c_amp, 0.0, amp_f);
  gsl_blas_dgemv(CblasTrans, 1.0, romdata->Bphi, romdata_coeff->c_phi, 0.0, phi_f);

  // Setup 1d splines in frequency
  gsl_interp_accel *acc_amp = gsl_interp_accel_alloc();
  gsl_spline *spline_amp = gsl_spline_alloc(gsl_interp_cspline, nk_amp);
  gsl_spline_init(spline_amp, gA, gsl_vector_const_ptr(amp_f,0), nk_amp);

  gsl_interp_accel *acc_phi = gsl_interp_accel_alloc();
  gsl_spline *spline_phi = gsl_spline_alloc(gsl_interp_cspline, nk_phi);
  gsl_spline_init(spline_phi, gPhi, gsl_vector_const_ptr(phi_f,0), nk_phi);


  size_t npts = 0;
  LIGOTimeGPS tC = {0, 0};
  UINT4 offset = 0; // Index shift between freqs and the frequency series
  REAL8Sequence *freqs = NULL;
  if (deltaF > 0)  { // freqs contains uniform frequency grid with spacing deltaF; we start at frequency 0
    /* Set up output array with size closest power of 2 */
    npts = NextPow2(fHigh_geom / deltaF_geom) + 1;
    if (fHigh_geom < fHigh * Mtot_sec) /* Resize waveform if user wants f_max larger than cutoff frequency */
      npts = NextPow2(fHigh * Mtot_sec / deltaF_geom) + 1;

    XLALGPSAdd(&tC, -1. / deltaF);  /* coalesce at t=0 */
    *hptilde = XLALCreateCOMPLEX16FrequencySeries("hptilde: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, npts);
    *hctilde = XLALCreateCOMPLEX16FrequencySeries("hctilde: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, npts);

    // Recreate freqs using only the lower and upper bounds
    UINT4 iStart = (UINT4) ceil(fLow_geom / deltaF_geom);
    UINT4 iStop = (UINT4) ceil(fHigh_geom / deltaF_geom);
    freqs = XLALCreateREAL8Sequence(iStop - iStart);
    if (!freqs) {
      XLAL_ERROR(XLAL_EFUNC, "Frequency array allocation failed.");
    }
    for (UINT4 i=iStart; i<iStop; i++)
      freqs->data[i-iStart] = i*deltaF_geom;

    offset = iStart;
  } else { // freqs contains frequencies with non-uniform spacing; we start at lowest given frequency
    npts = freqs_in->length;
    *hptilde = XLALCreateCOMPLEX16FrequencySeries("hptilde: FD waveform", &tC, fLow, 0, &lalStrainUnit, npts);
    *hctilde = XLALCreateCOMPLEX16FrequencySeries("hctilde: FD waveform", &tC, fLow, 0, &lalStrainUnit, npts);
    offset = 0;

    freqs = XLALCreateREAL8Sequence(freqs_in->length);
    if (!freqs) {
      XLAL_ERROR(XLAL_EFUNC, "Frequency array allocation failed.");
    }
    for (UINT4 i=0; i<freqs_in->length; i++)
      freqs->data[i] = freqs_in->data[i] * Mtot_sec;
  }


  if (!(*hptilde) || !(*hctilde))
  {
      XLALDestroyREAL8Sequence(freqs);
      gsl_spline_free(spline_amp);
      gsl_spline_free(spline_phi);
      gsl_interp_accel_free(acc_amp);
      gsl_interp_accel_free(acc_phi);
      gsl_vector_free(amp_f);
      gsl_vector_free(phi_f);
      SEOBNRROMdata_coeff_Cleanup(romdata_coeff);
      XLAL_ERROR(XLAL_EFUNC, "Waveform allocation failed.");
  }
  memset((*hptilde)->data->data, 0, npts * sizeof(COMPLEX16));
  memset((*hctilde)->data->data, 0, npts * sizeof(COMPLEX16));

  XLALUnitMultiply(&(*hptilde)->sampleUnits, &(*hptilde)->sampleUnits, &lalSecondUnit);
  XLALUnitMultiply(&(*hctilde)->sampleUnits, &(*hctilde)->sampleUnits, &lalSecondUnit);

  COMPLEX16 *pdata=(*hptilde)->data->data;
  COMPLEX16 *cdata=(*hctilde)->data->data;

  REAL8 cosi = cos(inclination);
  REAL8 pcoef = 0.5*(1.0 + cosi*cosi);
  REAL8 ccoef = cosi;

  REAL8 s = 1.0/sqrt(2.0); // Scale polarization amplitude so that strain agrees with FFT of SEOBNRv1
  double Mtot = Mtot_sec / LAL_MTSUN_SI;
  double amp0 = Mtot * amp_pre * Mtot_sec * LAL_MRSUN_SI / (distance); // Correct overall amplitude to undo mass-dependent scaling used in single-spin ROM

  // Evaluate reference phase for setting phiRef correctly
  double phase_change = gsl_spline_eval(spline_phi, fRef_geom, acc_phi) - 2*phiRef;

  // Assemble waveform from aplitude and phase
  for (UINT4 i=0; i<freqs->length; i++) { // loop over frequency points in sequence
    double f = freqs->data[i];
    if (f > Mf_ROM_max) continue; // We're beyond the highest allowed frequency; since freqs may not be ordered, we'll just skip the current frequency and leave zero in the buffer
    int j = i + offset; // shift index for frequency series if needed
    double A = gsl_spline_eval(spline_amp, f, acc_amp);
    double phase = gsl_spline_eval(spline_phi, f, acc_phi) - phase_change;
    COMPLEX16 htilde = s*amp0*A * cexp(I*phase);
    pdata[j] =      pcoef * htilde;
    cdata[j] = -I * ccoef * htilde;
  }

  /* Correct phasing so we coalesce at t=0 (with the definition of the epoch=-1/deltaF above) */

  // Get SEOBNRv1 ringdown frequency for 22 mode
  double Mf_final = SEOBNRROM_Ringdown_Mf_From_Mtot_q(Mtot_sec, q, chi, chi, SEOBNRv1);

  UINT4 L = freqs->length;
  // prevent gsl interpolation errors
  if (Mf_final > freqs->data[L-1])
    Mf_final = freqs->data[L-1];
  if (Mf_final < freqs->data[0])
  {
      XLALDestroyREAL8Sequence(freqs);
      gsl_spline_free(spline_amp);
      gsl_spline_free(spline_phi);
      gsl_interp_accel_free(acc_amp);
      gsl_interp_accel_free(acc_phi);
      gsl_vector_free(amp_f);
      gsl_vector_free(phi_f);
      SEOBNRROMdata_coeff_Cleanup(romdata_coeff);
      XLAL_ERROR(XLAL_EDOM, "f_ringdown < f_min");
  }

  // Time correction is t(f_final) = 1/(2pi) dphi/df (f_final)
  // We compute the dimensionless time correction t/M since we use geometric units.
  REAL8 t_corr = gsl_spline_eval_deriv(spline_phi, Mf_final, acc_phi) / (2*LAL_PI);

  // Now correct phase
  for (UINT4 i=0; i<freqs->length; i++) { // loop over frequency points in sequence
    double f = freqs->data[i] - fRef_geom;
    int j = i + offset; // shift index for frequency series if needed
    pdata[j] *= cexp(-2*LAL_PI * I * f * t_corr);
    cdata[j] *= cexp(-2*LAL_PI * I * f * t_corr);
  }

  XLALDestroyREAL8Sequence(freqs);

  gsl_spline_free(spline_amp);
  gsl_spline_free(spline_phi);
  gsl_interp_accel_free(acc_amp);
  gsl_interp_accel_free(acc_phi);
  gsl_vector_free(amp_f);
  gsl_vector_free(phi_f);
  SEOBNRROMdata_coeff_Cleanup(romdata_coeff);

  return(XLAL_SUCCESS);
}
/**
 * Wrapper similar to XLALSimInspiralChooseFDWaveform() for waveforms to be generated a specific freqencies.
 * Returns the waveform in the frequency domain at the frequencies of the REAL8Sequence frequencies.
 */
int XLALSimInspiralChooseFDWaveformSequence(
    COMPLEX16FrequencySeries **hptilde,     /**< FD plus polarization */
    COMPLEX16FrequencySeries **hctilde,     /**< FD cross polarization */
    REAL8 phiRef,                           /**< reference orbital phase (rad) */
    REAL8 m1,                               /**< mass of companion 1 (kg) */
    REAL8 m2,                               /**< mass of companion 2 (kg) */
    REAL8 S1x,                              /**< x-component of the dimensionless spin of object 1 */
    REAL8 S1y,                              /**< y-component of the dimensionless spin of object 1 */
    REAL8 S1z,                              /**< z-component of the dimensionless spin of object 1 */
    REAL8 S2x,                              /**< x-component of the dimensionless spin of object 2 */
    REAL8 S2y,                              /**< y-component of the dimensionless spin of object 2 */
    REAL8 S2z,                              /**< z-component of the dimensionless spin of object 2 */
    REAL8 f_ref,                            /**< Reference frequency (Hz) */
    REAL8 distance,                         /**< distance of source (m) */
    REAL8 inclination,                      /**< inclination of source (rad) */
    LALDict *LALpars,                       /**< LALDictionary containing non-mandatory variables/flags */
    Approximant approximant,                /**< post-Newtonian approximant to use for waveform production */
    REAL8Sequence *frequencies              /**< sequence of frequencies for which the waveform will be computed. Pass in NULL (or None in python) for standard f_min to f_max sequence. */
)
{
    int ret;
    unsigned int j;
    REAL8 pfac, cfac;
    REAL8 LNhatx, LNhaty, LNhatz;

    /* Support variables for precessing wfs*/
    REAL8 incl;
    REAL8 spin1x,spin1y,spin1z;
    REAL8 spin2x,spin2y,spin2z;

    /* Variables for IMRPhenomP and IMRPhenomPv2 */
    REAL8 chi1_l, chi2_l, chip, thetaJ, alpha0;

    /* General sanity checks that will abort
     *
     * If non-GR approximants are added, include them in
     * XLALSimInspiralApproximantAcceptTestGRParams()
     */
    if ( !XLALSimInspiralWaveformParamsNonGRAreDefault(LALpars) && XLALSimInspiralApproximantAcceptTestGRParams(approximant) != LAL_SIM_INSPIRAL_TESTGR_PARAMS ) {
        XLALPrintError("XLAL Error - %s: Passed in non-NULL testGRparams for an approximant that does not use them\n", __func__);
        XLAL_ERROR(XLAL_EINVAL);
    }
    if (!frequencies) XLAL_ERROR(XLAL_EFAULT);
    REAL8 f_min = frequencies->data[0];

    /* General sanity check the input parameters - only give warnings! */
    if( m1 < 0.09 * LAL_MSUN_SI )
    XLALPrintWarning("XLAL Warning - %s: Small value of m1 = %e (kg) = %e (Msun) requested...Perhaps you have a unit conversion error?\n", __func__, m1, m1/LAL_MSUN_SI);
    if( m2 < 0.09 * LAL_MSUN_SI )
    XLALPrintWarning("XLAL Warning - %s: Small value of m2 = %e (kg) = %e (Msun) requested...Perhaps you have a unit conversion error?\n", __func__, m2, m2/LAL_MSUN_SI);
    if( m1 + m2 > 1000. * LAL_MSUN_SI )
    XLALPrintWarning("XLAL Warning - %s: Large value of total mass m1+m2 = %e (kg) = %e (Msun) requested...Signal not likely to be in band of ground-based detectors.\n", __func__, m1+m2, (m1+m2)/LAL_MSUN_SI);
    if( S1x*S1x + S1y*S1y + S1z*S1z > 1.000001 )
    XLALPrintWarning("XLAL Warning - %s: S1 = (%e,%e,%e) with norm > 1 requested...Are you sure you want to violate the Kerr bound?\n", __func__, S1x, S1y, S1z);
    if( S2x*S2x + S2y*S2y + S2z*S2z > 1.000001 )
    XLALPrintWarning("XLAL Warning - %s: S2 = (%e,%e,%e) with norm > 1 requested...Are you sure you want to violate the Kerr bound?\n", __func__, S2x, S2y, S2z);
    if( f_min < 1. )
    XLALPrintWarning("XLAL Warning - %s: Small value of fmin = %e requested...Check for errors, this could create a very long waveform.\n", __func__, f_min);
    if( f_min > 40.000001 )
    XLALPrintWarning("XLAL Warning - %s: Large value of fmin = %e requested...Check for errors, the signal will start in band.\n", __func__, f_min);

    /* The non-precessing waveforms return h(f) for optimal orientation
     * (i=0, Fp=1, Fc=0; Lhat pointed toward the observer)
     * To get generic polarizations we multiply by inclination dependence
     * and note hc(f) \propto -I * hp(f)
     * Non-precessing waveforms multiply hp by pfac, hc by -I*cfac
     */
    cfac = cos(inclination);
    pfac = 0.5 * (1. + cfac*cfac);

    REAL8 lambda1=XLALSimInspiralWaveformParamsLookupTidalLambda1(LALpars);
    REAL8 lambda2=XLALSimInspiralWaveformParamsLookupTidalLambda2(LALpars);

    switch (approximant)
    {
        /* inspiral-only models */
        case TaylorF2:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFrameAxisIsDefault(LALpars) )
                ABORT_NONDEFAULT_FRAME_AXIS(LALpars);
            if( !XLALSimInspiralWaveformParamsModesChoiceIsDefault(LALpars) )
                ABORT_NONDEFAULT_MODES_CHOICE(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);

            /* Call the waveform driver routine */
            ret = XLALSimInspiralTaylorF2Core(hptilde, frequencies, phiRef,
                    m1, m2, S1z, S2z, f_ref, 0., distance, LALpars);
            if (ret == XLAL_FAILURE) XLAL_ERROR(XLAL_EFUNC);
            /* Produce both polarizations */
            *hctilde = XLALCreateCOMPLEX16FrequencySeries("FD hcross",
                    &((*hptilde)->epoch), (*hptilde)->f0, 0.0,
                    &((*hptilde)->sampleUnits), (*hptilde)->data->length);
            for(j = 0; j < (*hptilde)->data->length; j++) {
                (*hctilde)->data->data[j] = -I*cfac * (*hptilde)->data->data[j];
                (*hptilde)->data->data[j] *= pfac;
            }
            break;
        /* inspiral-merger-ringdown models */
        case SEOBNRv1_ROM_EffectiveSpin:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if (!checkAlignedSpinsEqual(S1z, S2z))
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);

            ret = XLALSimIMRSEOBNRv1ROMEffectiveSpinFrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, XLALSimIMRPhenomBComputeChi(m1, m2, S1z, S2z));
            break;

        case SEOBNRv1_ROM_DoubleSpin:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);

            ret = XLALSimIMRSEOBNRv1ROMDoubleSpinFrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, S1z, S2z);
            break;

        case SEOBNRv2_ROM_EffectiveSpin:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);

            ret = XLALSimIMRSEOBNRv2ROMEffectiveSpinFrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, XLALSimIMRPhenomBComputeChi(m1, m2, S1z, S2z));
            break;

        case SEOBNRv2_ROM_DoubleSpin:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);

            ret = XLALSimIMRSEOBNRv2ROMDoubleSpinFrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, S1z, S2z);
            break;

        case SEOBNRv2_ROM_DoubleSpin_HI:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);

            ret = XLALSimIMRSEOBNRv2ROMDoubleSpinHIFrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, S1z, S2z, -1);
            break;

        case Lackey_Tidal_2013_SEOBNRv2_ROM:
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFlagsAreDefault(LALpars) )
                ABORT_NONDEFAULT_LALDICT_FLAGS(LALpars);
            if( !checkTransverseSpinsZero(S1x, S1y, S2x, S2y) )
                ABORT_NONZERO_TRANSVERSE_SPINS(LALpars);

            ret = XLALSimIMRLackeyTidal2013FrequencySequence(hptilde, hctilde, frequencies,
                    phiRef, f_ref, distance, inclination, m1, m2, S1z, lambda2);
            break;

        case IMRPhenomP:
	    XLALSimInspiralInitialConditionsPrecessingApproxs(&incl,&spin1x,&spin1y,&spin1z,&spin2x,&spin2y,&spin2z,inclination,S1x,S1y,S1z,S2x,S2y,S2z,m1,m2,f_ref,phiRef,XLALSimInspiralWaveformParamsLookupFrameAxis(LALpars));
            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFrameAxisIsDefault(LALpars) )
                ABORT_NONDEFAULT_FRAME_AXIS(LALpars);/* Default is LAL_SIM_INSPIRAL_FRAME_AXIS_VIEW : z-axis along direction of GW propagation (line of sight). */
            if( !XLALSimInspiralWaveformParamsModesChoiceIsDefault(LALpars) )
                ABORT_NONDEFAULT_MODES_CHOICE(LALpars);
          /* Default is (2,2) or l=2 modes. */
            if( !checkTidesZero(lambda1, lambda2) )
                ABORT_NONZERO_TIDES(LALpars);
            LNhatx = sin(incl);
            LNhaty = 0.;
            LNhatz = cos(incl);
            /* Tranform to model parameters */
            if(f_ref==0.0)
                f_ref = f_min; /* Default reference frequency is minimum frequency */
            XLALSimIMRPhenomPCalculateModelParameters(
                &chi1_l, &chi2_l, &chip, &thetaJ, &alpha0,
                m1, m2, f_ref,
                LNhatx, LNhaty, LNhatz,
                spin1x, spin1y, spin1z,
                spin2x, spin2y, spin2z, IMRPhenomPv1_V);
            /* Call the waveform driver routine */
            ret = XLALSimIMRPhenomPFrequencySequence(hptilde, hctilde, frequencies,
              chi1_l, chi2_l, chip, thetaJ,
              m1, m2, distance, alpha0, phiRef, f_ref, IMRPhenomPv1_V, NULL);
            if (ret == XLAL_FAILURE) XLAL_ERROR(XLAL_EFUNC);
            break;

        case IMRPhenomPv2:
	    XLALSimInspiralInitialConditionsPrecessingApproxs(&incl,&spin1x,&spin1y,&spin1z,&spin2x,&spin2y,&spin2z,inclination,S1x,S1y,S1z,S2x,S2y,S2z,m1,m2,f_ref,phiRef,XLALSimInspiralWaveformParamsLookupFrameAxis(LALpars));

            /* Waveform-specific sanity checks */
            if( !XLALSimInspiralWaveformParamsFrameAxisIsDefault(LALpars) )
                ABORT_NONDEFAULT_FRAME_AXIS(LALpars);/* Default is LAL_SIM_INSPIRAL_FRAME_AXIS_VIEW : z-axis along direction of GW propagation (line of sight). */
            if( !XLALSimInspiralWaveformParamsModesChoiceIsDefault(LALpars) )
                ABORT_NONDEFAULT_MODES_CHOICE(LALpars);
          /* Default is (2,2) or l=2 modes. */
            if( !checkTidesZero(lambda1, lambda2) )
	        ABORT_NONZERO_TIDES(LALpars);
            LNhatx = sin(incl);
            LNhaty = 0.;
            LNhatz = cos(incl);
            /* Tranform to model parameters */
            if(f_ref==0.0)
	      f_ref = f_min; /* Default reference frequency is minimum frequency */
            XLALSimIMRPhenomPCalculateModelParameters(
                &chi1_l, &chi2_l, &chip, &thetaJ, &alpha0,
                m1, m2, f_ref,
                LNhatx, LNhaty, LNhatz,
                spin1x, spin1y, spin1z,
                spin2x, spin2y, spin2z, IMRPhenomPv2_V);
            /* Call the waveform driver routine */
            ret = XLALSimIMRPhenomPFrequencySequence(hptilde, hctilde, frequencies,
              chi1_l, chi2_l, chip, thetaJ,
              m1, m2, distance, alpha0, phiRef, f_ref, IMRPhenomPv2_V, NULL);
            if (ret == XLAL_FAILURE) XLAL_ERROR(XLAL_EFUNC);
            break;

        default:
            XLALPrintError("FD version of approximant not implemented in lalsimulation\n");
            XLAL_ERROR(XLAL_EINVAL);
    }

    if (ret == XLAL_FAILURE) XLAL_ERROR(XLAL_EFUNC);

    return ret;
}
/**
 * Chooses between different approximants when requesting a waveform to be generated
 * Returns the waveform in the frequency domain.
 * The parameters passed must be in SI units.
 *
 * This version allows caching of waveforms. The most recently generated
 * waveform and its parameters are stored. If the next call requests a waveform
 * that can be obtained by a simple transformation, then it is done.
 * This bypasses the waveform generation and speeds up the code.
 */
int XLALSimInspiralChooseFDWaveformFromCache(
        COMPLEX16FrequencySeries **hptilde,     /**< +-polarization waveform */
        COMPLEX16FrequencySeries **hctilde,     /**< x-polarization waveform */
        REAL8 phiRef,                           /**< reference orbital phase (rad) */
        REAL8 deltaF,                           /**< sampling interval (Hz) */
        REAL8 m1,                               /**< mass of companion 1 (kg) */
        REAL8 m2,                               /**< mass of companion 2 (kg) */
        REAL8 S1x,                              /**< x-component of the dimensionless spin of object 1 */
        REAL8 S1y,                              /**< y-component of the dimensionless spin of object 1 */
        REAL8 S1z,                              /**< z-component of the dimensionless spin of object 1 */
        REAL8 S2x,                              /**< x-component of the dimensionless spin of object 2 */
        REAL8 S2y,                              /**< y-component of the dimensionless spin of object 2 */
        REAL8 S2z,                              /**< z-component of the dimensionless spin of object 2 */
        REAL8 f_min,                            /**< starting GW frequency (Hz) */
        REAL8 f_max,                            /**< ending GW frequency (Hz) */
        REAL8 f_ref,                            /**< Reference GW frequency (Hz) */
        REAL8 r,                                /**< distance of source (m) */
        REAL8 i,                                /**< inclination of source (rad) */
        LALDict *LALpars,                       /**< LALDictionary containing non-mandatory variables/flags */
        Approximant approximant,                /**< post-Newtonian approximant to use for waveform production */
        LALSimInspiralWaveformCache *cache,     /**< waveform cache structure */
        REAL8Sequence *frequencies              /**< sequence of frequencies for which the waveform will be computed. Pass in NULL (or None in python) for standard f_min to f_max sequence. */
        )
{
    int status;
    size_t j;
    REAL8 dist_ratio, incl_ratio_plus, incl_ratio_cross, phase_diff;
    COMPLEX16 exp_dphi;
    CacheVariableDiffersBitmask changedParams;


    // If nonGRparams are not NULL, don't even try to cache.
    if ( !XLALSimInspiralWaveformParamsNonGRAreDefault(LALpars) || (!cache) ) {
        if (frequencies != NULL)
            return XLALSimInspiralChooseFDWaveformSequence(hptilde, hctilde, phiRef,
                                                           m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_ref,
                                                           r, i,
                                                           LALpars, approximant,frequencies);
        else
            return XLALSimInspiralChooseFDWaveform(hptilde, hctilde, m1, m2,
				S1x, S1y, S1z, S2x, S2y, S2z, r, i, phiRef,
				0., 0., 0., deltaF, f_min, f_max, f_ref,
				LALpars,
				approximant);
    }

    // Check which parameters have changed
    changedParams = CacheArgsDifferenceBitmask(cache, phiRef, deltaF,
            m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_min, f_ref, f_max, r, i,
	    LALpars, approximant, frequencies);

    // No parameters have changed! Copy the cached polarizations
    if( changedParams == NO_DIFFERENCE ) {
        *hptilde = XLALCutCOMPLEX16FrequencySeries(cache->hptilde, 0,
                cache->hptilde->data->length);
        if (*hptilde == NULL) return XLAL_ENOMEM;
        *hctilde = XLALCutCOMPLEX16FrequencySeries(cache->hctilde, 0,
                cache->hctilde->data->length);
        if (*hctilde == NULL) {
            XLALDestroyCOMPLEX16FrequencySeries(*hptilde);
            *hptilde = NULL;
            return XLAL_ENOMEM;
        }

        return XLAL_SUCCESS;
    }

    // Intrinsic parameters have changed. We must generate a new waveform
    if( (changedParams & INTRINSIC) != 0 ) {
        if ( frequencies != NULL ){
            status =  XLALSimInspiralChooseFDWaveformSequence(hptilde, hctilde, phiRef,
                m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_ref,
	        r, i, LALpars, approximant, frequencies);
        }
        else {
	  status = XLALSimInspiralChooseFDWaveform(hptilde, hctilde, m1, m2,
						   S1x, S1y, S1z, S2x, S2y, S2z,
						   r, i, phiRef, 0., 0., 0.,
						   deltaF, f_min, f_max, f_ref,
						   LALpars, approximant);
        }
        if (status == XLAL_FAILURE) return status;

        return StoreFDHCache(cache, *hptilde, *hctilde, phiRef, deltaF, m1, m2,
			     S1x, S1y, S1z, S2x, S2y, S2z, f_min, f_ref, f_max, r, i, LALpars, approximant, frequencies);
    }

    // case 1: Non-precessing, 2nd harmonic only
    if( approximant == TaylorF2 || approximant == TaylorF2RedSpin
                || approximant == TaylorF2RedSpinTidal
                || approximant == IMRPhenomA || approximant == IMRPhenomB
                || approximant == IMRPhenomC ) {
        // If polarizations are not cached we must generate a fresh waveform
        // FIXME: Will need to check hlms and/or dynamical variables as well
        if( cache->hptilde == NULL || cache->hctilde == NULL) {
            if ( frequencies != NULL ){
                status =  XLALSimInspiralChooseFDWaveformSequence(hptilde, hctilde, phiRef,
                    m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_ref,
		    r, i, LALpars, approximant,frequencies);
            }
            else {
	      status = XLALSimInspiralChooseFDWaveform(hptilde, hctilde, m1, m2,
						       S1x, S1y, S1z, S2x, S2y, S2z, r, i, phiRef,
						       0., 0., 0., deltaF, f_min, f_max, f_ref,
						       LALpars, approximant);
            }
            if (status == XLAL_FAILURE) return status;

            return StoreFDHCache(cache, *hptilde, *hctilde, phiRef, deltaF,
                    m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_min, f_ref, f_max, r, i,
                    LALpars, approximant, frequencies);
        }

        // Set transformation coefficients for identity transformation.
        // We'll adjust them depending on which extrinsic parameters changed.
        dist_ratio = incl_ratio_plus = incl_ratio_cross = 1.;
        phase_diff = 0.;
        exp_dphi = 1.;

        if( changedParams & PHI_REF ) {
            // Only 2nd harmonic present, so {h+,hx} \propto e^(2 i phiRef)
            phase_diff = 2.*(phiRef - cache->phiRef);
            exp_dphi = cpolar(1., phase_diff);
        }
        if( changedParams & INCLINATION) {
            // Rescale h+, hx by ratio of new/old inclination dependence
            incl_ratio_plus = (1.0 + cos(i)*cos(i))
                    / (1.0 + cos(cache->i)*cos(cache->i));
            incl_ratio_cross = cos(i) / cos(cache->i);
        }
        if( changedParams & DISTANCE ) {
            // Rescale h+, hx by ratio of (1/new_dist)/(1/old_dist) = old/new
            dist_ratio = cache->r / r;
        }

        // Create the output polarizations
        *hptilde = XLALCreateCOMPLEX16FrequencySeries(cache->hptilde->name,
                &(cache->hptilde->epoch), cache->hptilde->f0,
                cache->hptilde->deltaF, &(cache->hptilde->sampleUnits),
                cache->hptilde->data->length);
        if (*hptilde == NULL) return XLAL_ENOMEM;

        *hctilde = XLALCreateCOMPLEX16FrequencySeries(cache->hctilde->name,
                &(cache->hctilde->epoch), cache->hctilde->f0,
                cache->hctilde->deltaF, &(cache->hctilde->sampleUnits),
                cache->hctilde->data->length);
        if (*hctilde == NULL) {
            XLALDestroyCOMPLEX16FrequencySeries(*hptilde);
            *hptilde = NULL;
            return XLAL_ENOMEM;
        }

        // Get new polarizations by transforming the old
        incl_ratio_plus *= dist_ratio;
        incl_ratio_cross *= dist_ratio;
        for (j = 0; j < cache->hptilde->data->length; j++) {
            (*hptilde)->data->data[j] = exp_dphi * incl_ratio_plus
                    * cache->hptilde->data->data[j];
            (*hctilde)->data->data[j] = exp_dphi * incl_ratio_cross
                    * cache->hctilde->data->data[j];
        }

        return XLAL_SUCCESS;
    }

    // case 2: Precessing
    /*else if( approximant == SpinTaylorF2 ) {

    }*/

    // Catch-all. Unsure what to do, don't try to cache.
    // Basically, you requested a waveform type which is not setup for caching
    // b/c of lack of interest or it's unclear what/how to cache for that model
    else {
        if ( frequencies != NULL ){
            return XLALSimInspiralChooseFDWaveformSequence(hptilde, hctilde, phiRef,
                    m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, f_ref,
                    r, i, LALpars, approximant,frequencies);
        }
        else {
	  return XLALSimInspiralChooseFDWaveform(hptilde, hctilde, m1, m2,
						 S1x, S1y, S1z, S2x, S2y, S2z,
						 r, i, phiRef, 0., 0., 0.,
						 deltaF, f_min, f_max, f_ref,
						 NULL, approximant);
        }
    }

}
CAMLprim value wrapLALInferenceIFOData(value options) {
  CAMLparam1(options);
  CAMLlocal2(data, option);

  LALInferenceIFOData *d = NULL;
  ProcessParamsTable *ppt = NULL;

  /* Set srate. */
  option = Field(options, 0);
  ppt = addCommandLineOption(ppt, "--srate", String_val(option));

  /* Flow's */
  option = Field(options, 1);
  if (caml_string_length(option) == 0) {
    /* Do nothing. */
  } else {
    ppt = addCommandLineOption(ppt, "--flow", String_val(option));
  }

  /* Fhigh's */
  option = Field(options, 2);
  if (caml_string_length(option) == 0) {
    /* Do nothing. */
  } else {
    ppt = addCommandLineOption(ppt, "--fhigh", String_val(option));
  }

  option = Field(options, 3);
  ppt = addCommandLineOption(ppt, "--cache", String_val(option));
  
  option = Field(options, 4);
  ppt = addCommandLineOption(ppt, "--IFO", String_val(option));

  option = Field(options, 5);
  ppt = addCommandLineOption(ppt, "--dataseed", String_val(option));
  
  option = Field(options, 6);
  ppt = addCommandLineOption(ppt, "--PSDstart", String_val(option));
  
  option = Field(options, 7);
  ppt = addCommandLineOption(ppt, "--trigtime", String_val(option));
  
  option = Field(options, 8);
  ppt = addCommandLineOption(ppt, "--PSDlength", String_val(option));

  option = Field(options, 9);
  ppt = addCommandLineOption(ppt, "--seglen", String_val(option));

  option = Field(options, 10);
  if (Is_block(option)) {
    ppt = addCommandLineOption(ppt, "--injXML", String_val(Field(option,0)));
  }

  d = LALInferenceReadData(ppt);
  LALInferenceInjectInspiralSignal(d,ppt);
		
  LALInferenceIFOData *dElt = d;
  while (dElt != NULL) {
    /*If two IFOs have the same sampling rate, they should have the
      same timeModelh*, freqModelh*, and modelParams variables to
      avoid excess computation in model waveform generation in the
      future*/
    LALInferenceIFOData * dEltCompare=d;
    int foundIFOwithSameSampleRate=0;
    while (dEltCompare != NULL && dEltCompare!=dElt) {
      if(dEltCompare->timeData->deltaT == dElt->timeData->deltaT){
        dElt->timeModelhPlus=dEltCompare->timeModelhPlus;
        dElt->freqModelhPlus=dEltCompare->freqModelhPlus;
        dElt->timeModelhCross=dEltCompare->timeModelhCross;				
        dElt->freqModelhCross=dEltCompare->freqModelhCross;				
        dElt->modelParams=dEltCompare->modelParams;	
        foundIFOwithSameSampleRate=1;	
        break;
      }
      dEltCompare = dEltCompare->next;
    }
    if(!foundIFOwithSameSampleRate){
      dElt->timeModelhPlus  = XLALCreateREAL8TimeSeries("timeModelhPlus",
                                                        &(dElt->timeData->epoch),
                                                        0.0,
                                                        dElt->timeData->deltaT,
                                                        &lalDimensionlessUnit,
                                                        dElt->timeData->data->length);
      dElt->timeModelhCross = XLALCreateREAL8TimeSeries("timeModelhCross",
                                                        &(dElt->timeData->epoch),
                                                        0.0,
                                                        dElt->timeData->deltaT,
                                                        &lalDimensionlessUnit,
                                                        dElt->timeData->data->length);
      dElt->freqModelhPlus = XLALCreateCOMPLEX16FrequencySeries("freqModelhPlus",
                                                                &(dElt->freqData->epoch),
                                                                0.0,
                                                                dElt->freqData->deltaF,
                                                                &lalDimensionlessUnit,
                                                                dElt->freqData->data->length);
      dElt->freqModelhCross = XLALCreateCOMPLEX16FrequencySeries("freqModelhCross",
                                                                 &(dElt->freqData->epoch),
                                                                 0.0,
                                                                 dElt->freqData->deltaF,
                                                                 &lalDimensionlessUnit,
                                                                 dElt->freqData->data->length);
      dElt->modelParams = calloc(1, sizeof(LALInferenceVariables));
    }
    dElt = dElt->next;
  }

  deletePPT(ppt);

  data = alloc_ifo_data(d);

  CAMLreturn(data);
}
/*
 * Core function for computing the ROM waveform.
 * Evaluates projection coefficients and shifts in time and phase at desired q.
 * Construct 1D splines for amplitude and phase.
 * Compute strain waveform from amplitude and phase.
*/
static INT4 EOBNRv2HMROMCore(
  COMPLEX16FrequencySeries **hptilde,
  COMPLEX16FrequencySeries **hctilde,
  REAL8 phiRef,
  REAL8 deltaF,
  REAL8 fLow,
  REAL8 fHigh,
  REAL8 fRef,
  REAL8 distance,
  REAL8 inclination,
  REAL8 Mtot_sec,
  REAL8 q)
{
  INT4 ret = XLAL_SUCCESS;
  INT4 i;
  INT4 j;
  double tpeak22estimate = 0.;
  /* Check output arrays */
  if(!hptilde || !hctilde) XLAL_ERROR(XLAL_EFAULT);
  if(*hptilde || *hctilde)
  {
    XLALPrintError("(*hptilde) and (*hctilde) are supposed to be NULL, but got %p and %p\n",(*hptilde),(*hctilde));
    XLAL_ERROR(XLAL_EFAULT);
  }

  /* Check if the data has been set up */
  if(__lalsim_EOBNRv2HMROM_setup) {
    XLALPrintError("Error: the ROM data has not been set up\n");
    XLAL_ERROR(XLAL_EFAULT);
  }
  /* Set the global pointers to data */
  ListmodesEOBNRHMROMdata* listdata = *__lalsim_EOBNRv2HMROM_data;
  ListmodesEOBNRHMROMdata_interp* listdata_interp = *__lalsim_EOBNRv2HMROM_interp;

  /* Global amplitude prefactor - includes total mass scaling, Fourier scaling, distance scaling, and undoing an additional arbitrary scaling */
  REAL8 Mtot_msol = Mtot_sec / LAL_MTSUN_SI; /* Mtot_msol and M_ROM in units of solar mass */
  REAL8 amp0 = (Mtot_msol/M_ROM) * Mtot_sec * 1.E-16 * 1.E6 * LAL_PC_SI / distance;

  /* Highest allowed geometric frequency for the first mode of listmode in the ROM - used for fRef
   * by convention, we use the first mode of listmode (presumably the 22) for phiref */
  ListmodesEOBNRHMROMdata* listdata_ref = ListmodesEOBNRHMROMdata_GetMode(listdata, listmode[0][0], listmode[0][1]);
  EOBNRHMROMdata* data_ref = listdata_ref->data;
  REAL8 Mf_ROM_max_ref = gsl_vector_get(data_ref->freq, nbfreq-1);
  /* Convert to geometric units for frequency */
  REAL8 fLow_geom = fLow * Mtot_sec;
  REAL8 fHigh_geom = fHigh * Mtot_sec;
  REAL8 fRef_geom = fRef * Mtot_sec;
  REAL8 deltaF_geom = deltaF * Mtot_sec;

  /* Enforce allowed geometric frequency range */
  if (fLow_geom < Mf_ROM_min) { /* Enforce minimal frequency */
    XLALPrintWarning("Starting frequency Mflow=%g is smaller than lowest frequency in ROM Mf=%g. Starting at lowest frequency in ROM.\n", fLow_geom, Mf_ROM_min);
    fLow_geom = Mf_ROM_min;
  }
  /* Default highest frequency */
  if (fHigh == 0)
    fHigh_geom = Mf_ROM_max;
  /* In case the user asks for a frequency higher than covered by the ROM, we keep it that way as we will just 0-pad the waveform (and do it anyway for some modes) */
  if (fRef_geom > Mf_ROM_max_ref || fRef_geom == 0)
    fRef_geom = Mf_ROM_max_ref; /* If fRef > fhigh or 0 we reset fRef to default value of cutoff frequency for the first mode of the list (presumably the 22 mode) */
  if (0 < fRef_geom && fRef_geom < Mf_ROM_min) {
    XLALPrintWarning("Reference frequency Mf_ref=%g is smaller than lowest frequency in ROM Mf=%g. Setting it to the lowest frequency in ROM.\n", fLow_geom, Mf_ROM_min);
    fRef_geom = Mf_ROM_min;
  }
  /* Set up output array with size closest power of 2 - fHigh is the upper frequency specified by the user */
  size_t nbpt = NextPow2(fHigh_geom / deltaF_geom) + 1;

  /* Internal storage for the projection coefficients and shifts in time and phase */
  /* Initialized only once, and reused for the different modes */
  EOBNRHMROMdata_coeff *data_coeff = NULL;
  EOBNRHMROMdata_coeff_Init(&data_coeff);
  /* Create spherical harmonic frequency series that will contain the hlm's */
  SphHarmFrequencySeries** hlmsphharmfreqseries = XLALMalloc(sizeof(SphHarmFrequencySeries));
  *hlmsphharmfreqseries = NULL;

  /* GPS time definition - common to all modes */
  LIGOTimeGPS tC;
  XLALGPSAdd(&tC, -1. / deltaF);  /* coalesce at t=0 */

  /* The phase change imposed by phiref, from the phase of the first mode in the list - to be set in the first step of the loop on the modes */
  REAL8 phase_change_ref = 0;

  /* Main loop over the modes */
  for( i=0; i<nbmode; i++ ){
    UINT4 l = listmode[i][0];
    INT4 m = listmode[i][1];

    /* Getting the relevant modes in the lists of data */
    ListmodesEOBNRHMROMdata* listdata_mode = ListmodesEOBNRHMROMdata_GetMode(listdata, l, m);
    ListmodesEOBNRHMROMdata_interp* listdata_interp_mode = ListmodesEOBNRHMROMdata_interp_GetMode(listdata_interp, l, m);

    /* Evaluating the projection coefficients and shift in time and phase */
    ret |= Evaluate_Spline_Data(q, listdata_interp_mode->data_interp, data_coeff);

    /* Evaluating the unnormalized amplitude and unshifted phase vectors for the mode */
    /* Notice a change in convention: B matrices are transposed with respect to the B matrices in SEOBNRROM */
    /* amp_pts = Bamp . Camp_coeff */
    /* phi_pts = Bphi . Cphi_coeff */
    gsl_vector* amp_f = gsl_vector_alloc(nbfreq);
    gsl_vector* phi_f = gsl_vector_alloc(nbfreq);
    gsl_blas_dgemv(CblasNoTrans, 1.0, listdata_mode->data->Bamp, data_coeff->Camp_coeff, 0.0, amp_f);
    gsl_blas_dgemv(CblasNoTrans, 1.0, listdata_mode->data->Bphi, data_coeff->Cphi_coeff, 0.0, phi_f);

    /* The downsampled frequencies for the mode - we undo the rescaling of the frequency for the 44 and 55 modes */
    gsl_vector* freq_ds = gsl_vector_alloc(nbfreq);
    gsl_vector_memcpy(freq_ds, listdata_mode->data->freq);
    if ( l==4 && m==4) gsl_vector_scale( freq_ds, 1./Scaling44(q));
    if ( l==5 && m==5) gsl_vector_scale( freq_ds, 1./Scaling55(q));

    /* Evaluating the shifts in time and phase - conditional scaling for the 44 and 55 modes */
    /* Note: the stored values of 'shifttime' correspond actually to 2pi*Deltat */
    SplineList* shifttime_splinelist = listdata_interp_mode->data_interp->shifttime_interp;
    SplineList* shiftphase_splinelist = listdata_interp_mode->data_interp->shiftphase_interp;
    REAL8 twopishifttime;
    if( l==4 && m==4) {
      twopishifttime = gsl_spline_eval(shifttime_splinelist->spline, q, shifttime_splinelist->accel) * Scaling44(q);
    }
    else if( l==5 && m==5) {
      twopishifttime = gsl_spline_eval(shifttime_splinelist->spline, q, shifttime_splinelist->accel) * Scaling55(q);
    }
    else {
      twopishifttime = gsl_spline_eval(shifttime_splinelist->spline, q, shifttime_splinelist->accel);
    }
    REAL8 shiftphase = gsl_spline_eval(shiftphase_splinelist->spline, q, shiftphase_splinelist->accel);

    /* If first mode in the list, assumed to be the 22 mode, set totalshifttime and phase_change_ref */
    if( i==0 ) {
      if(l==2 && m==2) {
      /* Setup 1d cubic spline for the phase of the 22 mode */
      gsl_interp_accel* accel_phi22 = gsl_interp_accel_alloc();
      gsl_spline* spline_phi22 = gsl_spline_alloc(gsl_interp_cspline, nbfreq);
      gsl_spline_init(spline_phi22, gsl_vector_const_ptr(freq_ds,0), gsl_vector_const_ptr(phi_f,0), nbfreq);
      /* Compute the shift in time needed to set the peak of the 22 mode roughly at t=0 */
      /* We use the SPA formula tf = -(1/2pi)*dPsi/df to estimate the correspondence between frequency and time */
      /* The frequency corresponding to the 22 peak is omega22peak/2pi, with omega22peak taken from the fit to NR in Pan&al 1106 EOBNRv2HM paper */
      double f22peak = fmin(omega22peakOfq(q)/(2*LAL_PI), Mf_ROM_max_ref); /* We ensure we evaluate the spline within its range */
      tpeak22estimate = -1./(2*LAL_PI) * gsl_spline_eval_deriv(spline_phi22, f22peak, accel_phi22);
      /* Determine the change in phase (to be propagated to all modes) required to have phi22(fRef) = 2*phiRef */
      phase_change_ref = 2*phiRef + (gsl_spline_eval(spline_phi22, fRef_geom, accel_phi22) - (twopishifttime - 2*LAL_PI*tpeak22estimate) * fRef_geom - shiftphase);

      gsl_spline_free(spline_phi22);
      gsl_interp_accel_free(accel_phi22);
      }
      else {
	XLALPrintError("Error: the first mode in listmode must be the 22 mode to set the changes in phase and time \n");
	XLAL_ERROR(XLAL_EFAILED);
      }
    }
    /* Total shift in time, and total change in phase for this mode */
    double totaltwopishifttime = twopishifttime - 2*LAL_PI*tpeak22estimate;
    double constphaseshift = (double) m/listmode[0][1] * phase_change_ref + shiftphase;

    /* Initialize the complex series for the mode - notice that metadata used here is useless, only the one for the final output will matter */
    COMPLEX16FrequencySeries* mode = XLALCreateCOMPLEX16FrequencySeries("mode hlm", &tC, 0.0, deltaF, &lalStrainUnit, nbpt);
    memset(mode->data->data, 0, nbpt * sizeof(COMPLEX16));
    /* Setup 1d cubic spline for the phase and amplitude of the mode */
    gsl_interp_accel* accel_phi = gsl_interp_accel_alloc();
    gsl_interp_accel* accel_amp = gsl_interp_accel_alloc();
    gsl_spline* spline_phi = gsl_spline_alloc(gsl_interp_cspline, nbfreq);
    gsl_spline* spline_amp = gsl_spline_alloc(gsl_interp_cspline, nbfreq);
    gsl_spline_init(spline_phi, gsl_vector_const_ptr(freq_ds,0), gsl_vector_const_ptr(phi_f,0), nbfreq);
    gsl_spline_init(spline_amp, gsl_vector_const_ptr(freq_ds,0), gsl_vector_const_ptr(amp_f,0), nbfreq);
    /* Interval in frequency covered by the ROM */
    REAL8 fLow_geom_mode = gsl_vector_get(freq_ds, 0);
    REAL8 fHigh_geom_mode = fmin(gsl_vector_get(freq_ds, nbfreq-1), fHigh_geom);
    /* Initialize the loop - values outside this range in j are 0 by default */
    INT4 jStart = (UINT4) ceil(fLow_geom_mode / deltaF_geom);
    INT4 jStop = (UINT4) ceil(fHigh_geom_mode / deltaF_geom);
    COMPLEX16 *modedata = mode->data->data;
    /* Mode-dependent complete amplitude prefactor */
    REAL8 amp_pre = amp0 * ModeAmpFactor( l, m, q);
    /* Loop on the frequency samples chosen to evaluate the waveform */
    /* We set apart the first and last step to avoid falling outside of the range of the splines by numerical errors */
    REAL8 f, A, phase;

    f = fmax(fLow_geom_mode, jStart*deltaF_geom);
    A = gsl_spline_eval(spline_amp, f, accel_amp);
    phase = -gsl_spline_eval(spline_phi, f, accel_phi) + totaltwopishifttime * f + constphaseshift; /* Minus sign put here, in the internals of the ROM model \Psi = -phase */
    modedata[jStart] = amp_pre * A * cexp(I*phase);

    for (j=jStart+1; j<jStop-1; j++) {
      f = j*deltaF_geom;
      A = gsl_spline_eval(spline_amp, f, accel_amp);
      phase = -gsl_spline_eval(spline_phi, f, accel_phi) + totaltwopishifttime * f + constphaseshift; /* Minus sign put here, in the internals of the ROM model \Psi = -phase */
      modedata[j] = amp_pre * A * cexp(I*phase);
    }

    f = fmin(fHigh_geom_mode, (jStop-1)*deltaF_geom);
    A = gsl_spline_eval(spline_amp, f, accel_amp);
    phase = -gsl_spline_eval(spline_phi, f, accel_phi) + totaltwopishifttime * f + constphaseshift; /* Minus sign put here, in the internals of the ROM model \Psi = -phase */
    modedata[jStop-1] = amp_pre * A * cexp(I*phase);

    /* Add the computed mode to the SphHarmFrequencySeries structure */
    *hlmsphharmfreqseries = XLALSphHarmFrequencySeriesAddMode(*hlmsphharmfreqseries, mode, l, m);

    /* Cleanup for the mode */
    gsl_spline_free(spline_amp);
    gsl_spline_free(spline_phi);
    gsl_interp_accel_free(accel_amp);
    gsl_interp_accel_free(accel_phi);
    gsl_vector_free(amp_f);
    gsl_vector_free(phi_f);
    gsl_vector_free(freq_ds);
    XLALDestroyCOMPLEX16FrequencySeries(mode);

  }
  /* Cleanup of the coefficients data structure */
  EOBNRHMROMdata_coeff_Cleanup(data_coeff);

  /* Combining the modes for a hplus, hcross output */
  /* Initialize the complex series hplus, hcross */
  *hptilde = XLALCreateCOMPLEX16FrequencySeries("hptilde: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, nbpt);
  *hctilde = XLALCreateCOMPLEX16FrequencySeries("hctilde: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, nbpt);

  if (!(hptilde) || !(*hctilde)) XLAL_ERROR(XLAL_EFUNC);
  memset((*hptilde)->data->data, 0, nbpt * sizeof(COMPLEX16));
  memset((*hctilde)->data->data, 0, nbpt * sizeof(COMPLEX16));

  XLALUnitDivide(&(*hptilde)->sampleUnits, &(*hptilde)->sampleUnits, &lalSecondUnit);
  XLALUnitDivide(&(*hctilde)->sampleUnits, &(*hctilde)->sampleUnits, &lalSecondUnit);

  /* Adding the modes to form hplus, hcross
   * - use of a function that copies XLALSimAddMode but for Fourier domain structures */
  INT4 sym; /* sym will decide whether to add the -m mode (when equatorial symmetry is present) */
  for( i=0; i<nbmode; i++){
    INT4 l = listmode[i][0];
    INT4 m = listmode[i][1];
    COMPLEX16FrequencySeries* mode = XLALSphHarmFrequencySeriesGetMode(*hlmsphharmfreqseries, l, m);
    if ( m==0 ) sym = 0; /* We test for hypothetical m=0 modes */
    else sym = 1;
    FDAddMode( *hptilde, *hctilde, mode, inclination, 0., l, m, sym); /* The phase \Phi is set to 0 - assumes phiRef is defined as half the phase of the 22 mode h22 (or the first mode in the list), not for h = hplus-I hcross */
  }

  /* Destroying the list of frequency series for the modes, including the COMPLEX16FrequencySeries that it contains */
  XLALDestroySphHarmFrequencySeries(*hlmsphharmfreqseries);
  XLALFree(hlmsphharmfreqseries);

  /* Additional complex conjugation of hptilde, hctilde - due to the difference in convention for the Fourier transform between LAL and the ROM internals */
  COMPLEX16* datap = (*hptilde)->data->data;
  COMPLEX16* datac = (*hctilde)->data->data;
  for ( j = 0; j < (INT4) (*hptilde)->data->length; ++j ) {
    datap[j] = conj(datap[j]);
  }
  for ( j = 0; j < (INT4) (*hctilde)->data->length; ++j ) {
    datac[j] = conj(datac[j]);
  }

  return(XLAL_SUCCESS);
}
/**
 * Computes the stationary phase approximation to the Fourier transform of
 * a chirp waveform with phase given by \eqref{eq_InspiralFourierPhase_f2}
 * and amplitude given by expanding \f$1/\sqrt{\dot{F}}\f$. If the PN order is
 * set to -1, then the highest implemented order is used.
 *
 * See arXiv:0810.5336 and arXiv:astro-ph/0504538 for spin corrections
 * to the phasing.
 * See arXiv:1303.7412 for spin-orbit phasing corrections at 3 and 3.5PN order
 */
int XLALSimInspiralSpinTaylorF2(
        COMPLEX16FrequencySeries **hplus_out,  /**< FD hplus waveform */
        COMPLEX16FrequencySeries **hcross_out, /**< FD hcross waveform */
        const REAL8 phi_ref,                   /**< reference orbital phase (rad) */
        const REAL8 deltaF,                    /**< frequency resolution */
        const REAL8 m1_SI,                     /**< mass of companion 1 (kg) */
        const REAL8 m2_SI,                     /**< mass of companion 2 (kg) */
        const REAL8 s1x,                             /**< initial value of S1x */
        const REAL8 s1y,                             /**< initial value of S1y */
        const REAL8 s1z,                             /**< initial value of S1z */
        const REAL8 lnhatx,                          /**< initial value of LNhatx */
        const REAL8 lnhaty,                          /**< initial value of LNhaty */
        const REAL8 lnhatz,                          /**< initial value of LNhatz */
        const REAL8 fStart,                    /**< start GW frequency (Hz) */
        const REAL8 fEnd,                      /**< highest GW frequency (Hz) of waveform generation - if 0, end at Schwarzschild ISCO */
        const REAL8 f_ref,                     /**< Reference GW frequency (Hz) - if 0 reference point is coalescence */
        const REAL8 r,                         /**< distance of source (m) */
	LALDict *moreParams, /**< Linked list of extra. Pass in NULL (or None in python) for standard waveform. Set "sideband",m to get a single sideband (m=-2..2) */
        const INT4 phaseO,                     /**< twice PN phase order */
        const INT4 amplitudeO                  /**< twice PN amplitude order */
        )
{
    /* external: SI; internal: solar masses */
    const REAL8 m1 = m1_SI / LAL_MSUN_SI;
    const REAL8 m2 = m2_SI / LAL_MSUN_SI;
    const REAL8 m = m1 + m2;
    const REAL8 m_sec = m * LAL_MTSUN_SI;  /* total mass in seconds */
    const REAL8 eta = m1 * m2 / (m * m);
    const REAL8 piM = LAL_PI * m_sec;
    const REAL8 vISCO = 1. / sqrt(6.);
    const REAL8 fISCO = vISCO * vISCO * vISCO / piM;
    REAL8 shft, amp0, f_max;
    size_t i, n, iStart;
    COMPLEX16 *data_plus = NULL;
    COMPLEX16 *data_cross = NULL;
    LIGOTimeGPS tC = {0, 0};

    /* If f_ref = 0, use f_ref = f_low for everything except the phase offset */
    const REAL8 v_ref = f_ref > 0. ? cbrt(piM*f_ref) : cbrt(piM*fStart);

    REAL8 alpha, alpha_ref;
    COMPLEX16 prec_plus, prec_cross, phasing_fac;
    bool enable_precession = true; /* Handle the non-spinning case separately */
    int mm;

    LALSimInspiralSF2Orientation orientation;
    XLALSimInspiralSF2CalculateOrientation(&orientation, m1, m2, v_ref, lnhatx, lnhaty, lnhatz, s1x, s1y, s1z);

    LALSimInspiralSF2Coeffs coeffs;
    XLALSimInspiralSF2CalculateCoeffs(&coeffs, m1, m2, orientation.chi, orientation.kappa);
    enable_precession = orientation.chi != 0. && orientation.kappa != 1. && orientation.kappa != -1.;

    alpha_ref = enable_precession ? XLALSimInspiralSF2Alpha(v_ref, coeffs) - orientation.alpha0 : 0.;

    COMPLEX16 SBplus[5]; /* complex sideband factors for plus pol, mm=2 is first entry */
    COMPLEX16 SBcross[5]; /* complex sideband factors for cross pol, mm=2 is first entry */
    REAL8 emission[5]; /* emission factor for each sideband */
    if ( !XLALSimInspiralWaveformParamsSidebandIsDefault(moreParams))
    {
        for(mm = -2; mm <= 2; mm++)
        {
            SBplus[2-mm] = XLALSimInspiralSF2Polarization(orientation.thetaJ, orientation.psiJ, mm);
            SBcross[2-mm] = XLALSimInspiralSF2Polarization(orientation.thetaJ, orientation.psiJ+LAL_PI/4., mm);
        }
    }
    else
    {
        memset(SBplus, 0, 5 * sizeof(COMPLEX16));
        memset(SBcross, 0, 5 * sizeof(COMPLEX16));
        mm = (int) XLALSimInspiralWaveformParamsLookupSideband(moreParams);
        SBplus[2-mm] = XLALSimInspiralSF2Polarization(orientation.thetaJ, orientation.psiJ, mm);
        SBcross[2-mm] = XLALSimInspiralSF2Polarization(orientation.thetaJ, orientation.psiJ+LAL_PI/4., mm);
    }

    const REAL8 chi1L = orientation.chi*orientation.kappa;
    const REAL8 chi1sq = orientation.chi*orientation.chi;
    /* FIXME: Cannot yet set QM constant in ChooseFDWaveform interface */
    /* phasing coefficients */
    PNPhasingSeries pfa;
    XLALSimInspiralPNPhasing_F2(&pfa, m1, m2, chi1L, 0., chi1sq, 0., 0., moreParams);

    REAL8 pfaN = 0.; REAL8 pfa1 = 0.;
    REAL8 pfa2 = 0.; REAL8 pfa3 = 0.; REAL8 pfa4 = 0.;
    REAL8 pfa5 = 0.; REAL8 pfl5 = 0.;
    REAL8 pfa6 = 0.; REAL8 pfl6 = 0.;
    REAL8 pfa7 = 0.; REAL8 pfa8 = 0.;

    switch (phaseO)
    {
        case -1:
        case 7:
            pfa7 = pfa.v[7];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 6:
            pfa6 = pfa.v[6];
            pfl6 = pfa.vlogv[6];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 5:
            pfa5 = pfa.v[5];
            pfl5 = pfa.vlogv[5];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 4:
            pfa4 = pfa.v[4];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 3:
            pfa3 = pfa.v[3];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 2:
            pfa2 = pfa.v[2];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 1:
            pfa1 = pfa.v[1];
#if __GNUC__ >= 7
            __attribute__ ((fallthrough));
#endif
        case 0:
            pfaN = pfa.v[0];
            break;
        default:
            XLAL_ERROR(XLAL_ETYPE, "Invalid phase PN order %d", phaseO);
    }

    /* Add the zeta factor to the phasing, since it looks like a pN series.
     * This is the third Euler angle after alpha and beta.
     */
    if (enable_precession)
    {
        pfa2 += 2.*coeffs.prec_fac*coeffs.zc[0];
        pfa3 += 2.*coeffs.prec_fac*coeffs.zc[1];
        pfa4 += 2.*coeffs.prec_fac*coeffs.zc[2];
        pfl5 += 2.*coeffs.prec_fac*coeffs.zc[3];
        pfa6 += 2.*coeffs.prec_fac*coeffs.zc[4];
        pfa7 += 2.*coeffs.prec_fac*coeffs.zc[5];
        pfa8 += 2.*coeffs.prec_fac*coeffs.zc[6];
    }

    /* Validate expansion order arguments.
     * This must be done here instead of in the OpenMP parallel loop
     * because when OpenMP parallelization is turned on, early exits
     * from loops (via return or break statements) are not permitted.
     */

    /* Validate amplitude PN order. */
    if (amplitudeO != 0) { XLAL_ERROR(XLAL_ETYPE, "Invalid amplitude PN order %d", amplitudeO); }

    /* energy coefficients - not currently used, but could for MECO
    const REAL8 dETaN = 2. * XLALSimInspiralPNEnergy_0PNCoeff(eta);
    const REAL8 dETa1 = 2. * XLALSimInspiralPNEnergy_2PNCoeff(eta);
    const REAL8 dETa2 = 3. * XLALSimInspiralPNEnergy_4PNCoeff(eta);
    const REAL8 dETa3 = 4. * XLALSimInspiralPNEnergy_6PNCoeff(eta);
    */

    COMPLEX16FrequencySeries *hplus;
    COMPLEX16FrequencySeries *hcross;

    /* Perform some initial checks */
    if (!hplus_out) XLAL_ERROR(XLAL_EFAULT);
    if (!hcross_out) XLAL_ERROR(XLAL_EFAULT);
    if (*hplus_out) XLAL_ERROR(XLAL_EFAULT);
    if (*hcross_out) XLAL_ERROR(XLAL_EFAULT);
    if (m1_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (m2_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (fStart <= 0) XLAL_ERROR(XLAL_EDOM);
    if (f_ref < 0) XLAL_ERROR(XLAL_EDOM);
    if (r <= 0) XLAL_ERROR(XLAL_EDOM);

    /* allocate htilde */
    if ( fEnd == 0. ) // End at ISCO
        f_max = fISCO;
    else // End at user-specified freq.
        f_max = fEnd;
    n = (size_t) (f_max / deltaF + 1);
    XLALGPSAdd(&tC, -1 / deltaF);  /* coalesce at t=0 */
    /* Allocate hplus and hcross */
    hplus = XLALCreateCOMPLEX16FrequencySeries("hplus: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, n);
    if (!hplus) XLAL_ERROR(XLAL_EFUNC);
    memset(hplus->data->data, 0, n * sizeof(COMPLEX16));
    XLALUnitMultiply(&hplus->sampleUnits, &hplus->sampleUnits, &lalSecondUnit);
    hcross = XLALCreateCOMPLEX16FrequencySeries("hcross: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, n);
    if (!hcross) XLAL_ERROR(XLAL_EFUNC);
    memset(hcross->data->data, 0, n * sizeof(COMPLEX16));
    XLALUnitMultiply(&hcross->sampleUnits, &hcross->sampleUnits, &lalSecondUnit);

    /* extrinsic parameters */
    amp0 = -4. * m1 * m2 / r * LAL_MRSUN_SI * LAL_MTSUN_SI * sqrt(LAL_PI/12.L);
    amp0 *= sqrt(-2. * XLALSimInspiralPNEnergy_0PNCoeff(eta)/XLALSimInspiralPNFlux_0PNCoeff(eta));
    shft = LAL_TWOPI * (tC.gpsSeconds + 1e-9 * tC.gpsNanoSeconds);

    /* Fill with non-zero vals from fStart to f_max */
    iStart = (size_t) ceil(fStart / deltaF);
    data_plus = hplus->data->data;
    data_cross = hcross->data->data;

    /* Compute the SPA phase at the reference point */
    REAL8 ref_phasing = 0.;
    if (f_ref > 0.)
    {
        const REAL8 logvref = log(v_ref);
        const REAL8 v2ref = v_ref * v_ref;
        const REAL8 v3ref = v_ref * v2ref;
        const REAL8 v4ref = v_ref * v3ref;
        const REAL8 v5ref = v_ref * v4ref;
        ref_phasing = (pfaN + pfa1 * v_ref +pfa2 * v2ref + pfa3 * v3ref + pfa4 * v4ref) / v5ref + (pfa5 + pfl5 * logvref) + (pfa6 + pfl6 * logvref) * v_ref + pfa7 * v2ref + pfa8 * v3ref;
    } /* end of if (f_ref > 0.) */

    #pragma omp parallel for
    for (i = iStart; i < n; i++) {
        const REAL8 f = i * deltaF;
        const REAL8 v = cbrt(piM*f);
        const REAL8 logv = log(v);
        const REAL8 v2 = v * v;
        const REAL8 v3 = v * v2;
        const REAL8 v4 = v * v3;
        const REAL8 v5 = v * v4;
        REAL8 phasing = (pfaN + pfa1*v + pfa2 * v2 + pfa3 * v3 + pfa4 * v4) / v5 + (pfa5 + pfl5 * logv) + (pfa6 + pfl6 * logv) * v + pfa7 * v2 + pfa8 * v3;
        COMPLEX16 amp = amp0 / (v3 * sqrt(v));

        alpha = enable_precession ? XLALSimInspiralSF2Alpha(v, coeffs) - alpha_ref : 0.;

        COMPLEX16 u = cos(alpha) + 1.0j*sin(alpha);
        XLALSimInspiralSF2Emission(emission, v, coeffs);
        prec_plus = SBplus[0]*emission[0]*u*u
                    + SBplus[1]*emission[1]*u
                    + SBplus[2]*emission[2]
                    + SBplus[3]*emission[3]/u
                    + SBplus[4]*emission[4]/u/u;
        prec_cross= SBcross[0]*emission[0]*u*u
                    + SBcross[1]*emission[1]*u
                    + SBcross[2]*emission[2]
                    + SBcross[3]*emission[3]/u
                    + SBcross[4]*emission[4]/u/u;
        // Note the factor of 2 b/c phi_ref is orbital phase
        phasing += shft * f - 2.*phi_ref - ref_phasing - LAL_PI_4;
        phasing_fac = cos(phasing) - 1.0j*sin(phasing);
        data_plus[i] = amp * prec_plus * phasing_fac;
        data_cross[i] = amp * prec_cross * phasing_fac;
    }

    *hplus_out = hplus;
    *hcross_out = hcross;
    return XLAL_SUCCESS;
}
Esempio n. 15
0
REAL8 calculate_snr_from_strain_and_psd_real8(  REAL8TimeSeries *strain,
                                       REAL8FrequencySeries  *psd,
                                       REAL8                 startFreq,
                                       const CHAR            ifo[3])
{

  REAL8 ret = -1, snrSq, freq, psdValue;
  REAL8 deltaF;
  REAL8FFTPlan *pfwd;
  COMPLEX16FrequencySeries *fftData;
  UINT4 k;

  /* create the time series */
  deltaF  = strain->deltaT * strain->data->length;
  fftData = XLALCreateCOMPLEX16FrequencySeries( strain->name,  &(strain->epoch),
                                               0, deltaF, &lalDimensionlessUnit,
                                               strain->data->length/2 + 1 );

  /* perform the fft */
  pfwd = XLALCreateForwardREAL8FFTPlan( strain->data->length, 0 );
  XLALREAL8TimeFreqFFT( fftData, strain, pfwd );

  /* The PSD, if provided, comes in as it was in the original file  */
  /* since we don't know deltaF until we get here.  Interpolate now */
  if ( psd )
  {
    psd = XLALInterpolatePSD(psd, 1.0 / deltaF);
  }

  /* compute the SNR for initial LIGO at design */
  for ( snrSq = 0, k = 0; k < fftData->data->length; k++ )
  {
    freq = fftData->deltaF * k;

    if ( psd )
    {
      if ( freq < startFreq || k > psd->data->length )
        continue;
      psdValue  = psd->data->data[k];
      psdValue /= 9e-46;
    }
    else if ( ifo[0] == 'V' )
    {
      if (freq < 35)
        continue;
      LALVIRGOPsd( NULL, &psdValue, freq );
      psdValue /= 9e-46;
    }
    else
    {
      if (freq < 40)
        continue;
      LALLIGOIPsd( NULL, &psdValue, freq );
    }

    fftData->data->data[k] /= 3e-23;

    snrSq += creal(fftData->data->data[k]) * creal(fftData->data->data[k]) / psdValue;
    snrSq += cimag(fftData->data->data[k]) * cimag(fftData->data->data[k]) / psdValue;
  }

  snrSq *= 4*fftData->deltaF;

  XLALDestroyREAL8FFTPlan( pfwd );
  XLALDestroyCOMPLEX16FrequencySeries( fftData );

  if ( psd )
    XLALDestroyREAL8FrequencySeries( psd );

  ret = sqrt(snrSq);

  printf("Obtained snr=%f\n", ret);
  return ret;
}
Esempio n. 16
0
/**
 * Computes the stationary phase approximation to the Fourier transform of
 * a chirp waveform. The amplitude is given by expanding \f$1/\sqrt{\dot{F}}\f$.
 * If the PN order is set to -1, then the highest implemented order is used.
 *
 * @note f_ref is the GW frequency at which phi_ref is defined. The most common
 * choice in the literature is to choose the reference point as "coalescence",
 * when the frequency becomes infinite. This is the behavior of the code when
 * f_ref==0. If f_ref > 0, phi_ref sets the orbital phase at that GW frequency.
 *
 * See arXiv:0810.5336 and arXiv:astro-ph/0504538 for spin corrections
 * to the phasing.
 * See arXiv:1303.7412 for spin-orbit phasing corrections at 3 and 3.5PN order
 *
 * The spin and tidal order enums are defined in LALSimInspiralWaveformFlags.h
 */
int XLALSimInspiralTaylorF2(
        COMPLEX16FrequencySeries **htilde_out, /**< FD waveform */
        const REAL8 phi_ref,                   /**< reference orbital phase (rad) */
        const REAL8 deltaF,                    /**< frequency resolution */
        const REAL8 m1_SI,                     /**< mass of companion 1 (kg) */
        const REAL8 m2_SI,                     /**< mass of companion 2 (kg) */
        const REAL8 S1z,                       /**<  z component of the spin of companion 1 */
        const REAL8 S2z,                       /**<  z component of the spin of companion 2  */
        const REAL8 fStart,                    /**< start GW frequency (Hz) */
        const REAL8 fEnd,                      /**< highest GW frequency (Hz) of waveform generation - if 0, end at Schwarzschild ISCO */
        const REAL8 f_ref,                     /**< Reference GW frequency (Hz) - if 0 reference point is coalescence */
        const REAL8 r,                         /**< distance of source (m) */
        const REAL8 quadparam1,                /**< quadrupole deformation parameter of body 1 (dimensionless, 1 for BH) */
        const REAL8 quadparam2,                /**< quadrupole deformation parameter of body 2 (dimensionless, 1 for BH) */
        const REAL8 lambda1,                   /**< (tidal deformation of body 1)/(mass of body 1)^5 */
        const REAL8 lambda2,                   /**< (tidal deformation of body 2)/(mass of body 2)^5 */
        const INT4 phaseO,                     /**< twice PN phase order */
        const INT4 amplitudeO,                  /**< twice PN amplitude order */
        LALDict *p /**< Linked list containing the extra testing GR parameters >**/
        )
{
    /* external: SI; internal: solar masses */
    const REAL8 m1 = m1_SI / LAL_MSUN_SI;
    const REAL8 m2 = m2_SI / LAL_MSUN_SI;
    const REAL8 m = m1 + m2;
    const REAL8 m_sec = m * LAL_MTSUN_SI;  /* total mass in seconds */
    // const REAL8 eta = m1 * m2 / (m * m);
    const REAL8 piM = LAL_PI * m_sec;
    const REAL8 vISCO = 1. / sqrt(6.);
    const REAL8 fISCO = vISCO * vISCO * vISCO / piM;
    //const REAL8 m1OverM = m1 / m;
    // const REAL8 m2OverM = m2 / m;
    REAL8 shft, f_max;
    size_t i, n;
    INT4 iStart;
    REAL8Sequence *freqs = NULL;
    LIGOTimeGPS tC = {0, 0};
    int ret;

    COMPLEX16FrequencySeries *htilde = NULL;

    /* Perform some initial checks */
    if (!htilde_out) XLAL_ERROR(XLAL_EFAULT);
    if (*htilde_out) XLAL_ERROR(XLAL_EFAULT);
    if (m1_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (m2_SI <= 0) XLAL_ERROR(XLAL_EDOM);
    if (fStart <= 0) XLAL_ERROR(XLAL_EDOM);
    if (f_ref < 0) XLAL_ERROR(XLAL_EDOM);
    if (r <= 0) XLAL_ERROR(XLAL_EDOM);

    /* allocate htilde */
    if ( fEnd == 0. ) // End at ISCO
        f_max = fISCO;
    else // End at user-specified freq.
        f_max = fEnd;
    if (f_max <= fStart) XLAL_ERROR(XLAL_EDOM);

    n = (size_t) (f_max / deltaF + 1);
    XLALGPSAdd(&tC, -1 / deltaF);  /* coalesce at t=0 */
    htilde = XLALCreateCOMPLEX16FrequencySeries("htilde: FD waveform", &tC, 0.0, deltaF, &lalStrainUnit, n);
    if (!htilde) XLAL_ERROR(XLAL_EFUNC);
    memset(htilde->data->data, 0, n * sizeof(COMPLEX16));
    XLALUnitMultiply(&htilde->sampleUnits, &htilde->sampleUnits, &lalSecondUnit);

    /* Fill with non-zero vals from fStart to f_max */
    iStart = (INT4) ceil(fStart / deltaF);

    /* Sequence of frequencies where waveform model is to be evaluated */
    freqs = XLALCreateREAL8Sequence(n - iStart);

    /* extrinsic parameters */
    shft = LAL_TWOPI * (tC.gpsSeconds + 1e-9 * tC.gpsNanoSeconds);

    #pragma omp parallel for
    for (i = iStart; i < n; i++) {
        freqs->data[i-iStart] = i * deltaF;
    }
    XLALSimInspiralWaveformParamsInsertTidalLambda1(p,lambda1);
    XLALSimInspiralWaveformParamsInsertTidalLambda2(p,lambda2);
    XLALSimInspiralWaveformParamsInsertdQuadMon1(p,quadparam1-1.);
    XLALSimInspiralWaveformParamsInsertdQuadMon2(p,quadparam2-1.);
    XLALSimInspiralWaveformParamsInsertPNPhaseOrder(p,phaseO);
    XLALSimInspiralWaveformParamsInsertPNAmplitudeOrder(p,amplitudeO);
    ret = XLALSimInspiralTaylorF2Core(&htilde, freqs, phi_ref, m1_SI, m2_SI,
                                      S1z, S2z, f_ref, shft, r, p);

    XLALDestroyREAL8Sequence(freqs);

    *htilde_out = htilde;

    return ret;
}
Esempio n. 17
0
/* creates a waveform in the frequency domain; the waveform might be generated
 * in the time-domain and transformed */
int create_fd_waveform(COMPLEX16FrequencySeries ** htilde_plus, COMPLEX16FrequencySeries ** htilde_cross, struct params p)
{
    clock_t timer_start = 0;
    double chirplen, deltaF;
    int chirplen_exp;

    /* length of the chirp in samples */
    chirplen = imr_time_bound(p.f_min, p.m1, p.m2, p.s1z, p.s2z) * p.srate;
    /* make chirplen next power of two */
    frexp(chirplen, &chirplen_exp);
    chirplen = ldexp(1.0, chirplen_exp);
    deltaF = p.srate / chirplen;
    if (p.verbose)
        fprintf(stderr, "using frequency resolution deltaF = %g Hz\n", deltaF);

    if (p.condition) {
        if (p.verbose) {
            fprintf(stderr, "generating waveform in frequency domain using XLALSimInspiralFD...\n");
            timer_start = clock();
        }
        XLALSimInspiralFD(htilde_plus, htilde_cross, p.m1, p.m2, p.s1x, p.s1y, p.s1z, p.s2x, p.s2y, p.s2z, p.distance, p.inclination, p.phiRef, p.longAscNodes, p.eccentricity, p.meanPerAno, deltaF, p.f_min, 0.5 * p.srate, p.fRef, p.params, p.approx);
        if (p.verbose)
            fprintf(stderr, "generation took %g seconds\n", (double)(clock() - timer_start) / CLOCKS_PER_SEC);
    } else if (p.domain == LAL_SIM_DOMAIN_FREQUENCY) {
        if (p.verbose) {
            fprintf(stderr, "generating waveform in frequency domain using XLALSimInspiralChooseFDWaveform...\n");
            timer_start = clock();
        }
        XLALSimInspiralChooseFDWaveform(htilde_plus, htilde_cross, p.m1, p.m2, p.s1x, p.s1y, p.s1z, p.s2x, p.s2y, p.s2z, p.distance, p.inclination, p.phiRef, p.longAscNodes, p.eccentricity, p.meanPerAno, deltaF, p.f_min, 0.5 * p.srate, p.fRef, p.params, p.approx);
        if (p.verbose)
            fprintf(stderr, "generation took %g seconds\n", (double)(clock() - timer_start) / CLOCKS_PER_SEC);
    } else {
        REAL8TimeSeries *h_plus = NULL;
        REAL8TimeSeries *h_cross = NULL;
        REAL8FFTPlan *plan;

        /* generate time domain waveform */
        if (p.verbose) {
            fprintf(stderr, "generating waveform in time domain using XLALSimInspiralChooseTDWaveform...\n");
            timer_start = clock();
        }
        XLALSimInspiralChooseTDWaveform(&h_plus, &h_cross, p.m1, p.m2, p.s1x, p.s1y, p.s1z, p.s2x, p.s2y, p.s2z, p.distance, p.inclination, p.phiRef, p.longAscNodes, p.eccentricity, p.meanPerAno, 1.0 / p.srate, p.f_min, p.fRef, p.params, p.approx);
        if (p.verbose)
            fprintf(stderr, "generation took %g seconds\n", (double)(clock() - timer_start) / CLOCKS_PER_SEC);

        /* resize the waveforms to the required length */
        XLALResizeREAL8TimeSeries(h_plus, h_plus->data->length - (size_t) chirplen, (size_t) chirplen);
        XLALResizeREAL8TimeSeries(h_cross, h_cross->data->length - (size_t) chirplen, (size_t) chirplen);

        /* put the waveform in the frequency domain */
        /* (the units will correct themselves) */
        if (p.verbose) {
            fprintf(stderr, "transforming waveform to frequency domain...\n");
            timer_start = clock();
        }
        *htilde_plus = XLALCreateCOMPLEX16FrequencySeries("htilde_plus", &h_plus->epoch, 0.0, deltaF, &lalDimensionlessUnit, (size_t) chirplen / 2 + 1);
        *htilde_cross = XLALCreateCOMPLEX16FrequencySeries("htilde_cross", &h_cross->epoch, 0.0, deltaF, &lalDimensionlessUnit, (size_t) chirplen / 2 + 1);
        plan = XLALCreateForwardREAL8FFTPlan((size_t) chirplen, 0);
        XLALREAL8TimeFreqFFT(*htilde_cross, h_cross, plan);
        XLALREAL8TimeFreqFFT(*htilde_plus, h_plus, plan);
        if (p.verbose)
            fprintf(stderr, "transformation took %g seconds\n", (double)(clock() - timer_start) / CLOCKS_PER_SEC);

        /* clean up */
        XLALDestroyREAL8FFTPlan(plan);
        XLALDestroyREAL8TimeSeries(h_cross);
        XLALDestroyREAL8TimeSeries(h_plus);
    }
    return 0;
}
int compare_template(LALInferenceRunState *runState)
{
  REAL8 oldPhase=0.0,mbPhase=0.0;
  REAL8 h0MatchPlus,h0MatchCross;
  REAL8 hmbMatchPlus,hmbMatchCross;

  REAL8 target_snr = 50; /* Max SNR that we expect to handle */
  REAL8 tolerance = 0.1/(target_snr*target_snr); /* Error in likelihood of 0.1 at target SNR */

  COMPLEX16FrequencySeries *oldTemplatePlus=NULL,*oldTemplateCross=NULL,*newTemplatePlus=NULL,*newTemplateCross=NULL;

  
  runState->threads[0]->model->templt=&LALInferenceTemplateXLALSimInspiralChooseWaveform;
  LALInferenceTemplateNullFreqdomain(runState->threads[0]->model);
  /* FIXME: Have to call the function once before result is repeatable!!! */
  runState->likelihood(runState->threads[0]->model->params,runState->data, runState->threads[0]->model);
  oldTemplatePlus = runState->threads[0]->model->freqhPlus;
  oldTemplateCross = runState->threads[0]->model->freqhCross;

  runState->threads[0]->model->freqhPlus=XLALCreateCOMPLEX16FrequencySeries("mbtemplate",&oldTemplatePlus->epoch,oldTemplatePlus->f0,oldTemplatePlus->deltaF,&lalDimensionlessUnit,oldTemplatePlus->data->length);
  runState->threads[0]->model->freqhCross=XLALCreateCOMPLEX16FrequencySeries("mbtemplate",&oldTemplateCross->epoch,oldTemplateCross->f0,oldTemplateCross->deltaF,&lalDimensionlessUnit,oldTemplateCross->data->length);
  
  /* Clear the template */
  LALInferenceTemplateNullFreqdomain(runState->threads[0]->model);

  runState->likelihood(runState->threads[0]->model->params,runState->data, runState->threads[0]->model);
  LALInferenceDumpWaveforms(runState->threads[0]->model, "normal");
  if(LALInferenceCheckVariable(runState->threads[0]->model->params,"phase_maxl"))
    oldPhase=LALInferenceGetREAL8Variable(runState->threads[0]->model->params,"phase_maxl");
  
  runState->threads[0]->model->templt=&LALInferenceTemplateXLALSimInspiralChooseWaveformPhaseInterpolated;
  
  runState->likelihood(runState->threads[0]->model->params,runState->data, runState->threads[0]->model);
  LALInferenceDumpWaveforms(runState->threads[0]->model, "multiband");
  
  REAL8 SNRsqPlus =LALInferenceComputeFrequencyDomainOverlap(runState->data,runState->threads[0]->model->freqhPlus->data,runState->threads[0]->model->freqhPlus->data);
  REAL8 SNRsqCross =LALInferenceComputeFrequencyDomainOverlap(runState->data,runState->threads[0]->model->freqhCross->data,runState->threads[0]->model->freqhCross->data);
  
  if(LALInferenceCheckVariable(runState->threads[0]->model->params,"phase_maxl"))
    mbPhase=LALInferenceGetREAL8Variable(runState->threads[0]->model->params,"phase_maxl");
  
  newTemplatePlus = runState->threads[0]->model->freqhPlus;
  newTemplateCross = runState->threads[0]->model->freqhCross;

  h0MatchPlus = LALInferenceComputeFrequencyDomainOverlap(runState->data,oldTemplatePlus->data,oldTemplatePlus->data);
  h0MatchCross = LALInferenceComputeFrequencyDomainOverlap(runState->data,oldTemplateCross->data,oldTemplateCross->data);

  hmbMatchPlus = LALInferenceComputeFrequencyDomainOverlap(runState->data,newTemplatePlus->data,newTemplatePlus->data);
  hmbMatchCross = LALInferenceComputeFrequencyDomainOverlap(runState->data,newTemplateCross->data,newTemplateCross->data);
  /* Want to check that <h0|h0>+<h_mb|h_mb>-2<h0|h_mb> < tolerance */
  
  fprintf(stdout,"Parameter values:\n");
  LALInferencePrintVariables(runState->threads[0]->model->params);
  
  COMPLEX16 mismatchplus = compute_mismatch(runState->data, newTemplatePlus , oldTemplatePlus);
  COMPLEX16 mismatchcross = compute_mismatch(runState->data, newTemplateCross , oldTemplateCross);
  
  COMPLEX16 innerPlus = LALInferenceComputeFrequencyDomainComplexOverlap(runState->data, newTemplatePlus->data, oldTemplatePlus->data);
  COMPLEX16 innerCross = LALInferenceComputeFrequencyDomainComplexOverlap(runState->data, newTemplateCross->data, oldTemplateCross->data);
  
  int result = (1.0-cabs(innerPlus)/h0MatchPlus < tolerance) && (1.0 - cabs(innerCross)/h0MatchCross < tolerance);
  
  fprintf(stdout,"\n\n");
  fprintf(stdout,"|<h0|hmb>/<h0|h0>| = %lf (+) %lf (x)\n",(cabs(innerPlus)/h0MatchPlus),(cabs(innerCross)/h0MatchCross));
  fprintf(stdout,"SNR    = %lf (+), %lf (x)\n",sqrt(h0MatchPlus),sqrt(h0MatchCross));
  fprintf(stdout,"SNR mb = %lf (+), %lf (x)\n",sqrt(hmbMatchPlus),sqrt(hmbMatchCross));
  fprintf(stdout,"cplx <h|h0> (+): %lf*exp(%lfi), (x): %lf*exp(%lfi)\n",cabs(innerPlus),carg(innerPlus),cabs(innerCross),carg(innerCross));

  fprintf(stdout,"Normalised mismatch (|h0-h|/|h0|)^2: %le (+), %le (x)\n",cabs(mismatchplus)/SNRsqPlus,cabs(mismatchcross)/SNRsqCross);
  fprintf(stdout,"Tolerance = %le\n",tolerance);
  fprintf(stdout,"Test result: %s\n",result?"passed":"failed");
  fprintf(stdout,"Interpolation good up to SNR %lf (+), %lf (x)\n",
	  sqrt(tolerance*target_snr*target_snr *SNRsqPlus/ mismatchplus),
	  sqrt(tolerance*target_snr*target_snr *SNRsqCross/ mismatchplus) );
  if(LALInferenceCheckVariable(runState->threads[0]->model->params,"phase_maxl"))
    fprintf(stdout,"max Like phase:\tnormal = %lf, phaseinterp = %lf\n",oldPhase,mbPhase);
  return(result);
}