LALSimulation  5.4.0.1-fe68b98
LALSimIMRPhenomX_precession.c
Go to the documentation of this file.
1 
2 /*
3  * Copyright (C) 2018/2019 Geraint Pratten
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with with program; see the file COPYING. If not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  * MA 02110-1301 USA
19  */
20 
21  #ifdef __cplusplus
22  extern "C" {
23  #endif
24 
25 /**
26  * \author Geraint Pratten
27  *
28  */
29 
30 #include <gsl/gsl_sf_elljac.h>
31 #include <gsl/gsl_sf_ellint.h>
32 
33 #include <lal/SphericalHarmonics.h>
34 #include "LALSimIMR.h"
35 
36 #include "LALSimIMRPhenomX.h"
40 #include "LALSimIMRPhenomX_PNR.h"
41 #include "LALSimIMRPhenomXHM_qnm.h"
42 
43 #include <lal/LALAdaptiveRungeKuttaIntegrator.h>
44 
45 #define MIN(a,b) (((a)<(b))?(a):(b))
46 #define MAX(a,b) (((a)>(b))?(a):(b))
47 
48 
49 #ifndef _OPENMP
50 #define omp ignore
51 #endif
52 
53 #ifndef PHENOMXHMDEBUG
54 #define DEBUG 0
55 #else
56 #define DEBUG 1
57 #endif
58 
59 /* ~~~~~~~~~~ Functions to Perform Frame Transformations and Populate Structs ~~~~~~~~~~ */
60  /**
61  Function to populate the IMRPhenomXPrecessionStruct:
62  - Calculates frame transformation
63  - PN Euler angles
64  - Frame transformations
65  - Orbital angular momenta etc
66  */
70  REAL8 m1_SI,
71  REAL8 m2_SI,
72  REAL8 chi1x,
73  REAL8 chi1y,
74  REAL8 chi1z,
75  REAL8 chi2x,
76  REAL8 chi2y,
77  REAL8 chi2z,
78  LALDict *lalParams,
79  INT4 debug_flag
80 )
81 {
82  /*
83  Here we assume m1 > m2, q > 1, dm = m1 - m2 = delta = sqrt(1-4eta) > 0
84  */
85  pWF->LALparams = lalParams;
86 
87  /* Pre-cache useful powers here */
88  pPrec->sqrt2 = 1.4142135623730951;
89  pPrec->sqrt5 = 2.23606797749978981;
90  pPrec->sqrt6 = 2.44948974278317788;
91  pPrec->sqrt7 = 2.64575131106459072;
92  pPrec->sqrt10 = 3.16227766016838;
93  pPrec->sqrt14 = 3.74165738677394133;
94  pPrec->sqrt15 = 3.87298334620741702;
95  pPrec->sqrt70 = 8.36660026534075563;
96  pPrec->sqrt30 = 5.477225575051661;
97  pPrec->sqrt2p5 = 1.58113883008419;
98 
99  pPrec->debug_prec = debug_flag;
100 
101  // Get IMRPhenomX precession version from LAL dictionary
103  if (pPrec->IMRPhenomXPrecVersion == 300) pPrec->IMRPhenomXPrecVersion = 223;
104 
105  REAL8 chi_in_plane = sqrt(chi1x*chi1x+chi1y*chi1y+chi2x*chi2x+chi2y*chi2y);
106  if(chi_in_plane<1e-7 && (pPrec->IMRPhenomXPrecVersion==320||pPrec->IMRPhenomXPrecVersion==321||pPrec->IMRPhenomXPrecVersion==310||pPrec->IMRPhenomXPrecVersion==311))
107  {
108  pPrec->IMRPhenomXPrecVersion=102;
109  }
110 
111  if( (pPrec->IMRPhenomXPrecVersion==320||pPrec->IMRPhenomXPrecVersion==321||pPrec->IMRPhenomXPrecVersion==310||pPrec->IMRPhenomXPrecVersion==311))
112  {
113 
114  int status=XLAL_SUCCESS;
115  pPrec->PNarrays = XLALMalloc(sizeof(PhenomXPInspiralArrays));
116 
117  // check mode array to estimate frequency range over which splines will need to be evaluated
118  LALValue *ModeArray = XLALSimInspiralWaveformParamsLookupModeArray(lalParams);
119  if (ModeArray != NULL)
120  {
121  IMRPhenomX_GetandSetModes(ModeArray,pPrec);
122  XLALDestroyValue(ModeArray);
123  }
124 
125  // buffer for GSL interpolation to succeed
126  REAL8 buffer=(pWF->deltaF>0.)? 3.*pWF->deltaF: 0.5;
127  REAL8 flow=(pWF->fMin-buffer)*2./pPrec->M_MAX;
128  XLAL_CHECK(flow>0.,XLAL_EDOM,"Error in %s: starting frequency for SpinTaylor angles must be positive!",__func__);
129  status=IMRPhenomX_InspiralAngles_SpinTaylor(pPrec->PNarrays,chi1x,chi1y,chi1z,chi2x,chi2y,chi2z,flow,pPrec->IMRPhenomXPrecVersion,pWF,lalParams);
130 
131  // if PN numerical integration fails, default to MSA+fallback to NNLO
132  if(status==XLAL_FAILURE) {
133  LALFree(pPrec->PNarrays);
134  XLAL_PRINT_WARNING("Warning: due to a failure in the SpinTaylor routines, the model will default to MSA angles.");
135  pPrec->IMRPhenomXPrecVersion=223;
136  }
137  // end of SpinTaylor code
138 
139  }
140 
141  // Get expansion order for MSA system of equations. Default is taken to be 5.
143 
144  // Get toggle for PNR angles
146  pPrec->IMRPhenomXPNRUseTunedAngles = PNRUseTunedAngles;
147 
148  // Get PNR angle interpolation tolerance
150 
151  // Get toggle for symmetric waveform
153  pPrec->IMRPhenomXAntisymmetricWaveform = AntisymmetricWaveform;
154 
155  // Set toggle for polarization calculation: +1 for symmetric waveform (default), -1 for antisymmetric waveform; refer to XXXX.YYYYY for details
156  pPrec->PolarizationSymmetry = 1.0;
157 
158  //
159  int pflag = pPrec->IMRPhenomXPrecVersion;
160  if(pflag != 101 && pflag != 102 && pflag != 103 && pflag != 104 && pflag != 220 && pflag != 221 && pflag != 222 && pflag != 223 && pflag != 224 && pflag!=310 && pflag!=311 && pflag!=320 && pflag!=321)
161  {
162  XLAL_ERROR(XLAL_EINVAL, "Error in IMRPhenomXGetAndSetPrecessionVariables: Invalid precession flag. Allowed versions are 101, 102, 103, 104, 220, 221, 222, 223, 224, 310, 311, 320 or 321.\n");
163  }
164 
165  switch( pflag )
166  {
167  case 101: // NNLO single spin PNEuler angles + 2PN non-spinning L
168  case 102: // NNLO single spin PNEuler angles + 3PN spinning L
169  case 103: // NNLO single spin PNEuler angles + 4PN spinning L
170  case 104: // NNLO single spin PNEuler angles + 4PN spinning L + LOS terms in L
171  {
172  break;
173  }
174  case 220: // MSA using expressions as detailed in arXiv:1703.03967. Defaults to NNLO v102 if MSA fails.
175  case 221: // MSA using expressions as detailed in arXiv:1703.03967. Terminal failure if MSA fails.
176  case 222: // MSA using expressions as implemented in LALSimInspiralFDPrecAngles. Terminal failure if MSA fails.
177  case 223: // MSA using expressions as implemented in LALSimInspiralFDPrecAngles. Defaults to NNLO v102 if MSA fails.
178  case 224: // MSA using expressions as detailed in arXiv:1703.03967, with \zeta_0 and \phi_{z,0} as in LALSimInspiralFDPrecAngles. Defaults to NNLO v102 if MSA fails.
179  {
180  /*
181  Double-spin model using angles from Chatziioannou et al, PRD, 95, 104004, (2017), arXiv:1703.03967
182  Uses 3PN L
183  */
184  #if DEBUG == 1
185  printf("Initializing MSA system...\n");
186  #endif
187 
188  if(pPrec->ExpansionOrder < -1 || pPrec->ExpansionOrder > 5)
189  {
190  XLAL_ERROR(XLAL_EINVAL, "Error in IMRPhenomXGetAndSetPrecessionVariables: Invalid expansion order for MSA corrections. Default is 5, allowed values are [-1,0,1,2,3,4,5].\n");
191  }
192  break;
193 
194  }
195 
196  case 310: // Numerical integration of SpinTaylor equations, constant angles in MRD
197  case 311: // Numerical integration of SpinTaylor equations, constant angles in MRD, BBH precession
198  case 320: // Numerical integration of SpinTaylor equations, analytical continuation in MRD
199  case 321: // Numerical integration of SpinTaylor equations, analytical continuation in MRD, BBH precession
200  {
201  break;
202  }
203 
204 
205  default:
206  {
207  XLAL_ERROR(XLAL_EINVAL, "Error in IMRPhenomXGetAndSetPrecessionVariables: IMRPhenomXPrecessionVersion not recognized.\n");
208  break;
209  }
210  }
211 
212  // get first digit of precessing version: this tags the method employed to compute the Euler angles
213  // 1: NNLO; 2: MSA; 3: SpinTaylor (numerical)
214  int precversionTag=(pPrec->IMRPhenomXPrecVersion-(pPrec->IMRPhenomXPrecVersion%100))/100;
215  pPrec->precessing_tag=precversionTag;
216 
217  /* Define a number of convenient local parameters */
218  const REAL8 m1 = m1_SI / pWF->Mtot_SI; /* Normalized mass of larger companion: m1_SI / Mtot_SI */
219  const REAL8 m2 = m2_SI / pWF->Mtot_SI; /* Normalized mass of smaller companion: m2_SI / Mtot_SI */
220  const REAL8 M = (m1 + m2); /* Total mass in solar units */
221 
222  // Useful powers of mass
223  const REAL8 m1_2 = m1 * m1;
224  const REAL8 m1_3 = m1 * m1_2;
225  const REAL8 m1_4 = m1 * m1_3;
226  const REAL8 m1_5 = m1 * m1_4;
227  const REAL8 m1_6 = m1 * m1_5;
228  const REAL8 m1_7 = m1 * m1_6;
229  const REAL8 m1_8 = m1 * m1_7;
230 
231  const REAL8 m2_2 = m2 * m2;
232 
233  pWF->M = M;
234  pWF->m1_2 = m1_2;
235  pWF->m2_2 = m2_2;
236 
237  const REAL8 q = m1/m2; // q = m1 / m2 > 1.0
238 
239  // Powers of eta
240  const REAL8 eta = pWF->eta;
241  const REAL8 eta2 = eta*eta;
242  const REAL8 eta3 = eta*eta2;
243  const REAL8 eta4 = eta*eta3;
244  const REAL8 eta5 = eta*eta4;
245  const REAL8 eta6 = eta*eta5;
246 
247  // \delta in terms of q > 1
248  const REAL8 delta = pWF->delta;
249  const REAL8 delta2 = delta*delta;
250  const REAL8 delta3 = delta*delta2;
251 
252  // Cache these powers, as we use them regularly
253  pPrec->eta = eta;
254  pPrec->eta2 = eta2;
255  pPrec->eta3 = eta3;
256  pPrec->eta4 = eta4;
257 
258  pPrec->inveta = 1.0 / eta;
259  pPrec->inveta2 = 1.0 / eta2;
260  pPrec->inveta3 = 1.0 / eta3;
261  pPrec->inveta4 = 1.0 / eta4;
262  pPrec->sqrt_inveta = 1.0 / sqrt(eta);
263 
264  const REAL8 chi_eff = pWF->chiEff;
265 
266  pPrec->twopiGM = LAL_TWOPI * LAL_G_SI * (m1_SI + m2_SI) / LAL_C_SI / LAL_C_SI / LAL_C_SI;
267  pPrec->piGM = LAL_PI * (m1_SI + m2_SI) * (LAL_G_SI / LAL_C_SI) / (LAL_C_SI * LAL_C_SI);
268 
269  /* Set spin variables in pPrec struct */
270  pPrec->chi1x = chi1x;
271  pPrec->chi1y = chi1y;
272  pPrec->chi1z = chi1z;
273  pPrec->chi1_norm = sqrt(chi1x*chi1x + chi1y*chi1y + chi1z*chi1z);
274 
275  pPrec->chi2x = chi2x;
276  pPrec->chi2y = chi2y;
277  pPrec->chi2z = chi2z;
278  pPrec->chi2_norm = sqrt(chi2x*chi2x + chi2y*chi2y + chi2z*chi2z);
279 
280  /* Check that spins obey Kerr bound */
281  if((!PNRUseTunedAngles)||(pWF->PNR_SINGLE_SPIN != 1)){ /*Allow the single-spin mapping for PNR to break the Kerr limit*/
282  XLAL_CHECK(fabs(pPrec->chi1_norm) <= 1.0, XLAL_EDOM, "Error in IMRPhenomXSetPrecessionVariables: |S1/m1^2| must be <= 1.\n");
283  XLAL_CHECK(fabs(pPrec->chi2_norm) <= 1.0, XLAL_EDOM, "Error in IMRPhenomXSetPrecessionVariables: |S2/m2^2| must be <= 1.\n");
284  }
285 
286  /* Calculate dimensionful spins */
287  pPrec->S1x = chi1x * m1_2;
288  pPrec->S1y = chi1y * m1_2;
289  pPrec->S1z = chi1z * m1_2;
290  pPrec->S1_norm = fabs(pPrec->chi1_norm) * m1_2;
291 
292  pPrec->S2x = chi2x * m2_2;
293  pPrec->S2y = chi2y * m2_2;
294  pPrec->S2z = chi2z * m2_2;
295  pPrec->S2_norm = fabs(pPrec->chi2_norm) * m2_2;
296 
297  // Useful powers
298  pPrec->S1_norm_2 = pPrec->S1_norm * pPrec->S1_norm;
299  pPrec->S2_norm_2 = pPrec->S2_norm * pPrec->S2_norm;
300 
301  pPrec->chi1_perp = sqrt(chi1x*chi1x + chi1y*chi1y);
302  pPrec->chi2_perp = sqrt(chi2x*chi2x + chi2y*chi2y);
303 
304  /* Get spin projections */
305  pPrec->S1_perp = (m1_2) * sqrt(chi1x*chi1x + chi1y*chi1y);
306  pPrec->S2_perp = (m2_2) * sqrt(chi2x*chi2x + chi2y*chi2y);
307 
308  /* Norm of in-plane vector sum: Norm[ S1perp + S2perp ] */
309  pPrec->STot_perp = sqrt( (pPrec->S1x+pPrec->S2x)*(pPrec->S1x+pPrec->S2x) + (pPrec->S1y+pPrec->S2y)*(pPrec->S1y+pPrec->S2y) );
310 
311  /* This is called chiTot_perp to distinguish from Sperp used in contrusction of chi_p. For normalization, see Sec. IV D of arXiv:2004.06503 */
312  pPrec->chiTot_perp = pPrec->STot_perp * (M*M) / m1_2;
313  /* Store pPrec->chiTot_perp to pWF so that it can be used in XCP modifications (PNRUseTunedCoprec) */
314  pWF->chiTot_perp = pPrec->chiTot_perp;
315 
316  /* disable tuned PNR angles, tuned coprec and mode asymmetries in low in-plane spin limit */
317  if((chi_in_plane < 1e-7)&&(pPrec->IMRPhenomXPNRUseTunedAngles == 1)&&(pWF->PNR_SINGLE_SPIN != 1)){
319  PNRUseTunedAngles = 0;
320  pPrec->IMRPhenomXPNRUseTunedAngles = 0;
322  AntisymmetricWaveform = 0;
325  }
326 
327  /*
328  Calculate the effective precessing spin parameter (Schmidt et al, PRD 91, 024043, 2015):
329  - m1 > m2, so body 1 is the larger black hole
330  */
331  pPrec->A1 = 2.0 + (3.0 * m2) / (2.0 * m1);
332  pPrec->A2 = 2.0 + (3.0 * m1) / (2.0 * m2);
333  pPrec->ASp1 = pPrec->A1 * pPrec->S1_perp;
334  pPrec->ASp2 = pPrec->A2 * pPrec->S2_perp;
335 
336  /* S_p = max(A1 S1_perp, A2 S2_perp) */
337  const REAL8 num = (pPrec->ASp2 > pPrec->ASp1) ? pPrec->ASp2 : pPrec->ASp1;
338  const REAL8 den = (m2 > m1) ? pPrec->A2*(m2_2) : pPrec->A1*(m1_2);
339 
340  /* chi_p = max(A1 * Sp1 , A2 * Sp2) / (A_i * m_i^2) where i is the index of the larger BH */
341  const REAL8 chip = num / den;
342  const REAL8 chi1L = chi1z;
343  const REAL8 chi2L = chi2z;
344 
345 
346  pPrec->chi_p = chip;
347  // (PNRUseTunedCoprec)
348  pWF->chi_p = pPrec->chi_p;
349  pPrec->phi0_aligned = pWF->phi0;
350 
351  /* Effective (dimensionful) aligned spin */
352  pPrec->SL = chi1L*m1_2 + chi2L*m2_2;
353 
354  /* Effective (dimensionful) in-plane spin */
355  pPrec->Sperp = chip * m1_2; /* m1 > m2 */
356 
357  pPrec->MSA_ERROR = 0;
358 
359  pPrec->pWF22AS = NULL;
360 
361  /* Calculate parameter for two-spin to single-spin map used in PNR and XCP */
362  /* Initialize PNR variables */
363  pPrec->chi_singleSpin = 0.0;
364  pPrec->costheta_singleSpin = 0.0;
365  pPrec->costheta_final_singleSpin = 0.0;
366  pPrec->chi_singleSpin_antisymmetric = 0.0;
367  pPrec->theta_antisymmetric = 0.0;
368  pPrec->PNR_HM_Mflow = 0.0;
369  pPrec->PNR_HM_Mfhigh = 0.0;
370 
371  pPrec->PNR_q_window_lower = 0.0;
372  pPrec->PNR_q_window_upper = 0.0;
373  pPrec->PNR_chi_window_lower = 0.0;
374  pPrec->PNR_chi_window_upper = 0.0;
375  // pPrec->PNRInspiralScaling = 0;
376 
378  XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomX_PNR_GetAndSetPNRVariables failed in IMRPhenomXGetAndSetPrecessionVariables.\n");
379 
380  pPrec->alphaPNR = 0.0;
381  pPrec->betaPNR = 0.0;
382  pPrec->gammaPNR = 0.0;
383 
384  /*...#...#...#...#...#...#...#...#...#...#...#...#...#...#.../
385  / Get and/or store CoPrec params into pWF and pPrec /
386  /...#...#...#...#...#...#...#...#...#...#...#...#...#...#...*/
387 
388  status = IMRPhenomX_PNR_GetAndSetCoPrecParams(pWF,pPrec,lalParams);
390  "Error: IMRPhenomX_PNR_GetAndSetCoPrecParams failed \
391  in IMRPhenomXGetAndSetPrecessionVariables.\n");
392 
393  /*..#...#...#...#...#...#...#...#...#...#...#...#...#...#...*/
394 
395 
396 
397  //
398  if( pflag == 220 || pflag == 221 || pflag == 222 || pflag == 223 || pflag == 224 )
399  {
400  #if DEBUG == 1
401  printf("Evaluating MSA system.\n");
402  printf("Expansion Order : %d\n",pPrec->ExpansionOrder);
403  #endif
404 
406 
407  if(pPrec->MSA_ERROR == 1)
408  {
409  // In version 220, 223 and 224 if the MSA system fails to initialize we default to the NNLO PN angles using the 3PN aligned-spin orbital angular momentum
410  if(pflag == 220 || pflag == 223 || pflag == 224)
411  {
412  XLAL_PRINT_WARNING("Warning: Initialization of MSA system failed. Defaulting to NNLO angles using 3PN aligned-spin approximation.");
413  pPrec->IMRPhenomXPrecVersion = 102;
414  pflag = pPrec->IMRPhenomXPrecVersion;
415  }
416  else // Otherwise, if the MSA system fails to initialize we trigger a terminal error
417  {
418  XLAL_ERROR(XLAL_EDOM,"Error: IMRPhenomX_Initialize_MSA_System failed to initialize. Terminating.\n");
419  }
420  }
421  }
422 
423  #if DEBUG == 1
424  printf("In IMRPhenomXSetPrecessionVariables... \n\n");
425  printf("chi_p : %e\n",pPrec->chi_p);
426  printf("phic : %e\n",pPrec->phi0_aligned);
427  printf("SL : %e\n",pPrec->SL);
428  printf("Sperp : %e\n\n",pPrec->Sperp);
429  #endif
430 
431  /*...#...#...#...#...#...#...#...#...#...#...#...#...#...#.../
432  / Compute and set final spin and RD frequency /
433  /...#...#...#...#...#...#...#...#...#...#...#...#...#...#...*/
434  IMRPhenomX_SetPrecessingRemnantParams(pWF,pPrec,lalParams);
435  /*..#...#...#...#...#...#...#...#...#...#...#...#...#...#...*/
436 
437  /* Useful powers of \chi_p */
438  const REAL8 chip2 = chip * chip;
439 
440  /* Useful powers of spins aligned with L */
441  const REAL8 chi1L2 = chi1L * chi1L;
442  const REAL8 chi2L2 = chi2L * chi2L;
443 
444  const REAL8 log16 = 2.772588722239781;
445 
446  /* Cache the orbital angular momentum coefficients for future use.
447 
448  References:
449  - Kidder, PRD, 52, 821-847, (1995), arXiv:gr-qc/9506022
450  - Blanchet, LRR, 17, 2, (2014), arXiv:1310.1528
451  - Bohe et al, 1212.5520v2
452  - Marsat, CQG, 32, 085008, (2015), arXiv:1411.4118
453  */
454  switch( pflag )
455  {
456  /* 2PN non-spinning orbital angular momentum (as per IMRPhenomPv2) */
457  case 101:
458  {
459  pPrec->L0 = 1.0;
460  pPrec->L1 = 0.0;
461  pPrec->L2 = ((3.0/2.0) + (eta/6.0));
462  pPrec->L3 = 0.0;
463  pPrec->L4 = (81.0 + (-57.0 + eta)*eta)/24.;
464  pPrec->L5 = 0.0;
465  pPrec->L6 = 0.0;
466  pPrec->L7 = 0.0;
467  pPrec->L8 = 0.0;
468  pPrec->L8L = 0.0;
469  break;
470  }
471  /* 3PN orbital angular momentum */
472  case 102:
473  case 220:
474  case 221:
475  case 224:
476  case 310:
477  case 311:
478  case 320:
479  case 321:
480  {
481  pPrec->L0 = 1.0;
482  pPrec->L1 = 0.0;
483  pPrec->L2 = 3.0/2. + eta/6.0;
484  pPrec->L3 = (5*(chi1L*(-2 - 2*delta + eta) + chi2L*(-2 + 2*delta + eta)))/6.;
485  pPrec->L4 = (81 + (-57 + eta)*eta)/24.;
486  pPrec->L5 = (-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.;
487  pPrec->L6 = (10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*powers_of_lalpi.two))/1296.;
488  pPrec->L7 = 0.0;
489  pPrec->L8 = 0.0;
490 
491  // This is the log(x) term
492  pPrec->L8L = 0.0;
493  break;
494 
495  }
496  /* 3PN orbital angular momentum using non-conserved spin norms as per LALSimInspiralFDPrecAngles.c */
497  case 222:
498  case 223:
499  {
500  pPrec->L0 = 1.0;
501  pPrec->L1 = 0.0;
502  pPrec->L2 = 3.0/2. + eta/6.0;
503  pPrec->L3 = (-7*(chi1L + chi2L + chi1L*delta - chi2L*delta) + 5*(chi1L + chi2L)*eta)/6.;
504  pPrec->L4 = (81 + (-57 + eta)*eta)/24.;
505  pPrec->L5 = (-1650*(chi1L + chi2L + chi1L*delta - chi2L*delta) + 1336*(chi1L + chi2L)*eta + 511*(chi1L - chi2L)*delta*eta + 28*(chi1L + chi2L)*eta2)/600.;
506  pPrec->L6 = (10935 + eta*(-62001 + 1674*eta + 7*eta2 + 2214*powers_of_lalpi.two))/1296.;
507  pPrec->L7 = 0.0;
508  pPrec->L8 = 0.0;
509 
510  // This is the log(x) term
511  pPrec->L8L = 0.0;
512  break;
513  }
514  /* 4PN orbital angular momentum */
515  case 103:
516  {
517  pPrec->L0 = 1.0;
518  pPrec->L1 = 0.0;
519  pPrec->L2 = 3.0/2. + eta/6.0;
520  pPrec->L3 = (5*(chi1L*(-2 - 2*delta + eta) + chi2L*(-2 + 2*delta + eta)))/6.;
521  pPrec->L4 = (81 + (-57 + eta)*eta)/24.;
522  pPrec->L5 = (-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.;
523  pPrec->L6 = (10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*powers_of_lalpi.two))/1296.;
524  pPrec->L7 = (chi2L*(-324 + eta*(1119 - 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta)))
525  - chi1L*(324 + eta*(-1119 + 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta))))/32.;
526  pPrec->L8 = 2835/128. - (eta*(-10677852 + 100*eta*(-640863 + eta*(774 + 11*eta))
527  + 26542080*LAL_GAMMA + 675*(3873 + 3608*eta)*powers_of_lalpi.two))/622080. - (64*eta*log16)/3.;
528 
529  pPrec->L8L = -(64.0/3.0) * eta;
530  break;
531  }
532  /*
533  4PN orbital angular momentum + leading order in spin at all PN orders terms.
534  - Marsat, CQG, 32, 085008, (2015), arXiv:1411.4118
535  - Siemonsen et al, PRD, 97, 064010, (2018), arXiv:1606.08832
536  */
537  case 104:
538  {
539  pPrec->L0 = 1.0;
540  pPrec->L1 = 0.0;
541  pPrec->L2 = 3.0/2. + eta/6.0;
542  pPrec->L3 = (5*(chi1L*(-2 - 2*delta + eta) + chi2L*(-2 + 2*delta + eta)))/6.;
543  pPrec->L4 = (81 + (-57 + eta)*eta)/24.;
544  pPrec->L5 = (-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.;
545  pPrec->L6 = (10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*powers_of_lalpi.two))/1296.;
546  pPrec->L7 = (chi2L*(-324 + eta*(1119 - 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta)))
547  - chi1L*(324 + eta*(-1119 + 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta))))/32.;
548  pPrec->L8 = 2835/128. - (eta*(-10677852 + 100*eta*(-640863 + eta*(774 + 11*eta))
549  + 26542080*LAL_GAMMA + 675*(3873 + 3608*eta)*powers_of_lalpi.two))/622080. - (64*eta*log16)/3.;
550 
551  // This is the log(x) term at 4PN, x^4/2 * log(x)
552  pPrec->L8L = -(64.0/3.0) * eta;
553 
554  // Leading order in spin at all PN orders, note that the 1.5PN terms are already included. Here we have additional 2PN and 3.5PN corrections.
555  pPrec->L4 += (chi1L2*(1 + delta - 2*eta) + 4*chi1L*chi2L*eta - chi2L2*(-1 + delta + 2*eta))/2.;
556  pPrec->L7 += (3*(chi1L + chi2L)*eta*(chi1L2*(1 + delta - 2*eta) + 4*chi1L*chi2L*eta - chi2L2*(-1 + delta + 2*eta)))/4.;
557 
558  break;
559  }
560 
561  default:
562  {
563  XLAL_ERROR(XLAL_EINVAL,"Error: IMRPhenomXPrecVersion not recognized. Requires version 101, 102, 103, 104, 220, 221, 222, 223, 224, 310, 311, 320 or 321.\n");
564  break;
565  }
566  }
567 
568  /* Reference orbital angular momentum */
569  pPrec->LRef = M * M * XLALSimIMRPhenomXLPNAnsatz(pWF->v_ref, pWF->eta / pWF->v_ref, pPrec->L0, pPrec->L1, pPrec->L2, pPrec->L3, pPrec->L4, pPrec->L5, pPrec->L6, pPrec->L7, pPrec->L8, pPrec->L8L);
570 
571  /*
572  In the following code block we construct the convetions that relate the source frame and the LAL frame.
573 
574  A detailed discussion of the conventions can be found in Appendix C and D of arXiv:2004.06503 and https://dcc.ligo.org/LIGO-T1500602
575  */
576 
577  /* Get source frame (*_Sf) J = L + S1 + S2. This is an instantaneous frame in which L is aligned with z */
578  pPrec->J0x_Sf = (m1_2)*chi1x + (m2_2)*chi2x;
579  pPrec->J0y_Sf = (m1_2)*chi1y + (m2_2)*chi2y;
580  pPrec->J0z_Sf = (m1_2)*chi1z + (m2_2)*chi2z + pPrec->LRef;
581 
582  pPrec->J0 = sqrt(pPrec->J0x_Sf*pPrec->J0x_Sf + pPrec->J0y_Sf*pPrec->J0y_Sf + pPrec->J0z_Sf*pPrec->J0z_Sf);
583 
584  /* Get angle between J0 and LN (z-direction) */
585  if(pPrec->J0 < 1e-10)
586  {
587  XLAL_PRINT_WARNING("Warning: |J0| < 1e-10. Setting thetaJ = 0.\n");
588  pPrec->thetaJ_Sf = 0.0;
589  }
590  else
591  {
592  pPrec->thetaJ_Sf = acos(pPrec->J0z_Sf / pPrec->J0);
593  }
594 
595  const double phiRef = pWF->phiRef_In;
596 
598 
599  if ( !(convention == 0 || convention == 1 || convention == 5 || convention == 6 || convention == 7))
600  {
601  XLAL_ERROR(XLAL_EINVAL,"Error: IMRPhenomXPConvention not recognized. Requires version 0, 1, 5, 6 or 7.\n");
602  }
603 
604  #if DEBUG == 1
605  printf("\n*** Convention = %i\n", convention);
606  #endif
607 
608  /* Get azimuthal angle of J0 in the source frame */
609  if(fabs(pPrec->J0x_Sf) < MAX_TOL_ATAN && fabs(pPrec->J0y_Sf) < MAX_TOL_ATAN)
610  {
611  #if DEBUG == 1
612  printf("\nAligned spin limit!\n");
613  #endif
614 
615  /* Impose the aligned spin limit */
616  switch(convention)
617  {
618  case 0:
619  case 5:
620  {
621  pPrec->phiJ_Sf = LAL_PI/2.0 - phiRef;
622  break;
623  }
624  case 1:
625  case 6:
626  case 7:
627  {
628  pPrec->phiJ_Sf = 0;
629  break;
630  }
631 
632  }
633  }
634  else
635  {
636  pPrec->phiJ_Sf = atan2(pPrec->J0y_Sf, pPrec->J0x_Sf); /* azimuthal angle of J0 in the source frame */
637  }
638  pPrec->phi0_aligned = - pPrec->phiJ_Sf;
639 
640  switch(convention)
641  {
642  case 0:
643  {
644  pWF->phi0 = pPrec->phi0_aligned;
645  break;
646  }
647  case 1:
648  {
649  pWF->phi0 = 0;
650  break;
651  }
652  case 5:
653  case 6:
654  case 7:
655  {
656  break;
657  }
658  }
659 
660  /*
661  Here we follow the same prescription as in IMRPhenomPv2:
662 
663  Now rotate from SF to J frame to compute alpha0, the azimuthal angle of LN, as well as
664  thetaJ, the angle between J and N.
665 
666  The J frame is defined by imposing that J points in the z-direction and the line of sight N is in the xz-plane
667  (with positive projection along x).
668 
669  The components of any vector in the (new) J-frame can be obtained by rotation from the (old) source frame (SF).
670  This is done by multiplying by: RZ[-kappa].RY[-thetaJ].RZ[-phiJ]
671 
672  Note that kappa is determined by rotating N with RY[-thetaJ].RZ[-phiJ], which brings J to the z-axis, and
673  taking the opposite of the azimuthal angle of the rotated N.
674  */
675 
676  /* Determine kappa via rotations, as above */
677  pPrec->Nx_Sf = sin(pWF->inclination)*cos((LAL_PI / 2.0) - phiRef);
678  pPrec->Ny_Sf = sin(pWF->inclination)*sin((LAL_PI / 2.0) - phiRef);
679  pPrec->Nz_Sf = cos(pWF->inclination);
680 
681  REAL8 tmp_x = pPrec->Nx_Sf;
682  REAL8 tmp_y = pPrec->Ny_Sf;
683  REAL8 tmp_z = pPrec->Nz_Sf;
684 
685  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
686  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
687 
688  /* Note difference in overall - sign w.r.t PhenomPv2 code */
689  pPrec->kappa = XLALSimIMRPhenomXatan2tol(tmp_y,tmp_x, MAX_TOL_ATAN);
690 
691  /* Now determine alpha0 by rotating LN. In the source frame, LN = {0,0,1} */
692  tmp_x = 0.0;
693  tmp_y = 0.0;
694  tmp_z = 1.0;
695  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
696  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
697  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
698 
699  if (fabs(tmp_x) < MAX_TOL_ATAN && fabs(tmp_y) < MAX_TOL_ATAN)
700  {
701  /* This is the aligned spin case */
702  #if DEBUG == 1
703  printf("\nAligned-spin case.\n");
704  #endif
705 
706  switch(convention)
707  {
708  case 0:
709  case 5:
710  {
711  pPrec->alpha0 = LAL_PI;
712  break;
713  }
714  case 1:
715  case 6:
716  case 7:
717  {
718  pPrec->alpha0 = LAL_PI - pPrec->kappa;
719  break;
720  }
721  }
722  }
723  else
724  {
725  switch(convention)
726  {
727  case 0:
728  case 5:
729  {
730  pPrec->alpha0 = atan2(tmp_y,tmp_x);
731  break;
732  }
733  case 1:
734  case 6:
735  case 7:
736  {
737  pPrec->alpha0 = LAL_PI - pPrec->kappa;
738  break;
739  }
740  }
741  }
742 
743 
744  switch(convention)
745  {
746  case 0:
747  case 5:
748  {
749  /* Now determine thetaJN by rotating N */
750  tmp_x = pPrec->Nx_Sf;
751  tmp_y = pPrec->Ny_Sf;
752  tmp_z = pPrec->Nz_Sf;
753  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
754  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
755  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
756 
757  /* We don't need the y-component but we will store it anyway */
758  pPrec->Nx_Jf = tmp_x;
759  pPrec->Ny_Jf = tmp_y;
760  pPrec->Nz_Jf = tmp_z;
761 
762  /* This is a unit vector, so no normalization */
763  pPrec->thetaJN = acos(pPrec->Nz_Jf);
764  break;
765  }
766  case 1:
767  case 6:
768  case 7:
769  {
770  REAL8 J0dotN = (pPrec->J0x_Sf * pPrec->Nx_Sf) + (pPrec->J0y_Sf * pPrec->Ny_Sf) + (pPrec->J0z_Sf * pPrec->Nz_Sf);
771  pPrec->thetaJN = acos( J0dotN / pPrec->J0 );
772  pPrec->Nz_Jf = cos(pPrec->thetaJN);
773  pPrec->Nx_Jf = sin(pPrec->thetaJN);
774  break;
775  }
776  }
777 
778 
779  /*
780  Define the polarizations used. This follows the conventions adopted for IMRPhenomPv2.
781 
782  The IMRPhenomP polarizations are defined following the conventions in Arun et al (arXiv:0810.5336),
783  i.e. projecting the metric onto the P, Q, N triad defining where: P = (N x J) / |N x J|.
784 
785  However, the triad X,Y,N used in LAL (the "waveframe") follows the definition in the
786  NR Injection Infrastructure (Schmidt et al, arXiv:1703.01076).
787 
788  The triads differ from each other by a rotation around N by an angle \zeta. We therefore need to rotate
789  the polarizations by an angle 2 \zeta.
790  */
791  pPrec->Xx_Sf = -cos(pWF->inclination) * sin(phiRef);
792  pPrec->Xy_Sf = -cos(pWF->inclination) * cos(phiRef);
793  pPrec->Xz_Sf = +sin(pWF->inclination);
794 
795  tmp_x = pPrec->Xx_Sf;
796  tmp_y = pPrec->Xy_Sf;
797  tmp_z = pPrec->Xz_Sf;
798 
799  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
800  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
801  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
802 
803 
804  /*
805  The components tmp_i are now the components of X in the J frame.
806 
807  We now need the polar angle of this vector in the P, Q basis of Arun et al:
808 
809  P = (N x J) / |NxJ|
810 
811  Note, that we put N in the (pos x)z half plane of the J frame
812  */
813 
814  switch(convention)
815  {
816  case 0:
817  case 5:
818  {
819  /* Get polar angle of X vector in J frame in the P,Q basis of Arun et al */
820  pPrec->PArunx_Jf = +0.0;
821  pPrec->PAruny_Jf = -1.0;
822  pPrec->PArunz_Jf = +0.0;
823 
824  /* Q = (N x P) by construction */
825  pPrec->QArunx_Jf = pPrec->Nz_Jf;
826  pPrec->QAruny_Jf = 0.0;
827  pPrec->QArunz_Jf = -pPrec->Nx_Jf;
828  break;
829  }
830  case 1:
831  case 6:
832  case 7:
833  {
834  /* Get polar angle of X vector in J frame in the P,Q basis of Arun et al */
835  pPrec->PArunx_Jf = pPrec->Nz_Jf;
836  pPrec->PAruny_Jf = 0;
837  pPrec->PArunz_Jf = -pPrec->Nx_Jf;
838 
839  /* Q = (N x P) by construction */
840  pPrec->QArunx_Jf = 0;
841  pPrec->QAruny_Jf = 1;
842  pPrec->QArunz_Jf = 0;
843  break;
844  }
845  }
846 
847  // (X . P)
848  pPrec->XdotPArun = (tmp_x * pPrec->PArunx_Jf) + (tmp_y * pPrec->PAruny_Jf) + (tmp_z * pPrec->PArunz_Jf);
849 
850  // (X . Q)
851  pPrec->XdotQArun = (tmp_x * pPrec->QArunx_Jf) + (tmp_y * pPrec->QAruny_Jf) + (tmp_z * pPrec->QArunz_Jf);
852 
853  /* Now get the angle zeta */
854  pPrec->zeta_polarization = atan2(pPrec->XdotQArun, pPrec->XdotPArun);
855 
856  /* ********** PN Euler Angle Coefficients ********** */
857  /*
858  This uses the single spin PN Euler angles as per IMRPhenomPv2
859  */
860 
861  /* ********** PN Euler Angle Coefficients ********** */
862  switch( pflag )
863  {
864  case 101:
865  case 102:
866  case 103:
867  case 104:
868  {
869  /*
870  This uses the single spin PN Euler angles as per IMRPhenomPv2
871  */
872 
873  /* Post-Newtonian Euler Angles: alpha */
874  REAL8 chiL = (1.0 + q) * (chi_eff / q);
875  REAL8 chiL2 = chiL * chiL;
876 
877  pPrec->alpha1 = -35/192. + (5*delta)/(64.*m1);
878 
879  pPrec->alpha2 = ((15*chiL*delta*m1)/128. - (35*chiL*m1_2)/128.)/eta;
880 
881  pPrec->alpha3 = -5515/3072. + eta*(-515/384. - (15*delta2)/(256.*m1_2)
882  + (175*delta)/(256.*m1)) + (4555*delta)/(7168.*m1)
883  + ((15*chip2*delta*m1_3)/128. - (35*chip2*m1_4)/128.)/eta2;
884 
885  /* This is the term proportional to log(w) */
886  pPrec->alpha4L = ((5*chiL*delta2)/16. - (5*chiL*delta*m1)/3. + (2545*chiL*m1_2)/1152.
887  + ((-2035*chiL*delta*m1)/21504.
888  + (2995*chiL*m1_2)/9216.)/eta + ((5*chiL*chip2*delta*m1_5)/128.
889  - (35*chiL*chip2*m1_6)/384.)/eta3
890  - (35*LAL_PI)/48. + (5*delta*LAL_PI)/(16.*m1));
891 
892  pPrec->alpha5 = (5*(-190512*delta3*eta6 + 2268*delta2*eta3*m1*(eta2*(323 + 784*eta)
893  + 336*(25*chiL2 + chip2)*m1_4) + 7*m1_3*(8024297*eta4 + 857412*eta5
894  + 3080448*eta6 + 143640*chip2*eta2*m1_4
895  - 127008*chip2*(-4*chiL2 + chip2)*m1_8
896  + 6048*eta3*((2632*chiL2 + 115*chip2)*m1_4 - 672*chiL*m1_2*LAL_PI))
897  + 3*delta*m1_2*(-5579177*eta4 + 80136*eta5 - 3845520*eta6
898  + 146664*chip2*eta2*m1_4 + 127008*chip2*(-4*chiL2 + chip2)*m1_8
899  - 42336*eta3*((726*chiL2 + 29*chip2)*m1_4
900  - 96*chiL*m1_2*LAL_PI))))/(6.5028096e7*eta4*m1_3);
901 
902  /* Post-Newtonian Euler Angles: epsilon */
903  pPrec->epsilon1 = -35/192. + (5*delta)/(64.*m1);
904 
905  pPrec->epsilon2 = ((15*chiL*delta*m1)/128. - (35*chiL*m1_2)/128.)/eta;
906 
907  pPrec->epsilon3 = -5515/3072. + eta*(-515/384. - (15*delta2)/(256.*m1_2)
908  + (175*delta)/(256.*m1)) + (4555*delta)/(7168.*m1);
909 
910  /* This term is proportional to log(w) */
911  pPrec->epsilon4L = (5*chiL*delta2)/16. - (5*chiL*delta*m1)/3. + (2545*chiL*m1_2)/1152.
912  + ((-2035*chiL*delta*m1)/21504. + (2995*chiL*m1_2)/9216.)/eta - (35*LAL_PI)/48.
913  + (5*delta*LAL_PI)/(16.*m1);
914 
915  pPrec->epsilon5 = (5*(-190512*delta3*eta3 + 2268*delta2*m1*(eta2*(323 + 784*eta)
916  + 8400*chiL2*m1_4)
917  - 3*delta*m1_2*(eta*(5579177 + 504*eta*(-159 + 7630*eta))
918  + 254016*chiL*m1_2*(121*chiL*m1_2 - 16*LAL_PI))
919  + 7*m1_3*(eta*(8024297 + 36*eta*(23817 + 85568*eta))
920  + 338688*chiL*m1_2*(47*chiL*m1_2 - 12*LAL_PI))))/(6.5028096e7*eta*m1_3);
921 
922  break;
923  }
924  case 220:
925  case 221:
926  case 222:
927  case 223:
928  case 224:
929  case 310:
930  case 311:
931  case 320:
932  case 321:
933  {
934  pPrec->alpha1 = 0;
935  pPrec->alpha2 = 0;
936  pPrec->alpha3 = 0;
937  pPrec->alpha4L = 0;
938  pPrec->alpha5 = 0;
939  pPrec->epsilon1 = 0;
940  pPrec->epsilon2 = 0;
941  pPrec->epsilon3 = 0;
942  pPrec->epsilon4L = 0;
943  pPrec->epsilon5 = 0;
944  break;
945  }
946  default:
947  {
948  XLAL_ERROR(XLAL_EINVAL,"Error: IMRPhenomXPrecVersion not recognized. Requires version 101, 102, 103, 104, 220, 221, 222, 223, 224, 310, 311, 320 or 321.\n");
949  break;
950  }
951  }
952 
953  REAL8 alpha_offset = 0, epsilon_offset = 0;
954 
955  #if DEBUG == 1
956  printf("thetaJN : %e\n", pPrec->thetaJN);
957  printf("phiJ_Sf : %e\n", pPrec->phiJ_Sf);
958  printf("alpha0 : %e\n", pPrec->alpha0);
959  printf("pi-kappa : %e\n", LAL_PI-pPrec->kappa);
960  printf("kappa : %e\n", pPrec->kappa);
961  printf("pi/2 - phiRef : %e\n", LAL_PI_2 - phiRef);
962  printf("zeta_polarization : %.16e\n", pPrec->zeta_polarization);
963  printf("zeta_polarization : %.16e\n", acos(pPrec->XdotPArun));
964  printf("zeta_polarization : %.16e\n", asin(pPrec->XdotQArun));
965  printf("zeta_polarization : %.16e\n\n", LAL_PI_2 - acos(pPrec->XdotQArun));
966  printf("alpha1 : %e\n", pPrec->alpha1);
967  printf("alpha2 : %e\n", pPrec->alpha2);
968  printf("alpha3 : %e\n", pPrec->alpha3);
969  printf("alpha4L : %e\n", pPrec->alpha4L);
970  printf("alpha5 : %e\n\n", pPrec->alpha5);
971  #endif
972 
973 
974  switch(convention)
975  {
976  case 0:
977  pPrec->epsilon0 = 0;
978  break;
979  case 1:
980  case 6:
981  pPrec->epsilon0 = pPrec->phiJ_Sf - LAL_PI;
982  break;
983  case 5:
984  case 7:
985  pPrec->epsilon0 = 0;
986  break;
987  }
988 
989  if(convention == 5 || convention == 7)
990  {
991  pPrec->alpha_offset = -pPrec->alpha0;
992  pPrec->epsilon_offset = 0;
993  pPrec->alpha_offset_1 = -pPrec->alpha0;
994  pPrec->epsilon_offset_1 = 0;
995  pPrec->alpha_offset_3 = -pPrec->alpha0;
996  pPrec->epsilon_offset_3 = 0;
997  pPrec->alpha_offset_4 = -pPrec->alpha0;
998  pPrec->epsilon_offset_4 = 0;
999  }
1000  else
1001  {
1002  /* Get initial Get \alpha and \epsilon offsets at \omega = pi * M * f_{Ref} */
1003  Get_alphaepsilon_atfref(&alpha_offset, &epsilon_offset, 2, pPrec, pWF);
1004  pPrec->alpha_offset = alpha_offset;
1005  pPrec->epsilon_offset = epsilon_offset;
1006  pPrec->alpha_offset_1 = alpha_offset;
1007  pPrec->epsilon_offset_1 = epsilon_offset;
1008  pPrec->alpha_offset_3 = alpha_offset;
1009  pPrec->epsilon_offset_3 = epsilon_offset;
1010  pPrec->alpha_offset_4 = alpha_offset;
1011  pPrec->epsilon_offset_4 = epsilon_offset;
1012  }
1013 
1014  pPrec->cexp_i_alpha = 0.;
1015  pPrec->cexp_i_epsilon = 0.;
1016  pPrec->cexp_i_betah = 0.;
1017 
1018  /* Activate multibanding for Euler angles it threshold !=0. Only for PhenomXPHM. */
1020  {
1021  /* User switched off multibanding */
1022  pPrec->MBandPrecVersion = 0;
1023  }
1024  else
1025  {
1026  /* User requested multibanding */
1027  pPrec->MBandPrecVersion = 1;
1028 
1029  /* Switch off multiband for very high mass as in IMRPhenomXHM. */
1030  if(pWF->Mtot > 500)
1031  {
1032  XLAL_PRINT_WARNING("Very high mass, only merger in frequency band, multibanding not efficient, switching off for non-precessing modes and Euler angles.");
1033  pPrec->MBandPrecVersion = 0;
1035  }
1036 
1037  if(pPrec->IMRPhenomXPrecVersion < 200)
1038  {
1039  /* The NNLO angles can have a worse, even pathological, behaviour for high mass ratio and double spin cases.
1040  The waveform will look noisy, we switch off the multibanding for mass ratio above 8 to avoid worsen even more the waveform. */
1041  if(pWF->q > 8)
1042  {
1043  XLAL_PRINT_WARNING("Very high mass ratio, NNLO angles may become pathological, switching off multibanding for angles.\n");
1045  pPrec->MBandPrecVersion = 0;
1046  }
1047  }
1048  /* The MSA angles give quite 'noisy' waveforms in this corner of parameter space so we switch off multibanding to avoid worsen the waveform. */
1049  else if ( pWF->q > 50 && pWF->Mtot > 100 )
1050  {
1052  pPrec->MBandPrecVersion = 0;
1053  }
1054 
1055  }
1056 
1057 
1058  const REAL8 ytheta = pPrec->thetaJN;
1059  const REAL8 yphi = 0.0;
1060  pPrec->Y2m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, -2);
1061  pPrec->Y2m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, -1);
1062  pPrec->Y20 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 0);
1063  pPrec->Y21 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 1);
1064  pPrec->Y22 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 2);
1065  pPrec->Y3m3 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -3);
1066  pPrec->Y3m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -2);
1067  pPrec->Y3m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -1);
1068  pPrec->Y30 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 0);
1069  pPrec->Y31 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 1);
1070  pPrec->Y32 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 2);
1071  pPrec->Y33 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 3);
1072  pPrec->Y4m4 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -4);
1073  pPrec->Y4m3 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -3);
1074  pPrec->Y4m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -2);
1075  pPrec->Y4m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -1);
1076  pPrec->Y40 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 0);
1077  pPrec->Y41 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 1);
1078  pPrec->Y42 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 2);
1079  pPrec->Y43 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 3);
1080  pPrec->Y44 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 4);
1081 
1082  /*
1083  Check whether maximum opening angle becomes larger than \pi/2 or \pi/4.
1084 
1085  If (L + S_L) < 0, then Wigner-d Coefficients will not track the angle between J and L, meaning
1086  that the model may become pathological as one moves away from the aligned-spin limit.
1087 
1088  If this does not happen, then max_beta will be the actual maximum opening angle.
1089 
1090  This function uses a 2PN non-spinning approximation to the orbital angular momentum L, as
1091  the roots can be analytically derived.
1092 
1093  Returns XLAL_PRINT_WARNING if model is in a pathological regime.
1094  */
1096 
1097  return XLAL_SUCCESS;
1098 }
1099 /* Function to set remnant quantities related to final
1100 spin within IMRPhenomXGetAndSetPrecessionVariables */
1104  LALDict *lalParams
1105 ){
1106 
1107  /*
1108 
1109  The strategy for setting the precessing remnant spin in PhenomX
1110  is multifaceted becuase there are not only the various options
1111  implemented for the original PhenomXP, but there are also the
1112  options needed for PNR's construction, and its turning off of
1113  its CoPrecessing model outside of the PNR calibration region.
1114  In that latter case, the final spin transitions from the
1115  non-precessing final spin, where PNR's CoPrecessing model is
1116  tuned, to the precessing final spin, as is needed for the EZH
1117  effective ringdown result.
1118 
1119  A layer on top of this, is the need for the l=m=2 fundamental
1120  QNM frequency to be computed, in some way, amid all of these
1121  scenarios.
1122 
1123  This function exists to draw a conceptual circle around all of
1124  this mess.
1125 
1126  Most comments below are cogent, but some have been left for
1127  historical record?
1128 
1129  */
1130 
1131  /* Define shorthand variables for PNR CoPrec options */
1132  INT4 status = 0;
1133 
1134  // Toggle for PNR coprecessing tuning
1135  INT4 PNRUseInputCoprecDeviations = pPrec->IMRPhenomXPNRUseInputCoprecDeviations;
1136 
1137  // Toggle for enforced use of non-precessing spin as is required during tuning of PNR's coprecessing model
1138  INT4 PNRUseTunedCoprec = pPrec->IMRPhenomXPNRUseTunedCoprec;
1139 
1140  // High-level toggle for whether to apply deviations
1141  INT4 APPLY_PNR_DEVIATIONS = pWF->APPLY_PNR_DEVIATIONS;
1142 
1143  /*
1144  HISTOICAL COMMENT
1145  Update the final spin in pWF to account for precessing spin effects:
1146 
1147  XLALSimIMRPhenomXPrecessingFinalSpin2017(eta,chi1L,chi2L,chi_perp);
1148 
1149  Note that chi_perp gets weighted by an appropriate mass factor
1150 
1151  q_factor = m1/M (when m1 > m2)
1152  REAL8 Sperp = chip * q_factor * q_factor;
1153  REAL8 af = copysign(1.0, af_parallel) * sqrt(Sperp*Sperp + af_parallel*af_parallel);
1154  */
1155  REAL8 M = pWF->M;
1156  REAL8 af_parallel = XLALSimIMRPhenomXFinalSpin2017(pWF->eta,pPrec->chi1z,pPrec->chi2z);
1157  double Lfinal = M*M*af_parallel - pWF->m1_2*pPrec->chi1z - pWF->m2_2*pPrec->chi2z;
1158 
1160  if (fsflag == 4 && pPrec->precessing_tag!=3) fsflag = 3;
1161 
1162  /* For PhenomPNR, we wil use the PhenomPv2 final spin function's result, modified such that its sign is given by sign( cos(betaRD) ). See the related fsflag case below. */
1163  if (PNRUseTunedCoprec) fsflag = 5;
1164 
1165  /* When tuning the coprecessing model, we wish to enforce use of the non-precessing final spin. See the related fsflag case below. */
1166  if (PNRUseInputCoprecDeviations) fsflag = 6;
1167 
1168  /* Generate and store ringdown value of precession angle beta. This is to be used for e.g. setting the sign of the final spin, and calculating the effective ringdown frequency */
1169  if( PNRUseTunedCoprec ){
1170  pWF->betaRD = IMRPhenomX_PNR_GenerateRingdownPNRBeta( pWF, pPrec);
1171  }
1172 
1173  //
1174  double chi1L = pPrec->chi1z;
1175  double chi2L = pPrec->chi2z;
1176 
1177  //
1178  int pflag = pPrec->IMRPhenomXPrecVersion;
1179 
1180  //
1181  switch(fsflag)
1182  {
1183  case 0:
1184  pWF->afinal_prec = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chi_p);
1185  break;
1186  case 1:
1187  pWF->afinal_prec = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chi1x);
1188  break;
1189  case 2:
1190  case 4:
1191  pWF->afinal_prec = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chiTot_perp);
1192  break;
1193  case 3:
1194  if( pflag == 220 || pflag == 221 || pflag == 222 || pflag == 223 || pflag == 224 )
1195  {
1196  if(pPrec->MSA_ERROR == 1 )
1197  {
1198  XLAL_PRINT_WARNING("Initialization of MSA system failed. Defaulting to final spin version 0.\n");
1199  pWF->afinal_prec = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chi_p);
1200  }
1201  else
1202  {
1203 
1204  INT2 sign = 1;
1206  sign = copysign(1, af_parallel);
1207  }
1208  pWF->afinal_prec = sign * sqrt( pPrec->SAv2 + Lfinal*Lfinal + 2.0*Lfinal*(pPrec->S1L_pav + pPrec->S2L_pav) ) / (M*M);
1209 
1210  }
1211  }
1212  else
1213  {
1214  XLAL_PRINT_WARNING("Error: XLALSimInspiralWaveformParamsLookupPhenomXPFinalSpinMod version 3 requires PrecVersion 220, 221, 222, 223 or 224. Defaulting to version 0.\n");
1215  pWF->afinal_prec = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chi_p);
1216  }
1217  break;
1218 
1219  case 5:
1220  {
1221  /*-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~*
1222  Implement Pv2 final spin but with sign derived from EZH's model for ringdown beta.
1223  *-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~*/
1224 
1225  // Use these as input into method for effective RD frequency
1226  // * get the value of ringdown beta
1227  INT2 sign = 1;
1228  sign = copysign(1, cos(pWF->betaRD) );
1229 
1230  /* Calculate Pv2 final spin without alteration. Below we alter it. NOTE that XLALSimIMRPhenomXPrecessingFinalSpin2017 appears to be an explicit copy of the actual Pv2 final spin function, FinalSpinIMRPhenomD_all_in_plane_spin_on_larger_BH. The original PhenomX have not referenced this code duplication. */
1231  double afinal_prec_Pv2 = XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,chi1L,chi2L,pPrec->chi_p);
1232 
1233  // The eqiuvalent PhenomPv2 code reference would look like the commented line below.
1234  // double afinal_prec_Pv2 = FinalSpinIMRPhenomD_all_in_plane_spin_on_larger_BH( m1, m2, chi1L, chi2L, chi_p );
1235 
1236  // Define the PNR final spin to be the Pv2 final spin magnitude, with direction given by sign of cos betaRD
1237  pWF->afinal_prec = sign * fabs(afinal_prec_Pv2);
1238 
1239  // // Experimental version of final spin
1240  // pWF->afinal_prec = cos(pWF->betaRD) * fabs(afinal_prec_Pv2);
1241  }
1242  break;
1243 
1244  case 6:
1245  {
1246  /*-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~*
1247  During PNR tuning, we wish to evaluate the coprecessing model with the same final spin that would be used in PhenomXHM. We implement this here.
1248  *-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~*/
1249  pWF->afinal_prec = pWF->afinal_nonprec;
1250  }
1251  break;
1252  default:
1253  {
1254  XLAL_ERROR(XLAL_EDOM,"Error: XLALSimInspiralWaveformParamsLookupPhenomXPFinalSpinMod version not recognized. Requires PhenomXPFinalSpinMod of 0, 1, 2, 3, or 5.\n");
1255  }
1256  }
1257 
1258  /* (PNRUseTunedCoprec) When not generating PNR make NO destinction between afinal and afinal_prec */
1259  if( !PNRUseTunedCoprec ){
1260  pWF->afinal = pWF->afinal_prec;
1261  } else {
1262  /* ELSE, use the non-precessing final spin defined in IMRPhenomXSetWaveformVariables. XCP uses the non-precessing parameters as a base upon which to add precessing deviations. The line below is added only for clarity: pWF->afinal is already equal to pWF->afinal_nonprec as is set in IMRPhenomXSetWaveformVariables */
1263 
1264  /* pWF->afinal = pWF->afinal_nonprec */
1265  // ABOVE but commented out, we see what the final spin assignment WOULD BE if NO WINDOWING of coprec tuning were used
1266 
1267  pWF->afinal = (pWF->pnr_window)*pWF->afinal_nonprec + (1.0-pWF->pnr_window)*pWF->afinal_prec;
1268  // Above: NOTE that as PNR is turned off outside of its calibration window, we want to turn on use of the precessing final spin as defined in the code section above
1269 
1270  #if DEBUG == 1
1271  printf("*** PNR Co-precessing model in use (l=m=2) ***\n\n");
1272  printf("PNR window : %e\n", pWF->pnr_window);
1273  printf("pWF->afinal : %e\n",pWF->afinal);
1274  printf("pWF->afinal_prec : %e\n",pWF->afinal_prec);
1275  printf("pWF->afinal_nonprec : %e\n",pWF->afinal_nonprec);
1276  #endif
1277  }
1278 
1279 
1280  if( fabs(pWF->afinal) > 1.0 )
1281  {
1282  XLAL_PRINT_WARNING("Warning: Final spin magnitude %g > 1. Setting final spin magnitude = 1.", pWF->afinal);
1283  pWF->afinal = copysign(1.0, pWF->afinal);
1284  }
1285 
1286  /* Update ringdown and damping frequency: no precession; to be used for PNR tuned deviations */
1287  pWF->fRING = evaluate_QNMfit_fring22(pWF->afinal) / (pWF->Mfinal);
1288  pWF->fDAMP = evaluate_QNMfit_fdamp22(pWF->afinal) / (pWF->Mfinal);
1289  //pWF->fISCO = XLALSimIMRPhenomXfISCO(pWF->afinal);
1290 
1291  #if DEBUG == 1
1292  printf("afinal (prec) : %e\n",pWF->afinal);
1293  printf("fring (prec) : %e\n",pWF->fRING);
1294  printf("fdamp (prec) : %e\n\n",pWF->fDAMP);
1295  #endif
1296 
1297  // Copy IMRPhenomXReturnCoPrec to pWF
1299  #if DEBUG == 1
1300  printf("pPrec->IMRPhenomXReturnCoPrec : %i\n",pPrec->IMRPhenomXReturnCoPrec);
1301  printf("pWF->IMRPhenomXReturnCoPrec : %i\n",pWF->IMRPhenomXReturnCoPrec);
1302  #endif
1303 
1304  //
1305  if( APPLY_PNR_DEVIATIONS )
1306  {
1307  /* Add an overall deviation to the high-level ringdown frequency (PNRUseTunedCoprec) */
1308  pWF->fRING = pWF->fRING - (pWF->PNR_DEV_PARAMETER * pWF->NU5);
1309  pWF->fDAMP = pWF->fDAMP + (pWF->PNR_DEV_PARAMETER * pWF->NU6);
1310  }
1311 
1312  // we want to define the quantities below if PNR is used (PNRUseTunedCoprec). In particular, pWF->fRINGEffShiftDividedByEmm is used by the HMs
1313  // Define identifiers for perturbation theory frequencies
1314  if( PNRUseTunedCoprec && (pWF->PNR_SINGLE_SPIN != 1))
1315  {
1316  const REAL8 fRING22_prec = evaluate_QNMfit_fring22(pWF->afinal_prec) / (pWF->Mfinal);
1317  const REAL8 fRING21_prec = evaluate_QNMfit_fring21(pWF->afinal_prec) / (pWF->Mfinal);
1318  pWF->fRING22_prec = fRING22_prec;
1319 
1320  // * Calculate and store single quantity needed to determine effective ringdown frequencies for all QNMs
1321  pWF->fRINGEffShiftDividedByEmm = (1.0-fabs(cos(pWF->betaRD))) * ( fRING22_prec - fRING21_prec );
1322 
1323 
1324  // As we turn off PNR tuning, we want to turn on use of the effective ringdown frequency
1325  // NOTE that when pWF->pnr_window=0, this should reduce to the def of pWF->fRINGCP below
1326 
1327  const INT4 emm = 2;
1328  // NOTE that we use pWF->fRING and not fRING22_prec below because of how pWF->afinal is defined using (1-pWF->pnr_window)
1329  pWF->fRING = pWF->fRING - (1.0-pWF->pnr_window) * emm * pWF->fRINGEffShiftDividedByEmm;
1330  // pWF->fRING = (pWF->pnr_window)*pWF->fRING - (1-pWF->pnr_window) * emm * pWF->fRINGEffShiftDividedByEmm;
1331 
1332  #if DEBUG == 1
1333  printf("pflag : %i\n",pflag);
1334  printf("pWF->betaRD : %e\n",pWF->betaRD);
1335  printf("pWF->fRINGEffShiftDividedByEm : %e\n", pWF->fRINGEffShiftDividedByEmm);
1336  printf("fring22 (prec) : %e\n",fRING22_prec);
1337  printf("fRING : %e\n",pWF->fRING);
1338  #endif
1339 
1340  }
1341 
1342  //
1343  return status;
1344 
1345 };
1346 
1347 /** Get alpha and epsilon offset depending of the mprime (second index of the non-precessing mode) */
1348 void Get_alphaepsilon_atfref(REAL8 *alpha_offset, REAL8 *epsilon_offset, UINT4 mprime, IMRPhenomXPrecessionStruct *pPrec, IMRPhenomXWaveformStruct *pWF)
1349 {
1350  /* Compute the offsets due to the choice of integration constant in alpha and epsilon PN formula */
1351  double omega_ref = pWF->piM * pWF->fRef * 2./mprime;
1352 
1353  int pflag = pPrec->IMRPhenomXPrecVersion;
1354 
1355  /* Explicitly enumerate MSA flags */
1356  if(pflag == 220 || pflag == 221 || pflag == 222 || pflag == 223 || pflag == 224)
1357  {
1358  const double v = cbrt ( omega_ref );
1359  const vector vangles = IMRPhenomX_Return_phi_zeta_costhetaL_MSA(v,pWF,pPrec);
1360 
1361  *alpha_offset = vangles.x - pPrec->alpha0;
1362  *epsilon_offset = vangles.y - pPrec->epsilon0;
1363  }
1364 
1365  else
1366  {
1367  double logomega_ref = log(omega_ref);
1368  double omega_ref_cbrt = cbrt(omega_ref);
1369  double omega_ref_cbrt2 = omega_ref_cbrt * omega_ref_cbrt;
1370 
1371  *alpha_offset = (
1372  pPrec->alpha1 / omega_ref
1373  + pPrec->alpha2 / omega_ref_cbrt2
1374  + pPrec->alpha3 / omega_ref_cbrt
1375  + pPrec->alpha4L * logomega_ref
1376  + pPrec->alpha5 * omega_ref_cbrt - pPrec->alpha0
1377  );
1378 
1379  *epsilon_offset = (
1380  pPrec->epsilon1 / omega_ref
1381  + pPrec->epsilon2 / omega_ref_cbrt2
1382  + pPrec->epsilon3 / omega_ref_cbrt
1383  + pPrec->epsilon4L * logomega_ref
1384  + pPrec->epsilon5 * omega_ref_cbrt - pPrec->epsilon0
1385  );
1386  }
1387 
1388  return;
1389 };
1390 
1391 
1392 /**
1393  This is a convenient wrapper function for PN orbital angular momentum.
1394 */
1396  REAL8 v, /**< Input velocity */
1397  REAL8 LNorm, /**< Orbital angular momentum normalization */
1398  REAL8 L0, /**< Newtonian orbital angular momentum (i.e. LN = 1.0*LNorm) */
1399  REAL8 L1, /**< 0.5PN Orbital angular momentum */
1400  REAL8 L2, /**< 1.0PN Orbital angular momentum */
1401  REAL8 L3, /**< 1.5PN Orbital angular momentum */
1402  REAL8 L4, /**< 2.0PN Orbital angular momentum */
1403  REAL8 L5, /**< 2.5PN Orbital angular momentum */
1404  REAL8 L6, /**< 3.0PN Orbital angular momentum */
1405  REAL8 L7, /**< 3.5PN Orbital angular momentum */
1406  REAL8 L8, /**< 4.0PN Orbital angular momentum */
1407  REAL8 L8L /**< 4.0PN logarithmic orbital angular momentum term */
1408 )
1409 {
1410  const REAL8 x = v*v;
1411  const REAL8 x2 = x*x;
1412  const REAL8 x3 = x*x2;
1413  const REAL8 x4 = x*x3;
1414  const REAL8 sqx = sqrt(x);
1415 
1416  /*
1417  Here LN is the Newtonian pre-factor: LN = \eta / \sqrt{x} :
1418 
1419  L = L_N \sum_a L_a x^{a/2}
1420  = L_N [ L0 + L1 x^{1/2} + L2 x^{2/2} + L3 x^{3/2} + ... ]
1421 
1422  */
1423  return LNorm * (L0 + L1*sqx + L2*x + L3*(x*sqx) + L4*x2 + L5*(x2*sqx) + L6*x3 + L7*(x3*sqx) + L8*x4 + L8L*x4*log(x));
1424 }
1425 
1426 
1427 /**
1428  2PN non-spinning orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
1429 
1430  - Bohe et al, 1212.5520v2, Eq 4.7
1431 */
1433 {
1434  const REAL8 eta2 = eta*eta;
1435  const REAL8 x = v*v;
1436  const REAL8 x2 = x*x;
1437  const REAL8 sqx = v;
1438 
1439  return (eta / sqx) * ( 1.0 + x * (3/2. + eta/6.) + x2 * (27/8. - (19*eta)/8. + eta2/24.) );
1440 }
1441 
1442 /**
1443  3PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
1444 
1445  Includes linear in spin corrections up to 3.5PN
1446 
1447  - Bohe et al, 1212.5520v2, Eq 4.7
1448 */
1449 REAL8 XLALSimIMRPhenomXL3PNAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
1450 {
1451  const REAL8 eta2 = eta*eta;
1452 
1453  const REAL8 x = v*v;
1454  const REAL8 x2 = x*x;
1455  const REAL8 x3 = x2*x;
1456  const REAL8 sqx = v;
1457 
1458  return (eta/sqx) * (
1459  1.0 // Newtonian
1460  + (3/2. + eta/6.) * x // 1PN
1461  + (chi1L*(-5/3. - (5*delta)/3. + (5*eta)/6.) + chi2L*(-5/3. + (5*delta)/3. + (5*eta)/6.)) * x * sqx // 1.5PN
1462  + (27/8. - (19*eta)/8. + eta2/24.) * x2 // 2PN
1463  + ((-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.) * x2 * sqx // 2.5PN
1464  + ((10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*LAL_TWOPI))/1296.) * x3 // 3PN
1465  );
1466 }
1467 
1468 /**
1469  4PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
1470 
1471  - Bohe et al, 1212.5520v2, Eq 4.7
1472  - Marsat, CQG, 32, 085008, (2015), arXiv:1411.4118
1473  - Siemonsen et al, PRD, 97, 064010, (2018), arXiv:1606.08832
1474 */
1475 REAL8 XLALSimIMRPhenomXL4PNAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
1476 {
1477  const REAL8 x = v*v;
1478  const REAL8 x2 = x*x;
1479  const REAL8 x3 = x2*x;
1480  const REAL8 x4 = x3*x;
1481  const REAL8 sqx = sqrt(x);
1482  const REAL8 logx = log(x);
1483 
1484  return (eta/sqx) * (
1485  1.0 //Newtonian
1486  + ((9 + eta)/6.) * x // 1PN
1487  + ((5*chi1L*(-2 - 2*delta + eta) + 5*chi2L*(-2 + 2*delta + eta))/6.) * x * sqx // 1.5PN
1488  + ((81 + (-57 + eta)*eta)/24.) * x2 // 2PN
1489  + ((-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.) * x2 * sqx //2.5PN
1490  + ((10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*LAL_TWOPI))/1296.) * x3 //3PN
1491  + ((chi2L*(-324 + eta*(1119 - 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta)))
1492  - chi1L*(324 + eta*(-1119 + 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta))))/32.) * x3 * sqx //3.5PN
1493  + (2835/128. - (eta*(-10677852 + 100*eta*(-640863 + eta*(774 + 11*eta)) + 26542080*LAL_GAMMA
1494  + 675*(3873 + 3608*eta)*LAL_TWOPI))/622080. - (64*eta*log(16.))/3.) * x4 //4PN
1495  + (-64*eta/3.) * x4 * logx // 4PN log[x] term
1496  );
1497 }
1498 
1499 /**
1500  4PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
1501 
1502  - Bohe et al, 1212.5520v2, Eq 4.7
1503  - Marsat, CQG, 32, 085008, (2015), arXiv:1411.4118
1504  - Siemonsen et al, PRD, 97, 064010, (2018), arXiv:1606.08832
1505 */
1506 REAL8 XLALSimIMRPhenomXL4PNLOSIAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
1507 {
1508  const REAL8 chi1L2 = chi1L * chi1L;
1509  const REAL8 chi2L2 = chi2L * chi2L;
1510 
1511  const REAL8 x = v*v;
1512  const REAL8 x2 = x*x;
1513  const REAL8 x3 = x2*x;
1514  const REAL8 x4 = x3*x;
1515  const REAL8 sqx = sqrt(x);
1516  const REAL8 logx = log(x);
1517 
1518  return (eta/sqx) * (
1519  1.0 //Newtonian
1520  + ((9 + eta)/6.) * x // 1PN
1521  + ((5*chi1L*(-2 - 2*delta + eta) + 5*chi2L*(-2 + 2*delta + eta))/6.) * x * sqx // 1.5PN
1522  + ((81 + (-57 + eta)*eta)/24.) * x2 // 2PN
1523  + ((-7*(chi1L*(72 + delta*(72 - 31*eta) + eta*(-121 + 2*eta)) + chi2L*(72 + eta*(-121 + 2*eta) + delta*(-72 + 31*eta))))/144.) * x2 * sqx //2.5PN
1524  + ((10935 + eta*(-62001 + eta*(1674 + 7*eta) + 2214*LAL_TWOPI))/1296.) * x3 //3PN
1525  + ((chi2L*(-324 + eta*(1119 - 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta)))
1526  - chi1L*(324 + eta*(-1119 + 2*eta*(172 + eta)) + delta*(324 + eta*(-633 + 14*eta))))/32.) * x3 * sqx //3.5PN
1527  + (2835/128. - (eta*(-10677852 + 100*eta*(-640863 + eta*(774 + 11*eta)) + 26542080*LAL_GAMMA
1528  + 675*(3873 + 3608*eta)*LAL_TWOPI))/622080. - (64*eta*log(16.))/3.) * x4 //4PN
1529  + (-64*eta/3.) * x4 * logx // 4PN log[x] term
1530  + ((chi1L2*(1 + delta - 2*eta) + 4*chi1L*chi2L*eta - chi2L2*(-1 + delta + 2*eta))/2.) * x2 // 2PN LOS term, quadratic in spin
1531  + ((3*(chi1L + chi2L)*eta*(chi1L2*(1 + delta - 2*eta) + 4*chi1L*chi2L*eta - chi2L2*(-1 + delta + 2*eta)))/4.) * x3 // 3.5PN LOS term, cubic in spin
1532  );
1533 }
1534 
1535 /**
1536  External wrapper function to next-to-next-to-leading (NNLO) in spin-orbit
1537  expression for the PN Euler angle alpha. This expression is derived by PN
1538  re-expanding and averaging over the orientation of the spin in the orbital plane.
1539 
1540  - P Schmidt, 2014, http://orca.cf.ac.uk/64062/
1541  - A Bohé et al, https://dcc.ligo.org/LIGO-T1500602
1542  - Discussion in arXiv:2004.06503 Sec IV A
1543 */
1544 /* Wrapper to NNLO PN alpha angle */
1546  const REAL8 f, /**< Geometric frequency */
1547  const REAL8 eta, /**< Symmetric mass rato */
1548  const REAL8 chi1L, /**< Dimensionless aligned spin of larger BH */
1549  const REAL8 chi2L, /**< Dimensionless aligned spin of smaller BH */
1550  const REAL8 chip, /**< Effective precession parameter: Schmidt, Ohme, Hannam, PRD, 91,024043 (2015) */
1551  const REAL8 alpha0 /**< Euler angle at reference Frequency, defines a constant offset */
1552 )
1553 {
1554  const REAL8 omega = LAL_PI * f;
1555  const REAL8 logomega = log(omega);
1556  const REAL8 omega_cbrt = cbrt(omega);
1557  const REAL8 omega_cbrt2 = omega_cbrt*omega_cbrt;
1558 
1559  /* Note that we assume: m1 > m2 and dm = m1 - m2 > 0 */
1560  const REAL8 delta = sqrt(1.0 - 4.0*eta);
1561  const REAL8 delta2 = delta*delta;
1562  const REAL8 delta3 = delta*delta2;
1563 
1564  const REAL8 m1 = 0.5*(1.0 + delta);
1565  const REAL8 m1_2 = m1*m1;
1566  const REAL8 m1_3 = m1*m1_2;
1567  const REAL8 m1_4 = m1*m1_3;
1568  const REAL8 m1_5 = m1*m1_4;
1569  const REAL8 m1_6 = m1_3*m1_3;
1570  const REAL8 m1_8 = m1_4*m1_4;
1571 
1572  const REAL8 m2 = 0.5*(1.0 - delta);
1573  const REAL8 q = m1/m2;
1574 
1575  const REAL8 eta2 = eta*eta;
1576  const REAL8 eta3 = eta*eta2;
1577  const REAL8 eta4 = eta*eta3;
1578  const REAL8 eta5 = eta*eta4;
1579  const REAL8 eta6 = eta*eta5;
1580 
1581  const REAL8 chi_eff = m1*chi1L + m2*chi2L;
1582  const REAL8 chiL = (1.0 + q) * chi_eff / q;
1583  const REAL8 chiL2 = chiL*chiL;
1584  const REAL8 chip2 = chip*chip;
1585 
1586  const REAL8 alpha1 = (-35/192. - (5*delta)/(64.*m1));
1587  const REAL8 alpha2 = (((-15*chiL*delta*m1)/128. - (35*chiL*m1_2)/128.)/eta);
1588  const REAL8 alpha3 = (-5515/3072. + eta*(-515/384. - (15*delta2)/(256.*m1_2) - (175*delta)/(256.*m1)) - (4555*delta)/(7168.*m1)
1589  + ((-15*chip2*delta*m1_3)/128. - (35*chip2*m1_4)/128.)/eta2);
1590  const REAL8 alpha4 = (((5*chiL*delta2)/16. + (5*chiL*delta*m1)/3. + (2545*chiL*m1_2)/1152. + ((2035*chiL*delta*m1)/21504.
1591  + (2995*chiL*m1_2)/9216.)/eta + ((-5*chiL*chip2*delta*m1_5)/128.
1592  - (35*chiL*chip2*m1_6)/384.)/eta3 - (35*LAL_PI)/48. - (5*delta*LAL_PI)/(16.*m1)));
1593  const REAL8 alpha5 = ((5*(190512*delta3*eta6 + 2268*delta2*eta3*m1*(eta2*(323 + 784*eta) + 336*(25*chiL2 + chip2)*m1_4)
1594  + 7*m1_3*(8024297*eta4 + 857412*eta5 + 3080448*eta6 + 143640*chip2*eta2*m1_4 - 127008*chip2*(-4*chiL2 + chip2)*m1_8
1595  + 6048*eta3*((2632*chiL2 + 115*chip2)*m1_4 - 672*chiL*m1_2*LAL_PI)) + 3*delta*m1_2*(5579177*eta4
1596  - 80136*eta5 + 3845520*eta6 - 146664*chip2*eta2*m1_4 - 127008*chip2*(-4*chiL2 + chip2)*m1_8
1597  + 42336*eta3*((726*chiL2 + 29*chip2)*m1_4 - 96*chiL*m1_2*LAL_PI))))/(6.5028096e7*eta4*m1_3));
1598 
1599  return (alpha1/omega + alpha2/omega_cbrt2 + alpha3/omega_cbrt + alpha4*logomega + alpha5*omega_cbrt + alpha0);
1600 }
1601 
1602 
1603 /** External wrapper to NNLO PN epsilon angle. See documentation above XLALSimIMRPhenomXPNEuleralphaNNLO. */
1605  REAL8 f, /**< Geometric frequency */
1606  REAL8 eta, /**< Symmetric mass rato */
1607  REAL8 chi1L, /**< Dimensionless aligned spin of larger BH */
1608  REAL8 chi2L, /**< Dimensionless aligned spin of smaller BH */
1609  REAL8 chip, /**< Effective precession parameter: Schmidt, Ohme, Hannam, PRD, 91,024043 (2015) */
1610  REAL8 epsilon0 /**< Euler angle at reference Frequency, defines a constant offset */
1611 )
1612 {
1613  const REAL8 omega = LAL_PI * f;
1614  const REAL8 logomega = log(omega);
1615  const REAL8 omega_cbrt = cbrt(omega);
1616  const REAL8 omega_cbrt2 = omega_cbrt*omega_cbrt;
1617 
1618  /* Note that we assume: m1 > m2 and dm = m1 - m2 > 0 */
1619  const REAL8 delta = sqrt(1.0 - 4.0*eta);
1620  const REAL8 delta2 = delta*delta;
1621  const REAL8 delta3 = delta*delta2;
1622 
1623  const REAL8 m1 = 0.5*(1.0 + delta);
1624  const REAL8 m1_2 = m1*m1;
1625  const REAL8 m1_3 = m1*m1_2;
1626  const REAL8 m1_4 = m1*m1_3;
1627  const REAL8 m1_5 = m1*m1_4;
1628  const REAL8 m1_6 = m1_3*m1_3;
1629  const REAL8 m1_8 = m1_4*m1_4;
1630 
1631  const REAL8 m2 = 0.5*(1.0 - delta);
1632  const REAL8 q = m1/m2;
1633 
1634  const REAL8 eta2 = eta*eta;
1635  const REAL8 eta3 = eta*eta2;
1636  const REAL8 eta4 = eta*eta3;
1637  const REAL8 eta5 = eta*eta4;
1638  const REAL8 eta6 = eta*eta5;
1639 
1640  const REAL8 chi_eff = m1*chi1L + m2*chi2L;
1641  const REAL8 chiL = (1.0 + q) * chi_eff / q;
1642  const REAL8 chiL2 = chiL*chiL;
1643  const REAL8 chip2 = chip*chip;
1644 
1645  const REAL8 epsilon1 = (-35/192. - (5*delta)/(64.*m1));
1646  const REAL8 epsilon2 = (((-15*chiL*delta*m1)/128. - (35*chiL*m1_2)/128.)/eta);
1647  const REAL8 epsilon3 = (-5515/3072. + eta*(-515/384. - (15*delta2)/(256.*m1_2) - (175*delta)/(256.*m1)) - (4555*delta)/(7168.*m1)
1648  + ((-15*chip2*delta*m1_3)/128. - (35*chip2*m1_4)/128.)/eta2);
1649  const REAL8 epsilon4L = (((5*chiL*delta2)/16. + (5*chiL*delta*m1)/3. + (2545*chiL*m1_2)/1152. + ((2035*chiL*delta*m1)/21504.
1650  + (2995*chiL*m1_2)/9216.)/eta + ((-5*chiL*chip2*delta*m1_5)/128.
1651  - (35*chiL*chip2*m1_6)/384.)/eta3 - (35*LAL_PI)/48. - (5*delta*LAL_PI)/(16.*m1)));
1652  const REAL8 epsilon5 = ((5*(190512*delta3*eta6 + 2268*delta2*eta3*m1*(eta2*(323 + 784*eta) + 336*(25*chiL2 + chip2)*m1_4)
1653  + 7*m1_3*(8024297*eta4 + 857412*eta5 + 3080448*eta6 + 143640*chip2*eta2*m1_4 - 127008*chip2*(-4*chiL2 + chip2)*m1_8
1654  + 6048*eta3*((2632*chiL2 + 115*chip2)*m1_4 - 672*chiL*m1_2*LAL_PI)) + 3*delta*m1_2*(5579177*eta4
1655  - 80136*eta5 + 3845520*eta6 - 146664*chip2*eta2*m1_4 - 127008*chip2*(-4*chiL2 + chip2)*m1_8
1656  + 42336*eta3*((726*chiL2 + 29*chip2)*m1_4 - 96*chiL*m1_2*LAL_PI))))/(6.5028096e7*eta4*m1_3));
1657 
1658  return (epsilon1/omega + epsilon2/omega_cbrt2 + epsilon3/omega_cbrt + epsilon4L*logomega + epsilon5*omega_cbrt + epsilon0);
1659 
1660 }
1661 
1662 /** Internal function to calculate alpha using pre-cached NNLO PN expressions */
1664  IMRPhenomXPrecessionStruct *pPrec, /**< IMRPhenomX Precession Struct */
1665  const double omega, /**< Orbital frequency */
1666  const double omega_cbrt2, /**< Orbital frequency */
1667  const double omega_cbrt, /**< Cubic root of orbital frequency */
1668  const double logomega /**< Natural logarithm of orbital frequency */
1669 )
1670 {
1671  double alpha = 0.0;
1672 
1673  /* Note that alpha_offset already includes the term alpha0 */
1674  alpha = (
1675  pPrec->alpha1 / omega
1676  + pPrec->alpha2 / omega_cbrt2
1677  + pPrec->alpha3 / omega_cbrt
1678  + pPrec->alpha4L * logomega
1679  + pPrec->alpha5 * omega_cbrt
1680  - pPrec->alpha_offset
1681  );
1682 
1683  return alpha;
1684 }
1685 
1686 /** Internal function to calculate epsilon using pre-cached NNLO PN expressions */
1688  IMRPhenomXPrecessionStruct *pPrec, /**< IMRPhenomX Precession Struct */
1689  const double omega, /**< Orbital frequency */
1690  const double omega_cbrt2, /**< Orbital frequency */
1691  const double omega_cbrt, /**< Cubic root of orbital frequency */
1692  const double logomega /**< Natural logarithm of orbital frequency */
1693 )
1694 {
1695  double epsilon = 0.0;
1696 
1697  /* Note that epsilon_offset already includes the term epsilon0 */
1698  epsilon = (
1699  pPrec->epsilon1 / omega
1700  + pPrec->epsilon2 / omega_cbrt2
1701  + pPrec->epsilon3 / omega_cbrt
1702  + pPrec->epsilon4L * logomega
1703  + pPrec->epsilon5 * omega_cbrt
1704  - pPrec->epsilon_offset
1705  );
1706 
1707  return epsilon;
1708 }
1709 
1710 /** Core twisting up routine, see Section III A of arXiv:2004.06503 */
1712  const REAL8 Mf, /**< Frequency (Hz) */
1713  const COMPLEX16 hAS, /**< Underlying aligned-spin IMRPhenomXAS strain */
1714  IMRPhenomXWaveformStruct *pWF, /**< IMRPhenomX Waveform Struct */
1715  IMRPhenomXPrecessionStruct *pPrec, /**< IMRPhenomXP Precession Struct */
1716  COMPLEX16 *hp, /**< [out] h_+ polarization \f$\tilde h_+\f$ */
1717  COMPLEX16 *hc /**< [out] h_x polarization \f$\tilde h_x\f$ */
1718 )
1719 {
1720  XLAL_CHECK(hp != NULL, XLAL_EFAULT);
1721  XLAL_CHECK(hc != NULL, XLAL_EFAULT);
1722 
1723  /* Euler angles */
1724  double alpha = 0.0;
1725  double epsilon = 0.0;
1726 
1727  double cBetah = 0.0;
1728  double sBetah = 0.0;
1729 
1730  const double omega = LAL_PI * Mf;
1731  const double logomega = log(omega);
1732  const double omega_cbrt = cbrt(omega);
1733  const double omega_cbrt2 = omega_cbrt * omega_cbrt;
1734 
1735  const double v = omega_cbrt;
1736 
1737  double s, s2, cos_beta;
1738 
1739  if(pPrec->IMRPhenomXPNRUseTunedAngles)
1740  {
1741  alpha = pPrec->alphaPNR - pPrec->alpha_offset;
1742  epsilon = -1.0 * pPrec->gammaPNR - pPrec->epsilon_offset;
1743  cos_beta = cos(pPrec->betaPNR);
1744  }
1745  else
1746  {
1747  switch(pPrec->IMRPhenomXPrecVersion)
1748  {
1749  /* ~~~~~ Use NNLO PN Euler Angles - Appendix G of arXiv:2004.06503 and https://dcc.ligo.org/LIGO-T1500602 ~~~~~ */
1750  case 101:
1751  case 102:
1752  case 103:
1753  case 104:
1754  {
1755  alpha = IMRPhenomX_PN_Euler_alpha_NNLO(pPrec,omega,omega_cbrt2,omega_cbrt,logomega);
1756  epsilon = IMRPhenomX_PN_Euler_epsilon_NNLO(pPrec,omega,omega_cbrt2,omega_cbrt,logomega);
1757 
1758  const REAL8 L = XLALSimIMRPhenomXLPNAnsatz(v, pWF->eta/v, pPrec->L0, pPrec->L1, pPrec->L2, pPrec->L3, pPrec->L4, pPrec->L5, pPrec->L6, pPrec->L7, pPrec->L8, pPrec->L8L);
1759 
1760  /*
1761  We ignore the sign of L + SL below:
1762  s := Sp / (L + SL)
1763  */
1764  s = pPrec->Sperp / (L + pPrec->SL);
1765  s2 = s*s;
1766  cos_beta = copysign(1.0, L + pPrec->SL) / sqrt(1.0 + s2);
1767 
1768  break;
1769  }
1770  case 220:
1771  case 221:
1772  case 222:
1773  case 223:
1774  case 224:
1775  {
1776  vector vangles = {0.,0.,0.};
1777 
1778  /* ~~~~~ Euler Angles from Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967 ~~~~~ */
1779  vangles = IMRPhenomX_Return_phi_zeta_costhetaL_MSA(v,pWF,pPrec);
1780 
1781  alpha = vangles.x - pPrec->alpha_offset;
1782  epsilon = vangles.y - pPrec->epsilon_offset;
1783  cos_beta = vangles.z;
1784 
1785  break;
1786  }
1787  default:
1788  {
1789  XLAL_ERROR(XLAL_EINVAL,"Error: IMRPhenomXPrecessionVersion not recognized. Recommended default is 223.\n");
1790  break;
1791  }
1792  }
1793  }
1794 
1795 
1796  INT4 status = 0;
1797  status = IMRPhenomXWignerdCoefficients_cosbeta(&cBetah, &sBetah, cos_beta);
1798  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Call to IMRPhenomXWignerdCoefficients_cosbeta failed.");
1799 
1800  /* Useful powers of the Wigner coefficients */
1801  const REAL8 cBetah2 = cBetah * cBetah;
1802  const REAL8 cBetah3 = cBetah * cBetah2;
1803  const REAL8 cBetah4 = cBetah * cBetah3;
1804  const REAL8 sBetah2 = sBetah * sBetah;
1805  const REAL8 sBetah3 = sBetah * sBetah2;
1806  const REAL8 sBetah4 = sBetah * sBetah3;
1807 
1808 
1809  /*
1810  Compute the Wigner d coefficients, see Appendix A of arXiv:2004.06503
1811  d22 = Table[WignerD[{2, mp, 2}, 0, -\[Beta], 0], {mp, -2, 2}]
1812  d2m2 = Table[WignerD[{2, mp, -2}, 0, -\[Beta], 0], {mp, -2, 2}]
1813  */
1814  // d22 = {d^2_{-2,2} , d^2_{-1,2}, d^2_{0,2}, d^2_{1,2}, d^2_{2,2} }
1815  const COMPLEX16 d22[5] = {sBetah4, 2.0*cBetah*sBetah3, pPrec->sqrt6*sBetah2*cBetah2, 2.0*cBetah3*sBetah, cBetah4};
1816 
1817  // d2m2 = {d^2_{-2,-2} , d^2_{-1,-2}, d^2_{0,-2}, d^2_{1,-2}, d^2_{2,-2} }
1818  const COMPLEX16 d2m2[5] = {d22[4], -d22[3], d22[2], -d22[1], d22[0]}; /* Exploit symmetry d^2_{-2,m} = (-1)^m d^2_{2,-m}*/
1819 
1820  const COMPLEX16 Y2mA[5] = {pPrec->Y2m2, pPrec->Y2m1, pPrec->Y20, pPrec->Y21, pPrec->Y22};
1821 
1822  /* Precompute powers of e^{i m alpha} */
1823  COMPLEX16 cexp_i_alpha = cexp(+I*alpha);
1824  COMPLEX16 cexp_2i_alpha = cexp_i_alpha * cexp_i_alpha;
1825  COMPLEX16 cexp_mi_alpha = 1.0 / cexp_i_alpha;
1826  COMPLEX16 cexp_m2i_alpha = cexp_mi_alpha * cexp_mi_alpha;
1827  COMPLEX16 cexp_im_alpha_l2[5] = {cexp_m2i_alpha, cexp_mi_alpha, 1.0, cexp_i_alpha, cexp_2i_alpha};
1828 
1829  COMPLEX16 hp_sum = 0;
1830  COMPLEX16 hc_sum = 0;
1831 
1832  REAL8 polarizationSymmetry = pPrec->PolarizationSymmetry;
1833 
1834  /* Loop over m' modes and perform the actual twisting up */
1835  for(int m=-2; m<=2; m++)
1836  {
1837  /* Transfer functions, see Eq. 3.5 and 3.6 of arXiv:2004.06503 */
1838  COMPLEX16 A2m2emm = cexp_im_alpha_l2[-m+2] * d2m2[m+2] * Y2mA[m+2]; /* = cexp(I*m*alpha) * d22[m+2] * Y2mA[m+2] */
1839  COMPLEX16 A22emmstar = cexp_im_alpha_l2[m+2] * d22[m+2] * conj(Y2mA[m+2]); /* = cexp(-I*m*alpha) * d2m2[m+2] * conj(Y2mA[m+2]) */
1840  hp_sum += A2m2emm + polarizationSymmetry * A22emmstar;
1841  hc_sum += I*(A2m2emm - polarizationSymmetry * A22emmstar);
1842  }
1843 
1844  /* Note that \gamma = - \epsilon */
1845  COMPLEX16 eps_phase_hP = cexp(-2.0*I*epsilon) * hAS / 2.0;
1846 
1847  /* Return h_+ and h_x */
1848  *hp = eps_phase_hP * hp_sum;
1849  *hc = eps_phase_hP * hc_sum;
1850 
1851  /* When debugging, save angles to output file. */
1852  #if DEBUG == 1
1853  FILE *fileangle;
1854  char fileSpec[40];
1855 
1856  sprintf(fileSpec, "angles_XP.dat");
1857 
1858  fileangle = fopen(fileSpec,"a");
1859 
1860  // COMPLEX16 cexp_i_epsilon = cexp(I*epsilon);
1861  // COMPLEX16 cexp_i_betah = cBetah + I*sBetah;
1862  //fprintf(fileangle, "%.16e %.16e %.16e %.16e %.16e %.16e %.16e\n", XLALSimIMRPhenomXUtilsMftoHz(Mf, pWF->Mtot), creal(cexp_i_alpha), cimag(cexp_i_alpha), creal(cexp_i_epsilon), cimag(cexp_i_epsilon), creal(cexp_i_betah), cimag(cexp_i_betah));
1863  fprintf(fileangle, "%.16e %.16e %.16e %.16e\n", XLALSimIMRPhenomXUtilsMftoHz(Mf, pWF->Mtot), alpha, epsilon, cos_beta);
1864  fclose(fileangle);
1865  #endif
1866 
1867  return XLAL_SUCCESS;
1868 }
1869 
1871  REAL8 *cos_beta_half, /**< [out] cos(beta/2) */
1872  REAL8 *sin_beta_half, /**< [out] sin(beta/2) */
1873  const REAL8 cos_beta /**< cos(beta) */
1874 )
1875 {
1876  /* Note that the results here are indeed always non-negative */
1877  *cos_beta_half = + sqrt( fabs(1.0 + cos_beta) / 2.0 ); /* cos(beta/2) */
1878  *sin_beta_half = + sqrt( fabs(1.0 - cos_beta) / 2.0 ); /* sin(beta/2) */
1879 
1880  return XLAL_SUCCESS;
1881 }
1882 
1884  REAL8 *cos_beta_half, /**< [out] cos(beta/2) */
1885  REAL8 *sin_beta_half, /**< [out] sin(beta/2) */
1886  const REAL8 v, /**< Cubic root of (Pi * Frequency (geometric)) */
1887  IMRPhenomXWaveformStruct *pWF, /**< IMRPhenomX waveform struct */
1888  IMRPhenomXPrecessionStruct *pPrec /**< IMRPhenomX precession struct */
1889 )
1890 {
1891  /* Orbital angular momentum */
1892  const REAL8 L = XLALSimIMRPhenomXLPNAnsatz(v, pWF->eta/v, pPrec->L0, pPrec->L1, pPrec->L2, pPrec->L3, pPrec->L4, pPrec->L5, pPrec->L6, pPrec->L7, pPrec->L8, pPrec->L8L);
1893 
1894  /*
1895  We ignore the sign of L + SL below:
1896  s := Sp / (L + SL)
1897  */
1898  const REAL8 s = pPrec->Sperp / (L + pPrec->SL);
1899  const REAL8 s2 = s*s;
1900  const REAL8 cos_beta = copysign(1.0, L + pPrec->SL) / sqrt(1.0 + s2);
1901 
1902  *cos_beta_half = + sqrt( fabs(1.0 + cos_beta) / 2.0 ); /* cos(beta/2) */
1903  *sin_beta_half = + sqrt( fabs(1.0 - cos_beta) / 2.0 ); /* sin(beta/2) */
1904 
1905  return XLAL_SUCCESS;
1906 }
1907 
1908 /**
1909  Helper function to check if maximum opening angle > pi/2 or pi/4 and issues a warning. See discussion in https://dcc.ligo.org/LIGO-T1500602
1910 */
1912  IMRPhenomXWaveformStruct *pWF, /**< IMRPhenomX Waveform Struct */
1913  IMRPhenomXPrecessionStruct *pPrec /**< IMRPhenomXP Precession Struct */
1914 )
1915 {
1916  const REAL8 eta = pWF->eta;
1917 
1918  /* For now, use the 2PN non-spinning maximum opening angle */
1919  const REAL8 v_at_max_beta = sqrt(2.0 / 3.0) * sqrt( (-9.0 - eta + sqrt(1539.0 - 1008.0*eta + 19.0*eta*eta)) / (81 - 57*eta + eta*eta) );
1920 
1921  REAL8 cBetah = 0.0;
1922  REAL8 sBetah = 0.0;
1923 
1924  INT4 status;
1925  status = IMRPhenomXWignerdCoefficients(&cBetah, &sBetah, v_at_max_beta, pWF, pPrec);
1926  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Call to IMRPhenomXWignerdCoefficients failed.");
1927 
1928  const REAL8 L_min = XLALSimIMRPhenomXL2PNNS(v_at_max_beta,eta);
1929  const REAL8 max_beta = 2.0 * acos(cBetah);
1930 
1931  /*
1932  If L + SL becomes < 0, WignerdCoefficients does not track the angle between J and L.
1933  The model may become pathological as one moves away from the aligned spin limit.
1934 
1935  If this does not happen, then max_beta is the actual maximum opening angle as predicted by the model.
1936  */
1937  if ((L_min + pPrec->SL) < 0. && pPrec->chi_p > 0.)
1938  {
1939  XLAL_PRINT_WARNING("The maximum opening angle exceeds Pi/2.\nThe model may be pathological in this regime.");
1940  }
1941  else if (max_beta > LAL_PI_4)
1942  {
1943  XLAL_PRINT_WARNING("The maximum opening angle %g is larger than Pi/4.\nThe model has not been tested against NR in this regime.", max_beta);
1944  }
1945 
1946  return XLAL_SUCCESS;
1947 }
1948 
1949 
1950 
1951 
1952 /* ~~~~~~~~~~ Routines to Calculate PN Angles following the MSA approach of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967 ~~~~~~~~~~*/
1953 /** Returns the 3PN accurate orbital angular momentum as implemented in LALSimInspiralFDPrecAngles_internals.c */
1954 double IMRPhenomX_L_norm_3PN_of_v(const double v, const double v2, const double L_norm, IMRPhenomXPrecessionStruct *pPrec)
1955 {
1956  return L_norm*(1. + v2*(pPrec->constants_L[0] + v*pPrec->constants_L[1] + v2*(pPrec->constants_L[2] + v*pPrec->constants_L[3] + v2*(pPrec->constants_L[4]))));
1957 }
1958 
1959 
1960 /** Wrapper to generate \f$\phi_z\f$, \f$\zeta\f$ and \f$\cos \theta_L\f$ at a given frequency */
1962  const double v, /**< Velocity */
1963  IMRPhenomXWaveformStruct *pWF, /**< IMRPhenomX waveform struct */
1964  IMRPhenomXPrecessionStruct *pPrec /**< IMRPhenomX precession struct */
1965 )
1966 {
1967  vector vout = {0.,0.,0.};
1968 
1969  /* Change code here to determine PN order passed for L */
1970  const double L_norm = pWF->eta / v;
1971  const double J_norm = IMRPhenomX_JNorm_MSA(L_norm,pPrec);
1972 
1973  double L_norm3PN = 0.0;
1974 
1975  /* Orbital angular momentum at 3PN, coefficients are pre-cached when initializing precession struct */
1976  if(pPrec->IMRPhenomXPrecVersion == 222 || pPrec->IMRPhenomXPrecVersion == 223)
1977  {
1978  L_norm3PN = IMRPhenomX_L_norm_3PN_of_v(v, v*v, L_norm, pPrec);
1979  }
1980  else
1981  {
1982  L_norm3PN = XLALSimIMRPhenomXLPNAnsatz(v, L_norm, pPrec->L0, pPrec->L1, pPrec->L2, pPrec->L3, pPrec->L4, pPrec->L5, pPrec->L6, pPrec->L7, pPrec->L8, pPrec->L8L);
1983  }
1984  const double J_norm3PN = IMRPhenomX_JNorm_MSA(L_norm3PN,pPrec);
1985 
1986  /*
1987  Get roots to S^2 equation :
1988  vroots.x = A1 = S_{3}^2
1989  vroots.y = A2 = S_{-}^2
1990  vroots.z = A3 = S_{+}^2
1991  */
1992  const vector vRoots = IMRPhenomX_Return_Roots_MSA(L_norm,J_norm,pPrec);
1993 
1994  pPrec->S32 = vRoots.x;
1995  pPrec->Smi2 = vRoots.y;
1996  pPrec->Spl2 = vRoots.z;
1997 
1998  pPrec->Spl2mSmi2 = pPrec->Spl2 - pPrec->Smi2;
1999  pPrec->Spl2pSmi2 = pPrec->Spl2 + pPrec->Smi2;
2000  pPrec->Spl = sqrt(pPrec->Spl2);
2001  pPrec->Smi = sqrt(pPrec->Smi2);
2002 
2003  const double SNorm = IMRPhenomX_Return_SNorm_MSA(v,pPrec);
2004  pPrec->S_norm = SNorm;
2005  pPrec->S_norm_2 = SNorm * SNorm;
2006 
2007  vector vMSA = {0.,0.,0.};
2008  if(fabs(pPrec->Smi2 - pPrec->Spl2) > 1.e-5)
2009  {
2010  /* Get phiz_0_MSA and zeta_0_MSA */
2011  vMSA = IMRPhenomX_Return_MSA_Corrections_MSA(v, L_norm, J_norm, pPrec);
2012  }
2013 
2014  const double phiz_MSA = vMSA.x;
2015  const double zeta_MSA = vMSA.y;
2016 
2017  const double phiz = IMRPhenomX_Return_phiz_MSA(v,J_norm,pPrec);
2018  const double zeta = IMRPhenomX_Return_zeta_MSA(v,pPrec);
2019  double cos_theta_L = IMRPhenomX_costhetaLJ(L_norm3PN,J_norm3PN,SNorm);
2020 
2021  vout.x = phiz + phiz_MSA;
2022  vout.y = zeta + zeta_MSA;
2023  vout.z = cos_theta_L;
2024 
2025  return vout;
2026 }
2027 
2028 /** This function initializes all the core variables required for the MSA system. This will be called first. */
2030 {
2031  /*
2032  Sanity check on the precession version
2033  */
2034  int pflag = pPrec->IMRPhenomXPrecVersion;
2035  if(pflag != 220 && pflag != 221 && pflag != 222 && pflag != 223 && pflag != 224)
2036  {
2037  XLAL_ERROR(XLAL_EINVAL,"Error: MSA system requires IMRPhenomXPrecVersion 220, 221, 222, 223 or 224.\n");
2038  }
2039 
2040  /*
2041  First initialize the system of variables needed for Chatziioannou et al, PRD, 88, 063011, (2013), arXiv:1307.4418:
2042  - Racine et al, PRD, 80, 044010, (2009), arXiv:0812.4413
2043  - Favata, PRD, 80, 024002, (2009), arXiv:0812.0069
2044  - Blanchet et al, PRD, 84, 064041, (2011), arXiv:1104.5659
2045  - Bohe et al, CQG, 30, 135009, (2013), arXiv:1303.7412
2046  */
2047  const double eta = pPrec->eta;
2048  const double eta2 = pPrec->eta2;
2049  const double eta3 = pPrec->eta3;
2050  const double eta4 = pPrec->eta4;
2051 
2052  const double m1 = pWF->m1;
2053  const double m2 = pWF->m2;
2054 
2055  /* PN Coefficients for d \omega / d t as per LALSimInspiralFDPrecAngles_internals.c */
2056  const double domegadt_constants_NS[17] = {96./5.,-1486./35.,-264./5.,384.*LAL_PI/5.,34103./945.,13661./105.,944./15.,LAL_PI*(-4159./35.),LAL_PI*(-2268./5.),(16447322263./7276500. + LAL_PI*LAL_PI*512./5. - LAL_LN2*109568./175. -LAL_GAMMA*54784./175.),(-56198689./11340. + LAL_PI*LAL_PI*902./5.),1623./140.,-1121./27.,-54784./525.,-LAL_PI*883./42.,LAL_PI*71735./63.,LAL_PI*73196./63.};
2057  const double domegadt_constants_SO[18] = {-904./5.,-120.,-62638./105.,4636./5.,-6472./35.,3372./5.,-LAL_PI*720.,-LAL_PI*2416./5.,-208520./63.,796069./105.,-100019./45.,-1195759./945.,514046./105.,-8709./5.,-LAL_PI*307708./105.,LAL_PI*44011./7.,-LAL_PI*7992./7.,LAL_PI*151449./35.};
2058  const double domegadt_constants_SS[4] = {-494./5.,-1442./5.,-233./5.,-719./5.};
2059 
2060  const double L_csts_nonspin[9] = {3./2.,1./6.,27./8.,-19./8.,1./24.,135./16.,-6889/144.+ 41./24.*LAL_PI*LAL_PI,31./24.,7./1296.};
2061  const double L_csts_spinorbit[6] = {-14./6.,-3./2.,-11./2.,133./72.,-33./8.,7./4.};
2062 
2063 
2064  /*
2065  Note that Chatziioannou et al use q = m2/m1, where m1 > m2 and therefore q < 1
2066  IMRPhenomX assumes m1 > m2 and q > 1. For the internal MSA code, flip q and
2067  dump this to pPrec->qq, where qq explicitly dentoes that this is 0 < q < 1.
2068  */
2069  const double q = m2 / m1; // m2 / m1, q < 1, m1 > m2
2070  const double invq = 1.0 / q; // m2 / m1, q < 1, m1 > m2
2071  pPrec->qq = q;
2072  pPrec->invqq = invq;
2073 
2074  const double mu = (m1 * m2) / (m1 + m2);
2075 
2076  #if DEBUG == 1
2077  printf("m1 = %.6f\n\n",pWF->m1);
2078  printf("m2 = %.6f\n\n",pWF->m2);
2079  printf("q (<1) = %.6f\n\n",pPrec->qq);
2080  #endif
2081 
2082  /* \delta and powers of \delta in terms of q < 1, should just be m1 - m2 */
2083  pPrec->delta_qq = (1.0 - pPrec->qq) / (1.0 + pPrec->qq);
2084  pPrec->delta2_qq = pPrec->delta_qq * pPrec->delta_qq;
2085  pPrec->delta3_qq = pPrec->delta_qq * pPrec->delta2_qq;
2086  pPrec->delta4_qq = pPrec->delta_qq * pPrec->delta3_qq;
2087 
2088  /* Initialize empty vectors */
2089  vector S1v = {0.,0.,0.};
2090  vector S2v = {0.,0.,0.};
2091 
2092  /* Define source frame such that \hat{L} = {0,0,1} with L_z pointing along \hat{z}. */
2093  vector Lhat = {0.,0.,1.};
2094 
2095  /* Set LHat variables - these are fixed. */
2096  pPrec->Lhat_cos_theta = 1.0; /* Cosine of Polar angle of orbital angular momentum */
2097  pPrec->Lhat_phi = 0.0; /* Azimuthal angle of orbital angular momentum */
2098  pPrec->Lhat_theta = 0.0; /* Polar angle of orbital angular momentum */
2099 
2100  /* Dimensionful spin vectors, note eta = m1 * m2 and q = m2/m1 */
2101  S1v.x = pPrec->chi1x * eta/q; /* eta / q = m1^2 */
2102  S1v.y = pPrec->chi1y * eta/q;
2103  S1v.z = pPrec->chi1z * eta/q;
2104 
2105  S2v.x = pPrec->chi2x * eta*q; /* eta * q = m2^2 */
2106  S2v.y = pPrec->chi2y * eta*q;
2107  S2v.z = pPrec->chi2z * eta*q;
2108 
2109  REAL8 S1_0_norm = IMRPhenomX_vector_L2_norm(S1v);
2110  REAL8 S2_0_norm = IMRPhenomX_vector_L2_norm(S2v);
2111 
2112  /* Initial dimensionful spin vectors at reference frequency */
2113  /* S1 = {S1x,S1y,S1z} */
2114  pPrec->S1_0.x = S1v.x;
2115  pPrec->S1_0.y = S1v.y;
2116  pPrec->S1_0.z = S1v.z;
2117 
2118  /* S2 = {S2x,S2y,S2z} */
2119  pPrec->S2_0.x = S2v.x;
2120  pPrec->S2_0.y = S2v.y;
2121  pPrec->S2_0.z = S2v.z;
2122 
2123  /* Reference velocity v and v^2 */
2124  pPrec->v_0 = cbrt( pPrec->piGM * pWF->fRef );
2125  pPrec->v_0_2 = pPrec->v_0 * pPrec->v_0;
2126 
2127  /* Reference orbital angular momenta */
2128  vector L_0 = {0.,0.,0.};
2129  L_0 = IMRPhenomX_vector_scalar(Lhat,pPrec->eta / pPrec->v_0);
2130  pPrec->L_0 = L_0;
2131 
2132  #if DEBUG == 1
2133  printf("v_0 = %.6f\n\n",pPrec->v_0);
2134 
2135  printf("chi1x = %.6f\n",pPrec->chi1x);
2136  printf("chi1y = %.6f\n",pPrec->chi1y);
2137  printf("chi1z = %.6f\n\n",pPrec->chi1z);
2138 
2139  printf("chi2x = %.6f\n",pPrec->chi2x);
2140  printf("chi2y = %.6f\n",pPrec->chi2y);
2141  printf("chi2z = %.6f\n\n",pPrec->chi2z);
2142 
2143  printf("S1_0.x = %.6f\n",pPrec->S1_0.x);
2144  printf("S1_0.y = %.6f\n",pPrec->S1_0.y);
2145  printf("S1_0.z = %.6f\n",pPrec->S1_0.z);
2146  printf("S1_0 = %.6f\n\n",S1_0_norm);
2147 
2148  printf("S2_0.x = %.6f\n",pPrec->S2_0.x);
2149  printf("S2_0.y = %.6f\n",pPrec->S2_0.y);
2150  printf("S2_0.z = %.6f\n",pPrec->S2_0.z);
2151  printf("S2_0 = %.6f\n\n",S2_0_norm);
2152  #endif
2153 
2154  /* Inner products used in MSA system */
2155  double dotS1L, dotS2L, dotS1Ln, dotS2Ln, dotS1S2;
2156 
2157  dotS1L = IMRPhenomX_vector_dot_product(S1v,Lhat);
2158  dotS2L = IMRPhenomX_vector_dot_product(S2v,Lhat);
2159  dotS1S2 = IMRPhenomX_vector_dot_product(S1v,S2v);
2160  dotS1Ln = dotS1L / S1_0_norm;
2161  dotS2Ln = dotS2L / S2_0_norm;
2162 
2163  /* Add dot products to struct */
2164  pPrec->dotS1L = dotS1L;
2165  pPrec->dotS2L = dotS2L;
2166  pPrec->dotS1S2 = dotS1S2;
2167  pPrec->dotS1Ln = dotS1Ln;
2168  pPrec->dotS2Ln = dotS2Ln;
2169 
2170  #if DEBUG == 1
2171  printf("Lhat_0.x = %.6f\n",Lhat.x);
2172  printf("Lhat_0.y = %.6f\n",Lhat.y);
2173  printf("Lhat_0.z = %.6f\n\n",Lhat.z);
2174 
2175  printf("dotS1L = %.6f\n",pPrec->dotS1L);
2176  printf("dotS2L = %.6f\n",pPrec->dotS2L);
2177  printf("dotS1Ln = %.6f\n",pPrec->dotS1Ln);
2178  printf("dotS2Ln = %.6f\n",pPrec->dotS2Ln);
2179  printf("dotS1S2 = %.6f\n\n",pPrec->dotS1S2);
2180  #endif
2181 
2182  /* Coeffcients for PN orbital angular momentum at 3PN, as per LALSimInspiralFDPrecAngles_internals.c */
2183  pPrec->constants_L[0] = (L_csts_nonspin[0] + eta*L_csts_nonspin[1]);
2184  pPrec->constants_L[1] = IMRPhenomX_Get_PN_beta(L_csts_spinorbit[0], L_csts_spinorbit[1], pPrec);
2185  pPrec->constants_L[2] = (L_csts_nonspin[2] + eta*L_csts_nonspin[3] + eta*eta*L_csts_nonspin[4]);
2186  pPrec->constants_L[3] = IMRPhenomX_Get_PN_beta((L_csts_spinorbit[2]+L_csts_spinorbit[3]*eta), (L_csts_spinorbit[4]+L_csts_spinorbit[5]*eta), pPrec);
2187  pPrec->constants_L[4] = (L_csts_nonspin[5]+L_csts_nonspin[6]*eta +L_csts_nonspin[7]*eta*eta+L_csts_nonspin[8]*eta*eta*eta);
2188 
2189  /* Effective total spin */
2190  const double Seff = (1.0 + q) * pPrec->dotS1L + (1 + (1.0/q))*pPrec->dotS2L;
2191  const double Seff2 = Seff * Seff;
2192 
2193  pPrec->Seff = Seff;
2194  pPrec->Seff2 = Seff2;
2195 
2196  #if DEBUG == 1
2197  printf("Seff = %.6f\n\n",pPrec->Seff);
2198  #endif
2199 
2200  /* Initial total spin, S = S1 + S2 */
2201  vector S0 = {0.,0.,0.};
2202  S0 = IMRPhenomX_vector_sum(S1v,S2v);
2203 
2204  /* Cache total spin in the precession struct */
2205  pPrec->S_0 = S0;
2206 
2207  #if DEBUG == 1
2208  printf("S_0_x = %.6f\n",pPrec->S_0.x);
2209  printf("S_0_y = %.6f\n",pPrec->S_0.y);
2210  printf("S_0_z = %.6f\n\n",pPrec->S_0.z);
2211  #endif
2212 
2213  /* Initial total angular momentum, J = L + S1 + S2 */
2214  pPrec->J_0 = IMRPhenomX_vector_sum(pPrec->L_0,pPrec->S_0);
2215 
2216  #if DEBUG == 1
2217  printf("J_0_x = %.6f\n",pPrec->J_0.x);
2218  printf("J_0_y = %.6f\n",pPrec->J_0.y);
2219  printf("J_0_z = %.6f\n\n",pPrec->J_0.z);
2220  #endif
2221 
2222  /* Norm of total initial spin */
2223  pPrec->S_0_norm = IMRPhenomX_vector_L2_norm(S0);
2224  pPrec->S_0_norm_2 = pPrec->S_0_norm * pPrec->S_0_norm;
2225 
2226  /* Norm of orbital and total angular momenta */
2227  pPrec->L_0_norm = IMRPhenomX_vector_L2_norm(pPrec->L_0);
2228  pPrec->J_0_norm = IMRPhenomX_vector_L2_norm(pPrec->J_0);
2229 
2230  const double L0norm = pPrec->L_0_norm;
2231  const double J0norm = pPrec->J_0_norm;
2232 
2233  #if DEBUG == 1
2234  printf("L_0_norm = %.6f\n",pPrec->L_0_norm);
2235  printf("J_0_norm = %.6f\n\n",pPrec->J_0_norm);
2236  #endif
2237 
2238  /* Useful powers */
2239  pPrec->S_0_norm_2 = pPrec->S_0_norm * pPrec->S_0_norm;
2240  pPrec->J_0_norm_2 = pPrec->J_0_norm * pPrec->J_0_norm;
2241  pPrec->L_0_norm_2 = pPrec->L_0_norm * pPrec->L_0_norm;
2242 
2243  /* Vector for obtaining B, C, D coefficients */
2244  UNUSED vector vBCD;
2246 
2247  #if DEBUG == 1
2248  printf("B = %.6f\n",vBCD.x);
2249  printf("C = %.6f\n",vBCD.y);
2250  printf("D = %.6f\n\n",vBCD.z);
2251  #endif
2252 
2253  /*
2254  Get roots to S^2 equation : S^2_+, S^2_-, S^2_3
2255  vroots.x = A1 = S_{3}^2
2256  vroots.y = A2 = S_{-}^2
2257  vroots.z = A3 = S_{+}^2
2258  */
2259  vector vRoots = {0.,0.,0.};
2260 
2261  vRoots = IMRPhenomX_Return_Roots_MSA(pPrec->L_0_norm,pPrec->J_0_norm,pPrec);
2262 
2263  // Set roots
2264  pPrec->Spl2 = vRoots.z;
2265  pPrec->Smi2 = vRoots.y;
2266  pPrec->S32 = vRoots.x;
2267 
2268  // S^2_+ + S^2_-
2269  pPrec->Spl2pSmi2 = pPrec->Spl2 + pPrec->Smi2;
2270 
2271  // S^2_+ - S^2_-
2272  pPrec->Spl2mSmi2 = pPrec->Spl2 - pPrec->Smi2;
2273 
2274  // S_+ and S_-
2275  pPrec->Spl = sqrt(pPrec->Spl2);
2276  pPrec->Smi = sqrt(pPrec->Smi2);
2277 
2278  /* Eq. 45 of PRD 95, 104004, (2017), arXiv:1703.03967, set from initial conditions */
2279  pPrec->SAv2 = 0.5 * ( pPrec->Spl2pSmi2 );
2280  pPrec->SAv = sqrt(pPrec->SAv2);
2281  pPrec->invSAv2 = 1.0 / pPrec->SAv2;
2282  pPrec->invSAv = 1.0 / pPrec->SAv;
2283 
2284  #if DEBUG == 1
2285  printf("From vRoots... \n");
2286  printf("Spl2 = %.6f\n",pPrec->Spl2);
2287  printf("Smi2 = %.6f\n",pPrec->Smi2);
2288  printf("S32 = %.6f\n",pPrec->S32);
2289  printf("SAv2 = %.6f\n",pPrec->SAv2);
2290  printf("SAv = %.6f\n\n",pPrec->SAv);
2291  #endif
2292 
2293  /* c_1 is determined by Eq. 41 of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2294  const double c_1 = 0.5 * (J0norm*J0norm - L0norm*L0norm - pPrec->SAv2) / pPrec->L_0_norm * eta;
2295  const double c1_2 = c_1 * c_1;
2296 
2297  /* Useful powers and combinations of c_1 */
2298  pPrec->c1 = c_1;
2299  pPrec->c12 = c_1 * c_1;
2300  pPrec->c1_over_eta = c_1 / eta;
2301 
2302  /* Average spin couplings over one precession cycle: A9 - A14 of arXiv:1703.03967 */
2303  const double omqsq = (1.0 - q) * (1.0 - q) + 1e-16;
2304  const double omq2 = (1.0 - q * q) + 1e-16;
2305 
2306  /* Precession averaged spin couplings, Eq. A9 - A14 of arXiv:1703.03967, note that we only use the initial values */
2307  pPrec->S1L_pav = (c_1 * (1.0 + q) - q * eta * Seff) / (eta * omq2);
2308  pPrec->S2L_pav = - q * (c_1 * (1.0 + q) - eta * Seff) / (eta * omq2);
2309  pPrec->S1S2_pav = 0.5 * pPrec->SAv2 - 0.5*(pPrec->S1_norm_2 + pPrec->S2_norm_2);
2310  pPrec->S1Lsq_pav = (pPrec->S1L_pav*pPrec->S1L_pav) + ((pPrec->Spl2mSmi2)*(pPrec->Spl2mSmi2) * pPrec->v_0_2) / (32.0 * eta2 * omqsq);
2311  pPrec->S2Lsq_pav = (pPrec->S2L_pav*pPrec->S2L_pav) + (q*q*(pPrec->Spl2mSmi2)*(pPrec->Spl2mSmi2) * pPrec->v_0_2) / (32.0 * eta2 * omqsq);
2312  pPrec->S1LS2L_pav = pPrec->S1L_pav*pPrec->S2L_pav - q * (pPrec->Spl2mSmi2)*(pPrec->Spl2mSmi2)*pPrec->v_0_2 / (32.0 * eta2 * omqsq);
2313 
2314  /* Spin couplings in arXiv:1703.03967 */
2315  pPrec->beta3 = ( (113./12.) + (25./4.)*(m2/m1) )*pPrec->S1L_pav + ( (113./12.) + (25./4.)*(m1/m2) )*pPrec->S2L_pav;
2316 
2317  pPrec->beta5 = ( ( (31319./1008.) - (1159./24.)*eta) + (m2/m1)*((809./84) - (281./8.)*eta) )*pPrec->S1L_pav
2318  + ( ( (31319./1008.) - (1159./24.)*eta) + (m1/m2)*((809./84) - (281./8.)*eta) )*pPrec->S2L_pav;
2319 
2320  pPrec->beta6 = LAL_PI * ( ( (75./2.) + (151./6.)*(m2/m1))*pPrec->S1L_pav + ( (75./2.) + (151./6.)*(m1/m2))*pPrec->S2L_pav );
2321 
2322  pPrec->beta7 = (
2323  ( (130325./756) - (796069./2016)*eta + (100019./864.)*eta2 ) + (m2/m1)*( (1195759./18144) - (257023./1008.)*eta + (2903/32.)*eta2 ) * pPrec->S1L_pav
2324  + ( (130325./756) - (796069./2016)*eta + (100019./864.)*eta2 ) + (m1/m2)*( (1195759./18144) - (257023./1008.)*eta + (2903/32.)*eta2 ) * pPrec->S2L_pav
2325  );
2326 
2327  pPrec->sigma4 = (1.0/mu) * ( (247./48.)*pPrec->S1S2_pav - (721./48.)*pPrec->S1L_pav*pPrec->S2L_pav )
2328  + (1.0/(m1*m1)) * ( (233./96.)*pPrec->S1_norm_2 - (719./96.)*pPrec->S1Lsq_pav )
2329  + (1.0/(m2*m2)) * ( (233./96.)*pPrec->S2_norm_2 - (719./96.)*pPrec->S2Lsq_pav );
2330 
2331  /* Compute PN coefficients using precession-averaged spin couplings */
2332  pPrec->a0 = 96.0 * eta / 5.0;
2333 
2334  /* These are all normalized by a factor of a0 */
2335  pPrec->a2 = -(743./336.) - (11.0/4.)*eta;
2336  pPrec->a3 = 4.0 * LAL_PI - pPrec->beta3;
2337  pPrec->a4 = (34103./18144.) + (13661./2016.)*eta + (59./18.)*eta2 - pPrec->sigma4;
2338  pPrec->a5 = -(4159./672.)*LAL_PI - (189./8.)*LAL_PI*eta - pPrec->beta5;
2339  pPrec->a6 = (16447322263./139708800.) + (16./3.)*LAL_PI*LAL_PI - (856./105)*log(16.) - (1712./105.)*LAL_GAMMA - pPrec->beta6
2340  + eta*( (451./48)*LAL_PI*LAL_PI - (56198689./217728.) ) + eta2*(541./896.) - eta3*(5605./2592.);
2341  pPrec->a7 = -(4415./4032.)*LAL_PI + (358675./6048.)*LAL_PI*eta + (91495./1512.)*LAL_PI*eta2 - pPrec->beta7;
2342 
2343  // Coefficients are weighted by an additional factor of a_0
2344  pPrec->a2 *= pPrec->a0;
2345  pPrec->a3 *= pPrec->a0;
2346  pPrec->a4 *= pPrec->a0;
2347  pPrec->a5 *= pPrec->a0;
2348  pPrec->a6 *= pPrec->a0;
2349  pPrec->a7 *= pPrec->a0;
2350 
2351  #if DEBUG == 1
2352  printf("a0 = %.6f\n",pPrec->a0);
2353  printf("a2 = %.6f\n",pPrec->a2);
2354  printf("a3 = %.6f\n",pPrec->a3);
2355  printf("a4 = %.6f\n",pPrec->a4);
2356  printf("a5 = %.6f\n\n",pPrec->a5);
2357  #endif
2358 
2359  /* For versions 222 and 223, we compute PN coefficients using initial spin couplings, as per LALSimInspiralFDPrecAngles_internals.c */
2360  if(pflag == 222 || pflag == 223)
2361  {
2362  pPrec->a0 = eta*domegadt_constants_NS[0];
2363  pPrec->a2 = eta*(domegadt_constants_NS[1] + eta*(domegadt_constants_NS[2]));
2364  pPrec->a3 = eta*(domegadt_constants_NS[3] + IMRPhenomX_Get_PN_beta(domegadt_constants_SO[0], domegadt_constants_SO[1], pPrec));
2365  pPrec->a4 = eta*(domegadt_constants_NS[4] + eta*(domegadt_constants_NS[5] + eta*(domegadt_constants_NS[6])) + IMRPhenomX_Get_PN_sigma(domegadt_constants_SS[0], domegadt_constants_SS[1], pPrec) + IMRPhenomX_Get_PN_tau(domegadt_constants_SS[2], domegadt_constants_SS[3], pPrec));
2366  pPrec->a5 = eta*(domegadt_constants_NS[7] + eta*(domegadt_constants_NS[8]) + IMRPhenomX_Get_PN_beta((domegadt_constants_SO[2] + eta*(domegadt_constants_SO[3])), (domegadt_constants_SO[4] + eta*(domegadt_constants_SO[5])), pPrec));
2367  }
2368 
2369  /* Debugging */
2370  #if DEBUG == 1
2371  printf("Using list of coefficients... \n");
2372  printf("a0 = %.6f\n",pPrec->a0);
2373  printf("a2 = %.6f\n",pPrec->a2);
2374  printf("a3 = %.6f\n",pPrec->a3);
2375  printf("a4 = %.6f\n",pPrec->a4);
2376  printf("a5 = %.6f\n\n",pPrec->a5);
2377  #endif
2378 
2379  /* Useful powers of a_0 */
2380  pPrec->a0_2 = pPrec->a0*pPrec->a0;
2381  pPrec->a0_3 = pPrec->a0_2*pPrec->a0;
2382  pPrec->a2_2 = pPrec->a2*pPrec->a2;
2383 
2384  /*
2385  Calculate g coefficients as in Appendix A of Chatziioannou et al, PRD, 95, 104004, (2017), arXiv:1703.03967.
2386  These constants are used in TaylorT2 where domega/dt is expressed as an inverse polynomial
2387  */
2388  pPrec->g0 = 1. / pPrec->a0;
2389 
2390  // Eq. A2 (1703.03967)
2391  pPrec->g2 = -(pPrec->a2 / pPrec->a0_2);
2392 
2393  // Eq. A3 (1703.03967)
2394  pPrec->g3 = -(pPrec->a3/pPrec->a0_2);
2395 
2396  // Eq.A4 (1703.03967)
2397  pPrec->g4 = -(pPrec->a4*pPrec->a0 - pPrec->a2_2) / pPrec->a0_3;
2398 
2399  // Eq. A5 (1703.03967)
2400  pPrec->g5 = -(pPrec->a5*pPrec->a0 - 2.0*pPrec->a3*pPrec->a2) / pPrec->a0_3;
2401 
2402  #if DEBUG == 1
2403  printf("g0 = %.6f\n",pPrec->g0);
2404  printf("g2 = %.6f\n",pPrec->g2);
2405  printf("g3 = %.6f\n",pPrec->g3);
2406  printf("g4 = %.6f\n",pPrec->g4);
2407  printf("g5 = %.6f\n\n",pPrec->g5);
2408  #endif
2409 
2410  // Useful powers of delta
2411  const double delta = pPrec->delta_qq;
2412  const double delta2 = delta * delta;
2413  const double delta3 = delta * delta2;
2414  const double delta4 = delta * delta3;
2415 
2416  // These are the phase coefficients of Eq. 51 of PRD, 95, 104004, (2017), arXiv:1703.03967
2417  pPrec->psi0 = 0.0;
2418  pPrec->psi1 = 0.0;
2419  pPrec->psi2 = 0.0;
2420 
2421  /* \psi_1 is defined in Eq. C1 of Appendix C in PRD, 95, 104004, (2017), arXiv:1703.03967 */
2422  pPrec->psi1 = 3.0 * (2.0 * eta2 * Seff - c_1) / (eta * delta2);
2423 
2424  double c_1_over_nu = pPrec->c1_over_eta;
2425  double c_1_over_nu_2 = c_1_over_nu * c_1_over_nu;
2426  double one_p_q_sq = (1.+q) * (1.+q);
2427  double Seff_2 = Seff * Seff;
2428  double q_2 = q * q;
2429  double one_m_q_sq = (1.-q)*(1.-q);
2430  double one_m_q2_2 = (1. - q_2) * (1. - q_2);
2431  double one_m_q_4 = one_m_q_sq * one_m_q_sq;
2432 
2433  double term1, term2, term3, term4, term5, term6, term7, term8;
2434 
2435  /* This implements the Delta term as in LALSimInspiralFDPrecAngles.c
2436  c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L145
2437  */
2438  if(pflag == 222 || pflag == 223)
2439  {
2440  const double Del1 = 4. * c_1_over_nu_2 * one_p_q_sq;
2441  const double Del2 = 8. * c_1_over_nu * q * (1. + q) * Seff;
2442  const double Del3 = 4. * (one_m_q2_2 * pPrec->S1_norm_2 - q_2 * Seff_2);
2443  const double Del4 = 4. * c_1_over_nu_2 * q_2 * one_p_q_sq;
2444  const double Del5 = 8. * c_1_over_nu * q_2 * (1. + q) * Seff;
2445  const double Del6 = 4. * (one_m_q2_2 * pPrec->S2_norm_2 - q_2 * Seff_2);
2446  pPrec->Delta = sqrt( fabs( (Del1 - Del2 - Del3) * (Del4 - Del5 - Del6) ));
2447  }
2448  else
2449  {
2450  /* Coefficients of \Delta as defined in Eq. C3 of Appendix C in PRD, 95, 104004, (2017), arXiv:1703.03967. */
2451  term1 = c1_2 * eta / (q * delta4);
2452  term2 = -2.0 * c_1 * eta3 * (1.0 + q) * Seff / (q * delta4);
2453  term3 = -eta2 * (delta2 * pPrec->S1_norm_2 - eta2 * Seff2) / delta4;
2454  /*
2455  Is this 1) (c1_2 * q * eta / delta4) or 2) c1_2*eta2/delta4?
2456 
2457  - In paper.pdf, the expression 1) is used.
2458 
2459  Using eta^2 leads to higher frequency oscillations, use q * eta
2460  */
2461  term4 = c1_2 * eta * q / delta4;
2462  term5 = -2.0*c_1*eta3*(1.0 + q)*Seff / delta4;
2463  term6 = -eta2 * (delta2*pPrec->S2_norm_2 - eta2*Seff2) / delta4;
2464 
2465  /* \Delta as in Eq. C3 of Appendix C in PRD, 95, 104004, (2017) */
2466  pPrec->Delta = sqrt( fabs( (term1 + term2 + term3) * (term4 + term5 + term6) ) );
2467  }
2468 
2469  /* This implements the Delta term as in LALSimInspiralFDPrecAngles.c
2470  c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L160
2471  */
2472  if(pflag == 222 || pflag == 223)
2473  {
2474  const double u1 = 3. * pPrec->g2 / pPrec->g0;
2475  const double u2 = 0.75 * one_p_q_sq / one_m_q_4;
2476  const double u3 = -20. * c_1_over_nu_2 * q_2 * one_p_q_sq;
2477  const double u4 = 2. * one_m_q2_2 * (q * (2. + q) * pPrec->S1_norm_2 + (1. + 2. * q) * pPrec->S2_norm_2 - 2. * q * pPrec->SAv2);
2478  const double u5 = 2. * q_2 * (7. + 6. * q + 7. * q_2) * 2. * c_1_over_nu * Seff;
2479  const double u6 = 2. * q_2 * (3. + 4. * q + 3. * q_2) * Seff_2;
2480  const double u7 = q * pPrec->Delta;
2481 
2482  /* Eq. C2 (1703.03967) */
2483  pPrec->psi2 = u1 + u2*(u3 + u4 + u5 - u6 + u7);
2484  }
2485  else
2486  {
2487  /* \psi_2 is defined in Eq. C2 of Appendix C in PRD, 95, 104004, (2017). Here we implement system of equations as in paper.pdf */
2488  term1 = 3.0 * pPrec->g2 / pPrec->g0;
2489 
2490  /* q^2 or no q^2 in term2? Consensus on retaining q^2 term: https://git.ligo.org/waveforms/reviews/phenompv3hm/issues/7 */
2491  term2 = 3.0 * q * q / (2.0 * eta3);
2492  term3 = 2.0 * pPrec->Delta;
2493  term4 = -2.0*eta2*pPrec->SAv2 / delta2;
2494  term5 = -10.*eta*c1_2 / delta4;
2495  term6 = 2.0 * eta2 * (7.0 + 6.0*q + 7.0*q*q) * c_1 * Seff / (omqsq * delta2);
2496  term7 = -eta3 * (3.0 + 4.0*q + 3.0*q*q) * Seff2 / (omqsq * delta2);
2497  term8 = eta * (q * (2.0+q)*pPrec->S1_norm_2 + (1.0 + 2.0*q)*pPrec->S2_norm_2) / ( omqsq );
2498 
2499  /* \psi_2, C2 of Appendix C of PRD, 95, 104004, (2017) */
2500  pPrec->psi2 = term1 + term2 * (term3 + term4 + term5 + term6 + term7 + term8);
2501  }
2502 
2503  #if DEBUG == 1
2504  printf("psi1 = %.6f\n",pPrec->psi1);
2505  printf("psi2 = %.6f\n\n",pPrec->psi2);
2506  #endif
2507 
2508  /* Eq. D1 of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2509  const double Rm = pPrec->Spl2 - pPrec->Smi2;
2510  const double Rm_2 = Rm * Rm;
2511 
2512  /* Eq. D2 and D3 Appendix D of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2513  const double cp = pPrec->Spl2 * eta2 - c1_2;
2514  double cm = pPrec->Smi2 * eta2 - c1_2;
2515 
2516  /*
2517  Check if cm goes negative, this is likely pathological. If so, set MSA_ERROR to 1, so that waveform generator can handle
2518  the error approriately
2519  */
2520  // if(cm < 0.0)
2521  // {
2522  // pPrec->MSA_ERROR = 1;
2523  // XLAL_PRINT_ERROR("Error, coefficient cm = %.16f, which is negative and likely to be pathological. Triggering MSA failure.\n",cm);
2524  // }
2525 
2526  /* fabs is here to help enforce positive definite cpcm */
2527  const double cpcm = fabs( cp * cm );
2528  const double sqrt_cpcm = sqrt(cpcm);
2529 
2530  /* Eq. D4 in PRD, 95, 104004, (2017), arXiv:1703.03967 ; Note difference to published version. */
2531  const double a1dD = 0.5 + 0.75/eta;
2532 
2533  /* Eq. D5 in PRD, 95, 104004, (2017), arXiv:1703.03967 */
2534  const double a2dD = -0.75*Seff/eta;
2535 
2536  /* Eq. E3 in PRD, 95, 104004, (2017), arXiv:1703.03967 ; Note that this is Rm * D2 */
2537  const double D2RmSq = (cp - sqrt_cpcm) / eta2 ;
2538 
2539  /* Eq. E4 in PRD, 95, 104004, (2017), arXiv:1703.03967 ; Note that this is Rm^2 * D4 */
2540  const double D4RmSq = -0.5*Rm*sqrt_cpcm/eta2 - cp/eta4*(sqrt_cpcm - cp);
2541 
2542  const double S0m = pPrec->S1_norm_2 - pPrec->S2_norm_2;
2543 
2544  /* Difference of spin norms squared, as used in Eq. D6 of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2545  double aw = (-3.*(1. + q)/q*(2.*(1. + q)*eta2*Seff*c_1 - (1. + q)*c1_2 + (1. - q)*eta2*S0m));
2546  double cw = 3./32./eta*Rm_2;
2547  double dw = 4.0*cp - 4.0*D2RmSq*eta2;
2548  double hw = -2.0*(2.0*D2RmSq - Rm)*c_1;
2549  double fw = Rm*D2RmSq - D4RmSq - 0.25*Rm_2;
2550 
2551  const double adD = aw / dw;
2552  const double hdD = hw / dw;
2553  const double cdD = cw / dw;
2554  const double fdD = fw / dw;
2555 
2556  const double gw = 3./16./eta2/eta*Rm_2*(c_1 - eta2*Seff);
2557  const double gdD = gw / dw;
2558 
2559  /* Useful powers of the coefficients */
2560  const double hdD_2 = hdD * hdD;
2561  const double adDfdD = adD * fdD;
2562  const double adDfdDhdD = adDfdD * hdD;
2563  const double adDhdD_2 = adD * hdD_2;
2564 
2565  #if DEBUG == 1
2566  printf("\na1dD = %.6f\n",a1dD);
2567  printf("a2dD = %.6f\n",a2dD);
2568  printf("adD = %.6f\n",adD);
2569  printf("cdD = %.6f\n",cdD);
2570  printf("hdD = %.6f\n",hdD);
2571  printf("fdD = %.6f\n",fdD);
2572  printf("Rm = %.6f\n",Rm);
2573  printf("Delta = %.6f\n",pPrec->Delta);
2574  printf("sqrt_cpcm = %.6f\n",sqrt_cpcm);
2575  printf("c1 = %.6f\n",pPrec->c1);
2576  printf("gdD = %.6f\n\n",gdD);
2577  #endif
2578 
2579  // Eq. D10 in PRD, 95, 104004, (2017), arXiv:1703.03967
2580  pPrec->Omegaz0 = a1dD + adD;
2581 
2582  // Eq. D11 in PRD, 95, 104004, (2017), arXiv:1703.03967
2583  pPrec->Omegaz1 = a2dD - adD*Seff - adD*hdD;
2584 
2585  // Eq. D12 in PRD, 95, 104004, (2017), arXiv:1703.03967
2586  pPrec->Omegaz2 = adD*hdD*Seff + cdD - adD*fdD + adD*hdD_2;
2587 
2588  // Eq. D13 in PRD, 95, 104004, (2017), arXiv:1703.03967
2589  pPrec->Omegaz3 = (adDfdD - cdD - adDhdD_2)*(Seff + hdD) + adDfdDhdD;
2590 
2591  // Eq. D14 in PRD, 95, 104004, (2017), arXiv:1703.03967
2592  pPrec->Omegaz4 = (cdD + adDhdD_2 - 2.0*adDfdD)*(hdD*Seff + hdD_2 - fdD) - adD*fdD*fdD;
2593 
2594  // Eq. D15 in PRD, 95, 104004, (2017), arXiv:1703.03967
2595  pPrec->Omegaz5 = (cdD - adDfdD + adDhdD_2) * fdD * (Seff + 2.0*hdD) - (cdD + adDhdD_2 - 2.0*adDfdD) * hdD_2 * (Seff + hdD) - adDfdD*fdD*hdD;
2596 
2597  #if DEBUG == 1
2598  printf("Omegaz0 = %.6f\n",pPrec->Omegaz0);
2599  printf("Omegaz1 = %.6f\n",pPrec->Omegaz1);
2600  printf("Omegaz2 = %.6f\n",pPrec->Omegaz2);
2601  printf("Omegaz3 = %.6f\n",pPrec->Omegaz3);
2602  printf("Omegaz4 = %.6f\n",pPrec->Omegaz4);
2603  printf("Omegaz5 = %.6f\n\n",pPrec->Omegaz5);
2604  #endif
2605 
2606  /*
2607  If Omegaz5 > 1000, this is larger than we expect and the system may be pathological.
2608  - Set MSA_ERROR = 1 to trigger an error
2609  */
2610  if(fabs(pPrec->Omegaz5) > 1000.0)
2611  {
2612  pPrec->MSA_ERROR = 1;
2613  XLAL_PRINT_WARNING("Warning, |Omegaz5| = %.16f, which is larger than expected and may be pathological. Triggering MSA failure.\n",pPrec->Omegaz5);
2614  }
2615 
2616  const double g0 = pPrec->g0;
2617 
2618  /* Coefficients of Eq. 65, as defined in Equations D16 - D21 of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2619  pPrec->Omegaz0_coeff = 3.0 * g0 * pPrec->Omegaz0;
2620  pPrec->Omegaz1_coeff = 3.0 * g0 * pPrec->Omegaz1;
2621  pPrec->Omegaz2_coeff = 3.0 * (g0 * pPrec->Omegaz2 + pPrec->g2*pPrec->Omegaz0);
2622  pPrec->Omegaz3_coeff = 3.0 * (g0 * pPrec->Omegaz3 + pPrec->g2*pPrec->Omegaz1 + pPrec->g3*pPrec->Omegaz0);
2623  pPrec->Omegaz4_coeff = 3.0 * (g0 * pPrec->Omegaz4 + pPrec->g2*pPrec->Omegaz2 + pPrec->g3*pPrec->Omegaz1 + pPrec->g4*pPrec->Omegaz0);
2624  pPrec->Omegaz5_coeff = 3.0 * (g0 * pPrec->Omegaz5 + pPrec->g2*pPrec->Omegaz3 + pPrec->g3*pPrec->Omegaz2 + pPrec->g4*pPrec->Omegaz1 + pPrec->g5*pPrec->Omegaz0);
2625 
2626  /* Coefficients of zeta: in Appendix E of PRD, 95, 104004, (2017), arXiv:1703.03967 */
2627  const double c1oveta2 = c_1 / eta2;
2628  pPrec->Omegazeta0 = pPrec->Omegaz0;
2629  pPrec->Omegazeta1 = pPrec->Omegaz1 + pPrec->Omegaz0 * c1oveta2;
2630  pPrec->Omegazeta2 = pPrec->Omegaz2 + pPrec->Omegaz1 * c1oveta2;
2631  pPrec->Omegazeta3 = pPrec->Omegaz3 + pPrec->Omegaz2 * c1oveta2 + gdD;
2632  pPrec->Omegazeta4 = pPrec->Omegaz4 + pPrec->Omegaz3 * c1oveta2 - gdD*Seff - gdD*hdD;
2633  pPrec->Omegazeta5 = pPrec->Omegaz5 + pPrec->Omegaz4 * c1oveta2 + gdD*hdD*Seff + gdD*(hdD_2 - fdD);
2634 
2635  #if DEBUG == 1
2636  printf("Omegazeta0 = %.6f\n",pPrec->Omegazeta0);
2637  printf("Omegazeta1 = %.6f\n",pPrec->Omegazeta1);
2638  printf("Omegazeta2 = %.6f\n",pPrec->Omegazeta2);
2639  printf("Omegazeta3 = %.6f\n",pPrec->Omegazeta3);
2640  printf("Omegazeta4 = %.6f\n",pPrec->Omegazeta4);
2641  printf("Omegazeta5 = %.6f\n\n",pPrec->Omegazeta5);
2642  #endif
2643 
2644  pPrec->Omegazeta0_coeff = -pPrec->g0 * pPrec->Omegazeta0;
2645  pPrec->Omegazeta1_coeff = -1.5 * pPrec->g0 * pPrec->Omegazeta1;
2646  pPrec->Omegazeta2_coeff = -3.0*(pPrec->g0 * pPrec->Omegazeta2 + pPrec->g2*pPrec->Omegazeta0);
2647  pPrec->Omegazeta3_coeff = 3.0*(pPrec->g0 * pPrec->Omegazeta3 + pPrec->g2*pPrec->Omegazeta1 + pPrec->g3*pPrec->Omegazeta0);
2648  pPrec->Omegazeta4_coeff = 3.0*(pPrec->g0 * pPrec->Omegazeta4 + pPrec->g2*pPrec->Omegazeta2 + pPrec->g3*pPrec->Omegazeta1 + pPrec->g4*pPrec->Omegazeta0);
2649  pPrec->Omegazeta5_coeff = 1.5*(pPrec->g0*pPrec->Omegazeta5 + pPrec->g2*pPrec->Omegazeta3 + pPrec->g3*pPrec->Omegazeta2 + pPrec->g4*pPrec->Omegazeta1 + pPrec->g5*pPrec->Omegazeta0);
2650 
2651  /* Expansion order of corrections to retain */
2652  switch(ExpansionOrder)
2653  {
2654  /* Generate all orders */
2655  case -1:
2656  {
2657  break;
2658  }
2659  case 1:
2660  {
2661  pPrec->Omegaz1_coeff = 0.0;
2662  pPrec->Omegazeta1_coeff = 0.0;
2663  #if __GNUC__ >= 7 && !defined __INTEL_COMPILER
2664  __attribute__ ((fallthrough));
2665  #endif
2666 
2667  }
2668  case 2:
2669  {
2670  pPrec->Omegaz2_coeff = 0.0;
2671  pPrec->Omegazeta2_coeff = 0.0;
2672  #if __GNUC__ >= 7 && !defined __INTEL_COMPILER
2673  __attribute__ ((fallthrough));
2674  #endif
2675 
2676  }
2677  case 3:
2678  {
2679  pPrec->Omegaz3_coeff = 0.0;
2680  pPrec->Omegazeta3_coeff = 0.0;
2681  #if __GNUC__ >= 7 && !defined __INTEL_COMPILER
2682  __attribute__ ((fallthrough));
2683  #endif
2684 
2685  }
2686  case 4:
2687  {
2688  pPrec->Omegaz4_coeff = 0.0;
2689  pPrec->Omegazeta4_coeff = 0.0;
2690  #if __GNUC__ >= 7 && !defined __INTEL_COMPILER
2691  __attribute__ ((fallthrough));
2692  #endif
2693 
2694  }
2695  case 5:
2696  {
2697  pPrec->Omegaz5_coeff = 0.0;
2698  pPrec->Omegazeta5_coeff = 0.0;
2699  break;
2700  }
2701  default:
2702  {
2703  XLAL_ERROR(XLAL_EDOM, "Expansion order for MSA corrections = %i not recognized. Default is 5. Allowed values are: [-1,1,2,3,4,5].",ExpansionOrder);
2704  }
2705  }
2706 
2707  #if DEBUG == 1
2708  printf("Omegaz0_coeff = %.6f\n",pPrec->Omegaz0_coeff);
2709  printf("Omegaz1_coeff = %.6f\n",pPrec->Omegaz1_coeff);
2710  printf("Omegaz2_coeff = %.6f\n",pPrec->Omegaz2_coeff);
2711  printf("Omegaz3_coeff = %.6f\n",pPrec->Omegaz3_coeff);
2712  printf("Omegaz4_coeff = %.6f\n",pPrec->Omegaz4_coeff);
2713  printf("Omegaz5_coeff = %.6f\n\n",pPrec->Omegaz5_coeff);
2714 
2715  printf("Omegazeta0_coeff = %.6f\n",pPrec->Omegazeta0_coeff);
2716  printf("Omegazeta1_coeff = %.6f\n",pPrec->Omegazeta1_coeff);
2717  printf("Omegazeta2_coeff = %.6f\n",pPrec->Omegazeta2_coeff);
2718  printf("Omegazeta3_coeff = %.6f\n",pPrec->Omegazeta3_coeff);
2719  printf("Omegazeta4_coeff = %.6f\n",pPrec->Omegazeta4_coeff);
2720  printf("Omegazeta5_coeff = %.6f\n\n",pPrec->Omegazeta5_coeff);
2721  #endif
2722 
2723  /* Get psi0 term */
2724  double psi_of_v0 = 0.0;
2725  double mm = 0.0;
2726  double tmpB = 0.0;
2727  double volume_element = 0.0;
2728  double vol_sign = 0.0;
2729 
2730  #if DEBUG == 1
2731  printf("psi1 = %.6f\n",pPrec->psi1);
2732  printf("psi2 = %.6f\n\n",pPrec->psi2);
2733  printf("S_0_norm = %.6f\n\n",pPrec->S_0_norm);
2734  #endif
2735 
2736  /* Tolerance chosen to be consistent with implementation in LALSimInspiralFDPrecAngles */
2737  if( fabs(pPrec->Smi2 - pPrec->Spl2) < 1.0e-5)
2738  {
2739  pPrec->psi0 = 0.0;
2740  }
2741  else
2742  {
2743  mm = sqrt( (pPrec->Smi2 - pPrec->Spl2) / (pPrec->S32 - pPrec->Spl2) );
2744  tmpB = (pPrec->S_0_norm*pPrec->S_0_norm - pPrec->Spl2) / (pPrec->Smi2 - pPrec->Spl2);
2745 
2746  volume_element = IMRPhenomX_vector_dot_product( IMRPhenomX_vector_cross_product(L_0,S1v), S2v);
2747  vol_sign = (volume_element > 0) - (volume_element < 0);
2748 
2749  psi_of_v0 = IMRPhenomX_psiofv(pPrec->v_0, pPrec->v_0_2, 0.0, pPrec->psi1, pPrec->psi2, pPrec);
2750 
2751  if( tmpB < 0. || tmpB > 1. )
2752  {
2753  if(tmpB > 1.0 && (tmpB - 1.) < 0.00001)
2754  {
2755  pPrec->psi0 = gsl_sf_ellint_F(asin(vol_sign*sqrt(1.)) , mm, GSL_PREC_DOUBLE ) - psi_of_v0;
2756  }
2757  if(tmpB < 0.0 && tmpB > -0.00001)
2758  {
2759  pPrec->psi0 = gsl_sf_ellint_F(asin(vol_sign*sqrt(0.)), mm, GSL_PREC_DOUBLE ) - psi_of_v0;
2760  }
2761  }
2762  else
2763  {
2764  pPrec->psi0 = gsl_sf_ellint_F(asin( vol_sign * sqrt(tmpB) ), mm, GSL_PREC_DOUBLE) - psi_of_v0;
2765  }
2766  }
2767 
2768  #if DEBUG == 1
2769  printf("psi0_of_v0 = %.6f\n",psi_of_v0);
2770  printf("tmpB = %.6f\n",tmpB);
2771  printf("psi0 = %.6f\n\n",pPrec->psi0);
2772  #endif
2773 
2774  vector vMSA = {0.,0.,0.};
2775 
2776  double phiz_0 = 0.0;
2777  UNUSED double phiz_0_MSA = 0.0;
2778 
2779  double zeta_0 = 0.0;
2780  UNUSED double zeta_0_MSA = 0.0;
2781 
2782  /* Tolerance chosen to be consistent with implementation in LALSimInspiralFDPrecAngles */
2783  if( fabs(pPrec->Spl2 - pPrec->Smi2) > 1.e-5 )
2784  {
2785  vMSA = IMRPhenomX_Return_MSA_Corrections_MSA(pPrec->v_0,pPrec->L_0_norm,pPrec->J_0_norm,pPrec);
2786 
2787  phiz_0_MSA = vMSA.x;
2788  zeta_0_MSA = vMSA.y;
2789  }
2790 
2791  // Initial \phi_z
2792  pPrec->phiz_0 = 0.0;
2793  phiz_0 = IMRPhenomX_Return_phiz_MSA(pPrec->v_0,pPrec->J_0_norm,pPrec);
2794 
2795  // Initial \zeta
2796  pPrec->zeta_0 = 0.0;
2797  zeta_0 = IMRPhenomX_Return_zeta_MSA(pPrec->v_0,pPrec);
2798 
2799  pPrec->phiz_0 = - phiz_0 - vMSA.x;
2800  pPrec->zeta_0 = - zeta_0 - vMSA.y;
2801 
2802  #if DEBUG == 1
2803  printf("v_0 = %.6f\n",pPrec->v_0);
2804  printf("c1 = %.6f\n\n",pPrec->c1);
2805 
2806  printf("eta = %.6f\n",pPrec->eta);
2807  printf("eta2 = %.6f\n",pPrec->eta2);
2808  printf("eta3 = %.6f\n",pPrec->eta3);
2809  printf("eta4 = %.6f\n",pPrec->eta4);
2810  printf("ieta = %.6f\n",pPrec->inveta);
2811  printf("ieta2 = %.6f\n",pPrec->inveta2);
2812  printf("ieta3 = %.6f\n\n",pPrec->inveta3);
2813 
2814  printf("SAv = %.6f\n",pPrec->SAv);
2815  printf("SAv2 = %.6f\n\n",pPrec->SAv2);
2816  printf("invSAv2 = %.6f\n\n",pPrec->invSAv2);
2817  printf("invSAv = %.6f\n\n",pPrec->invSAv);
2818 
2819  printf("J_0_norm = %.6f\n",pPrec->J_0_norm);
2820  printf("L_0_norm = %.6f\n\n",pPrec->L_0_norm);
2821 
2822  printf("phiz_0 = %.6f\n",pPrec->phiz_0);
2823  printf("zeta_0 = %.6f\n",pPrec->zeta_0);
2824  printf("phiz_0_MSA = %.6f\n",phiz_0_MSA);
2825  printf("zeta_0_MSA = %.6f\n\n",zeta_0_MSA);
2826  #endif
2827 
2828  return XLAL_SUCCESS;
2829 }
2830 
2831 double IMRPhenomX_psiofv(const double v, const double v2, const double psi0, const double psi1, const double psi2, const IMRPhenomXPrecessionStruct *pPrec)
2832 {
2833  /* Equation 51 in arXiv:1703.03967 */
2834  return ( psi0 - 0.75*pPrec->g0 * pPrec->delta_qq * (1.0 + psi1*v + psi2*v2) / (v2*v) );
2835 }
2836 
2837 /**
2838  Here we solve for the roots of Eq 21 in Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
2839  - Roots for (d S^2)/(d t^2) = -A^2 (S^2 -S+^2)(S^2 - S-^2)(S^2 - S3^2)
2840  - Returns Spl2 (S+^2), Smi2 (S-^2) and S3^2
2841 
2842  - Note: agrees with independent implementation in Mathematica
2843 */
2844 vector IMRPhenomX_Return_Roots_MSA(double LNorm, double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
2845 {
2846  vector vout;
2847 
2848  double tmp1, tmp2, tmp3, tmp4, tmp5, tmp6;
2849  tmp1 = 0.0;
2850  tmp2 = 0.0;
2851  tmp3 = 0.0;
2852  tmp4 = 0.0;
2853  tmp5 = 0.0;
2854  tmp6 = 0.0;
2855 
2856  vector vBCD;
2857  vBCD = IMRPhenomX_Return_Spin_Evolution_Coefficients_MSA(LNorm, JNorm, pPrec);
2858 
2859  /* Update struct. Note, that this agreed with independent implementation in Mathematica. */
2860  const double B = vBCD.x;
2861  const double C = vBCD.y;
2862  const double D = vBCD.z;
2863 
2864  const double S1Norm2 = pPrec->S1_norm_2;
2865  const double S2Norm2 = pPrec->S2_norm_2;
2866 
2867  const double S0Norm2 = pPrec->S_0_norm_2;
2868 
2869  const double B2 = B * B;
2870  const double B3 = B2 * B;
2871  const double BC = B * C;
2872 
2873  const double p = C - B2 / 3;
2874  const double qc = (2.0/27.0)*B3 - BC/3.0 + D;
2875 
2876  const double sqrtarg = sqrt(-p/3.0);
2877  double acosarg = 1.5 * qc/p/sqrtarg;
2878 
2879  // Make sure that acosarg is appropriately bounded
2880  if(acosarg < -1)
2881  {
2882  acosarg = -1;
2883  }
2884  if(acosarg > 1)
2885  {
2886  acosarg = +1;
2887  }
2888  const double theta = acos(acosarg) / 3.0;
2889  const double cos_theta = cos(theta);
2890 
2891  const double dotS1Ln = pPrec->dotS1Ln;
2892  const double dotS2Ln = pPrec->dotS2Ln;
2893 
2894  double S32, Spl2, Smi2;
2895 
2896  // tmp1 = S32
2897  // tmp2 = Smi2
2898  // tmp3 = Spl2
2899  if(theta != theta || sqrtarg!=sqrtarg || dotS1Ln == 1 || dotS2Ln == 1 || dotS1Ln == -1 || dotS2Ln == -1 || S1Norm2 == 0
2900  || S2Norm2 == 0)
2901  {
2902  S32 = 0.0;
2903 
2904  Smi2 = S0Norm2;
2905 
2906  /*
2907  Add a numerical perturbation to prevent azimuthal precession angle
2908  from diverging.
2909  */
2910 
2911  // Smi2 = S02^2 + epsilon perturbation
2912  Spl2 = Smi2 + 1e-9;
2913  }
2914  else
2915  {
2916  /* E.g. see discussion on elliptic functions in arXiv:0711.4064 */
2917  tmp1 = 2.0*sqrtarg*cos(theta - 2.0*LAL_TWOPI/3.0) - B/3.0;
2918  tmp2 = 2.0*sqrtarg*cos(theta - LAL_TWOPI/3.0) - B/3.0;
2919  tmp3 = 2.0*sqrtarg*cos_theta - B/3.0;
2920 
2921  tmp4 = fmax(fmax(tmp1,tmp2),tmp3);
2922  tmp5 = fmin(fmin(tmp1,tmp2),tmp3);
2923 
2924  // As tmp1 and tmp3 are set by findind the min and max, find the remaining root
2925  if( (tmp4 - tmp3) > 0.0 && (tmp5 - tmp3) < 0.0)
2926  {
2927  tmp6 = tmp3;
2928  }
2929  else if( (tmp4 - tmp1) > 0.0 && (tmp5 - tmp1) < 0.0)
2930  {
2931  tmp6 = tmp1;
2932  }
2933  else
2934  {
2935  tmp6 = tmp2;
2936  }
2937 
2938  /*
2939  When Spl2 ~ 0 to numerical roundoff then Smi2 can sometimes be ~ negative causing NaN's.
2940  This occurs in a very limited portion of the parameter space where spins are ~ 0 to numerical roundoff.
2941  We can circumvent by enforcing +ve definite behaviour when tmp4 ~ 0. Note that S32 can often be negative, this is fine.
2942  */
2943  tmp4 = fabs(tmp4);
2944  tmp6 = fabs(tmp6);
2945 
2946  // Return the roots
2947  Spl2 = tmp4;
2948  S32 = tmp5;
2949  Smi2 = tmp6;
2950  }
2951 
2952  vout.x = S32;
2953  vout.y = Smi2;
2954  vout.z = Spl2;
2955 
2956  return vout;
2957 }
2958 
2959 /**
2960  Get norm of J using Eq 41 of Chatziioannou et al, PRD 95, 104004, (2017)
2961 */
2962 double IMRPhenomX_JNorm_MSA(const double LNorm, IMRPhenomXPrecessionStruct *pPrec)
2963 {
2964  const double JNorm2 = (LNorm*LNorm + (2.0 * LNorm * pPrec->c1_over_eta) + pPrec->SAv2);
2965  return sqrt(JNorm2);
2966 }
2967 
2968 /**
2969  Get norm of S, see PRD 95, 104004, (2017)
2970 */
2972 {
2973  /*
2974  sn, cn are Jacobi elliptic functions
2975  psi is the phase and m a parameter entering the Jacobi elliptic functions
2976  */
2977  double sn, cn, dn, m, psi;
2978  double v2 = v*v;
2979 
2980  /*
2981  If spin norms ~ cancel then we do not need to evaluate the Jacobi elliptic function.
2982  Check tolerance?
2983  */
2984  if( fabs(pPrec->Smi2 - pPrec->Spl2) < 1.0e-5 )
2985  {
2986  sn = 0.0;
2987  }
2988  else
2989  {
2990  /* Equation 25 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967 */
2991  m = (pPrec->Smi2 - pPrec->Spl2) / (pPrec->S32 - pPrec->Spl2);
2992 
2993  psi = IMRPhenomX_psiofv(v, v2, pPrec->psi0, pPrec->psi1, pPrec->psi2, pPrec);
2994 
2995  /* Evaluate the Jacobi ellptic functions */
2996  gsl_sf_elljac_e(psi, m, &sn, &cn, &dn);
2997  }
2998 
2999  /* Equation 23 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967 */
3000  const double SNorm2 = pPrec->Spl2 + (pPrec->Smi2 - pPrec->Spl2)*sn*sn;
3001 
3002  return sqrt(SNorm2);
3003 }
3004 
3005 
3006 /**
3007  Get coefficients for Eq 21 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3008 */
3009 vector IMRPhenomX_Return_Spin_Evolution_Coefficients_MSA(const double LNorm, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
3010 {
3011  vector vout;
3012 
3013  // Total angular momenta: J = L + S1 + S2
3014  const double JNorm2 = JNorm * JNorm;
3015 
3016  // Orbital angular momenta
3017  const double LNorm2 = LNorm * LNorm;
3018 
3019  // Dimensionfull spin angular momenta
3020  const double S1Norm2 = pPrec->S1_norm_2;
3021  const double S2Norm2 = pPrec->S2_norm_2;
3022 
3023  const double q = pPrec->qq;
3024  const double eta = pPrec->eta;
3025 
3026  const double J2mL2 = (JNorm2 - LNorm2);
3027  const double J2mL2Sq = J2mL2 * J2mL2;
3028 
3029  const double delta = pPrec->delta_qq;
3030  const double deltaSq = delta*delta;
3031 
3032 
3033  /*
3034  Note:
3035  S_{eff} \equiv \xi = (1 + q)(S1.L) + (1 + 1/q)(S2.L)
3036  */
3037  const double Seff = pPrec->Seff;
3038 
3039  // Note that we do not evaluate Eq. B1 here as it is v dependent whereas B, C and D are not
3040 
3041  // Set Eq. B2, B_coeff
3042  vout.x = (LNorm2 + S1Norm2)*q + 2.0*LNorm*Seff - 2.0*JNorm2 -
3043  S1Norm2 - S2Norm2 + (LNorm2 + S2Norm2)/q;
3044 
3045  // Set Eq. B3, C_coeff
3046  vout.y = J2mL2Sq - 2.0*LNorm*Seff*J2mL2 - 2.0*((1.0 - q)/q)*LNorm2*(S1Norm2 - q*S2Norm2) +
3047  4.0*eta*LNorm2*Seff*Seff - 2.0*delta*(S1Norm2 - S2Norm2)*Seff*LNorm +
3048  2.0*((1.0 - q)/q)*(q*S1Norm2 - S2Norm2)*JNorm2;
3049 
3050  // Set Eq. B4, D_coeff
3051  vout.z = ((1.0 - q)/q)*(S2Norm2 - q*S1Norm2)*J2mL2Sq
3052  + deltaSq*(S1Norm2 - S2Norm2)*(S1Norm2 - S2Norm2)*LNorm2/eta
3053  + 2.0*delta*LNorm*Seff*(S1Norm2 - S2Norm2)*J2mL2;
3054 
3055 
3056 
3057 
3058  return vout;
3059 }
3060 
3061 
3062 
3063 /**
3064  Get c constants from Appendix B (B6, B7, B8) of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3065 */
3066 vector IMRPhenomX_Return_Constants_c_MSA(const double v, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
3067 {
3068  const double v2 = v*v;
3069  const double v3 = v*v2;
3070  const double v4 = v2*v2;
3071  const double v6 = v3*v3;
3072 
3073  const double JNorm2 = JNorm * JNorm;
3074 
3075  vector vout = {0.,0.,0.};
3076 
3077  const double Seff = pPrec->Seff;
3078 
3079  if(pPrec->IMRPhenomXPrecVersion != 220)
3080  {
3081  // Equation B6 of Chatziioannou et al, PRD 95, 104004, (2017)
3082  vout.x = JNorm * ( 0.75*(1.0 - Seff*v) * v2 * (pPrec->eta3 + 4.0*pPrec->eta3*Seff*v
3083  - 2.0*pPrec->eta*(JNorm2 - pPrec->Spl2 + 2.0*(pPrec->S1_norm_2 - pPrec->S2_norm_2)*pPrec->delta_qq)*v2
3084  - 4.0*pPrec->eta*Seff*(JNorm2 - pPrec->Spl2)*v3 + (JNorm2 - pPrec->Spl2)*(JNorm2 - pPrec->Spl2)*v4*pPrec->inveta) );
3085 
3086  // Equation B7 of Chatziioannou et al, PRD 95, 104004, (2017)
3087  vout.y = JNorm * ( -1.5 * pPrec->eta * (pPrec->Spl2 - pPrec->Smi2)*(1.0 + 2.0*Seff*v - (JNorm2 - pPrec->Spl2)*v2*pPrec->inveta2) * (1.0 - Seff*v)*v4 );
3088 
3089  // Equation B8 of Chatziioannou et al, PRD 95, 104004, (2017)
3090  vout.z = JNorm * ( 0.75 * pPrec->inveta * (pPrec->Spl2 - pPrec->Smi2)*(pPrec->Spl2 - pPrec->Smi2)*(1.0 - Seff * v)*v6 );
3091  }
3092  else
3093  {
3094  /* This is as implemented in LALSimInspiralFDPrecAngles, should be equivalent to above code.
3095  c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L578
3096  */
3097  double v_2 = v * v;
3098  double v_3 = v * v_2;
3099  double v_4 = v_2 * v_2;
3100  double v_6 = v_2 * v_4;
3101  double J_norm = JNorm;
3102  double delta = pPrec->delta_qq;
3103  double eta = pPrec->eta;
3104  double eta_2 = eta * eta;
3105 
3106  vout.x = -0.75*((JNorm2-pPrec->Spl2)*(JNorm2-pPrec->Spl2)*v_4/(pPrec->eta) - 4.*(pPrec->eta)*(pPrec->Seff)*(JNorm2-pPrec->Spl2)*v_3-2.*(JNorm2-pPrec->Spl2+2*((pPrec->S1_norm_2)-(pPrec->S2_norm_2))*(delta))*(pPrec->eta)*v_2+(4.*(pPrec->Seff)*v+1)*(pPrec->eta)*(eta_2)) *J_norm*v_2*((pPrec->Seff)*v-1.);
3107  vout.y = 1.5*(pPrec->Smi2-pPrec->Spl2)*J_norm*((JNorm2-pPrec->Spl2)/(pPrec->eta)*v_2-2.*(pPrec->eta)*(pPrec->Seff)*v-(pPrec->eta))*((pPrec->Seff)*v-1.)*v_4;
3108  vout.z = -0.75*J_norm*((pPrec->Seff)*v-1.)*(pPrec->Spl2-pPrec->Smi2)*(pPrec->Spl2-pPrec->Smi2)*v_6/(pPrec->eta);
3109  }
3110 
3111  return vout;
3112 
3113 }
3114 
3115 /**
3116  Get d constants from Appendix B (B9, B10, B11) of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3117 */
3118 vector IMRPhenomX_Return_Constants_d_MSA(const double LNorm, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
3119 {
3120 
3121  const double LNorm2 = LNorm * LNorm;
3122  const double JNorm2 = JNorm * JNorm;
3123 
3124  vector vout = {0.,0.,0.};
3125 
3126  vout.x = -( JNorm2 - (LNorm + pPrec->Spl)*(LNorm + pPrec->Spl))
3127  * ( (JNorm2 - (LNorm - pPrec->Spl)*(LNorm - pPrec->Spl)) );
3128  vout.y = -2.0*(pPrec->Spl2 - pPrec->Smi2)*(JNorm2 + LNorm2 - pPrec->Spl2);
3129  vout.z = -(pPrec->Spl2 - pPrec->Smi2)*(pPrec->Spl2 - pPrec->Smi2);
3130 
3131  return vout;
3132 }
3133 
3134 
3135 
3136 /**
3137  Calculate (L dot J)
3138 */
3139 double IMRPhenomX_costhetaLJ(const double L_norm, const double J_norm, const double S_norm)
3140 {
3141 
3142  double costhetaLJ = 0.5*(J_norm*J_norm + L_norm*L_norm - S_norm*S_norm)/(L_norm * J_norm);
3143 
3144  if (costhetaLJ > 1.0) costhetaLJ = +1.0;
3145  if (costhetaLJ < -1.0) costhetaLJ = -1.0;
3146 
3147  return costhetaLJ;
3148 }
3149 
3150 /**
3151  Get \f$\psi\f$ using Eq 51 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
3152  - Here \f$\psi\f$ is the phase of S as in Eq 23
3153  - Note that the coefficients are defined in Appendix C (C1 and C2)
3154 */
3155 double IMRPhenomX_Return_Psi_MSA(double v, double v2, const IMRPhenomXPrecessionStruct *pPrec)
3156 {
3157  return ( -0.75 * pPrec->g0 * pPrec->delta_qq * (1.0 + pPrec->psi1*v + pPrec->psi2*v2) / (v2*v) );
3158 }
3159 
3160 /**
3161  Get \f$\dot{\psi}\f$ using Eq 24 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
3162 */
3163 double IMRPhenomX_Return_Psi_dot_MSA(const double v, const IMRPhenomXPrecessionStruct *pPrec)
3164 {
3165  /*
3166  \frac{d \psi}{d t} = \frac{A}{2} \sqrt{S+^2 - S3^2}
3167  */
3168 
3169  const double v2 = v*v;
3170 
3171  const double A_coeff = -1.5 * (v2 * v2 * v2) * (1.0 - v*pPrec->Seff) * pPrec->sqrt_inveta;
3172  const double psi_dot = 0.5 * A_coeff * sqrt(pPrec->Spl2 - pPrec->S32);
3173 
3174  return psi_dot;
3175 }
3176 
3177 /**
3178  Get \f$\phi_z\f$ using Eq 66 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
3179  - The coefficients are given in Appendix D (D15 - D26)
3180 */
3181 double IMRPhenomX_Return_phiz_MSA(const double v, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
3182 {
3183  const double invv = 1.0/v;
3184  const double invv2 = invv * invv;
3185 
3186  const double LNewt = (pPrec->eta/v);
3187 
3188  const double c1 = pPrec->c1;
3189  const double c12 = c1 * c1;
3190 
3191  const double SAv2 = pPrec->SAv2;
3192  const double SAv = pPrec->SAv;
3193  const double invSAv = pPrec->invSAv;
3194  const double invSAv2 = pPrec->invSAv2;
3195 
3196  // These are log functions defined in Eq. D27 and D28 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3197  const double log1 = log( fabs(c1 + JNorm*pPrec->eta + pPrec->eta*LNewt) );
3198  const double log2 = log( fabs(c1 + JNorm*SAv*v + SAv2*v) );
3199 
3200  // Eq. D22 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3201  const double phiz_0_coeff = (JNorm * pPrec->inveta4) * (0.5*c12 - c1*pPrec->eta2*invv/6.0 - SAv2*pPrec->eta2/3.0 - pPrec->eta4*invv2/3.0)
3202  - (c1 * 0.5 * pPrec->inveta)*(c12 * pPrec->inveta4 - SAv2 * pPrec->inveta2)*log1;
3203 
3204  // Eq. D23 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967;
3205  // Note the factor of c12 in the second term
3206  const double phiz_1_coeff = - 0.5 * JNorm * pPrec->inveta2 * (c1 + pPrec->eta * LNewt) + 0.5*pPrec->inveta3*(c12 - pPrec->eta2*SAv2)*log1;
3207 
3208  // Eq. D24 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3209  const double phiz_2_coeff = -JNorm + SAv*log2 - c1*log1*pPrec->inveta;
3210 
3211  // Eq. D25 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3212  const double phiz_3_coeff = JNorm*v - pPrec->eta*log1 + c1*log2*pPrec->invSAv;
3213 
3214  // Eq. D26 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3215  const double phiz_4_coeff = (0.5*JNorm*invSAv2*v)*(c1 + v*SAv2) - (0.5*invSAv2*invSAv)*(c12 - pPrec->eta2*SAv2)*log2;
3216 
3217  // Eq. D27 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3218  const double phiz_5_coeff = -JNorm*v*( (0.5*c12*invSAv2*invSAv2) - (c1*v*invSAv2/6.0) - v*v/3.0 - pPrec->eta2*invSAv2/3.0)
3219  + (0.5*c1*invSAv2*invSAv2*invSAv)*(c12 - pPrec->eta2*SAv2)*log2;
3220 
3221  /*
3222  Eq. 66 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3223 
3224  \phi_{z,-1} = \sum^5_{n=0} <\Omega_z>^(n) \phi_z^(n) + \phi_{z,-1}^0
3225 
3226  Note that the <\Omega_z>^(n) are given by pPrec->Omegazn_coeff's as in Eqs. D15-D20
3227  */
3228  double phiz_out = ( phiz_0_coeff*pPrec->Omegaz0_coeff
3229  + phiz_1_coeff*pPrec->Omegaz1_coeff
3230  + phiz_2_coeff*pPrec->Omegaz2_coeff
3231  + phiz_3_coeff*pPrec->Omegaz3_coeff
3232  + phiz_4_coeff*pPrec->Omegaz4_coeff
3233  + phiz_5_coeff*pPrec->Omegaz5_coeff
3234  + pPrec->phiz_0
3235  );
3236 
3237  if (phiz_out != phiz_out) phiz_out = 0;
3238 
3239  return (phiz_out);
3240 }
3241 
3242 /**
3243  Get \f$\zeta\f$ using Eq F5 in Appendix F of Chatziioannou et al, PRD 95, 104004, (2017):
3244 */
3245 double IMRPhenomX_Return_zeta_MSA(const double v, const IMRPhenomXPrecessionStruct *pPrec)
3246 {
3247  /*
3248  Eq. F5 of Chatziioannou et al, PRD 95, 104004, (2017)
3249 
3250  \zeta_{z,-1} = \eta v^{-3} \sum^5_{n=0} <\Omega_{\zeta}>^(n) v^(n) + \zeta_{-1}^0
3251 
3252  Note that the <\Omega_{\eta}>^(n) are given by pPrec->Omegazetan_coeff's as in Eqs. F6-F11
3253  */
3254 
3255  const double invv = 1.0/v;
3256  const double invv2 = invv*invv;
3257  const double invv3 = invv*invv2;
3258  const double v2 = v*v;
3259  const double logv = log(v);
3260 
3261  // \zeta_{z,-1}
3262  // Note factor of log(v) as per LALSimInspiralFDPrecAngles_internals.c, https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L718
3263  double zeta_out = pPrec->eta * (
3264  pPrec->Omegazeta0_coeff*invv3
3265  + pPrec->Omegazeta1_coeff*invv2
3266  + pPrec->Omegazeta2_coeff*invv
3267  + pPrec->Omegazeta3_coeff*logv
3268  + pPrec->Omegazeta4_coeff*v
3269  + pPrec->Omegazeta5_coeff*v2
3270  )
3271  + pPrec->zeta_0;
3272 
3273  if (zeta_out != zeta_out) zeta_out = 0;
3274 
3275  // \zeta_{z,-1}
3276  return zeta_out;
3277 }
3278 
3279 /*
3280  Get MSA corrections to \zeta and \phi_z using Eq. F19 in Appendix F and Eq. 67 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967 respectively:
3281  -
3282 */
3283 vector IMRPhenomX_Return_MSA_Corrections_MSA(double v, double LNorm, double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
3284 {
3285  vector c_vec = {0.,0.,0.};
3286  vector d_vec = {0.,0.,0.};
3287 
3288  int pflag = pPrec->IMRPhenomXPrecVersion;
3289 
3290  double v2 = v * v;
3291 
3292  vector vMSA = {0.,0.,0.};
3293 
3294  // Sets c0, c2 and c4 in pPrec as per Eq. B6-B8 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3295  c_vec = IMRPhenomX_Return_Constants_c_MSA(v, JNorm, pPrec);
3296 
3297  // Sets d0, d2 and d4 in pPrec as per Eq. B9-B11 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3298  d_vec = IMRPhenomX_Return_Constants_d_MSA(LNorm, JNorm, pPrec);
3299 
3300  const double c0 = c_vec.x;
3301  const double c2 = c_vec.y;
3302  const double c4 = c_vec.z;
3303 
3304  const double d0 = d_vec.x;
3305  const double d2 = d_vec.y;
3306  const double d4 = d_vec.z;
3307 
3308  // Pre-cache a bunch of useful variables
3309  const double two_d0 = 2.0 * d0;
3310 
3311  // Eq. B20 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3312  const double sd = sqrt( fabs(d2*d2 - 4.0*d0*d4) );
3313 
3314  // Eq. F20 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3315  const double A_theta_L = 0.5 * ( (JNorm/LNorm) + (LNorm/JNorm) - (pPrec->Spl2 / (JNorm * LNorm)) );
3316 
3317  // Eq. F21 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967
3318  const double B_theta_L = 0.5 * pPrec->Spl2mSmi2 / (JNorm * LNorm);
3319 
3320  // Coefficients for B16
3321  const double nc_num = 2.0*(d0 + d2 + d4);
3322  const double nc_denom = two_d0 + d2 + sd;
3323 
3324  // Equations B16 and B17 respectively
3325  const double nc = nc_num / nc_denom;
3326  const double nd = nc_denom / two_d0;
3327 
3328  const double sqrt_nc = sqrt(fabs(nc));
3329  const double sqrt_nd = sqrt(fabs(nd));
3330 
3331  // Get phase and phase evolution of S
3332  const double psi = IMRPhenomX_Return_Psi_MSA(v,v2,pPrec) + pPrec->psi0;
3333  const double psi_dot = IMRPhenomX_Return_Psi_dot_MSA(v,pPrec);
3334 
3335  // Trigonometric calls are expensive, pre-cache them
3336  // Note: arctan(tan(x)) = 0 if and only if x \in (−pi/2,pi/2).
3337  const double tan_psi = tan(psi);
3338  const double atan_psi = atan(tan_psi);
3339 
3340  double C1, C2, C2num, C2den;
3341 
3342  // Eq. B18
3343  C1 = -0.5 * (c0/d0 - 2.0*(c0+c2+c4)/nc_num);
3344 
3345  // Eq. B19
3346  C2num = c0*( -2.0*d0*d4 + d2*d2 + d2*d4 ) - c2*d0*( d2 + 2.0*d4 ) + c4*d0*( two_d0 + d2 );
3347  C2den = 2.0 * d0 * sd * (d0 + d2 + d4);
3348  C2 = C2num / C2den;
3349 
3350  // These are defined in Appendix B, B14 and B15 respectively
3351  double Cphi, Dphi;
3352  Cphi = (C1 + C2);
3353  Dphi = (C1 - C2);
3354 
3355  double phiz_0_MSA_Cphi_term, phiz_0_MSA_Dphi_term;
3356 
3357  /* Calculate C_phi term in Eq. 67 */
3358  if(nc == 1.0)
3359  {
3360  // Limit implied by Eq. 67
3361  phiz_0_MSA_Cphi_term = 0.0;
3362  }
3363  else
3364  {
3365  if(pflag == 222 || pflag == 223)
3366  {
3367  // As implemented in LALSimInspiralFDPrecAngles.c, c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L772
3368  phiz_0_MSA_Cphi_term = fabs( (c4 * d0 * ((2*d0+d2) + sd) - c2 * d0 * ((d2+2.*d4) - sd) - c0 * ((2*d0*d4) - (d2+d4) * (d2 - sd))) / (C2den)) * (sqrt_nc / (nc - 1.) * (atan_psi - atan(sqrt_nc * tan_psi))) / psi_dot;
3369  }
3370  else
3371  {
3372  // First term in Eq. 67
3373  phiz_0_MSA_Cphi_term = ( (Cphi / psi_dot) * sqrt_nc / (nc - 1.0) ) * atan( ( (1.0 - sqrt_nc) * tan_psi ) / ( 1.0 + (sqrt_nc * tan_psi * tan_psi) ) );
3374  }
3375  }
3376 
3377  /* Calculate D_phi term in Eq. 67 */
3378  if(nd == 1.0)
3379  {
3380  // Limit implied by Eq. 67
3381  phiz_0_MSA_Dphi_term = 0.0;
3382  }
3383  else
3384  {
3385  if(pflag == 222 || pflag == 223)
3386  {
3387  // As implemented in LALSimInspiralFDPrecAngles.c, c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L779
3388  phiz_0_MSA_Dphi_term = fabs( (-c4 * d0 * ((2*d0+d2) - sd) + c2 * d0 * ((d2+2.*d4) + sd) - c0 * (-(2*d0*d4) + (d2+d4) * (d2 + sd)))) / (C2den) * (sqrt_nd / (nd - 1.) * (atan_psi - atan(sqrt_nd * tan_psi))) / psi_dot;
3389  }
3390  else
3391  {
3392  // Second term in Eq. 67
3393  phiz_0_MSA_Dphi_term = ( (Dphi / psi_dot) * sqrt_nd / (nd - 1.0) ) * atan( ( (1.0 - sqrt_nd) * tan_psi ) / ( 1.0 + (sqrt_nd * tan_psi * tan_psi) ) );
3394  }
3395  }
3396 
3397  // Eq. 67
3398  vMSA.x = ( phiz_0_MSA_Cphi_term + phiz_0_MSA_Dphi_term );
3399 
3400  /*
3401  The first MSA correction to \zeta as given in Eq. F19
3402  */
3403  if(pflag == 222 || pflag == 223 || pflag == 224)
3404  {
3405  /*
3406  As implemented in LALSimInspiralFDPrecAngles.c, c.f. https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L786
3407  Note that Cphi and Dphi are *not* used but phiz_0_MSA_Cphi_term and phiz_0_MSA_Dphi_term are
3408  */
3409  vMSA.y = A_theta_L*vMSA.x + 2.*B_theta_L*d0*(phiz_0_MSA_Cphi_term/(sd-d2) - phiz_0_MSA_Dphi_term/(sd+d2));
3410  }
3411  else
3412  {
3413  /* Eq. F19 as in arXiv:1703.03967 */
3414  vMSA.y = ( ( A_theta_L * (Cphi + Dphi) ) + (2.0 * d0 * B_theta_L) * ( ( Cphi / (sd - d2) ) - ( Dphi / (sd + d2) ) ) ) / psi_dot;
3415  }
3416 
3417  // Return 0 if the angles are NAN
3418  if (vMSA.x != vMSA.x) vMSA.x = 0;
3419  if (vMSA.y != vMSA.y) vMSA.y = 0;
3420 
3421  // Obsolete component that we initialize to zero just in case
3422  vMSA.z = 0.0;
3423 
3424  return vMSA;
3425 }
3426 
3427 
3428 /**
3429  * Internal function to computes the PN spin-orbit couplings. As in LALSimInspiralFDPrecAngles.c
3430  * cf https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L798
3431  */
3432 double IMRPhenomX_Get_PN_beta(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
3433 {
3434  return (pPrec->dotS1L* (a + b*pPrec->qq) + pPrec->dotS2L*(a + b/pPrec->qq));
3435 }
3436 
3437 /**
3438  * Internal function to compute PN spin-spin couplings. As in LALSimInspiralFDPrecAngles.c
3439  * cf https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiralFDPrecAngles_internals.c#L806
3440  */
3441 double IMRPhenomX_Get_PN_sigma(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
3442 {
3443  return pPrec->inveta * (a*pPrec->dotS1S2 - b*pPrec->dotS1L*pPrec->dotS2L);
3444 }
3445 
3446 /**
3447  * Internal function to computes PN spin-spin couplings. As in LALSimInspiralFDPrecAngles.c
3448  */
3449 double IMRPhenomX_Get_PN_tau(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
3450 {
3451  return (pPrec->qq * ( (pPrec->S1_norm_2 * a) - b*pPrec->dotS1L*pPrec->dotS1L) + (a*pPrec->S2_norm_2 - b*pPrec->dotS2L*pPrec->dotS2L) / pPrec->qq) / pPrec->eta;
3452 }
3453 
3454 
3455 
3456 /* ~~~~~~~~~~ Vector Utility Functions ~~~~~~~~~~ */
3457 double IMRPhenomX_vector_dot_product(const vector v1, const vector v2)
3458 {
3459  return (v1.x*v2.x) + (v1.y*v2.y) + (v1.z*v2.z);
3460 }
3461 
3462 
3464 {
3465  vector v3;
3466  v3.x = v1.y*v2.z - v1.z*v2.y;
3467  v3.y = v1.z*v2.x - v1.x*v2.z;
3468  v3.z = v1.x*v2.y - v1.y*v2.x;
3469 
3470  return v3;
3471 }
3472 
3474 {
3475  const double dot_product = (v1.x*v1.x) + (v1.y*v1.y) + (v1.z*v1.z);
3476  return sqrt(dot_product);
3477 }
3478 
3479 vector IMRPhenomX_vector_scalar(const vector v1, const double a)
3480 {
3481  vector v2;
3482  v2.x = a * v1.x;
3483  v2.y = a * v1.y;
3484  v2.z = a * v1.z;
3485 
3486  return v2;
3487 }
3488 
3490 {
3491  vector v3;
3492  v3.x = v1.x + v2.x;
3493  v3.y = v1.y + v2.y;
3494  v3.z = v1.z + v2.z;
3495  return v3;
3496 }
3497 
3499 {
3500  vector v3;
3501  v3.x = v1.x - v2.x;
3502  v3.y = v1.y - v2.y;
3503  v3.z = v1.z - v2.z;
3504  return v3;
3505 }
3506 
3508 {
3509  vector v2;
3510  const double rsinth = v1.r * sin(v1.theta);
3511 
3512  v2.x = rsinth * cos(v1.phi);
3513  v2.y = rsinth * sin(v1.phi);
3514  v2.z = v1.r * cos(v1.theta);
3515 
3516  return v2;
3517 }
3518 
3520 {
3521  sphpolvector v2;
3522 
3523  v2.r = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z);
3524  v2.theta = acos(v1.z / v2.r);
3525  v2.phi = XLALSimIMRPhenomXatan2tol(v1.y,v1.x,1e-15);
3526 
3527  return v2;
3528 }
3529 
3530 /** Function to rotate vector about z axis by given angle */
3532 {
3533  vector v2;
3534 
3535  v2.x = v1.x * cos(angle) - v1.y * sin(angle);
3536  v2.y = v1.x * sin(angle) + v1.y * cos(angle);
3537  v2.z = v1.z;
3538 
3539  return v2;
3540 }
3541 
3542 /** Function to rotate vector about y axis by given angle */
3544 {
3545  vector v2;
3546 
3547  v2.x = + v1.x * cos(angle) + v1.z * sin(angle);
3548  v2.y = v1.y;
3549  v2.z = - v1.x * sin(angle) + v1.z * cos(angle);
3550 
3551  return v2;
3552 }
3553 
3554 
3555 /** Function to rotate vector about z axis by given angle */
3557 {
3558  const REAL8 tmpx = *vx;
3559  const REAL8 tmpy = *vy;
3560  const REAL8 tmpz = *vz;
3561 
3562  REAL8 cosa=cos(angle), sina=sin(angle);
3563 
3564  REAL8 tmp1 = tmpx*cosa - tmpy*sina;
3565  REAL8 tmp2 = tmpx*sina + tmpy*cosa;
3566 
3567  *vx = tmp1;
3568  *vy = tmp2;
3569  *vz = tmpz;
3570 }
3571 
3572 /** Function to rotate vector about y axis by given angle */
3574 {
3575  const REAL8 tmpx = *vx;
3576  const REAL8 tmpy = *vy;
3577  const REAL8 tmpz = *vz;
3578 
3579  REAL8 cosa=cos(angle), sina=sin(angle);
3580 
3581  REAL8 tmp1 = + tmpx*cosa + tmpz*sina;
3582  REAL8 tmp2 = - tmpx*sina + tmpz*cosa;
3583 
3584  *vx = tmp1;
3585  *vy = tmpy;
3586  *vz = tmp2;
3587 }
3588 
3589 REAL8 IMRPhenomX_Cartesian_to_SphericalPolar_theta(const double x, const double y, const UNUSED double z)
3590 {
3591  REAL8 theta;
3592  const REAL8 norm = sqrt(x*x + y*y + z*z);
3593 
3594  /* Note that we add a numerical offset of 1e-16 to prevent accidental division by zero */
3595  theta = acos(z / (norm + 1e-16));
3596 
3597  return theta;
3598 }
3599 
3600 REAL8 IMRPhenomX_Cartesian_to_SphericalPolar_phi(const double x, const double y, const UNUSED double z)
3601 {
3602  REAL8 phi;
3603 
3604  phi = XLALSimIMRPhenomXatan2tol(y,x,1e-16);
3605 
3606  return phi;
3607 }
3608 
3610 {
3611  vector v1;
3612 
3613  const double rsintheta = mag * sin(theta);
3614 
3615  v1.x = rsintheta * cos(phi);
3616  v1.y = rsintheta * sin(phi);
3617  v1.z = mag * cos(theta);
3618 
3619  return v1;
3620 }
3621 
3622 
3623 /** used in numerical evaluation of Euler angles */
3625  UINT4 origlen = start->data->length;
3626  start = XLALResizeREAL8TimeSeries(start, 0,
3627  start->data->length + end->data->length - 1);
3628 
3629  memcpy(start->data->data + origlen -2, end->data->data,
3630  (end->data->length)*sizeof(REAL8));
3631 
3632  XLALGPSAdd(&(start->epoch), -end->deltaT*(end->data->length - 1));
3634 
3635 
3636  return start;
3637 }
3638 
3639 
3640 /** Analytical continuation for alpha angle in MRD */
3641 int alphaMRD_coeff(gsl_spline spline_alpha, gsl_interp_accel accel_alpha, double fmaxPN, IMRPhenomXWaveformStruct *pWF, PhenomXPalphaMRD *alpha_params){
3642 
3643  int success = GSL_SUCCESS;
3644 
3645  double ftrans = fmaxPN;
3646  double f1 = 0.97 *ftrans ;
3647  double f2 = 0.99 *ftrans ;
3648  double f1sq = f1*f1, f2sq = f2*f2;
3649  double f1cube = f1sq*f1;
3650 
3651  double alpha1, alpha2, dalpha1;
3652 
3653  success = gsl_spline_eval_e(&spline_alpha, f1, &accel_alpha,&alpha1);
3654  alpha1 = -alpha1;
3655  if(success != GSL_SUCCESS)
3656  {
3657  XLALPrintError("XLAL Error - %s: Alpha could not be interpolated at f=%.5f\n",__func__,XLALSimIMRPhenomXUtilsMftoHz(f1,pWF->Mtot));
3658 
3659  }
3660 
3661  success = success + gsl_spline_eval_deriv_e (&spline_alpha, f1, &accel_alpha,&dalpha1);
3662  dalpha1 = -dalpha1;
3663  if(success != GSL_SUCCESS)
3664  {
3665  XLALPrintError("XLAL Error - %s: dalpha/df could not be interpolated at f=%.5f\n",__func__,XLALSimIMRPhenomXUtilsMftoHz(f1,pWF->Mtot));
3666 
3667  }
3668 
3669 
3670  success = success + gsl_spline_eval_e (&spline_alpha, f2, &accel_alpha,&alpha2);
3671  alpha2 = -alpha2;
3672  if(success != GSL_SUCCESS)
3673  {
3674  XLALPrintError("XLAL Error - %s: Alpha could not be interpolated at f=%.5f\n",__func__,XLALSimIMRPhenomXUtilsMftoHz(f2,pWF->Mtot));
3675 
3676  }
3677 
3678  double aC=0., bC=0., cC=0.;
3679 
3680  if(success == GSL_SUCCESS){
3681 
3682  aC = (f1cube*(f1 - f2)*(f1 + f2)*dalpha1 + 2*(pow(f1,4) - 2*f1sq*f2sq)*alpha1 + 2*pow(f2,4)*alpha2)/(2.*pow(f1sq - f2sq,2));
3683 
3684  bC = (pow(f1,4)*f2sq*(f1*(f1 - f2)*(f1 + f2)*dalpha1 + 2*f2sq*(-alpha1 + alpha2)))/(2.*pow(f1sq - f2sq,2));
3685 
3686  cC = (f1sq*(f1*(-pow(f1,4) + pow(f2,4))*dalpha1 + 4*pow(f2,4)*(alpha1 - alpha2)))/(2.*pow(f1sq - f2sq,2));
3687 
3688  }
3689  alpha_params->aRD = aC;
3690  alpha_params->bRD = bC;
3691  alpha_params->cRD = cC;
3692 
3693  return success;
3694 
3695 
3696  }
3697 
3698 double alphaMRD(double Mf, PhenomXPalphaMRD *alpha_params){
3699 
3700  double Mf2 = Mf*Mf;
3701  double Mf4 = Mf2*Mf2;
3702  double minalpha=alpha_params->aRD + alpha_params->bRD/Mf4 + alpha_params->cRD/Mf2;
3703 
3704  return (-minalpha);
3705 
3706  }
3707 
3708 double dalphaMRD(double Mf, PhenomXPalphaMRD *alpha_params){
3709 
3710  double Mf2 = Mf*Mf;
3711  double Mf3 = Mf2*Mf;
3712  double Mf5 = Mf2*Mf3;
3713 
3714  return (4.*alpha_params->bRD/Mf5+2.*alpha_params->cRD/Mf3);
3715 
3716  }
3717 
3718 /** Function to determine coefficients of analytical continuation of beta through MRD */
3719 int betaMRD_coeff(gsl_spline spline_cosb, gsl_interp_accel accel_cosb, double fmaxPN, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec){
3720 
3721  int success = GSL_SUCCESS;
3722  double fdamp = pWF->fDAMP;
3723 
3724  QNMFits *qnms = (QNMFits *) XLALMalloc(sizeof(QNMFits));
3726  pPrec->beta_params->dfdamp = qnms->fdamp_lm[0](pWF->afinal)/pWF->Mfinal-fdamp;
3727  LALFree(qnms);
3728 
3729  double kappa = 2.*LAL_PI*pPrec->beta_params->dfdamp;
3730 
3731  double f1 = 0.97 *fmaxPN ;
3732  double f2 = 0.98 *fmaxPN ;
3733  double f1sq = f1*f1, f2sq = f2*f2 ;
3734  double ef1 = exp(kappa*f1), ef2 = exp(kappa*f2);
3735 
3736  double cosbeta1;
3737  success = gsl_spline_eval_e(&spline_cosb, f1, &accel_cosb, &cosbeta1);
3738 
3739  double dcosbeta2;
3740  success=gsl_spline_eval_deriv_e (&spline_cosb, f2, &accel_cosb,&dcosbeta2);
3741 
3742  double cosbeta2;
3743  success = gsl_spline_eval_e(&spline_cosb, f2, &accel_cosb,&cosbeta2);
3744 
3745  double cosbetamax;
3746  success = gsl_spline_eval_e(&spline_cosb, pPrec->fmax_inspiral, &accel_cosb, &cosbetamax);
3747 
3748  double aC,bC,cC,dC;
3749 
3750  if(fabs(cosbeta1)>1 || fabs(cosbeta2)>1 || fabs(cosbetamax)>1||success!=GSL_SUCCESS)
3751  {
3752 
3753  aC=0.;
3754  bC=0.;
3755  cC=0.;
3756  dC=0. ;
3757  pPrec->beta_params->flat_RD=true;
3758  success = GSL_SUCCESS;
3759 
3760  }
3761 
3762  else{
3763 
3764  double beta1 = acos(cosbeta1);
3765  double beta2 = acos(cosbeta2);
3766  double sqrtarg = 1.-cosbeta2*cosbeta2;
3767  double dbeta2 = - dcosbeta2/sqrt((sqrtarg <= 0. ? 1. : sqrtarg));
3768 
3769 
3770  double off = (cosbetamax < 0. ? LAL_PI : 0.);
3771 
3772  aC= (-(ef1*pow(f1,4)*(off - beta1)) + ef2*pow(f2,3)*(f2*(-f1 + f2)*dbeta2 + (-(f2*(3 + f2*kappa)) + f1*(4 + f2*kappa))*(off - beta2)))/pow(f1 - f2,2);
3773 
3774  bC =(2*ef1*pow(f1,4)*f2*(off - beta1) + ef2*pow(f2,3)*((f1 - f2)*f2*(f1 + f2)*dbeta2 - (-(f2sq*(2 + f2*kappa)) + f1sq*(4 + f2*kappa))*(off - beta2)))/pow(f1 - f2,2);
3775 
3776  cC =(-(ef1*pow(f1,4)*f2sq*(off - beta1)) + ef2*f1*pow(f2,4)*(f2*(-f1 + f2)*dbeta2 + (-(f2*(2 + f2*kappa)) + f1*(3 + f2*kappa))*(off - beta2)))/pow(f1 - f2,2);
3777 
3778  dC = off;
3779 
3780 
3781  pPrec->beta_params->flat_RD=false;
3782 
3783  }
3784 
3785  pPrec->beta_params->aRD = aC;
3786  pPrec->beta_params->bRD = bC;
3787  pPrec->beta_params->cRD = cC;
3788  pPrec->beta_params->dRD = dC;
3789 
3790  pPrec->beta_params->cosbeta_sign = copysign(1.0, cosbetamax);
3791 
3792  return success;
3793 
3794  }
3795 
3796 
3797 
3798 double betaMRD(double Mf, UNUSED IMRPhenomXWaveformStruct *pWF,PhenomXPbetaMRD *beta_params){
3799 
3800  double beta=0.;
3801 
3802  if(beta_params->flat_RD)
3803  beta=acos(beta_params->cosbeta_sign);
3804  else
3805  {
3806  double kappa = 2.*LAL_PI*beta_params->dfdamp;
3807  double Mf2 = Mf*Mf;
3808  double Mf3 = Mf2* Mf;
3809  beta = exp(-Mf*kappa)/Mf*(beta_params->aRD/Mf+beta_params->bRD/(Mf2)+beta_params->cRD/Mf3)+beta_params->dRD;
3810 
3811  }
3812 
3813  return(beta);
3814 
3815  }
3816 
3817 
3818 /* Integrate minimal rotation condition to compute gamma once alpha and beta are known
3819  Uses Boole's rule
3820 */
3821 int gamma_from_alpha_cosbeta(double *gamma, double Mf, double deltaMf,IMRPhenomXWaveformStruct *pWF,IMRPhenomXPrecessionStruct *pPrec){
3822 
3823  REAL8 Mf_high = Mf+deltaMf;
3824  REAL8 alphadoti, Mf_aux, gammai;
3825  REAL8 step = (Mf_high-Mf)*0.25;
3826 
3827  double alphadotcosbeta[5], cosbeta_aux[5];
3828 
3829  int status_alpha=GSL_SUCCESS, status_beta=GSL_SUCCESS;
3830 
3831  if(Mf<=pPrec->ftrans_MRD)
3832  {
3833  for(UINT4 jdx=0; jdx<5; jdx++){
3834 
3835  Mf_aux=Mf+jdx*step;
3836  status_beta=gsl_spline_eval_e(pPrec->cosbeta_spline, Mf_aux, pPrec->cosbeta_acc,&cosbeta_aux[jdx]);
3837  status_alpha=gsl_spline_eval_deriv_e(pPrec->alpha_spline, Mf_aux, pPrec->alpha_acc,&alphadoti);
3838  XLAL_CHECK(status_alpha == GSL_SUCCESS && status_beta==GSL_SUCCESS, XLAL_EFUNC, "Error in %s: could not evaluate splines for alpha and/or gamma angles.\n",__func__);
3839  alphadotcosbeta[jdx]=cosbeta_aux[jdx]*alphadoti;
3840  }
3841 
3842  }
3843 
3844  else
3845  {
3846  for(UINT4 jdx=0; jdx<5; jdx++){
3847 
3848  Mf_aux=Mf+jdx*step;
3849  cosbeta_aux[jdx] = cos(betaMRD(Mf_aux,pWF,pPrec->beta_params));
3850  alphadoti = dalphaMRD(Mf_aux,pPrec->alpha_params);
3851  alphadotcosbeta[jdx]=cosbeta_aux[jdx]*alphadoti;
3852  }
3853  }
3854 
3855  gammai= -2.*step/45.*(7.*alphadotcosbeta[0]+32.*alphadotcosbeta[1]+12.*alphadotcosbeta[2]+32.*alphadotcosbeta[3]+7.*alphadotcosbeta[4]);
3856  *gamma = gammai;
3857 
3858  return(status_beta+status_alpha);
3859 
3860 
3861 }
3862 
3863 /** This function evaluates the SpinTaylor Euler angles on a frequency grid passed by the user. Used in LALSimIMRPhenomX.c. */
3865  REAL8Sequence **alphaFS, /**< [out] Alpha angle frequency series [out] */
3866  REAL8Sequence **cosbetaFS, /**< [out] cos(Beta) angle frequency series [out] */
3867  REAL8Sequence **gammaFS, /**< [out] Gamma angle frequency series [out] */
3868  REAL8Sequence *freqsIN, /**< [in] Frequency grid on which Euler angles will be evaluated [in]*/
3869  IMRPhenomXWaveformStruct *pWF, /**< [in] Waveform structure [in]*/
3870  IMRPhenomXPrecessionStruct *pPrec, /**< [in] Precession structure [in]*/
3871  LALDict *LALparams /**< LAL Dictionary struct */
3872  )
3873  {
3874 
3875  int status = XLAL_SUCCESS;
3876  REAL8 fRef= pWF->fRef;
3877  REAL8 fmin = freqsIN->data[0];
3878  REAL8 fmax = freqsIN->data[freqsIN->length-1];
3879  // some useful quantities in geom units
3880  REAL8 Mfmin=XLALSimIMRPhenomXUtilsHztoMf(fmin,pWF->Mtot);
3881 
3882  /* Sanity checks */
3883  if(fmin <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmin must be positive.\n"); }
3884  if(fmax <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmax must be positive.\n"); }
3885  if(fmax <= fmin) { XLAL_ERROR(XLAL_EDOM, "fmax must be larger than fmin.\n"); }
3886  if(fRef < fmin){ XLAL_ERROR(XLAL_EDOM, "fRef must be >= fmin.\n"); }
3887 
3888  size_t output_length = freqsIN->length;
3889 
3890  //Evaluate splines for alpha and cosbeta
3892  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_InterpolateAlphaBeta_SpinTaylor failed.\n",__func__);
3893 
3894  //Evaluate splines for gamma
3895  status = IMRPhenomX_InterpolateGamma_SpinTaylor(fmin,fmax,pWF,pPrec);
3896  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_InterpolateGamma_SpinTaylor failed.\n",__func__);
3897 
3898  REAL8 alphamin=0., cosbetamin=0.;
3899 
3900  status = gsl_spline_eval_e(pPrec->alpha_spline, Mfmin, pPrec->alpha_acc,&alphamin);
3901  if( status != GSL_SUCCESS)
3902  {
3903  XLALPrintError("Error in %s: could not evaluate alpha(f_min). Got alpha=%.4f \n",__func__,alphamin);
3905 
3906  status = gsl_spline_eval_e(pPrec->cosbeta_spline, Mfmin, pPrec->cosbeta_acc,&cosbetamin);
3907  if ( status != GSL_SUCCESS)
3908  {XLALPrintError("Error in %s: could not evaluate cosbeta(f_min). Got cosbeta=%.4f \n",__func__,cosbetamin);
3910 
3911  // check if we can evaluate alpha(fref) using PN or MRD analytical continuation
3912 
3913  if(pWF->MfRef<=pPrec->ftrans_MRD)
3914  status=gsl_spline_eval_e(pPrec->alpha_spline, pWF->MfRef, pPrec->alpha_acc, &pPrec->alpha_ref);
3915  else if(pPrec->IMRPhenomXPrecVersion==320||pPrec->IMRPhenomXPrecVersion==321)
3916  pPrec->alpha_ref = alphaMRD(pWF->MfRef,pPrec->alpha_params);
3917  else
3918  status=gsl_spline_eval_e(pPrec->alpha_spline, pPrec->ftrans_MRD, pPrec->alpha_acc, &pPrec->alpha_ref);
3919 
3920  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: could not evaluate alpha(f_ref).\n",__func__);
3921 
3922  *alphaFS = XLALCreateREAL8Sequence(output_length);
3923  *cosbetaFS = XLALCreateREAL8Sequence(output_length);
3924  *gammaFS = XLALCreateREAL8Sequence(output_length);
3925 
3926  REAL8 alphaOff = pPrec->alpha0;
3927  pPrec->alpha_offset=-pPrec->alpha_ref+alphaOff;
3928 
3929  (*gammaFS)->data[0] = 0.;
3930  (*alphaFS)->data[0] = alphamin-pPrec->alpha_ref+alphaOff;
3931  (*cosbetaFS)->data[0] = cosbetamin;
3932 
3933 
3934  REAL8 alphai=0., cosbetai=0., gammai=0.;
3935  REAL8 Mf;
3936 
3937 
3938  for( UINT4 i = 1; i < output_length; i++ ){
3939 
3940  Mf=XLALSimIMRPhenomXUtilsHztoMf(freqsIN->data[i],pWF->Mtot);
3941  REAL8 deltaMF =XLALSimIMRPhenomXUtilsHztoMf(freqsIN->data[i]-freqsIN->data[i-1],pWF->Mtot);
3942 
3943  if(Mf<pPrec->ftrans_MRD)
3944 
3945  {
3946 
3947  alphai = gsl_spline_eval(pPrec->alpha_spline, Mf, pPrec->alpha_acc);
3948  cosbetai = gsl_spline_eval(pPrec->cosbeta_spline, Mf, pPrec->cosbeta_acc);
3949  gammai = gsl_spline_eval(pPrec->gamma_spline, Mf, pPrec->gamma_acc);
3950 
3951  (*alphaFS)->data[i] = alphai+pPrec->alpha_offset;
3952  (*cosbetaFS)->data[i] = cosbetai;
3953  (*gammaFS)-> data[i] = gammai;
3954 
3955  }
3956 
3957 
3958  else {
3959 
3960  if(pPrec->IMRPhenomXPrecVersion==320 || pPrec->IMRPhenomXPrecVersion==321 ){
3961 
3962  (*alphaFS)->data[i]=alphaMRD(Mf,pPrec->alpha_params)+pPrec->alpha_offset;
3963  REAL8 beta_MRD=betaMRD(Mf,pWF,pPrec->beta_params);
3964  (*cosbetaFS)->data[i]=cos(beta_MRD);
3965  REAL8 deltagamma = 0.;
3966  status = gamma_from_alpha_cosbeta(&deltagamma,Mf, deltaMF,pWF,pPrec);
3967  if(status==GSL_SUCCESS)
3968  (*gammaFS) -> data[i] = (*gammaFS)-> data[i-1]+deltagamma;
3969  else (*gammaFS) -> data[i] = (*gammaFS)-> data[i-1];
3970  }
3971 
3972  // just continue angles into RD with constant values
3973 
3974  else{
3975  (*alphaFS)->data[i]=(*alphaFS)->data[i-1];
3976  (*cosbetaFS)->data[i]=(*cosbetaFS)-> data[i-1];
3977  (*gammaFS) -> data[i]=(*gammaFS)-> data[i-1];
3978  }
3979  }
3980 
3981  }
3982 
3983  return status;
3984 
3985  }
3986 
3987 /** This function builds and stores splines for \f$\alpha\f$ and \f$\cos\beta\f$ in the frequency range covered by PN, and computes a spline for \f$\gamma\f$ between fmin and fmax */
3989  REAL8 fmin, /**< [in] Minimum frequency of the gamma spline [in]*/
3990  REAL8 fmax, /**< [in] Maximum frequency of the gamma spline [in]*/
3991  IMRPhenomXWaveformStruct *pWF, /**< [in] Waveform structure [in]*/
3992  IMRPhenomXPrecessionStruct *pPrec, /**< [in] Precession structure [in]*/
3993  LALDict *LALparams /**< LAL Dictionary struct */
3994  )
3995  {
3996 
3997  int status = XLAL_SUCCESS;
3998  REAL8 fRef= pWF->fRef;
3999 
4000  /* Sanity checks */
4001  if(fmin <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmin must be positive.\n"); }
4002  if(fmax <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmax must be positive.\n"); }
4003  if(fmax <= fmin) { XLAL_ERROR(XLAL_EDOM, "fmax must be larger than fmin.\n"); }
4004  if(fRef < fmin){ XLAL_ERROR(XLAL_EDOM, "fRef must be >= fmin.\n"); }
4005 
4006  //Evaluate splines for alpha and cosbeta
4008  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_InterpolateAlphaBeta_SpinTaylor failed.\n",__func__);
4009 
4010  //Evaluate splines for gamma
4011  status = IMRPhenomX_InterpolateGamma_SpinTaylor(fmin,fmax,pWF,pPrec);
4012  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_InterpolateGamma_SpinTaylor failed.\n",__func__);
4013 
4014  // check if we can evaluate alpha(fref) using PN or an analytical continuation
4015 
4016  if(pWF->MfRef<=pPrec->ftrans_MRD)
4017  status=gsl_spline_eval_e(pPrec->alpha_spline, pWF->MfRef, pPrec->alpha_acc, &pPrec->alpha_ref);
4018  else if(pPrec->IMRPhenomXPrecVersion==320||pPrec->IMRPhenomXPrecVersion==321)
4019  pPrec->alpha_ref = alphaMRD(pWF->MfRef,pPrec->alpha_params);
4020  else
4021  status=gsl_spline_eval_e(pPrec->alpha_spline, pPrec->ftrans_MRD, pPrec->alpha_acc, &pPrec->alpha_ref);
4022 
4023  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: could not evaluate alpha(f_ref).\n",__func__);
4024 
4025  return status;
4026 
4027  }
4028 
4029 /** This function computes gamma from the minimal rotation condition and stores a spline for it */
4031  REAL8 fmin, /**< starting frequency (Hz) */
4032  REAL8 fmax, /**< maximum frequency (Hz) */
4033  IMRPhenomXWaveformStruct *pWF, /**< [in] Waveform structure [in]*/
4034  IMRPhenomXPrecessionStruct *pPrec /**< [in] Precession structure [in]*/
4035  )
4036  {
4037 
4038  int status = XLAL_SUCCESS;
4039  REAL8 fRef= pWF->fRef;
4040 
4041  // some useful quantities in geom units
4042  REAL8 Mfmin=XLALSimIMRPhenomXUtilsHztoMf(fmin,pWF->Mtot);
4043 
4044  /* Sanity checks */
4045  if(fmin <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmin must be positive.\n"); }
4046  if(fmax <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmax must be positive.\n"); }
4047  if(fmax <= fmin) { XLAL_ERROR(XLAL_EDOM, "fmax must be larger than fmin.\n"); }
4048  if(fRef < fmin){ XLAL_ERROR(XLAL_EDOM, "fRef must be >= fmin.\n"); }
4049 
4050  REAL8 seglen=XLALSimInspiralChirpTimeBound(pWF->fRef, pWF->m1_SI, pWF->m2_SI, pWF->chi1L,pWF->chi2L);
4051  REAL8 deltaFv1= 1./MAX(4.,pow(2, ceil(log(seglen)/log(2))));
4052  REAL8 deltaF = MIN(deltaFv1,0.1);
4053  REAL8 deltaMF = XLALSimIMRPhenomXUtilsHztoMf(deltaF,pWF->Mtot);
4054  // length of frequency series expected by the user
4055  size_t iStart = (size_t) (fmin / deltaF);
4056  size_t iStop = (size_t) (fmax / deltaF) + 1;
4057  size_t output_length = iStop-iStart;
4058  if(output_length<4)
4059  {
4060  XLALPrintError("Error in %s: no. of points is insufficient for spline interpolation of gamma",__func__);
4062  }
4063 
4064 
4065  REAL8Sequence *frequencies = NULL;
4066  frequencies=XLALCreateREAL8Sequence(output_length);
4067  frequencies->data[0]=Mfmin;
4068 
4069  REAL8Sequence *gamma_array = NULL;
4070  gamma_array=XLALCreateREAL8Sequence(output_length);
4071  gamma_array->data[0]=0.;
4072  REAL8 Mf;
4073  int gamma_status=GSL_SUCCESS;
4074 
4075  for( UINT4 i = 1; i < output_length; i++ )
4076  {
4077 
4078  Mf=Mfmin+i*deltaMF;
4079  frequencies->data[i]=Mf;
4080  REAL8 deltagamma=0.;
4081 
4082  if(Mf<pPrec->ftrans_MRD){
4083  gamma_status=gamma_from_alpha_cosbeta(&deltagamma,Mf, deltaMF,pWF,pPrec);
4084  gamma_array->data[i]=gamma_array->data[i-1]+deltagamma;
4085  }
4086  else {
4087  if(pPrec->IMRPhenomXPrecVersion==320 || pPrec->IMRPhenomXPrecVersion==321 ){
4088  gamma_status = gamma_from_alpha_cosbeta(&deltagamma, Mf, deltaMF,pWF,pPrec);
4089  gamma_array->data[i]=gamma_array->data[i-1]+deltagamma;
4090  }
4091  // just continue angles into RD with constant values
4092  else{
4093  gamma_array->data[i]=gamma_array->data[i-1];
4094  }
4095  }
4096 
4097  if(gamma_status!=GSL_SUCCESS) status=gamma_status;
4098 
4099  }
4100 
4101  if(status==GSL_SUCCESS)
4102  {
4103  pPrec->gamma_acc = gsl_interp_accel_alloc();
4104  pPrec->gamma_spline = gsl_spline_alloc(gsl_interp_cspline, output_length);
4105  gsl_spline_init(pPrec->gamma_spline, frequencies->data, gamma_array -> data, output_length);
4106  pPrec->gamma_ref=gsl_spline_eval(pPrec->gamma_spline, pWF->MfRef, pPrec->gamma_acc);
4107  }
4108 
4109  else{
4110 
4111  gsl_spline_free(pPrec->alpha_spline);
4112  gsl_interp_accel_free(pPrec->alpha_acc);
4113 
4114  gsl_spline_free(pPrec->cosbeta_spline);
4115  gsl_interp_accel_free(pPrec->cosbeta_acc);
4116 
4117 
4118  }
4119 
4120 
4121  XLALDestroyREAL8Sequence(frequencies);
4122  XLALDestroyREAL8Sequence(gamma_array);
4123 
4124  return status;
4125 
4126  }
4127 
4128 
4129 
4130 /** This function computes cubic splines of the alpha and beta inspiral Euler angles, which are then stored into a IMRPhenomXPrecessionStruct structure.
4131  - If the user passed PhenomXPFinalSpinMod=4, the function corrects the estimate for the final precessing spin based on the result of the PN integration.
4132  - For versions 32*, the function also computes the parameters needed to obtain a smooth MRD continuation of alpha/beta.
4133  - The memory allocated for the PN arrays is freed at the end of this function.
4134 */
4136  IMRPhenomXWaveformStruct *pWF, /**< [in] Waveform structure [in]*/
4137  IMRPhenomXPrecessionStruct *pPrec, /**< [in] Precession structure [in]*/
4138  LALDict *LALparams /**< LAL Dictionary struct */
4139  )
4140 {
4141 
4142  int status = XLAL_SUCCESS;
4143 
4144  size_t lenPN = pPrec->PNarrays->V_PN->data->length;
4145 
4146  REAL8Sequence *fgw =NULL ;
4147  /* Setup sequences for angles*/
4148  REAL8Sequence *alpha = NULL;
4149  REAL8Sequence *alphaaux = NULL;
4150  REAL8Sequence *cosbeta = NULL;
4151 
4152 
4153  fgw=XLALCreateREAL8Sequence(lenPN);
4155  alphaaux=XLALCreateREAL8Sequence(lenPN);
4156  cosbeta=XLALCreateREAL8Sequence(lenPN);
4157 
4158 
4159  REAL8 fgw_Mf, fgw_Hz, Mfmax_PN=0.;
4160  // i_max is used to discard possibly unphysical points in the calculation of the final spin
4161  UINT8 i_max=0;
4162  REAL8 LNhatx_temp,LNhaty_temp,LNhatz_temp;
4163 
4164  for(UINT8 i=0; i < lenPN; i++){
4165 
4166  LNhatx_temp = (pPrec->PNarrays->LNhatx_PN->data->data[i]);
4167  LNhaty_temp = (pPrec->PNarrays->LNhaty_PN->data->data[i]);
4168  LNhatz_temp = (pPrec->PNarrays->LNhatz_PN->data->data[i]);
4169 
4170  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &LNhatx_temp, &LNhaty_temp, &LNhatz_temp);
4171  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &LNhatx_temp, &LNhaty_temp, &LNhatz_temp);
4172  IMRPhenomX_rotate_z(-pPrec->kappa, &LNhatx_temp, &LNhaty_temp, &LNhatz_temp);
4173 
4174  fgw_Hz= pow(pPrec->PNarrays->V_PN->data->data[i],3.)/pPrec->piGM;
4175  fgw_Mf= XLALSimIMRPhenomXUtilsHztoMf(fgw_Hz,pWF->Mtot);
4176 
4177  if(fgw_Hz>0.){
4178 
4179  /* Compute Euler angles in the J frame */
4180  alphaaux->data[i] = atan2(LNhaty_temp, LNhatx_temp);
4181  cosbeta->data[i] = LNhatz_temp;
4182  fgw->data[i] = fgw_Mf;
4183 
4184  Mfmax_PN = fgw_Mf;
4185  i_max = i;
4186  }
4187 
4188  else
4189  break;
4190 
4191 
4192  }
4193 
4194  REAL8 fmax_inspiral = Mfmax_PN-pWF->deltaMF;
4195  if(fmax_inspiral > pWF->fRING-pWF->fDAMP) fmax_inspiral = 1.020 * pWF->fMECO;
4196 
4197  pPrec->ftrans_MRD = 0.98*fmax_inspiral;
4198  pPrec->fmax_inspiral= fmax_inspiral;
4199 
4200  // Interpolate alpha
4201  XLALSimIMRPhenomXUnwrapArray(alphaaux->data, alpha->data, lenPN);
4202  pPrec->alpha_acc = gsl_interp_accel_alloc();
4203  pPrec->alpha_spline = gsl_spline_alloc(gsl_interp_cspline, lenPN);
4204 
4205 
4206  status = gsl_spline_init(pPrec->alpha_spline, fgw->data, alpha->data, lenPN);
4207 
4208  if (status != GSL_SUCCESS)
4209  {
4210  XLALPrintError("Error in %s: error in computing gsl spline for alpha.\n",__func__);
4211  }
4212 
4213  // Interpolate cosbeta
4214  pPrec->cosbeta_acc = gsl_interp_accel_alloc();
4215  pPrec->cosbeta_spline = gsl_spline_alloc(gsl_interp_cspline, lenPN);
4216  status =gsl_spline_init(pPrec->cosbeta_spline, fgw->data, cosbeta->data, lenPN);
4217 
4218  if (status != GSL_SUCCESS)
4219  {
4220  XLALPrintError("Error in %s: error in computing gsl spline for cos(beta).\n",__func__);
4221  }
4222 
4223  REAL8 cosbetamax;
4224 
4225  status = gsl_spline_eval_e(pPrec->cosbeta_spline, fmax_inspiral, pPrec->cosbeta_acc,&cosbetamax);
4226  if(status != GSL_SUCCESS)
4227  {
4228  XLALPrintError("Error in %s: error in computing cosbeta.\n",__func__);
4229  }
4230 
4231  // estimate final spin using spins at the end of the PN integration
4232 
4234 
4235  REAL8 m1 = pWF->m1_SI / pWF->Mtot_SI;
4236  REAL8 m2 = pWF->m2_SI / pWF->Mtot_SI;
4237 
4238  vector Lnf = {pPrec->PNarrays->LNhatx_PN->data->data[i_max],pPrec->PNarrays->LNhaty_PN->data->data[i_max],pPrec->PNarrays->LNhatz_PN->data->data[i_max]};
4239  REAL8 Lnorm = sqrt(IMRPhenomX_vector_dot_product(Lnf,Lnf));
4240  vector S1f = {pPrec->PNarrays->S1x_PN->data->data[i_max],pPrec->PNarrays->S1y_PN->data->data[i_max],pPrec->PNarrays->S1z_PN->data->data[i_max]};
4241  vector S2f = {pPrec->PNarrays->S2x_PN->data->data[i_max],pPrec->PNarrays->S2y_PN->data->data[i_max],pPrec->PNarrays->S2z_PN->data->data[i_max]};
4242 
4243 
4244  REAL8 dotS1L = IMRPhenomX_vector_dot_product(S1f,Lnf)/Lnorm;
4245  REAL8 dotS2L = IMRPhenomX_vector_dot_product(S2f,Lnf)/Lnorm;
4246  vector S1_perp = IMRPhenomX_vector_diff(S1f,IMRPhenomX_vector_scalar(Lnf, dotS1L));
4247  S1_perp = IMRPhenomX_vector_scalar(S1_perp,m1*m1);
4248  vector S2_perp = IMRPhenomX_vector_diff(S2f,IMRPhenomX_vector_scalar(Lnf, dotS2L));
4249  S2_perp = IMRPhenomX_vector_scalar(S2_perp,m2*m2);
4250  vector Stot_perp = IMRPhenomX_vector_sum(S1_perp,S2_perp);
4251  REAL8 S_perp_norm = sqrt(IMRPhenomX_vector_dot_product(Stot_perp,Stot_perp));
4252  REAL8 chi_perp_norm = S_perp_norm *pow(m1 + m2,2)/pow(m1,2);
4253 
4254  pWF->afinal= copysign(1.0, cosbetamax)* XLALSimIMRPhenomXPrecessingFinalSpin2017(pWF->eta,dotS1L,dotS2L,chi_perp_norm);
4255 
4256  pWF->fRING = evaluate_QNMfit_fring22(pWF->afinal) / (pWF->Mfinal);
4257  pWF->fDAMP = evaluate_QNMfit_fdamp22(pWF->afinal) / (pWF->Mfinal);
4258  }
4259 
4260  // initialize parameters for RD continuation
4261  pPrec->alpha_params = XLALMalloc(sizeof(PhenomXPalphaMRD));
4262  pPrec->beta_params = XLALMalloc(sizeof(PhenomXPbetaMRD));
4263 
4264  if(pPrec->IMRPhenomXPrecVersion==320 || pPrec->IMRPhenomXPrecVersion==321){
4265 
4266  status = alphaMRD_coeff(*pPrec->alpha_spline, *pPrec->alpha_acc, pPrec->fmax_inspiral, pWF, pPrec->alpha_params);
4267  if(status!=XLAL_SUCCESS) XLALPrintError("XLAL Error in %s: error in computing parameters for MRD continuation of Euler angles.\n",__func__);
4268 
4269 
4270  status = betaMRD_coeff(*pPrec->cosbeta_spline, *pPrec->cosbeta_acc, pPrec->fmax_inspiral, pWF, pPrec);
4271  if(status!=XLAL_SUCCESS) XLALPrintError("XLAL Error in %s: error in computing parameters for MRD continuation of Euler angles.\n",__func__);
4272 
4273  }
4274 
4275 
4286  XLALFree(pPrec->PNarrays);
4287 
4288 
4289 
4291  XLALDestroyREAL8Sequence(alphaaux);
4292  XLALDestroyREAL8Sequence(cosbeta);
4294 
4295  if(status != GSL_SUCCESS){
4296 
4297  gsl_spline_free(pPrec->alpha_spline);
4298  gsl_spline_free(pPrec->cosbeta_spline);
4299  gsl_interp_accel_free(pPrec->alpha_acc);
4300  gsl_interp_accel_free(pPrec->cosbeta_acc);
4301 
4302 
4303  }
4304 
4305 
4306 
4307  return status;
4308 
4309  }
4310 
4311 
4312 
4313 
4314 /** Wrapper of XLALSimInspiralSpinTaylorPNEvolveOrbit : if integration is successful, stores arrays containing PN solution in a PhenomXPInspiralArrays struct */
4316  PhenomXPInspiralArrays *arrays, /**< [out] Struct containing solutions returned by PNEvolveOrbit */
4317  REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) at fRef */
4318  REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) at fRef */
4319  REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) at fRef */
4320  REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) at fRef */
4321  REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) at fRef */
4322  REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) at fRef */
4323  REAL8 fmin, /**< minimum frequency (Hz) */
4324  int PrecVersion, /**< precessing version (int) */
4325  IMRPhenomXWaveformStruct *pWF, /**< Waveform structure [in]*/
4326  LALDict *LALparams /**< LAL Dictionary struct */
4327  )
4328 {
4329 
4330  int status = XLAL_SUCCESS;
4331  REAL8 fRef=pWF->fRef;
4332 
4333  /* Sanity checks */
4334  if(fmin <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmin must be positive.\n"); }
4335  if(fRef < fmin){ XLAL_ERROR(XLAL_EDOM, "fRef must be >= fmin.\n"); }
4336 
4337 
4338  REAL8 m1_SI=pWF->m1_SI,m2_SI=pWF->m2_SI;
4339  REAL8 s1x=chi1x, s1y=chi1y, s1z=chi1z;
4340  REAL8 s2x=chi2x, s2y=chi2y, s2z=chi2z;
4341 
4342  UNUSED REAL8 piGM = LAL_PI * (pWF->m1_SI + pWF->m2_SI) * (LAL_G_SI / LAL_C_SI) / (LAL_C_SI * LAL_C_SI);
4343 
4344  REAL8 quadparam1=pWF->quadparam1;
4345  REAL8 quadparam2=pWF->quadparam2;
4346  REAL8 lambda1=pWF->lambda1;
4347  REAL8 lambda2=pWF->lambda2;
4348 
4349 
4350  // Tidal parameters are preloaded in aligned spin waveform structure. If using BH values in the twisting up, overwrite these. Note that the aligned spin model (i.e. the co-precessing modes) will still be using the NS tidal parameters
4351  if (PrecVersion==311 || PrecVersion==321)
4352  {
4353  quadparam1 = 1.;
4354  quadparam2 = 1.;
4355  lambda1 = 0.;
4356  lambda2 = 0.;
4357  }
4358 
4359 
4360  int phaseO = XLALSimInspiralWaveformParamsLookupPNPhaseOrder(LALparams);
4361  int spinO = XLALSimInspiralWaveformParamsLookupPNSpinOrder(LALparams);
4362  int tideO = XLALSimInspiralWaveformParamsLookupPNTidalOrder(LALparams);
4363  int lscorr = XLALSimInspiralWaveformParamsLookupLscorr(LALparams);
4364  if(lscorr!=0) XLAL_PRINT_WARNING("IMRPhenomXP with SpinTaylor angles was only reviewed for lscorr=0.\n");
4365 
4366 
4367  // enforce default orders if user does not specify any: this is added to ensure backward compatibility should default PN orders change in the future
4368  if(phaseO == -1) XLALSimInspiralWaveformParamsInsertPNPhaseOrder(LALparams,7);
4369  if(spinO == -1) XLALSimInspiralWaveformParamsInsertPNSpinOrder(LALparams,6);
4370  if(tideO == -1) XLALSimInspiralWaveformParamsInsertPNTidalOrder(LALparams,12);
4371 
4372  /* Initialize needed time series */
4373  REAL8TimeSeries *V = NULL;
4374  REAL8TimeSeries *Phi = NULL;
4375  REAL8TimeSeries *S1x = NULL;
4376  REAL8TimeSeries *S1y = NULL;
4377  REAL8TimeSeries *S1z = NULL;
4378  REAL8TimeSeries *S2x = NULL;
4379  REAL8TimeSeries *S2y = NULL;
4380  REAL8TimeSeries *S2z = NULL;
4381  REAL8TimeSeries *LNhatx = NULL;
4382  REAL8TimeSeries *LNhaty = NULL;
4383  REAL8TimeSeries *LNhatz = NULL;
4384  REAL8TimeSeries *E1x = NULL;
4385  REAL8TimeSeries *E1y = NULL;
4386  REAL8TimeSeries *E1z = NULL;
4387 
4388 
4389 
4390  REAL8 lnhatx,lnhaty,lnhatz, e1y,e1z,e1x;
4391  lnhatx = lnhaty = e1y = e1z = 0;
4392  lnhatz = e1x = 1.;
4393 
4394  // if the user does not specify any SpinTaylor approximant, default to SpinTaylorT4
4395  const char * approx_name=XLALSimInspiralWaveformParamsLookupPhenomXPSpinTaylorVersion(LALparams);
4396  if(approx_name==NULL)
4397  approx_name="SpinTaylorT4";
4399 
4400  REAL8 fS,fE;
4401 
4402  REAL8 fMECO_Hz=XLALSimIMRPhenomXUtilsMftoHz(pWF->fMECO,pWF->Mtot);
4403 
4404  // for versions 32* to work, we need to start integration in the inspiral
4405  if(fmin>fMECO_Hz &&(PrecVersion==320 || PrecVersion==321))
4406  {
4407  fmin=fMECO_Hz;
4408  }
4409 
4410  REAL8 fCut = XLALSimIMRPhenomXUtilsMftoHz(pWF->fRING+8.*pWF->fDAMP,pWF->Mtot);
4411  int n;
4412 
4414  if(coarse_fac < 1) { XLAL_ERROR(XLAL_EDOM, "Coarse factor must be >= 1!\n");}
4415 
4416  REAL8 deltaT_coarse = 0.5*coarse_fac/(fCut);
4417 
4418  fS=fmin;
4419  fE=fCut;
4420 
4421 
4422  if( fRef < LAL_REAL4_EPS || fabs(fRef - fmin) < LAL_REAL4_EPS )
4423 
4424  {
4425  fRef = fmin;
4426 
4427  n=XLALSimInspiralSpinTaylorPNEvolveOrbit(&V, &Phi, &S1x, &S1y, &S1z, &S2x, &S2y, &S2z, &LNhatx, &LNhaty, &LNhatz, &E1x, &E1y, &E1z, deltaT_coarse, m1_SI, m2_SI,fS,fE,s1x,s1y,s1z,s2x,s2y,s2z,lnhatx,lnhaty,lnhatz,e1x,e1y,e1z,lambda1,lambda2,quadparam1, quadparam2, spinO, tideO, phaseO, lscorr, approx);
4428 
4429  if( n < 0 )
4431  }
4432 
4433 
4434  else if((fRef - fmin) > LAL_REAL4_EPS )
4435  {
4436  /* Integrate backward to fStart */
4437  fS = fRef;
4438  fE = fmin-0.5;
4440  &S1x, &S1y, &S1z, &S2x, &S2y, &S2z,
4441  &LNhatx, &LNhaty, &LNhatz, &E1x, &E1y, &E1z,
4442  deltaT_coarse, m1_SI, m2_SI, fS, fE, s1x, s1y, s1z, s2x, s2y,
4443  s2z, lnhatx, lnhaty, lnhatz, e1x, e1y, e1z, lambda1, lambda2,
4444  quadparam1, quadparam2, spinO, tideO, phaseO, lscorr, approx);
4445 
4446 
4447  if( n < 0 ) XLAL_ERROR(XLAL_EFUNC);
4448 
4449 
4450  if(V->data->length>1){
4451 
4452  REAL8TimeSeries *V2=NULL, *Phi2=NULL, *S1x2=NULL, *S1y2=NULL, *S1z2=NULL, *S2x2=NULL, *S2y2=NULL, *S2z2=NULL;
4453  REAL8TimeSeries *LNhatx2=NULL, *LNhaty2=NULL, *LNhatz2=NULL, *E1x2=NULL, *E1y2=NULL, *E1z2=NULL;
4454 
4455 
4456  /* Integrate forward to end of waveform */
4457  fS = fRef;
4458  fE = fCut;
4460  &S1x2, &S1y2, &S1z2, &S2x2, &S2y2, &S2z2,
4461  &LNhatx2, &LNhaty2, &LNhatz2, &E1x2, &E1y2, &E1z2,
4462  deltaT_coarse, m1_SI, m2_SI, fS, fE, s1x, s1y, s1z, s2x, s2y,
4463  s2z, lnhatx, lnhaty, lnhatz, e1x, e1y, e1z, lambda1, lambda2,
4464  quadparam1, quadparam2, spinO, tideO, phaseO, lscorr, approx);
4465  if( n < 0 ) XLAL_ERROR(XLAL_EFUNC);
4466 
4467 
4468 
4469  // Stitch 2nd set of vectors onto 1st set.
4470  V = appendTS(V, V2);
4471  Phi = appendTS(Phi, Phi2);
4472  S1x = appendTS(S1x, S1x2);
4473  S1y = appendTS(S1y, S1y2);
4474  S1z = appendTS(S1z, S1z2);
4475  S2x = appendTS(S2x, S2x2);
4476  S2y = appendTS(S2y, S2y2);
4477  S2z = appendTS(S2z, S2z2);
4478  LNhatx = appendTS(LNhatx, LNhatx2);
4479  LNhaty = appendTS(LNhaty, LNhaty2);
4480  LNhatz = appendTS(LNhatz, LNhatz2);
4481  E1x = appendTS(E1x, E1x2);
4482  E1y = appendTS(E1y, E1y2);
4483  E1z = appendTS(E1z, E1z2);
4484  }
4485 
4486  else{
4487 
4496  XLALDestroyREAL8TimeSeries( LNhatx );
4497  XLALDestroyREAL8TimeSeries( LNhaty );
4498  XLALDestroyREAL8TimeSeries( LNhatz );
4502  // the failure will be caught by GetAndSetPrecessionVariables. If the integration fails, the waveform generation will default to prec. version 223
4503  return(XLAL_FAILURE);
4504 
4505  }
4506 
4507  }
4508 
4509  else
4510  {
4512  }
4513 
4514  size_t copyLength;
4515 
4516  if(coarse_fac>1)
4517  {
4518  // at high frequencies, perform a fine integration
4519  // stop some bins before the last one
4520 
4521  int lenLow=V->data->length;
4522  int nbuffer=MIN(9,lenLow-1);
4523 
4524  if(lenLow-1-nbuffer<0) nbuffer=lenLow-1;
4525 
4526  copyLength=lenLow-1-nbuffer;
4527 
4528  REAL8 vtrans = V->data->data[lenLow-1-nbuffer];
4529  REAL8 ftrans = pow(vtrans,3.)/piGM;
4530 
4531  REAL8 LNhatx_trans=LNhatx->data->data[lenLow-1-nbuffer];
4532  REAL8 LNhaty_trans=LNhaty->data->data[lenLow-1-nbuffer];
4533  REAL8 LNhatz_trans=LNhatz->data->data[lenLow-1-nbuffer];
4534 
4535  REAL8 E1x_trans, E1y_trans, E1z_trans;
4536  E1x_trans = E1x->data->data[lenLow-1-nbuffer];
4537  E1y_trans = E1y->data->data[lenLow-1-nbuffer];
4538  E1z_trans = E1z->data->data[lenLow-1-nbuffer];
4539 
4540  REAL8 S1x_trans, S1y_trans, S1z_trans, S2x_trans, S2y_trans, S2z_trans;
4541  S1x_trans = S1x->data->data[lenLow-1-nbuffer];
4542  S1y_trans = S1y->data->data[lenLow-1-nbuffer];
4543  S1z_trans = S1z->data->data[lenLow-1-nbuffer];
4544 
4545  S2x_trans = S2x->data->data[lenLow-1-nbuffer];
4546  S2y_trans = S2y->data->data[lenLow-1-nbuffer];
4547  S2z_trans = S2z->data->data[lenLow-1-nbuffer];
4548 
4549  fS=ftrans;
4550  fE=fCut;
4551  REAL8 deltaT = 0.5/(fCut);
4552 
4553  XLAL_CHECK(fS > 0., XLAL_EFUNC, "Error: Transition frequency in PN integration is not positive.\n");
4554 
4555  REAL8TimeSeries *Phi_PN=NULL, *E1x_PN=NULL, *E1y_PN=NULL, *E1z_PN=NULL;
4556 
4557  n=XLALSimInspiralSpinTaylorPNEvolveOrbit(&arrays->V_PN, &Phi_PN, &arrays->S1x_PN, &arrays->S1y_PN, &arrays->S1z_PN, &arrays->S2x_PN, &arrays->S2y_PN, &arrays->S2z_PN, &arrays->LNhatx_PN, &arrays->LNhaty_PN, &arrays->LNhatz_PN, &E1x_PN, &E1y_PN, &E1z_PN, deltaT, m1_SI, m2_SI,fS,fE,S1x_trans,S1y_trans,S1z_trans,S2x_trans,S2y_trans,S2z_trans,LNhatx_trans,LNhaty_trans,LNhatz_trans,E1x_trans, E1y_trans, E1z_trans,lambda1,lambda2,quadparam1, quadparam2, spinO, tideO, phaseO, lscorr, approx);
4558 
4559  // free high frequency arrays
4560  XLALDestroyREAL8TimeSeries( Phi_PN );
4561  XLALDestroyREAL8TimeSeries( E1x_PN );
4562  XLALDestroyREAL8TimeSeries( E1y_PN );
4563  XLALDestroyREAL8TimeSeries( E1z_PN );
4564 
4565  if( n < 0 )
4567 
4568  size_t lenPN=lenLow-nbuffer-1+arrays->V_PN->data->length;
4569 
4570  if(lenPN < 4) {
4571  XLALPrintError("Error in %s: no. of points is insufficient for spline interpolation",__func__);
4573  }
4574 
4575  // resize series to full length (coarse+fine)
4576  XLALResizeREAL8TimeSeries(arrays->V_PN,-(lenLow-nbuffer-1),lenPN);
4577  XLALResizeREAL8TimeSeries(arrays->LNhatx_PN,-(lenLow-nbuffer-1),lenPN);
4578  XLALResizeREAL8TimeSeries(arrays->LNhaty_PN,-(lenLow-nbuffer-1),lenPN);
4579  XLALResizeREAL8TimeSeries(arrays->LNhatz_PN,-(lenLow-nbuffer-1),lenPN);
4580  XLALResizeREAL8TimeSeries(arrays->S1x_PN,-(lenLow-nbuffer-1),lenPN);
4581  XLALResizeREAL8TimeSeries(arrays->S1y_PN,-(lenLow-nbuffer-1),lenPN);
4582  XLALResizeREAL8TimeSeries(arrays->S1z_PN,-(lenLow-nbuffer-1),lenPN);
4583  XLALResizeREAL8TimeSeries(arrays->S2x_PN,-(lenLow-nbuffer-1),lenPN);
4584  XLALResizeREAL8TimeSeries(arrays->S2y_PN,-(lenLow-nbuffer-1),lenPN);
4585  XLALResizeREAL8TimeSeries(arrays->S2z_PN,-(lenLow-nbuffer-1),lenPN);
4586 
4587  }
4588 
4589  else{
4590 
4591  copyLength=V->data->length-1;
4592  if(copyLength < 4) {
4593  XLALPrintError("Error in %s: no. of points is insufficient for spline interpolation",__func__);
4595  }
4596 
4597  LIGOTimeGPS ligotimegps_zero = LIGOTIMEGPSZERO;
4598 
4599  /* allocate memory for output vectors */
4600  arrays->V_PN = XLALCreateREAL8TimeSeries( "PN_EXPANSION_PARAMETER", &ligotimegps_zero, 0.,
4601  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4602  arrays->S1x_PN = XLALCreateREAL8TimeSeries( "SPIN1_X_COMPONENT", &ligotimegps_zero, 0.,
4603  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4604  arrays->S1y_PN = XLALCreateREAL8TimeSeries( "SPIN1_Y_COMPONENT", &ligotimegps_zero, 0.,
4605  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4606  arrays->S1z_PN = XLALCreateREAL8TimeSeries( "SPIN1_Z_COMPONENT", &ligotimegps_zero, 0.,
4607  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4608  arrays->S2x_PN = XLALCreateREAL8TimeSeries( "SPIN2_X_COMPONENT", &ligotimegps_zero, 0.,
4609  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4610  arrays->S2y_PN = XLALCreateREAL8TimeSeries( "SPIN2_Y_COMPONENT", &ligotimegps_zero, 0.,
4611  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4612  arrays->S2z_PN = XLALCreateREAL8TimeSeries( "SPIN2_Z_COMPONENT", &ligotimegps_zero, 0.,
4613  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4614  arrays->LNhatx_PN = XLALCreateREAL8TimeSeries( "LNHAT_X_COMPONENT", &ligotimegps_zero, 0.,
4615  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4616  arrays->LNhaty_PN = XLALCreateREAL8TimeSeries( "LNHAT_Y_COMPONENT", &ligotimegps_zero, 0.,
4617  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4618  arrays->LNhatz_PN = XLALCreateREAL8TimeSeries( "LNHAT_Z_COMPONENT", &ligotimegps_zero, 0.,
4619  deltaT_coarse, &lalDimensionlessUnit, copyLength);
4620 
4621 
4622 
4623  }
4624 
4625 
4626  // copy coarse-grid data into fine-grid arrays
4627  memcpy(arrays->V_PN->data->data,V->data->data,(copyLength)*sizeof(REAL8));
4628  memcpy(arrays->LNhatx_PN->data->data,LNhatx->data->data,(copyLength)*sizeof(REAL8));
4629  memcpy(arrays->LNhaty_PN->data->data,LNhaty->data->data,(copyLength)*sizeof(REAL8));
4630  memcpy(arrays->LNhatz_PN->data->data,LNhatz->data->data,(copyLength)*sizeof(REAL8));
4631  memcpy(arrays->S1x_PN->data->data,S1x->data->data,(copyLength)*sizeof(REAL8));
4632  memcpy(arrays->S1y_PN->data->data,S1y->data->data,(copyLength)*sizeof(REAL8));
4633  memcpy(arrays->S1z_PN->data->data,S1z->data->data,(copyLength)*sizeof(REAL8));
4634  memcpy(arrays->S2x_PN->data->data,S2x->data->data,(copyLength)*sizeof(REAL8));
4635  memcpy(arrays->S2y_PN->data->data,S2y->data->data,(copyLength)*sizeof(REAL8));
4636  memcpy(arrays->S2z_PN->data->data,S2z->data->data,(copyLength)*sizeof(REAL8));
4637 
4638 
4647  XLALDestroyREAL8TimeSeries( LNhatx );
4648  XLALDestroyREAL8TimeSeries( LNhaty );
4649  XLALDestroyREAL8TimeSeries( LNhatz );
4653 
4654  // check that the first frequency node returned is indeed below the fmin requested, to avoid interpolation errors. If not return an error which will trigger the fallback to MSA
4655  REAL8 fminPN=pow(arrays->V_PN->data->data[0],3.)/piGM;
4656  if(fminPN<0.||fminPN>fmin) return(XLAL_FAILURE);
4657 
4658  return status;
4659 
4660  }
4661 
4662 
4663 
4664 /** Wrapper of IMRPhenomX_SpinTaylorAnglesSplinesAll: fmin and fmax are determined by the function based on the mode content and binary's parameters . Used in LALSimIMRPhenomXPHM.c */
4668  LALDict *lalParams
4669 )
4670 
4671 {
4672  int status = XLAL_SUCCESS;
4674 
4675  // start below fMin to avoid interpolation artefacts
4676  REAL8 buffer = (pWF->deltaF>0.) ? 3.*pWF->deltaF : 0.5;
4677  REAL8 fminAngles = (pWF->fMin-buffer)*2./pPrec->M_MAX;
4678  // check we still pass a meaningful fmin
4679  XLAL_CHECK(fminAngles > 0., XLAL_EFUNC, "Error - %s: fMin is too low and numerical angles could not be computed.\n",__func__);
4680 
4681  // If MB is on, we take advantage of the fact that we can compute angles on an array
4682 
4683  if(thresholdPMB>0.)
4684  pPrec->Mfmax_angles = pWF->fRING+4.*pWF->fDAMP;
4685  else
4686  pPrec->Mfmax_angles = (MAX(pWF->MfMax,pWF->fRING+4.*pWF->fDAMP)+XLALSimIMRPhenomXUtilsHztoMf(buffer,pWF->Mtot))*2./pPrec->M_MIN;
4687  REAL8 fmaxAngles = XLALSimIMRPhenomXUtilsMftoHz(pPrec->Mfmax_angles,pWF->Mtot);
4688 
4689  // we add a few bins to fmax to make sure we do not run into interpolation errors
4690  status = IMRPhenomX_SpinTaylorAnglesSplinesAll(fminAngles,fmaxAngles,pWF,pPrec,lalParams);
4691  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "%s: IMRPhenomX_SpinTaylorAnglesSplinesAll failed.",__func__);
4692 
4693  status = gsl_spline_eval_e(pPrec->alpha_spline, pPrec->ftrans_MRD, pPrec->alpha_acc,&pPrec->alpha_ftrans);
4694  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "%s: could not compute alpha et the end of inspiral.",__func__);
4695 
4696  status = gsl_spline_eval_e(pPrec->cosbeta_spline, pPrec->ftrans_MRD, pPrec->cosbeta_acc,&pPrec->cosbeta_ftrans);
4697  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "%s: could not compute cosbeta et the end of inspiral.",__func__);
4698 
4699  status = gsl_spline_eval_e(pPrec->gamma_spline, pPrec->ftrans_MRD, pPrec->gamma_acc,&pPrec->gamma_ftrans);
4700  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "%s: could not compute gamma et the end of inspiral.",__func__);
4701 
4702  return status;
4703 
4704 }
4705 
4706 
4707 
4708 
4709 
4710 /** XLAL function that evaluates the SpinTaylor Euler angles on a frequency grid passed by the user. Used in LALSimIMRPhenomX.c. */
4712  REAL8Sequence **alphaFS, /**< [out] Alpha angle frequency series [out] */
4713  REAL8Sequence **cosbetaFS, /**< [out] cos(Beta) angle frequency series [out] */
4714  REAL8Sequence **gammaFS, /**< [out] Gamma angle frequency series [out] */
4715  REAL8 m1_SI, /**< Mass of companion 1 (kg) */
4716  REAL8 m2_SI, /**< Mass of companion 2 (kg) */
4717  REAL8 s1x, /**< x component of primary spin*/
4718  REAL8 s1y, /**< y component of primary spin*/
4719  REAL8 s1z, /**< z component of primary spin */
4720  REAL8 s2x, /**< x component of secondary spin*/
4721  REAL8 s2y, /**< y component of secondary spin*/
4722  REAL8 s2z, /**< z component of secondary spin */
4723  REAL8 fmin, /**< starting GW frequency (Hz) */
4724  REAL8 fmax, /**< maximum GW frequency (Hz) */
4725  REAL8 deltaF, /**< starting GW frequency (Hz) */
4726  REAL8 fRef, /**< reference GW frequency (Hz) */
4727  REAL8 phiRef, /**< reference orbital phase (rad) */
4728  LALDict *LALparams /**< LAL Dictionary struct */
4729  )
4730  {
4731 
4732  int status = XLAL_SUCCESS;
4734 
4735  /* Sanity checks */
4736  if(fRef < 0.0) { XLAL_ERROR(XLAL_EDOM, "fRef must be positive or set to 0 to ignore.\n"); }
4737  if(deltaF <= 0.0) { XLAL_ERROR(XLAL_EDOM, "deltaF must be positive.\n"); }
4738  if(m1_SI <= 0.0) { XLAL_ERROR(XLAL_EDOM, "m1_SI must be positive.\n"); }
4739  if(m2_SI <= 0.0) { XLAL_ERROR(XLAL_EDOM, "m2_SI must be positive.\n"); }
4740  if(fmin <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmin must be positive.\n"); }
4741  if(fmax <= 0.0) { XLAL_ERROR(XLAL_EDOM, "fmax must be positive.\n"); }
4742  if(fRef > 0.0 && fRef < fmin){ XLAL_ERROR(XLAL_EDOM, "fRef must be >= fmin or =0 to use fmin.\n"); }
4743 
4744  // length of frequency series expected by the user
4745  size_t iStart = (size_t) (fmin / deltaF);
4746  size_t iStop = (size_t) (fmax / deltaF) + 1;
4747  size_t output_length = iStop-iStart;
4748 
4749  status = XLALIMRPhenomXPCheckMassesAndSpins(&m1_SI,&m2_SI,&s1x,&s1y,&s1z,&s2x,&s2y,&s2z);
4750  XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: XLALIMRPhenomXPCheckMassesAndSpins failed.\n");
4751 
4752  /* Initialize IMR PhenomX Waveform struct and check that it initialized correctly */
4754  pWF = XLALMalloc(sizeof(IMRPhenomXWaveformStruct));
4755  status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, s1z, s2z, deltaF, fRef, phiRef, fmin, fmax, 1e6*LAL_PC_SI, 0., LALparams, 0);
4756  XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
4757 
4759  pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
4760  pPrec->M_MIN = 2, pPrec->M_MAX = 2;
4761  status = IMRPhenomXGetAndSetPrecessionVariables(pWF,pPrec,m1_SI,m2_SI,s1x,s1y,s1z,s2x,s2y,s2z,LALparams,0);
4762  XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXGetAndSetPrecessionVariables failed.\n");
4763 
4764  // if version has been changed because of fallback, raise error
4765  XLAL_CHECK(pPrec->IMRPhenomXPrecVersion == pversion, XLAL_EFUNC, "Error: %s failed.\n",__func__);
4766 
4767  //Evaluate splines for alpha and cosbeta
4769  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_InterpolateAlphaBeta_SpinTaylor failed.\n",__func__);
4770 
4771  REAL8 alphamin=0., cosbetamin=0.;
4772  int success=XLAL_SUCCESS;
4773  REAL8 Mfmin=XLALSimIMRPhenomXUtilsHztoMf(fmin,pWF->Mtot);
4774 
4775  success = gsl_spline_eval_e(pPrec->alpha_spline, Mfmin, pPrec->alpha_acc,&alphamin);
4776  success = success + gsl_spline_eval_e(pPrec->cosbeta_spline, Mfmin, pPrec->cosbeta_acc,&cosbetamin);
4777  XLAL_CHECK(XLAL_SUCCESS == success, XLAL_EFUNC, "Error: %s: could not evaluate angles at fMin.\n",__func__);
4778 
4779 
4780  // check if we can evaluate alpha(fref) using PN or an analytical continuation
4781 
4782  if(pWF->MfRef<pPrec->ftrans_MRD)
4783  success=gsl_spline_eval_e(pPrec->alpha_spline, pWF->MfRef, pPrec->alpha_acc, &pPrec->alpha_ref);
4784  else if(pPrec->IMRPhenomXPrecVersion==320||pPrec->IMRPhenomXPrecVersion==321)
4785  pPrec->alpha_ref = alphaMRD(pWF->MfRef,pPrec->alpha_params);
4786  else
4787  success=gsl_spline_eval_e(pPrec->alpha_spline, pPrec->ftrans_MRD, pPrec->alpha_acc, &pPrec->alpha_ref);
4788 
4789  XLAL_CHECK(XLAL_SUCCESS == success, XLAL_EFUNC, "Error: %s: could not evaluate angles at fRef.\n",__func__);
4790 
4791 
4792 
4793  *alphaFS = XLALCreateREAL8Sequence(output_length);
4794  *cosbetaFS = XLALCreateREAL8Sequence(output_length);
4795  *gammaFS = XLALCreateREAL8Sequence(output_length);
4796 
4797 
4798  REAL8 alphaOff = pPrec->alpha0;
4799  pPrec->alpha_offset=-pPrec->alpha_ref+alphaOff;
4800 
4801  // determine offset for gamma et the end
4802  (*gammaFS)->data[0] = 0.;
4803  (*alphaFS)->data[0] = alphamin-pPrec->alpha_ref+alphaOff;
4804  (*cosbetaFS)->data[0] = cosbetamin;
4805 
4806 
4807  REAL8 alphai=0., cosbetai=0., gammai=0.;
4808  REAL8 Mf;
4809  REAL8Sequence *frequencies = NULL;
4810  frequencies=XLALCreateREAL8Sequence(output_length);
4811  frequencies->data[0]=Mfmin;
4812 
4813  for( UINT4 i = 1; i < output_length; i++ ){
4814 
4815  Mf=Mfmin+i*pWF->deltaMF;
4816  frequencies->data[i]=Mf;
4817  REAL8 deltagamma=0.;
4818 
4819  if(Mf<pPrec->ftrans_MRD)
4820 
4821 
4822  {
4823 
4824  success = gsl_spline_eval_e(pPrec->alpha_spline, Mf, pPrec->alpha_acc,&alphai);
4825  success = success + gsl_spline_eval_e(pPrec->cosbeta_spline, Mf, pPrec->cosbeta_acc,&cosbetai);
4826  success = success + gamma_from_alpha_cosbeta(&deltagamma,Mf, pWF->deltaMF,pWF,pPrec);
4827  if(success != XLAL_SUCCESS)
4828  XLALPrintError("%s: Interpolation of SpinTaylor angles failed at f=%.5f)\n",__func__,XLALSimIMRPhenomXUtilsMftoHz(Mf,pWF->Mtot));
4829  gammai=(*gammaFS)->data[i-1]+deltagamma;
4830 
4831  (*alphaFS)->data[i] = alphai+pPrec->alpha_offset;
4832  (*cosbetaFS)->data[i] = cosbetai;
4833  (*gammaFS)-> data[i] = gammai;
4834 
4835 
4836  }
4837 
4838 
4839  else {
4840 
4841  if(pPrec->IMRPhenomXPrecVersion==320 || pPrec->IMRPhenomXPrecVersion==321){
4842 
4843  (*alphaFS)->data[i]=alphaMRD(Mf,pPrec->alpha_params)+pPrec->alpha_offset;
4844  REAL8 beta_MRD=betaMRD(Mf,pWF,pPrec->beta_params);
4845  (*cosbetaFS)->data[i]=cos(beta_MRD);
4846  status=gamma_from_alpha_cosbeta(&deltagamma,Mf, pWF->deltaMF,pWF,pPrec);
4847  if(success != XLAL_SUCCESS)
4848  XLALPrintError("%s: could not integrate minimal rotation condition at f=%.5f)\n",__func__,XLALSimIMRPhenomXUtilsMftoHz(Mf,pWF->Mtot));
4849  gammai=(*gammaFS)->data[i-1]+deltagamma;
4850  (*gammaFS)-> data[i] =gammai;
4851 
4852  }
4853 
4854  // just continue angles into RD with constant values
4855 
4856  else{
4857  (*alphaFS)->data[i]=(*alphaFS)->data[i-1];
4858  (*cosbetaFS)->data[i]=(*cosbetaFS)-> data[i-1];
4859  (*gammaFS) -> data[i]=(*gammaFS)-> data[i-1];
4860  }
4861  }
4862 
4863 
4864  }
4865 
4866 
4867  pPrec->gamma_acc = gsl_interp_accel_alloc();
4868  pPrec->gamma_spline = gsl_spline_alloc(gsl_interp_cspline, output_length);
4869  gsl_spline_init(pPrec->gamma_spline, frequencies->data, (*gammaFS) -> data, output_length);
4870  pPrec->gamma_ref=gsl_spline_eval(pPrec->gamma_spline, pWF->MfRef, pPrec->gamma_acc);
4871 
4872  for( UINT4 i = 0; i < output_length; i++ ) (*gammaFS) -> data[i]=(*gammaFS)-> data[i]-pPrec->gamma_ref-pPrec->epsilon0;
4873 
4874  LALFree(pPrec->alpha_params);
4875  LALFree(pPrec->beta_params);
4876 
4877  gsl_spline_free(pPrec->alpha_spline);
4878  gsl_interp_accel_free(pPrec->alpha_acc);
4879 
4880  gsl_spline_free(pPrec->cosbeta_spline);
4881  gsl_interp_accel_free(pPrec->cosbeta_acc);
4882 
4883  gsl_spline_free(pPrec->gamma_spline);
4884  gsl_interp_accel_free(pPrec->gamma_acc);
4885 
4886 
4887  LALFree(pWF);
4888  LALFree(pPrec);
4889  XLALDestroyREAL8Sequence(frequencies);
4890 
4891 
4892  return status;
4893 
4894  }
4895 
4896 
4897 
4899 
4900  INT2Sequence *modeseq;
4901  modeseq=XLALSimInspiralModeArrayReadModes(ModeArray);
4902 
4903  int nmodes=modeseq->length/2;
4904  float M_MAX=1., M_MIN=4.;
4905  for(int jj=0; jj<nmodes; jj++)
4906  {
4907  if(modeseq->data[2*jj+1]>M_MAX) M_MAX=(float)(modeseq->data[2*jj+1]);
4908  if(abs(modeseq->data[2*jj+1])<M_MIN) M_MIN=(float)abs(modeseq->data[2*jj+1]);
4909  }
4910 
4911  XLALDestroyINT2Sequence(modeseq);
4912 
4913  pPrec->M_MIN = M_MIN; pPrec->M_MAX=M_MAX;
4914  return;
4915 
4916 }
4917 
4918 /** Core twisting up routine for SpinTaylor angles */
4920  const COMPLEX16 hAS, /**< Underlying aligned-spin IMRPhenomXAS strain */
4921  REAL8 alpha, /**< cosbeta Euler angle series */
4922  REAL8 cos_beta, /**< cosbeta Euler angle series */
4923  REAL8 gamma, /**< gamma Euler angle series */
4924  IMRPhenomXPrecessionStruct *pPrec, /**< IMRPhenomXP Precession Struct */
4925  COMPLEX16 *hp, /**< [out] h_+ polarization \f$\tilde h_+\f$ */
4926  COMPLEX16 *hc /**< [out] h_x polarization \f$\tilde h_x\f$ */
4927 
4928 )
4929 {
4930  XLAL_CHECK(hp != NULL, XLAL_EFAULT);
4931  XLAL_CHECK(hc != NULL, XLAL_EFAULT);
4932 
4933 
4934  double cBetah = 0.0;
4935  double sBetah = 0.0;
4936 
4937  double epsilon=-(gamma-pPrec->gamma_ref)+pPrec->epsilon0;
4938 
4939  if(pPrec->IMRPhenomXPrecVersion!=310 && pPrec->IMRPhenomXPrecVersion!=311 && pPrec->IMRPhenomXPrecVersion!=320 && pPrec->IMRPhenomXPrecVersion!=321) XLAL_ERROR(XLAL_EDOM, "Error in IMRPhenomXPTwistUp22_NumericalAngles, incorrect precessing version passed (must be 310, 311, 320,or 321)!\n");
4940 
4941  INT4 status = 0;
4942  status = IMRPhenomXWignerdCoefficients_cosbeta(&cBetah, &sBetah, cos_beta);
4943  XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Call to IMRPhenomXWignerdCoefficients_cosbeta failed.");
4944 
4945  /* Useful powers of the Wigner coefficients */
4946  const REAL8 cBetah2 = cBetah * cBetah;
4947  const REAL8 cBetah3 = cBetah * cBetah2;
4948  const REAL8 cBetah4 = cBetah * cBetah3;
4949  const REAL8 sBetah2 = sBetah * sBetah;
4950  const REAL8 sBetah3 = sBetah * sBetah2;
4951  const REAL8 sBetah4 = sBetah * sBetah3;
4952 
4953  /*
4954  Compute the Wigner d coefficients, see Appendix A of arXiv:2004.06503
4955  d22 = Table[WignerD[{2, mp, 2}, 0, -\[Beta], 0], {mp, -2, 2}]
4956  d2m2 = Table[WignerD[{2, mp, -2}, 0, -\[Beta], 0], {mp, -2, 2}]
4957  */
4958  // d22 = {d^2_{-2,2} , d^2_{-1,2}, d^2_{0,2}, d^2_{1,2}, d^2_{2,2} }
4959  const COMPLEX16 d22[5] = {sBetah4, 2.0*cBetah*sBetah3, pPrec->sqrt6*sBetah2*cBetah2, 2.0*cBetah3*sBetah, cBetah4};
4960 
4961  // d2m2 = {d^2_{-2,-2} , d^2_{-1,-2}, d^2_{0,-2}, d^2_{1,-2}, d^2_{2,-2} }
4962  const COMPLEX16 d2m2[5] = {d22[4], -d22[3], d22[2], -d22[1], d22[0]}; /* Exploit symmetry d^2_{-2,m} = (-1)^m d^2_{2,-m}*/
4963 
4964  const COMPLEX16 Y2mA[5] = {pPrec->Y2m2, pPrec->Y2m1, pPrec->Y20, pPrec->Y21, pPrec->Y22};
4965 
4966  /* Precompute powers of e^{i m alpha} */
4967  COMPLEX16 cexp_i_alpha = cexp(+I*alpha);
4968  COMPLEX16 cexp_2i_alpha = cexp_i_alpha * cexp_i_alpha;
4969  COMPLEX16 cexp_mi_alpha = 1.0 / cexp_i_alpha;
4970  COMPLEX16 cexp_m2i_alpha = cexp_mi_alpha * cexp_mi_alpha;
4971  COMPLEX16 cexp_im_alpha_l2[5] = {cexp_m2i_alpha, cexp_mi_alpha, 1.0, cexp_i_alpha, cexp_2i_alpha};
4972 
4973  COMPLEX16 hp_sum = 0;
4974  COMPLEX16 hc_sum = 0;
4975 
4976  /* Loop over m' modes and perform the actual twisting up */
4977  for(int m=-2; m<=2; m++)
4978  {
4979  /* Transfer functions, see Eqs. 3.5-3.7 of arXiv:2004.06503 */
4980  COMPLEX16 A2m2emm = cexp_im_alpha_l2[-m+2] * d2m2[m+2] * Y2mA[m+2]; /* = cexp(I*m*alpha) * d22[m+2] * Y2mA[m+2] */
4981  COMPLEX16 A22emmstar = cexp_im_alpha_l2[m+2] * d22[m+2] * conj(Y2mA[m+2]); /* = cexp(-I*m*alpha) * d2m2[m+2] * conj(Y2mA[m+2]) */
4982  hp_sum += A2m2emm + A22emmstar;
4983  hc_sum += I*(A2m2emm - A22emmstar);
4984  }
4985 
4986  /* Note that \gamma = - \epsilon */
4987  COMPLEX16 eps_phase_hP = cexp(-2.0*I*epsilon) * hAS / 2.0;
4988 
4989  /* Return h_+ and h_x */
4990  *hp = eps_phase_hP * hp_sum;
4991  *hc = eps_phase_hP * hc_sum;
4992 
4993  return XLAL_SUCCESS;
4994 }
4995 
4996 
4997 #ifdef __cplusplus
4998 }
4999 #endif
5000 
REAL8 zeta
#define LALFree(p)
const double c1
const double g0
const double c2
const double c0
static double fdamp(double eta, double chi1, double chi2, double finspin)
fdamp is the complex part of the ringdown frequency 1508.07250 figure 9
#define MAX_TOL_ATAN
Tolerance used below which numbers are treated as zero for the calculation of atan2.
static double evaluate_QNMfit_fdamp22(double finalDimlessSpin)
static double evaluate_QNMfit_fring22(double finalDimlessSpin)
static double evaluate_QNMfit_fring21(double finalDimlessSpin)
static double double delta
IMRPhenomX_UsefulPowers powers_of_lalpi
REAL8 IMRPhenomX_PNR_GenerateRingdownPNRBeta(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
We evaluate beta at the final Mf_beta_upper connection frequency to approximate the final value of be...
INT4 IMRPhenomX_PNR_GetAndSetCoPrecParams(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
INT4 IMRPhenomX_PNR_GetAndSetPNRVariables(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function computes the required single-spin quantities used to parameterize the MR tuned function...
int IMRPhenomXSetWaveformVariables(IMRPhenomXWaveformStruct *wf, const REAL8 m1_SI, const REAL8 m2_SI, const REAL8 chi1L_In, const REAL8 chi2L_In, const REAL8 deltaF, const REAL8 fRef, const REAL8 phi0, const REAL8 f_min, const REAL8 f_max, const REAL8 distance, const REAL8 inclination, LALDict *LALParams, UNUSED const UINT4 debug)
double IMRPhenomX_psiofv(const double v, const double v2, const double psi0, const double psi1, const double psi2, const IMRPhenomXPrecessionStruct *pPrec)
double IMRPhenomX_Return_Psi_MSA(double v, double v2, const IMRPhenomXPrecessionStruct *pPrec)
Get using Eq 51 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
double IMRPhenomX_Get_PN_tau(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
Internal function to computes PN spin-spin couplings.
vector IMRPhenomX_vector_PolarToCartesian(const sphpolvector v1)
double dalphaMRD(double Mf, PhenomXPalphaMRD *alpha_params)
int betaMRD_coeff(gsl_spline spline_cosb, gsl_interp_accel accel_cosb, double fmaxPN, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
Function to determine coefficients of analytical continuation of beta through MRD.
vector IMRPhenomX_vector_sum(const vector v1, const vector v2)
int IMRPhenomX_SpinTaylorAnglesSplinesAll(REAL8 fmin, REAL8 fmax, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *LALparams)
This function builds and stores splines for and in the frequency range covered by PN,...
vector IMRPhenomX_vector_cross_product(const vector v1, const vector v2)
double IMRPhenomX_Return_Psi_dot_MSA(const double v, const IMRPhenomXPrecessionStruct *pPrec)
Get using Eq 24 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
vector IMRPhenomX_Return_MSA_Corrections_MSA(double v, double LNorm, double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
sphpolvector IMRPhenomX_vector_CartesianToPolar(const vector v1)
vector IMRPhenomX_Return_Spin_Evolution_Coefficients_MSA(const double LNorm, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
Get coefficients for Eq 21 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703....
double IMRPhenomX_Get_PN_beta(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
Internal function to computes the PN spin-orbit couplings.
int IMRPhenomXPSpinTaylorAnglesIMR(REAL8Sequence **alphaFS, REAL8Sequence **cosbetaFS, REAL8Sequence **gammaFS, REAL8Sequence *freqsIN, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *LALparams)
This function evaluates the SpinTaylor Euler angles on a frequency grid passed by the user.
int IMRPhenomX_InterpolateGamma_SpinTaylor(REAL8 fmin, REAL8 fmax, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function computes gamma from the minimal rotation condition and stores a spline for it.
double IMRPhenomX_Return_zeta_MSA(const double v, const IMRPhenomXPrecessionStruct *pPrec)
Get using Eq F5 in Appendix F of Chatziioannou et al, PRD 95, 104004, (2017):
vector IMRPhenomX_Return_Constants_d_MSA(const double LNorm, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
Get d constants from Appendix B (B9, B10, B11) of Chatziioannou et al, PRD 95, 104004,...
int IMRPhenomX_Initialize_MSA_System(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, int ExpansionOrder)
This function initializes all the core variables required for the MSA system.
#define MIN(a, b)
vector IMRPhenomX_Return_Roots_MSA(double LNorm, double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
Here we solve for the roots of Eq 21 in Chatziioannou et al, PRD 95, 104004, (2017),...
int IMRPhenomXPTwistUp22(const REAL8 Mf, const COMPLEX16 hAS, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, COMPLEX16 *hp, COMPLEX16 *hc)
Core twisting up routine, see Section III A of arXiv:2004.06503.
REAL8 XLALSimIMRPhenomXPNEuleralphaNNLO(const REAL8 f, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 chip, const REAL8 alpha0)
External wrapper function to next-to-next-to-leading (NNLO) in spin-orbit expression for the PN Euler...
double IMRPhenomX_PN_Euler_epsilon_NNLO(IMRPhenomXPrecessionStruct *pPrec, const double omega, const double omega_cbrt2, const double omega_cbrt, const double logomega)
Internal function to calculate epsilon using pre-cached NNLO PN expressions.
double IMRPhenomX_vector_L2_norm(const vector v1)
REAL8 XLALSimIMRPhenomXLPNAnsatz(REAL8 v, REAL8 LNorm, REAL8 L0, REAL8 L1, REAL8 L2, REAL8 L3, REAL8 L4, REAL8 L5, REAL8 L6, REAL8 L7, REAL8 L8, REAL8 L8L)
This is a convenient wrapper function for PN orbital angular momentum.
double IMRPhenomX_Return_SNorm_MSA(const double v, IMRPhenomXPrecessionStruct *pPrec)
Get norm of S, see PRD 95, 104004, (2017)
vector IMRPhenomX_vector_PolarToCartesian_components(const REAL8 mag, const REAL8 theta, const REAL8 phi)
int IMRPhenomX_InterpolateAlphaBeta_SpinTaylor(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *LALparams)
This function computes cubic splines of the alpha and beta inspiral Euler angles, which are then stor...
double IMRPhenomX_PN_Euler_alpha_NNLO(IMRPhenomXPrecessionStruct *pPrec, const double omega, const double omega_cbrt2, const double omega_cbrt, const double logomega)
Internal function to calculate alpha using pre-cached NNLO PN expressions.
INT4 IMRPhenomX_SetPrecessingRemnantParams(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
void IMRPhenomX_GetandSetModes(LALValue *ModeArray, IMRPhenomXPrecessionStruct *pPrec)
REAL8 XLALSimIMRPhenomXL2PNNS(const REAL8 v, const REAL8 eta)
2PN non-spinning orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
int IMRPhenomXWignerdCoefficients(REAL8 *cos_beta_half, REAL8 *sin_beta_half, const REAL8 v, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
REAL8 XLALSimIMRPhenomXL4PNAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
4PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
void IMRPhenomX_rotate_y(REAL8 angle, REAL8 *vx, REAL8 *vy, REAL8 *vz)
Function to rotate vector about y axis by given angle.
vector IMRPhenomX_vector_rotate_z(const REAL8 angle, const vector v1)
Function to rotate vector about z axis by given angle.
int gamma_from_alpha_cosbeta(double *gamma, double Mf, double deltaMf, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
vector IMRPhenomX_vector_scalar(const vector v1, const double a)
vector IMRPhenomX_vector_diff(const vector v1, const vector v2)
vector IMRPhenomX_Return_phi_zeta_costhetaL_MSA(const double v, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
Wrapper to generate , and at a given frequency.
void IMRPhenomX_rotate_z(const REAL8 angle, REAL8 *vx, REAL8 *vy, REAL8 *vz)
Function to rotate vector about z axis by given angle.
REAL8 XLALSimIMRPhenomXL4PNLOSIAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
4PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
double alphaMRD(double Mf, PhenomXPalphaMRD *alpha_params)
int IMRPhenomXPTwistUp22_NumericalAngles(const COMPLEX16 hAS, REAL8 alpha, REAL8 cos_beta, REAL8 gamma, IMRPhenomXPrecessionStruct *pPrec, COMPLEX16 *hp, COMPLEX16 *hc)
Core twisting up routine for SpinTaylor angles.
int IMRPhenomXPCheckMaxOpeningAngle(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
Helper function to check if maximum opening angle > pi/2 or pi/4 and issues a warning.
double IMRPhenomX_Get_PN_sigma(const double a, const double b, const IMRPhenomXPrecessionStruct *pPrec)
Internal function to compute PN spin-spin couplings.
int alphaMRD_coeff(gsl_spline spline_alpha, gsl_interp_accel accel_alpha, double fmaxPN, IMRPhenomXWaveformStruct *pWF, PhenomXPalphaMRD *alpha_params)
Analytical continuation for alpha angle in MRD.
REAL8 IMRPhenomX_Cartesian_to_SphericalPolar_theta(const double x, const double y, const UNUSED double z)
double IMRPhenomX_L_norm_3PN_of_v(const double v, const double v2, const double L_norm, IMRPhenomXPrecessionStruct *pPrec)
Returns the 3PN accurate orbital angular momentum as implemented in LALSimInspiralFDPrecAngles_intern...
REAL8 XLALSimIMRPhenomXL3PNAS(const REAL8 v, const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 delta)
3PN orbital angular momentum as a function of x = v^2 = (Pi M f)^{2/3}
double IMRPhenomX_costhetaLJ(const double L_norm, const double J_norm, const double S_norm)
Calculate (L dot J)
int IMRPhenomX_Initialize_Euler_Angles(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
Wrapper of IMRPhenomX_SpinTaylorAnglesSplinesAll: fmin and fmax are determined by the function based ...
REAL8 IMRPhenomX_Cartesian_to_SphericalPolar_phi(const double x, const double y, const UNUSED double z)
vector IMRPhenomX_vector_rotate_y(const REAL8 angle, const vector v1)
Function to rotate vector about y axis by given angle.
REAL8 XLALSimIMRPhenomXPNEulerepsilonNNLO(REAL8 f, REAL8 eta, REAL8 chi1L, REAL8 chi2L, REAL8 chip, REAL8 epsilon0)
External wrapper to NNLO PN epsilon angle.
static REAL8TimeSeries * appendTS(REAL8TimeSeries *start, REAL8TimeSeries *end)
used in numerical evaluation of Euler angles
vector IMRPhenomX_Return_Constants_c_MSA(const double v, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
Get c constants from Appendix B (B6, B7, B8) of Chatziioannou et al, PRD 95, 104004,...
double IMRPhenomX_JNorm_MSA(const double LNorm, IMRPhenomXPrecessionStruct *pPrec)
Get norm of J using Eq 41 of Chatziioannou et al, PRD 95, 104004, (2017)
int IMRPhenomX_InspiralAngles_SpinTaylor(PhenomXPInspiralArrays *arrays, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 fmin, int PrecVersion, IMRPhenomXWaveformStruct *pWF, LALDict *LALparams)
Wrapper of XLALSimInspiralSpinTaylorPNEvolveOrbit : if integration is successful, stores arrays conta...
double IMRPhenomX_Return_phiz_MSA(const double v, const double JNorm, const IMRPhenomXPrecessionStruct *pPrec)
Get using Eq 66 of Chatziioannou et al, PRD 95, 104004, (2017), arXiv:1703.03967:
double IMRPhenomX_vector_dot_product(const vector v1, const vector v2)
#define DEBUG
void Get_alphaepsilon_atfref(REAL8 *alpha_offset, REAL8 *epsilon_offset, UINT4 mprime, IMRPhenomXPrecessionStruct *pPrec, IMRPhenomXWaveformStruct *pWF)
Get alpha and epsilon offset depending of the mprime (second index of the non-precessing mode)
int IMRPhenomXWignerdCoefficients_cosbeta(REAL8 *cos_beta_half, REAL8 *sin_beta_half, const REAL8 cos_beta)
int IMRPhenomXGetAndSetPrecessionVariables(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, LALDict *lalParams, INT4 debug_flag)
Function to populate the IMRPhenomXPrecessionStruct:
int XLALSimIMRPhenomXPSpinTaylorAngles(REAL8Sequence **alphaFS, REAL8Sequence **cosbetaFS, REAL8Sequence **gammaFS, REAL8 m1_SI, REAL8 m2_SI, REAL8 s1x, REAL8 s1y, REAL8 s1z, REAL8 s2x, REAL8 s2y, REAL8 s2z, REAL8 fmin, REAL8 fmax, REAL8 deltaF, REAL8 fRef, REAL8 phiRef, LALDict *LALparams)
XLAL function that evaluates the SpinTaylor Euler angles on a frequency grid passed by the user.
double betaMRD(double Mf, UNUSED IMRPhenomXWaveformStruct *pWF, PhenomXPbetaMRD *beta_params)
#define MAX(a, b)
void IMRPhenomXHM_Initialize_QNMs(QNMFits *qnms)
void XLALSimIMRPhenomXUnwrapArray(double *in, double *out, int len)
Function to unwrap a time series that contains an angle, to obtain a continuous time series.
INT4 XLALIMRPhenomXPCheckMassesAndSpins(REAL8 *m1, REAL8 *m2, REAL8 *chi1x, REAL8 *chi1y, REAL8 *chi1z, REAL8 *chi2x, REAL8 *chi2y, REAL8 *chi2z)
Check if m1 > m2.
REAL8 XLALSimIMRPhenomXFinalSpin2017(REAL8 eta, REAL8 chi1L, REAL8 chi2L)
Final Dimensionless Spin, X Jimenez-Forteza et al, PRD, 95, 064024, (2017), arXiv:1611....
REAL8 XLALSimIMRPhenomXatan2tol(REAL8 a, REAL8 b, REAL8 tol)
REAL8 XLALSimIMRPhenomXUtilsHztoMf(REAL8 fHz, REAL8 Mtot_Msun)
Convert from frequency in Hz to geometric frequency.
REAL8 XLALSimIMRPhenomXUtilsMftoHz(REAL8 Mf, REAL8 Mtot_Msun)
Convert from geometric frequency to Hz.
REAL8 XLALSimIMRPhenomXPrecessingFinalSpin2017(const REAL8 eta, const REAL8 chi1L, const REAL8 chi2L, const REAL8 chi_inplane)
Wrapper for the final spin in generically precessing binary black holes.
REAL8 XLALSimInspiralChirpTimeBound(REAL8 fstart, REAL8 m1, REAL8 m2, REAL8 s1, REAL8 s2)
Routine to compute an overestimate of the inspiral time from a given frequency.
int XLALSimInspiralGetApproximantFromString(const char *waveform)
Parses a waveform string to determine approximant.
static double beta(const double a, const double b, const sysq *system)
Internal function that computes the spin-orbit couplings.
static REAL8 UNUSED C2(REAL8 e0, REAL8 f0)
static REAL8 UNUSED C1(REAL8 Mtotal)
LALValue * XLALSimInspiralWaveformParamsLookupModeArray(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPNPhaseOrder(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPExpansionOrder(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXPHMThresholdMband(LALDict *params, REAL8 value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPrecVersion(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPTransPrecessionMethod(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXPNRUseTunedAngles(LALDict *params, INT4 value)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXPHMThresholdMband(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPFinalSpinMod(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXAntisymmetricWaveform(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPConvention(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXPNRInterpTolerance(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXHMThresholdMband(LALDict *params, REAL8 value)
const char * XLALSimInspiralWaveformParamsLookupPhenomXPSpinTaylorVersion(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXPNRUseTunedCoprec(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPNPhaseOrder(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPNSpinOrder(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseTunedAngles(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXAntisymmetricWaveform(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPNTidalOrder(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPNTidalOrder(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPNSpinOrder(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPSpinTaylorCoarseFactor(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupLscorr(LALDict *params)
void XLALDestroyValue(LALValue *value)
REAL8 tmp3
REAL8 tmp5
REAL8 tmp1
REAL8 tmp6
Definition: SEOBNRv2_opt_.h:35
REAL8 tmp4
Definition: SEOBNRv2_opt_.h:7
#define fprintf
int s
Definition: bh_qnmode.c:137
REAL8 M
Definition: bh_qnmode.c:133
double i
Definition: bh_ringdown.c:118
double e
Definition: bh_ringdown.c:117
double theta
Definition: bh_sphwf.c:118
double flow
const double sn
const double u3
const double vy
const double u5
sigmaKerr data[0]
const double u2
const double vz
const double u4
const double B
const double vx
#define __attribute__(x)
#define LAL_LN2
#define LAL_PI_2
#define LAL_C_SI
#define LAL_PI
#define LAL_TWOPI
#define LAL_PC_SI
#define LAL_PI_4
#define LAL_GAMMA
#define LAL_G_SI
#define LAL_REAL4_EPS
uint64_t UINT8
#define LIGOTIMEGPSZERO
double complex COMPLEX16
double REAL8
int16_t INT2
uint32_t UINT4
int32_t INT4
void * XLALMalloc(size_t n)
void XLALFree(void *p)
Approximant
Enum that specifies the PN approximant to be used in computing the waveform.
int XLALSimInspiralSpinTaylorPNEvolveOrbit(REAL8TimeSeries **V, REAL8TimeSeries **Phi, REAL8TimeSeries **S1x, REAL8TimeSeries **S1y, REAL8TimeSeries **S1z, REAL8TimeSeries **S2x, REAL8TimeSeries **S2y, REAL8TimeSeries **S2z, REAL8TimeSeries **LNhatx, REAL8TimeSeries **LNhaty, REAL8TimeSeries **LNhatz, REAL8TimeSeries **E1x, REAL8TimeSeries **E1y, REAL8TimeSeries **E1z, REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 fStart, REAL8 fEnd, REAL8 s1x, REAL8 s1y, REAL8 s1z, REAL8 s2x, REAL8 s2y, REAL8 s2z, REAL8 lnhatx, REAL8 lnhaty, REAL8 lnhatz, REAL8 e1x, REAL8 e1y, REAL8 e1z, REAL8 lambda1, REAL8 lambda2, REAL8 quadparam1, REAL8 quadparam2, LALSimInspiralSpinOrder spinO, LALSimInspiralTidalOrder tideO, INT4 phaseO, INT4 lscorr, Approximant approx)
This function evolves the orbital equations for a precessing binary using the "TaylorT1/T5/T4" approx...
INT2Sequence * XLALSimInspiralModeArrayReadModes(LALValue *modes)
static const INT4 m
static const INT4 q
static const INT4 a
void XLALDestroyINT2Sequence(INT2Sequence *sequence)
void XLALDestroyREAL8Sequence(REAL8Sequence *sequence)
REAL8Sequence * XLALCreateREAL8Sequence(size_t length)
COMPLEX16 XLALSpinWeightedSphericalHarmonic(REAL8 theta, REAL8 phi, int s, int l, int m)
REAL8TimeSeries * XLALResizeREAL8TimeSeries(REAL8TimeSeries *series, int first, size_t length)
REAL8TimeSeries * XLALCreateREAL8TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
void XLALDestroyREAL8TimeSeries(REAL8TimeSeries *series)
const LALUnit lalDimensionlessUnit
#define XLAL_ERROR(...)
#define XLAL_CHECK(assertion,...)
#define XLAL_PRINT_WARNING(...)
int XLALPrintError(const char *fmt,...) _LAL_GCC_PRINTF_FORMAT_(1
XLAL_SUCCESS
XLAL_EFAULT
XLAL_EFUNC
XLAL_EDOM
XLAL_EINVAL
XLAL_FAILURE
LIGOTimeGPS * XLALGPSAdd(LIGOTimeGPS *epoch, REAL8 dt)
list x
list p
list y
list mu
string status
end
double alpha
Definition: sgwb.c:106
REAL8 epsilon_offset_3
offset passed to modes.
REAL8 Ny_Sf
Line-of-sight vector component in L frame.
PhenomXPbetaMRD * beta_params
Parameters needed for analytical MRD continuation of cosbeta.
REAL8 PNR_q_window_lower
Boundary values for PNR angle transition window.
INT4 ExpansionOrder
Flag to control expansion order of MSA system of equations.
REAL8 QArunz_Jf
Component of triad basis vector Q in J frame, arXiv:0810.5336.
REAL8 S2L_pav
Precession averaged coupling , Eq.
REAL8 QAruny_Jf
Component of triad basis vector Q in J frame, arXiv:0810.5336.
REAL8 alpha_offset_4
offset passed to modes.
REAL8 zeta_polarization
Angle to rotate the polarizations.
REAL8 v_0
Orbital velocity at reference frequency, .
REAL8 A2
Mass weighted pre-factor, see Eq.
REAL8 phiJ_Sf
Azimuthal angle of in the L frame.
REAL8 thetaJ_Sf
Angle between and (z-direction)
REAL8 PNR_HM_Mflow
Mf_alpha_lower stored from alphaParams struct, 2 A4 / 7 from arXiv:2107.08876.
REAL8 cosbeta_ftrans
Record value of cosbeta at end of inspiral, used in IMRPhenomXPHMTwistUp and IMRPhenomXPHMTwistUpOneM...
REAL8 alpha_ftrans
Record value of alpha at end of inspiral, used in IMRPhenomXPHMTwistUp and IMRPhenomXPHMTwistUpOneMod...
INT4 MBandPrecVersion
Flag to control multibanding for Euler angles.
REAL8 thetaJN
Angle between J0 and line of sight (z-direction)
REAL8 alpha_offset_3
offset passed to modes.
REAL8 epsilon_offset_4
offset passed to modes.
REAL8 alpha_offset_1
offset passed to modes.
REAL8 Spl2
Largest root of polynomial , Eq.
REAL8 Ny_Jf
Line-of-sight vector component in J frame.
REAL8 costheta_singleSpin
Polar angle of effective single spin, Eq.
REAL8 PArunz_Jf
Component of triad basis vector P in J frame, arXiv:0810.5336.
REAL8 S2Lsq_pav
Precession averaged coupling , Eq.
REAL8 Xx_Sf
Component of triad basis vector X in L frame.
REAL8 S32
Third root of polynomial , Eq.
IMRPhenomXWaveformStruct * pWF22AS
REAL8 gamma_ftrans
Record value of gamma at end of inspiral, used in IMRPhenomXPHMTwistUp and IMRPhenomXPHMTwistUpOneMod...
REAL8 PArunx_Jf
Component of triad basis vector P in J frame, arXiv:0810.5336.
REAL8 Nx_Jf
Line-of-sight vector component in J frame.
REAL8 QArunx_Jf
Component of triad basis vector Q in J frame, arXiv:0810.5336.
INT4 IMRPhenomXPrecVersion
Flag to set version of Euler angles used.
REAL8 Smi2
Smallest root of polynomial , Eq.
REAL8 Nz_Jf
Line-of-sight vector component in LJ frame.
INT4 MSA_ERROR
Flag to track errors in initialization of MSA system.
REAL8 chi_singleSpin_antisymmetric
magnitude of effective single spin of a two spin system for the antisymmetric waveform
REAL8 PAruny_Jf
Component of triad basis vector P in J frame, arXiv:0810.5336.
REAL8 Nz_Sf
Line-of-sight vector component in L frame.
PhenomXPalphaMRD * alpha_params
Parameters needed for analytical MRD continuation of alpha.
REAL8 S1S2_pav
Precession averaged coupling , Eq.
REAL8 S1Lsq_pav
Precession averaged coupling , Eq.
REAL8 Xy_Sf
Component of triad basis vector X in L frame.
REAL8 chi_singleSpin
Magnitude of effective single spin used for tapering two-spin angles, Eq.
REAL8 theta_antisymmetric
Polar angle effective single spin for antisymmetric waveform.
REAL8 epsilon_offset_1
offset passed to modes.
REAL8 A1
Mass weighted pre-factor, see Eq.
REAL8 phi0_aligned
Initial phase to feed the underlying aligned-spin model.
REAL8 v_0_2
Orbital velocity at reference frequency squared, .
REAL8 Xz_Sf
Component of triad basis vector X in L frame.
REAL8 Nx_Sf
Line-of-sight vector component in L frame.
REAL8 PNR_HM_Mfhigh
Mf_beta_lower stored from betaParams struct, Eq.
REAL8 S1LS2L_pav
Precession averaged coupling , Eq.
REAL8 S1L_pav
Precession averaged coupling , Eq.
REAL8 costheta_final_singleSpin
Polar angle of approximate final spin, see technical document FIXME: add reference.
UINT4 length
INT2 * data
fitQNM_fdamp fdamp_lm[N_HIGHERMODES_IMPLEMENTED]
REAL8Sequence * data
LIGOTimeGPS epoch
REAL8 * data
double V
Definition: unicorn.c:25
double deltaT
Definition: unicorn.c:24