LALSimulation  5.4.0.1-fe68b98
LALSimIMRPhenomX_PNR_internals.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2022 Cardiff University
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with with program; see the file COPYING. If not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  * MA 02110-1301 USA
18  */
19 
20 #ifdef __cplusplus
21 extern "C"
22 {
23 #endif
24 
25  /**
26  * \author Eleanor Hamilton, Sebastian Khan, Jonathan E. Thompson
27  *
28  */
29 
31 #include "LALSimIMRPhenomUtils.h"
37 
38 #ifndef _OPENMP
39 #define omp ignore
40 #endif
41 
42 #ifndef PHENOMXHMDEBUG
43 #define DEBUG 0
44 #else
45 #define DEBUG 1
46 #endif
47 
48  /**
49  * This function computes the required single-spin
50  * quantities used to parameterize the MR tuned functions from arXiv:2107.08876.
51  *
52  * We place these quantities in the already-existing precession struct
53  * to avoid extensive code modifications.
54  */
56  IMRPhenomXWaveformStruct *pWF, /**< PhenomX waveform struct */
57  IMRPhenomXPrecessionStruct *pPrec /**< PhenomX precession struct */
58  )
59  {
60  /* check for uninitialized structs */
61  XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
62  XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
63 
64  /* get needed quantities */
65  REAL8 m1 = pWF->m1 * pWF->Mtot;
66  REAL8 m2 = pWF->m2 * pWF->Mtot;
67  REAL8 q = pWF->q;
68  REAL8 chi1x = pPrec->chi1x;
69  REAL8 chi1y = pPrec->chi1y;
70  REAL8 chi2x = pPrec->chi2x;
71  REAL8 chi2y = pPrec->chi2y;
72 
73  REAL8 chieff = pWF->chiEff;
74  REAL8 chipar = pWF->Mtot * chieff / m1;
75  REAL8 chiperp = 0.0;
76  REAL8 costheta = 0.0;
77  REAL8 chiperp_antisymmetric = 0.0;
78  REAL8 theta_antisymmetric = 0.0;
79 
80  /* compute effective in-plane spin contribution from Eq. 17 of arXiv:2107.08876 */
81  if (q <= 1.5)
82  {
83  REAL8 chis = sqrt((m1 * m1 * chi1x + m2 * m2 * chi2x) * (m1 * m1 * chi1x + m2 * m2 * chi2x) + (m1 * m1 * chi1y + m2 * m2 * chi2y) * (m1 * m1 * chi1y + m2 * m2 * chi2y)) / (m1 * m1);
84  chiperp = sin((q - 1.0) * LAL_PI) * sin((q - 1.0) * LAL_PI) * pPrec->chi_p + cos((q - 1.0) * LAL_PI) * cos((q - 1.0) * LAL_PI) * chis;
85 
86  REAL8 antisymmetric_chis = sqrt((m1 * m1 * chi1x - m2 * m2 * chi2x) * (m1 * m1 * chi1x - m2 * m2 * chi2x) + (m1 * m1 * chi1y - m2 * m2 * chi2y) * (m1 * m1 * chi1y - m2 * m2 * chi2y)) / (m1 * m1);
87  chiperp_antisymmetric = sin((q - 1.0) * LAL_PI) * sin((q - 1.0) * LAL_PI) * pPrec->chi_p + cos((q - 1.0) * LAL_PI) * cos((q - 1.0) * LAL_PI) * antisymmetric_chis;
88  }
89  else
90  {
91  chiperp = pPrec->chi_p;
92  chiperp_antisymmetric = pPrec->chi_p;
93  }
94 
95  /* get the total magnitude, Eq. 18 of arXiv:2107.08876 */
96  REAL8 chi_mag = sqrt(chipar * chipar + chiperp * chiperp);
97  pPrec->chi_singleSpin = chi_mag;
98 
99  REAL8 chi_mag_antisymmetric = sqrt(chipar * chipar + chiperp_antisymmetric * chiperp_antisymmetric);
100  pPrec->chi_singleSpin_antisymmetric = chi_mag_antisymmetric;
101 
102  /* get the opening angle of the single spin, Eq. 19 of arXiv:2107.08876 */
103  if (chi_mag >= 1.0e-6)
104  {
105  costheta = chipar / chi_mag;
106  }
107  else
108  {
109  costheta = 0.;
110  }
111  if (chi_mag_antisymmetric >= 1.0e-6)
112  {
113  theta_antisymmetric = acos(chipar / chi_mag_antisymmetric);
114  }
115  else
116  {
117  theta_antisymmetric = 0.0;
118  }
119 
120  pPrec->costheta_singleSpin = costheta;
121  pPrec->theta_antisymmetric = theta_antisymmetric;
122 
123  /* compute an approximate final spin using single-spin mapping FIXME: add documentation */
124  REAL8 chi1L = chi_mag * costheta;
125  REAL8 chi2L = 0.0;
126 
127  REAL8 Xfparr = XLALSimIMRPhenomXFinalSpin2017(pWF->eta, chi1L, chi2L);
128 
129  /* rescale Xfperp to use the final total mass of 1 */
130  REAL8 qfactor = q / (1.0 + q);
131  REAL8 Xfperp = qfactor * qfactor * chi_mag * sqrt(1.0 - costheta * costheta);
132  REAL8 xf = sqrt(Xfparr * Xfparr + Xfperp * Xfperp);
133  if (xf > 1.0e-6)
134  {
135  pPrec->costheta_final_singleSpin = Xfparr / xf;
136  }
137  else
138  {
139  pPrec->costheta_final_singleSpin = 0.;
140  }
141 
142  /* Initialize frequency values */
143  pPrec->PNR_HM_Mflow = 0.0;
144  pPrec->PNR_HM_Mfhigh = 0.0;
145 
146  /* set angle window boundaries */
147  pPrec->PNR_q_window_lower = 8.5;
148  pPrec->PNR_q_window_upper = 12.0;
149  pPrec->PNR_chi_window_lower = 0.85;
150  pPrec->PNR_chi_window_upper = 1.2;
151 
152  /* set inspiral scaling flag for HM frequency map */
153  pPrec->PNRInspiralScaling = 0;
154  if ((q > pPrec->PNR_q_window_upper) || (pPrec->chi_singleSpin > pPrec->PNR_chi_window_upper))
155  {
156  pPrec->PNRInspiralScaling = 1;
157  }
158 
159  #if DEBUG == 1
160  printf("\nSetting PNR-related single-spin quantities:\n");
161  printf("chi_singleSpin : %e\n", pPrec->chi_singleSpin);
162  printf("chi_singleSpin_antisymmetric : %e\n", pPrec->chi_singleSpin_antisymmetric);
163  printf("costheta_singleSpin : %e\n", pPrec->costheta_singleSpin);
164  /* printf("theta_singleSpin_antisymmetric : %e\n", pPrec->theta_singleSpin_antisymmetric); */
165  printf("theta_antisymmetric : %e\n", pPrec->theta_antisymmetric);
166  printf("costheta_final_singleSpin : %e\n\n", pPrec->costheta_final_singleSpin);
167  #endif
168 
169  return XLAL_SUCCESS;
170  }
171 
172  /**
173  * These two functions create and populate the required
174  * parameter structs for PNR.
175  *
176  * - First, we check to see if we need to do the two-spin mapping;
177  * if so, the approximate single-spin PhenomX structs are computed.
178  *
179  * - Next we compute the structs for alpha and beta.
180  *
181  * - Finally, the second function is a wrapper for cleaning up memory.
182  */
184  IMRPhenomXWaveformStruct **pWF_SingleSpin, /**< PhenomX waveform struct with single spin parameters */
185  IMRPhenomXPrecessionStruct **pPrec_SingleSpin, /**< PhenomX precession struct with single spin parameters */
186  IMRPhenomX_PNR_alpha_parameters **alphaParams, /**< alpha paramter struct */
187  IMRPhenomX_PNR_beta_parameters **betaParams, /**< beta paramter struct */
188  IMRPhenomXWaveformStruct *pWF, /**< PhenomX waveform struct */
189  IMRPhenomXPrecessionStruct *pPrec, /**< PhenomX precession struct */
190  LALDict *lalParams /**< LAL dictionary struct */
191  )
192  {
193  /* check we should be here */
195  XLAL_CHECK(
196  UsePNR,
197  XLAL_EFUNC,
198  "Error: IMRPhenomX_PNR_PopulateStructs called without PNR angle activated!\n");
199 
200  INT4 status = 0;
201 
202  /* check for two-spin effects, if so generate associated single-spin structs */
203  if (IMRPhenomX_PNR_CheckTwoSpin(pPrec))
204  {
205  REAL8 chi = pPrec->chi_singleSpin;
207  *pWF_SingleSpin = XLALMalloc(sizeof(IMRPhenomXWaveformStruct));
209  *pWF_SingleSpin,
210  pWF->m1_SI,
211  pWF->m2_SI,
212  pWF->chi1L,
213  pWF->chi2L,
214  pWF->deltaF,
215  pWF->fRef,
216  pWF->phi0,
217  pWF->fMin,
218  pWF->fMax,
219  pWF->distance,
220  pWF->inclination,
221  lalParams,
222  DEBUG);
223  XLAL_CHECK(
224  XLAL_SUCCESS == status,
225  XLAL_EFUNC,
226  "Error: IMRPhenomXSetWaveformVariables failed.\n");
227 
228  /* the only needed changes for the single-spin mapping in pWF
229  * are to set the condition PNR_SINGLE_SPIN to overcome the
230  * Kerr limit checks for pPrec, and to update chiEff */
231  (*pWF_SingleSpin)->PNR_SINGLE_SPIN = 1;
232  (*pWF_SingleSpin)->chiEff = XLALSimIMRPhenomXchiEff(pWF->eta, chi * costheta, 0.0);
233 
234  *pPrec_SingleSpin = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
236  *pWF_SingleSpin,
237  *pPrec_SingleSpin,
238  pWF->m1_SI,
239  pWF->m2_SI,
240  chi * sin(acos(costheta)),
241  0.0,
242  chi * costheta,
243  0.0,
244  0.0,
245  0.0,
246  lalParams,
247  DEBUG);
248  XLAL_CHECK(
249  XLAL_SUCCESS == status,
250  XLAL_EFUNC,
251  "Error: IMRPhenomXGetAndSetPrecessionVariables failed.\n");
252  }
253 
254  /* generate alpha parameters */
255  *alphaParams = XLALMalloc(sizeof(IMRPhenomX_PNR_alpha_parameters));
256  status = IMRPhenomX_PNR_precompute_alpha_coefficients(*alphaParams, pWF, pPrec);
257  XLAL_CHECK(
258  XLAL_SUCCESS == status,
259  XLAL_EFUNC,
260  "Error: IMRPhenomX_PNR_precompute_alpha_coefficients failed.\n");
261 
262  status = IMRPhenomX_PNR_alpha_connection_parameters(*alphaParams, pWF, pPrec);
263  XLAL_CHECK(
264  XLAL_SUCCESS == status,
265  XLAL_EFUNC,
266  "Error: IMRPhenomX_PNR_alpha_connection_parameters failed.\n");
267 
268  /* generate beta parameters */
269  *betaParams = XLALMalloc(sizeof(IMRPhenomX_PNR_beta_parameters));
270  status = IMRPhenomX_PNR_precompute_beta_coefficients(*betaParams, pWF, pPrec);
271  XLAL_CHECK(
272  XLAL_SUCCESS == status,
273  XLAL_EFUNC,
274  "Error: IMRPhenomX_PNR_precompute_beta_coefficients failed.\n");
275 
276  status = IMRPhenomX_PNR_beta_connection_parameters(*betaParams, pWF, pPrec, *pWF_SingleSpin, *pPrec_SingleSpin);
277  XLAL_CHECK(
278  XLAL_SUCCESS == status,
279  XLAL_EFUNC,
280  "Error: IMRPhenomX_PNR_beta_connection_parameters failed.\n");
281 
282 #if DEBUG == 1
283  IMRPhenomX_PNR_AngleParameterDebugPrint(*alphaParams, *betaParams);
284 #endif
285 
286  return XLAL_SUCCESS;
287  }
288 
290  IMRPhenomXWaveformStruct **pWF_SingleSpin,
291  IMRPhenomXPrecessionStruct **pPrec_SingleSpin,
292  IMRPhenomX_PNR_alpha_parameters **alphaParams,
293  IMRPhenomX_PNR_beta_parameters **betaParams)
294  {
295  if (*pWF_SingleSpin != NULL)
296  {
297  LALFree(*pWF_SingleSpin);
298  }
299  if (*pPrec_SingleSpin != NULL)
300  {
301  if((*pPrec_SingleSpin)->pWF22AS)
302  {
303  LALFree((*pPrec_SingleSpin)->pWF22AS);
304  }
305  LALFree(*pPrec_SingleSpin);
306  }
307  if (*alphaParams != NULL)
308  {
309  LALFree(*alphaParams);
310  }
311  if (*betaParams != NULL)
312  {
313  LALFree(*betaParams);
314  }
315  }
316 
317  /**
318  * This function computes the frequency integral
319  * outlined in Eq. A7 of arXiv:2107.08876.
320  *
321  * Given frequency arrays of alpha and beta, compute
322  * spline interpolants and then use Boole's rule to
323  * numerically integrate.
324  */
326  REAL8Sequence *gamma, /**< gamma frequency series (rad) */
327  const REAL8Sequence *freqs, /**< input frequencies (Hz) */
328  const REAL8Sequence *alpha, /**< alpha frequency series (rad) */
329  const REAL8Sequence *beta /**< beta frequency series (rad) */
330  )
331  {
332  /* Check that all is well with the inputs */
333  XLAL_CHECK(gamma != NULL, XLAL_EFAULT);
334  XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
335  XLAL_CHECK(alpha != NULL, XLAL_EFAULT);
336  XLAL_CHECK(beta != NULL, XLAL_EFAULT);
337 
338  /* Initialise the struct containing splines of alpha and beta */
339  IMRPhenomX_PNR_angle_spline ab_splines;
340 
341  /* Construct splines of alpha and beta*/
342  gsl_spline *alpha_spline = gsl_spline_alloc(gsl_interp_cspline, freqs->length);
343  gsl_spline *beta_spline = gsl_spline_alloc(gsl_interp_cspline, freqs->length);
344 
345  gsl_interp_accel *alpha_acc = gsl_interp_accel_alloc();
346  gsl_interp_accel *beta_acc = gsl_interp_accel_alloc();
347 
348  gsl_spline_init(alpha_spline, freqs->data, alpha->data, freqs->length);
349  gsl_spline_init(beta_spline, freqs->data, beta->data, freqs->length);
350 
351  /* Populate combined angle spline struct */
352  ab_splines.alpha_spline = alpha_spline;
353  ab_splines.alpha_acc = alpha_acc;
354  ab_splines.beta_spline = beta_spline;
355  ab_splines.beta_acc = beta_acc;
356 
357  /* Initialize gamma to zero. Will be corrected with the reference value later. */
358  gamma->data[0] = 0.0;
359 
360  /* Numerically integrate \gamma' = -\alpha' \cos(\beta) to get gamma
361  * Eq. A7 of arXiv:2107.08876
362  * Integration is done using Boole's Rule. */
363  for (size_t i = 1; i < freqs->length; i++)
364  {
365  REAL8 t1 = freqs->data[i - 1];
366  REAL8 t2 = freqs->data[i];
367  /* Boole's Rule with stepsize h = deltaF / 4 */
368  gamma->data[i] = (gamma->data[i - 1] + (1.0 / 90.0) * (t2 - t1) * (7.0 * IMRPhenomX_PNR_alphadot_cosbeta(t1, &ab_splines) + 32.0 * IMRPhenomX_PNR_alphadot_cosbeta((t1 + 3.0 * t2) / 4.0, &ab_splines) + 12.0 * IMRPhenomX_PNR_alphadot_cosbeta((t1 + t2) / 2.0, &ab_splines) + 32.0 * IMRPhenomX_PNR_alphadot_cosbeta((3.0 * t1 + t2) / 4.0, &ab_splines) + 7.0 * IMRPhenomX_PNR_alphadot_cosbeta(t2, &ab_splines)));
369  }
370 
371  /* Free up memory allocation */
372  gsl_spline_free(alpha_spline);
373  gsl_spline_free(beta_spline);
374 
375  gsl_interp_accel_free(alpha_acc);
376  gsl_interp_accel_free(beta_acc);
377 
378  return XLAL_SUCCESS;
379  }
380 
381  /**
382  * This function computes the frequency integral
383  * outlined in Eq. A7 of arXiv:2107.08876.
384  *
385  * Given spline interpolants of alpha and beta, compute
386  * use Boole's rule to numerically integrate.
387  */
389  REAL8Sequence *gamma, /**< frequency series for gamma (rad) */
390  const REAL8Sequence *freqs, /**< input frequencies (Hz) */
391  IMRPhenomX_PNR_angle_spline *ab_splines /**< pnr angle spline struct */
392  )
393  {
394  /* Check that everything is initialized */
395  XLAL_CHECK(gamma != NULL, XLAL_EFAULT);
396  XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
397  XLAL_CHECK(ab_splines != NULL, XLAL_EFAULT);
398 
399  /* Initialize gamma to zero. Will be corrected with the reference value later. */
400  gamma->data[0] = 0.0;
401 
402  /* Numerically integrate \gamma' = -\alpha' \cos(\beta) to get gamma
403  * Eq. A7 of arXiv:2107.08876
404  * Integration is done using Boole's Rule. */
405  for (size_t i = 1; i < freqs->length; i++)
406  {
407  REAL8 t1 = freqs->data[i - 1];
408  REAL8 t2 = freqs->data[i];
409  /* Boole's Rule with stepsize h = deltaF / 4 */
410  gamma->data[i] = (gamma->data[i - 1] + (1.0 / 90.0) * (t2 - t1) * (7.0 * IMRPhenomX_PNR_alphadot_cosbeta(t1, ab_splines) + 32.0 * IMRPhenomX_PNR_alphadot_cosbeta((t1 + 3.0 * t2) / 4.0, ab_splines) + 12.0 * IMRPhenomX_PNR_alphadot_cosbeta((t1 + t2) / 2.0, ab_splines) + 32.0 * IMRPhenomX_PNR_alphadot_cosbeta((3.0 * t1 + t2) / 4.0, ab_splines) + 7.0 * IMRPhenomX_PNR_alphadot_cosbeta(t2, ab_splines)));
411  }
412 
413  return XLAL_SUCCESS;
414  }
415 
416  /**
417  * Wrapper function for computing the integrand in
418  * Eq. A7 of arXiv:2107.08876
419  */
421  REAL8 f, /**< frequency (Hz) */
422  IMRPhenomX_PNR_angle_spline *params /**< pnr angle interpolant struct */
423  )
424  {
425  REAL8 alphadot = gsl_spline_eval_deriv(params->alpha_spline, f, params->alpha_acc);
426  REAL8 beta = gsl_spline_eval(params->beta_spline, f, params->beta_acc);
427 
428  return -1.0 * alphadot * cos(beta);
429  }
430 
431  /**
432  * Computes a linear frequency map for the HM PNR angles
433  * based on the linear frequency mapping used originally in
434  * PhenomHM (Eq. 5 of arXiv:1708.00404)
435  *
436  * The specifics are given in Eq. #### of FIXME: add documentation
437  */
439  REAL8 Mf, /**< geometric evaluation frequency */
440  REAL8 ell, /**< polar index */
441  REAL8 emm, /**< azimuthal index */
442  REAL8 Mf_lower, /**< lower geometric transition frequency */
443  REAL8 Mf_upper, /**< upper geometric transition frequency */
444  REAL8 Mf_RD_22, /**< (2,2) geometric ringdown frequency */
445  REAL8 Mf_RD_lm, /**< (l,m) geometric ringdown frequency */
446  UINT4 INSPIRAL /**< flag to toggle inspiral scaling only */
447  )
448  {
449  /* initial break if mapping (2,2) */
450  if ((ell == 2) && (emm == 2))
451  {
452  return Mf;
453  }
454 
455  /* below the lower transition, or outside calibration region, using SPA scaling */
456  if ((Mf <= Mf_lower) || INSPIRAL)
457  {
458  return 2.0 * Mf / emm;
459  }
460 
461  /* check that the lower and upper transition frequencies make sense */
462  XLAL_CHECK(
463  Mf_lower <= Mf_upper,
464  XLAL_EFUNC,
465  "Error: Low-frequency cutoff is greater than high-frequency cutoff in IMRPhenomX_PNR_LinearFrequencyMap.\n");
466 
467  /* above the upper transition, shift by the difference of the RD frequencies */
468  if (Mf > Mf_upper)
469  {
470  return Mf - (Mf_RD_lm - Mf_RD_22);
471  }
472 
473  /* construct a linear transition between Mf_lower and Mf_upper scalings */
474  REAL8 A = IMRPhenomX_PNR_LinearFrequencySlope(emm, Mf_lower, Mf_upper, Mf_RD_22, Mf_RD_lm);
475  REAL8 B = 2.0 * Mf_lower / emm;
476 
477  return A * (Mf - Mf_lower) + B;
478  }
479 
480  /**
481  * Computes the slope of the linear frequency interpolation
482  * mapping used for the HM PNR angles.
483  *
484  * See Eq. ### of FIXME: add citation
485  */
487  REAL8 emm, /**< azimuthal index */
488  REAL8 Mf_lower, /**< lower geometric transition frequency */
489  REAL8 Mf_upper, /**< upper geometric transition frequency */
490  REAL8 Mf_RD_22, /**< (2,2) geometric ringdown frequency */
491  REAL8 Mf_RD_lm /**< (l,m) geometric ringdown frequency */
492  )
493  {
494  /* check that the lower and upper transition frequencies make sense */
495  XLAL_CHECK(
496  Mf_lower <= Mf_upper,
497  XLAL_EFUNC,
498  "Error: Low-frequency cutoff is greater than high-frequency cutoff in IMRPhenomX_PNR_LinearFrequencySlope.\n");
499 
500  /* compute the slope */
501  return (Mf_upper - (Mf_RD_lm - Mf_RD_22) - 2.0 * Mf_lower / emm) / (Mf_upper - Mf_lower);
502  }
503 
504  /**
505  * Compute the transition frequencies for the HM PNR angle
506  * mapping.
507  *
508  * See Eq. ### of FIXME: add documentation
509  */
511  REAL8 *Mf_low, /**< lower transition frequency */
512  REAL8 *Mf_high, /**< upper transition frequency */
513  REAL8 emmprime, /**< azimuthal index */
514  REAL8 Mf_RD_22, /**< (2,2) geometric ringdown frequency */
515  REAL8 Mf_RD_lm, /**< (l,m) geometric ringdown frequency */
516  IMRPhenomXPrecessionStruct *pPrec /**< PhenomX precession struct */
517  )
518  {
519  /* Check that everything is initialized */
520  XLAL_CHECK(Mf_low != NULL, XLAL_EFAULT);
521  XLAL_CHECK(Mf_high != NULL, XLAL_EFAULT);
522  XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
523 
524  /* specify choices for connection frequencies */
525  *Mf_low = 0.65 * pPrec->PNR_HM_Mflow * emmprime / 2.0;
526  *Mf_high = 1.1 * (pPrec->PNR_HM_Mfhigh + (Mf_RD_lm - Mf_RD_22));
527 
528  /* check for cases with (2,1) multipole where Mf_high might misbehave */
529  if ((*Mf_high < 0.0)||((Mf_RD_lm - Mf_RD_22 < 0.0)&&(*Mf_high < pPrec->PNR_HM_Mfhigh / 2.0))){
530  *Mf_high = pPrec->PNR_HM_Mfhigh;
531  }
532 
533  return XLAL_SUCCESS;
534  }
535 
536  /**
537  * Here we compute an appropriate deltaF to be used when generating
538  * the (2,2) angle interpolants and mapping them to the HMs.
539  *
540  * FIXME: add documentation
541  */
543  REAL8 f_min, /**< minimum starting frequency (Hz) */
544  IMRPhenomXWaveformStruct *pWF, /**< PhenomX waveform struct */
545  IMRPhenomXPrecessionStruct *pPrec /**< PhenomX precession struct */
546  )
547  {
548  /* First compute the deltaF required for a single-spin system. This assumes error
549  * scaling roughly like deltaF**4 in the cubic spline. */
550 
551  REAL8 error_tolerance = pPrec->IMRPhenomXPNRInterpTolerance;
552 
553  /* aligned-spin limit means we don't need high resolution on the prec angles (which should be zero) */
554  if((pPrec->chi1_perp==0.0)&&(pPrec->chi2_perp==0.0))
555  {
556  if(pWF->deltaF != 0){
557  return pWF->deltaF;
558  }
559 
560  return 0.1;
561  }
562 
563  /* Compute approximate scaling from leading-order alpha contribution, a la
564  * - Eq. 5.6 of arXiv:2004.06503
565  * - Eq. 2.8 of arXiv:2001.10897 */
567  REAL8 eta_term = sqrt(1.0 - 4.0 * pWF->eta);
568  REAL8 numerator = 3.0 * LAL_PI * Mf * Mf * Mf * Mf * Mf * error_tolerance * (1.0 + eta_term);
569  REAL8 denominator = 7.0 + 13.0 * eta_term;
570  REAL8 constant = 4.0 * sqrt(2.0 / 5.0);
571 
572  REAL8 deltaF_alpha = XLALSimPhenomUtilsMftoHz(constant * sqrt(sqrt(numerator / denominator)), pWF->Mtot);
573 
574  /* Check for two-spin oscillations!
575  * We approximate the frequency sampling required
576  * by first approximating the frequency of oscillation
577  * in the magnitude of the total spin computed by the MSA expansion */
578  if (IMRPhenomX_PNR_CheckTwoSpin(pPrec))
579  {
580  /* we have a well-behaved MSA expansion, so we grab precomputed terms */
581  REAL8 g0 = pPrec->g0;
582  REAL8 deltam = pPrec->delta_qq;
583  REAL8 psi1 = pPrec->psi1;
584  REAL8 psi2 = pPrec->psi2;
585 
586  REAL8 v0 = cbrt(LAL_PI * Mf);
587  REAL8 v02 = v0 * v0;
588  REAL8 v06 = v02 * v02 * v02;
589 
590  /* this is the frequency derivative of psi, Eq. 51 of arXiv:1703.03967
591  * approximate frequency of the two-spin oscillations */
592  REAL8 dpsi = g0 * deltam * LAL_PI / (4.0 * v06) * (3.0 + 2.0 * psi1 * v0 + psi2 * v02);
593  /* we can approximate the period by the inverse of dpsi */
594  REAL8 dpsiInv = fabs(1.0 / dpsi);
595 
596  /* when oscillations in beta bring its value close to zero, alpha can undergo
597  * sudden shifts by factors of pi. We need to predict this to provide enough sampling
598  * for good interpolation.
599  *
600  * To do this, we first make sure that we have a large-enough mass-ratio to provide noticeable drift
601  * in the in-plane spin orientations.
602  *
603  * Next we compute the minimum and maximum values that beta can take at the starting frequency
604  * of the interpolation. If betaMin is very close to zero AND it is significantly smaller than betaMax,
605  * then we have large oscillations and the potential for sharp jumps in alpha. We then divide the
606  * approximate two-spin oscillation period by 4 to increase sampling resolution. */
607 
608  REAL8 L_fmin = pWF->Mtot * pWF->Mtot * XLALSimIMRPhenomXLPNAnsatz(v0, pWF->eta / v0, pPrec->L0, pPrec->L1, pPrec->L2, pPrec->L3, pPrec->L4, pPrec->L5, pPrec->L6, pPrec->L7, pPrec->L8, pPrec->L8L);
609 
610  REAL8 betaMin = atan2(fabs(pPrec->S1_perp - pPrec->S2_perp), L_fmin + pPrec->SL);
611  REAL8 betaMax = atan2(pPrec->S1_perp + pPrec->S2_perp, L_fmin + pPrec->SL);
612 
613 #if DEBUG == 1
614  // Save angles into a file
615  FILE *fileangle;
616  char fileSpecIII[40];
617  sprintf(fileSpecIII, "PNR_HM_interpolation_data.dat");
618  fileangle = fopen(fileSpecIII, "w");
619 
620  fprintf(fileangle, "dpsi = %.16e \n", dpsi);
621  fprintf(fileangle, "pPrec->S1_perp = %.16e \n", pPrec->S1_perp);
622  fprintf(fileangle, "pPrec->S2_perp = %.16e \n", pPrec->S2_perp);
623  fprintf(fileangle, "pPrec->SL = %.16e \n", pPrec->SL);
624  fprintf(fileangle, "pPrec->LRef = %.16e \n\n", pPrec->LRef);
625  fprintf(fileangle, "L_fmin = %.16e \n\n", L_fmin);
626  fprintf(fileangle, "betaMin = %.16e \n", betaMin);
627  fprintf(fileangle, "betaMax = %.16e \n", betaMax);
628 
629  fclose(fileangle);
630 #endif
631 
632  if ((betaMin < 0.01) && (betaMin / betaMax < 0.55))
633  {
634  dpsiInv /= 4;
635  }
636 
637 
638  /* sample 4 points per oscillaion */
639  REAL8 deltaF_twospin = XLALSimPhenomUtilsMftoHz(dpsiInv / 4.0, pWF->Mtot);
640 
641  /* check that deltaF_twospin is smaller than the spacing required by alpha */
642  if ((deltaF_twospin < deltaF_alpha) && !isnan(dpsi))
643  {
644  /* put a hard limit of 0.01 on deltaF to avoid memory saturation */
645  return (deltaF_twospin < 1e-2) ? 1e-2 : deltaF_twospin;
646  }
647  }
648  /* put a hard limit of 0.01 on deltaF to avoid memory saturation */
649  return (deltaF_alpha < 1e-2) ? 1e-2 : deltaF_alpha;
650  }
651 
652  /**
653  * This function quickly checks to see if we expect two-spin effects
654  * to be present in the inspiral precession angles.
655  */
657  IMRPhenomXPrecessionStruct *pPrec /**< PhenomX precession struct */
658  )
659  {
660  /* Ensure we have:
661  * - non-zero spin on the primary
662  * - non-trivial spin on the secondary
663  * - activated the MSA angles
664  */
665  if ((pPrec->chi1_norm != 0.0) && (pPrec->chi2_norm >= 1.0e-3) && (pPrec->IMRPhenomXPrecVersion >= 200))
666  {
667  return 1;
668  }
669 
670  return 0;
671  }
672 
673  /**
674  * Evaluates a function at two points and interpolates between them.
675  */
677  const REAL8Sequence *angle, /**< input angle array (rad) */
678  const REAL8 f_ref, /**< reference frequency value (Hz) */
679  const REAL8Sequence *freqs, /**< evaluation frequency */
680  const REAL8 deltaF /**< evaluation frequency */
681  )
682  {
683  /* this function assumes a uniform frequency sampling */
684  /* Check that everything is initialized */
685  XLAL_CHECK(angle != NULL, XLAL_EFAULT);
686  XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
687 
688  /* check that f_ref is within range */
689  REAL8 f_min = freqs->data[0];
690  REAL8 f_max = freqs->data[freqs->length - 1];
691 
692  XLAL_CHECK((f_min <= f_ref) && (f_ref <= f_max),
693  XLAL_EFUNC,
694  "Error: f_ref does not fall within the evaluated frequencies of the angle in IMRPhenomX_PNR_AngleAtFRef.\n");
695 
696  size_t idx_fmin = (size_t)(f_min / deltaF);
697  size_t idx_eval = (f_ref == f_min) ? 0 : (size_t)(f_ref / deltaF) - idx_fmin;
698  size_t idx_eval_p1 = idx_eval + 1;
699 
701  angle->data[idx_eval], angle->data[idx_eval_p1],
702  freqs->data[idx_eval], freqs->data[idx_eval_p1],
703  f_ref);
704  }
705 
706  /**
707  * Evaluates a function at two points and interpolates between them.
708  */
710  REAL8 a0, /**< function evaluated at f0 */
711  REAL8 a1, /**< function evaluated at f1 */
712  REAL8 f0, /**< lower frequency value */
713  REAL8 f1, /**< upper frequency value */
714  REAL8 feval /**< evaluation frequency */
715  )
716  {
717  XLAL_CHECK(
718  (f0 <= feval) && (feval <= f1),
719  XLAL_EFUNC,
720  "Error: the evaluation frequency is not between the two sampling frequencies in IMRPhenomX_PNR_LinearInterpolate.\n");
721 
722  /* evaluate linear interpolation between f0 and f1 */
723  return a0 + (feval - f0) * (a1 - a0) / (f1 - f0);
724  }
725 
726  /**
727  * This code recomputes the skymapped locations in the J-frame
728  * using the new value of beta computed from the model. This beta
729  * corresponds to the orientation of the maximal emission direction
730  * relative to J, as opposed to the orientation of L.
731  *
732  * This code is mostly copied from LAlSimIMRPhenomX_precession.c with slight modifications.
733  */
735  REAL8 beta_ref, /**< reference opening angle (rad) */
736  IMRPhenomXWaveformStruct *pWF, /**< PhenomX waveform struct */
737  IMRPhenomXPrecessionStruct *pPrec, /**< PhenomX precession struct */
738  LALDict *lalParams /**< LAL dictionary struct */
739  )
740  {
741  /* Check that everything is initialized */
742  XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
743  XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
744  XLAL_CHECK(lalParams != NULL, XLAL_EFAULT);
745 
746  /* first check to see if nothing needs doing */
747  if ((pPrec->J0 < 1e-10))
748  {
749  return XLAL_SUCCESS;
750  }
751  REAL8 chi_in_plane = sqrt(pPrec->chi1x*pPrec->chi1x+pPrec->chi1y*pPrec->chi1y+pPrec->chi2x*pPrec->chi2x+pPrec->chi2y*pPrec->chi2y);
752  if (chi_in_plane < 1e-3)
753  {
754  pPrec->alpha_offset -= pPrec->alpha0;
755  return XLAL_SUCCESS;
756  }
757 
758  /* replace thetaJN with beta at f_Ref */
759  pPrec->thetaJ_Sf = beta_ref;
760 
761  /* re-run all the following code to get thetaJN, correct polarizations, and angle offsets */
762  /* annoying to duplicate code, but it's the easiest way to modify thetaJN */
763  const double phiRef = pWF->phiRef_In;
764 
766  if (!(convention == 0 || convention == 1 || convention == 5 || convention == 6 || convention == 7))
767  {
768  XLAL_ERROR(XLAL_EINVAL, "Error: IMRPhenomXPConvention not recognized. Requires version 0, 1, 5, 6 or 7.\n");
769  }
770 
771  /*
772  Here we follow the same prescription as in IMRPhenomPv2:
773 
774  Now rotate from SF to J frame to compute alpha0, the azimuthal angle of LN, as well as
775  thetaJ, the angle between J and N.
776 
777  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
778  (with positive projection along x).
779 
780  The components of any vector in the (new) J-frame can be obtained by rotation from the (old) source frame (SF).
781  This is done by multiplying by: RZ[-kappa].RY[-thetaJ].RZ[-phiJ]
782 
783  Note that kappa is determined by rotating N with RY[-thetaJ].RZ[-phiJ], which brings J to the z-axis, and
784  taking the opposite of the azimuthal angle of the rotated N.
785  */
786 
787  /* Determine kappa via rotations, as above */
788  pPrec->Nx_Sf = sin(pWF->inclination) * cos((LAL_PI / 2.0) - phiRef);
789  pPrec->Ny_Sf = sin(pWF->inclination) * sin((LAL_PI / 2.0) - phiRef);
790  pPrec->Nz_Sf = cos(pWF->inclination);
791 
792  REAL8 tmp_x = pPrec->Nx_Sf;
793  REAL8 tmp_y = pPrec->Ny_Sf;
794  REAL8 tmp_z = pPrec->Nz_Sf;
795 
796  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
797  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
798 
799  /* Note difference in overall - sign w.r.t PhenomPv2 code */
800  pPrec->kappa = XLALSimIMRPhenomXatan2tol(tmp_y, tmp_x, MAX_TOL_ATAN);
801 
802  /* Now determine alpha0 by rotating LN. In the source frame, LN = {0,0,1} */
803  tmp_x = 0.0;
804  tmp_y = 0.0;
805  tmp_z = 1.0;
806  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
807  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
808  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
809 
810  if (fabs(tmp_x) < MAX_TOL_ATAN && fabs(tmp_y) < MAX_TOL_ATAN)
811  {
812 /* This is the aligned spin case */
813 #if DEBUG == 1
814  printf("\nAligned-spin case.\n");
815 #endif
816 
817  switch (convention)
818  {
819  case 0:
820  case 5:
821  {
822  pPrec->alpha0 = LAL_PI;
823  break;
824  }
825  case 1:
826  case 6:
827  case 7:
828  {
829  pPrec->alpha0 = LAL_PI - pPrec->kappa;
830  break;
831  }
832  }
833  }
834  else
835  {
836  switch (convention)
837  {
838  case 0:
839  case 5:
840  {
841  pPrec->alpha0 = atan2(tmp_y, tmp_x);
842  break;
843  }
844  case 1:
845  case 6:
846  case 7:
847  {
848  pPrec->alpha0 = LAL_PI - pPrec->kappa;
849  break;
850  }
851  }
852  }
853 
854  /* update alpha_offset with alpha0 */
855 
856  pPrec->alpha_offset -= pPrec->alpha0;
857 
858  /* remove convention options for thetaJN using PNR, so that PNR beta is used instead of theta_JL */
859 
860  /* Now determine thetaJN by rotating N */
861  tmp_x = pPrec->Nx_Sf;
862  tmp_y = pPrec->Ny_Sf;
863  tmp_z = pPrec->Nz_Sf;
864  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
865  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
866  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
867 
868  /* We don't need the y-component but we will store it anyway */
869  pPrec->Nx_Jf = tmp_x;
870  pPrec->Ny_Jf = tmp_y;
871  pPrec->Nz_Jf = tmp_z;
872 
873  /* This is a unit vector, so no normalization */
874  pPrec->thetaJN = acos(pPrec->Nz_Jf);
875 
876  /*
877  Define the polarizations used. This follows the conventions adopted for IMRPhenomPv2.
878 
879  The IMRPhenomP polarizations are defined following the conventions in Arun et al (arXiv:0810.5336),
880  i.e. projecting the metric onto the P, Q, N triad defining where: P = (N x J) / |N x J|.
881 
882  However, the triad X,Y,N used in LAL (the "waveframe") follows the definition in the
883  NR Injection Infrastructure (Schmidt et al, arXiv:1703.01076).
884 
885  The triads differ from each other by a rotation around N by an angle \zeta. We therefore need to rotate
886  the polarizations by an angle 2 \zeta.
887  */
888  pPrec->Xx_Sf = -cos(pWF->inclination) * sin(phiRef);
889  pPrec->Xy_Sf = -cos(pWF->inclination) * cos(phiRef);
890  pPrec->Xz_Sf = +sin(pWF->inclination);
891 
892  tmp_x = pPrec->Xx_Sf;
893  tmp_y = pPrec->Xy_Sf;
894  tmp_z = pPrec->Xz_Sf;
895 
896  IMRPhenomX_rotate_z(-pPrec->phiJ_Sf, &tmp_x, &tmp_y, &tmp_z);
897  IMRPhenomX_rotate_y(-pPrec->thetaJ_Sf, &tmp_x, &tmp_y, &tmp_z);
898  IMRPhenomX_rotate_z(-pPrec->kappa, &tmp_x, &tmp_y, &tmp_z);
899 
900  /*
901  The components tmp_i are now the components of X in the J frame.
902 
903  We now need the polar angle of this vector in the P, Q basis of Arun et al:
904 
905  P = (N x J) / |NxJ|
906 
907  Note, that we put N in the (pos x)z half plane of the J frame
908  */
909 
910  switch (convention)
911  {
912  case 0:
913  case 5:
914  {
915  /* Get polar angle of X vector in J frame in the P,Q basis of Arun et al */
916  pPrec->PArunx_Jf = +0.0;
917  pPrec->PAruny_Jf = -1.0;
918  pPrec->PArunz_Jf = +0.0;
919 
920  /* Q = (N x P) by construction */
921  pPrec->QArunx_Jf = pPrec->Nz_Jf;
922  pPrec->QAruny_Jf = 0.0;
923  pPrec->QArunz_Jf = -pPrec->Nx_Jf;
924  break;
925  }
926  case 1:
927  case 6:
928  case 7:
929  {
930  /* Get polar angle of X vector in J frame in the P,Q basis of Arun et al */
931  pPrec->PArunx_Jf = pPrec->Nz_Jf;
932  pPrec->PAruny_Jf = 0;
933  pPrec->PArunz_Jf = -pPrec->Nx_Jf;
934 
935  /* Q = (N x P) by construction */
936  pPrec->QArunx_Jf = 0;
937  pPrec->QAruny_Jf = 1;
938  pPrec->QArunz_Jf = 0;
939  break;
940  }
941  }
942 
943  // (X . P)
944  pPrec->XdotPArun = (tmp_x * pPrec->PArunx_Jf) + (tmp_y * pPrec->PAruny_Jf) + (tmp_z * pPrec->PArunz_Jf);
945 
946  // (X . Q)
947  pPrec->XdotQArun = (tmp_x * pPrec->QArunx_Jf) + (tmp_y * pPrec->QAruny_Jf) + (tmp_z * pPrec->QArunz_Jf);
948 
949  /* Now get the angle zeta */
950  pPrec->zeta_polarization = atan2(pPrec->XdotQArun, pPrec->XdotPArun);
951 
952  const REAL8 ytheta = pPrec->thetaJN;
953  const REAL8 yphi = 0.0;
954  pPrec->Y2m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, -2);
955  pPrec->Y2m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, -1);
956  pPrec->Y20 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 0);
957  pPrec->Y21 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 1);
958  pPrec->Y22 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 2, 2);
959  pPrec->Y3m3 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -3);
960  pPrec->Y3m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -2);
961  pPrec->Y3m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, -1);
962  pPrec->Y30 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 0);
963  pPrec->Y31 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 1);
964  pPrec->Y32 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 2);
965  pPrec->Y33 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 3, 3);
966  pPrec->Y4m4 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -4);
967  pPrec->Y4m3 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -3);
968  pPrec->Y4m2 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -2);
969  pPrec->Y4m1 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, -1);
970  pPrec->Y40 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 0);
971  pPrec->Y41 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 1);
972  pPrec->Y42 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 2);
973  pPrec->Y43 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 3);
974  pPrec->Y44 = XLALSpinWeightedSphericalHarmonic(ytheta, yphi, -2, 4, 4);
975 
976  return XLAL_SUCCESS;
977  }
978 
979  /**
980  * This code recomputes the skymapped locations in the J-frame
981  * using the new value of beta computed from the model. This beta
982  * corresponds to the orientation of the maximal emission direction
983  * relative to J, as opposed to the orientation of L.
984  *
985  * This code is mostly copied from LAlSimIMRPhenomX_precession.c with slight modifications.
986  */
988  IMRPhenomXWaveformStruct *pWF, /**< PhenomX waveform struct */
989  UINT4 ell, /**< polar index */
990  UINT4 emmprime, /**< azimuthal index */
991  LALDict *lalParams /**< LAL Dictionary struct */
992  )
993  {
994  /* Check that everything is initialized */
995  XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
996  XLAL_CHECK(lalParams != NULL, XLAL_EFAULT);
997 
998  REAL8 effRD = 0.0;
999 
1000  /* if (2,2) return normal RD frequency */
1001  if ((ell == 2) & (emmprime == 2))
1002  {
1003  effRD = pWF->fRING;
1004  }
1005  else
1006  {
1007  /* compute QNM and pWFHM structs */
1008  QNMFits *qnms = (QNMFits *)XLALMalloc(sizeof(QNMFits));
1011  IMRPhenomXHM_SetHMWaveformVariables(ell, emmprime, pWFHM, pWF, qnms, lalParams);
1012 
1013  /* grab effective RD frequency */
1014  effRD = pWFHM->fRING;
1015 
1016  /* clean up memory allocation */
1017  LALFree(pWFHM);
1018  LALFree(qnms);
1019  }
1020 
1021  return effRD;
1022  }
1023 
1024  /**
1025  * Print various parameters in the angle structs.
1026  */
1028  IMRPhenomX_PNR_alpha_parameters *alphaParams, /**< alpha parameter struct */
1029  IMRPhenomX_PNR_beta_parameters *betaParams /**< beta parameter struct */
1030  )
1031  {
1032  // Save stuff into a file
1033  FILE *fileparams;
1034  char fileSpec[40];
1035  sprintf(fileSpec, "pnr_angle_parameters.dat");
1036 
1037  fileparams = fopen(fileSpec, "w");
1038 
1039  fprintf(fileparams, "~~~~~~ Alpha Coefficients ~~~~~~\n");
1040  fprintf(fileparams, "A1 = %.16e\n", alphaParams->A1);
1041  fprintf(fileparams, "A2 = %.16e\n", alphaParams->A2);
1042  fprintf(fileparams, "A3 = %.16e\n", alphaParams->A3);
1043  fprintf(fileparams, "A4 = %.16e\n\n", alphaParams->A4);
1044 
1045  fprintf(fileparams, "~~~~~~ Beta Coefficients ~~~~~~\n");
1046  fprintf(fileparams, "B0 = %.16e\n", betaParams->B0);
1047  fprintf(fileparams, "B1 = %.16e\n", betaParams->B1);
1048  fprintf(fileparams, "B2 = %.16e\n", betaParams->B2);
1049  fprintf(fileparams, "B3 = %.16e\n", betaParams->B3);
1050  fprintf(fileparams, "B4 = %.16e\n", betaParams->B4);
1051  fprintf(fileparams, "B5 = %.16e\n\n", betaParams->B5);
1052 
1053  fprintf(fileparams, "~~~~~~ Connection Values Alpha ~~~~~~\n");
1054  fprintf(fileparams, "Mf_alpha_lower = %.16e\n", alphaParams->Mf_alpha_lower);
1055  fprintf(fileparams, "Mf_alpha_upper = %.16e\n", alphaParams->Mf_alpha_upper);
1056  fprintf(fileparams, "alpha_lower = %.16e\n", alphaParams->alpha_lower);
1057  fprintf(fileparams, "alpha_upper = %.16e\n", alphaParams->alpha_upper);
1058  fprintf(fileparams, "derivative_alpha_lower = %.16e\n", alphaParams->derivative_alpha_lower);
1059  fprintf(fileparams, "derivative_alpha_upper = %.16e\n", alphaParams->derivative_alpha_upper);
1060  fprintf(fileparams, "alpha_interp_0 = %.16e\n", alphaParams->alpha_interp_0);
1061  fprintf(fileparams, "alpha_interp_1 = %.16e\n", alphaParams->alpha_interp_1);
1062  fprintf(fileparams, "alpha_interp_2 = %.16e\n", alphaParams->alpha_interp_2);
1063  fprintf(fileparams, "alpha_interp_3 = %.16e\n\n", alphaParams->alpha_interp_3);
1064 
1065  fprintf(fileparams, "~~~~~~ Connection Values Beta ~~~~~~\n");
1066  fprintf(fileparams, "Mf_beta_lower = %.16e\n", betaParams->Mf_beta_lower);
1067  fprintf(fileparams, "Mf_beta_upper = %.16e\n", betaParams->Mf_beta_upper);
1068  fprintf(fileparams, "beta_lower = %.16e\n", betaParams->beta_lower);
1069  fprintf(fileparams, "beta_upper = %.16e\n", betaParams->beta_upper);
1070  fprintf(fileparams, "derivative_beta_lower = %.16e\n", betaParams->derivative_beta_lower);
1071  fprintf(fileparams, "derivative_beta_upper = %.16e\n", betaParams->derivative_beta_upper);
1072  fprintf(fileparams, "beta_rescale_1 = %.16e\n", betaParams->beta_rescale_1);
1073  fprintf(fileparams, "beta_rescale_2 = %.16e\n\n", betaParams->beta_rescale_2);
1074 
1075  fclose(fileparams);
1076  }
1077 
1078 
1079  /* Compute window function to ensure smooth transition to PN expression for angles outside calibration. */
1083  ){
1084 
1085  double window_q_boundary = 8.5;
1086  double window_chi_boundary = 0.85;
1087 
1088  double window_width_q = 3.5;
1089  double window_width_chi = 0.35;
1090 
1091  double window_q_argument = (pWF->q - window_q_boundary ) / window_width_q;
1092  double window_chi_argument = (pPrec->chi_singleSpin - window_chi_boundary) / window_width_chi;
1093 
1094  // Window mass ratio
1095  double window_q_value;
1096  if ( (window_q_argument>0) && (window_q_argument<=1) ){
1097  window_q_value = 0.5*cos( window_q_argument * LAL_PI ) + 0.5;
1098  } else if ( window_q_argument>1 ) {
1099  window_q_value = 0;
1100  } else {
1101  window_q_value = 1;
1102  }
1103 
1104  // Window spin magnitude
1105  double window_chi_value;
1106  if ( (window_chi_argument>0) && (window_chi_argument<=1) ){
1107  window_chi_value = 0.5*cos( window_chi_argument * LAL_PI ) + 0.5;
1108  } else if ( window_chi_argument>1 ) {
1109  window_chi_value = 0;
1110  } else {
1111  window_chi_value = 1;
1112  }
1113 
1114  /* the final window is the product of the individiual windows */
1115  double angles_window = window_q_value * window_chi_value;
1116 
1117  return angles_window;
1118 
1119  }
1120 
1121 
1122  /* Compute window function which controls use of PNR coprecessing deviations. NOTE that it only makes sense to use this function inside of LALSimIMRPhenomX_precession.c */
1125  ){
1126 
1127  // NOTE that it only makes sense to use this function inside of LALSimIMRPhenomX_precession.c
1128 
1129  double window_q_boundary = 8.0;
1130  double window_theta_boundary = 150.0*LAL_PI/180.0;
1131  double window_a1_boundary = 0.8;
1132 
1133  double width_window_q = 0.50;
1134  double width_window_theta = 0.50;
1135  double width_window_a1 = 0.02;
1136 
1137  double window_q_argument = (pWF->q - window_q_boundary )/ width_window_q;
1138  double window_theta_argument = (pWF->theta_LS - window_theta_boundary )/ width_window_theta;
1139  double window_a1_argument = (pWF->a1 - window_a1_boundary )/ width_window_a1;
1140 
1141  // When the arguments are <=0, then the model is on
1142  // when they are between 0 and 1, the model is turning off
1143  // and when they are greater than 1, the model is off
1144 
1145  // For q
1146  double window_q_value;
1147  if ( (window_q_argument>0) && (window_q_argument<=1) ){
1148  window_q_value = 0.5*cos( window_q_argument * LAL_PI ) + 0.5;
1149  } else if ( window_q_argument>1 ) {
1150  window_q_value = 0;
1151  } else {
1152  window_q_value = 1;
1153  } //
1154 
1155  // For theta
1156  double window_theta_value;
1157  if ( (window_theta_argument>0) && (window_theta_argument<=1) ){
1158  window_theta_value = 0.5*cos( window_theta_argument * LAL_PI ) + 0.5;
1159  } else if ( window_theta_argument>1 ) {
1160  window_theta_value = 0;
1161  } else {
1162  window_theta_value = 1;
1163  } //
1164 
1165  // For a1
1166  double window_a1_value;
1167  if ( (window_a1_argument>0) && (window_a1_argument<=1) ){
1168  window_a1_value = 0.5*cos( window_a1_argument * LAL_PI ) + 0.5;
1169  } else if ( window_a1_argument>1 ) {
1170  window_a1_value = 0;
1171  } else {
1172  window_a1_value = 1;
1173  } //
1174 
1175  // the NET window will be the product of the individual windows so that any one parameter may turn the model off. We're only interested in this if PNR is desired via the pflag option.
1176  double pnr_window = window_q_value * window_theta_value * window_a1_value;
1177 
1178  //
1179  return pnr_window;
1180 
1181  }
1182 
1183  /* Compute XAS phase and phase derivative a reference frequency "f_inspiral_align" */
1187  ){
1188 
1189 
1190  /*
1191  Copy the current state of pWF to pPrec for use in PNR+XPHM, where we will similarly want to enforce phase alignment with XHM during inspiral.
1192 
1193  The code immediately below is very typical of annoying C language code:
1194  to copy the structure, one first must allocate memory for the vessel to
1195  hold the copy. Then one must copy the struct into that allocated
1196  momory. While there are "correct" versions of this code that do not
1197  require use of malloc, these versions essentially copy the pointer, so
1198  when pWF is changed, so is pWF22AS. We do not want that, so the use of
1199  malloc is essential.
1200  */
1201  IMRPhenomXWaveformStruct *pWF22AS = malloc(sizeof(IMRPhenomXWaveformStruct));
1202  memcpy( pWF22AS, pWF, sizeof(IMRPhenomXWaveformStruct) );
1203  pPrec->pWF22AS = pWF22AS;
1204  /* NOTE that the copy must be deleted upstream in order to avoid a memory leak */
1205 
1206  // /* Dev printing */
1207  // printf("--) pWF->fRING = %f\n",pWF->fRING);
1208 
1209  /* Define alignment frequency in fM (aka Mf). This is the
1210  frequency at which PNR coprecessing phase and phase
1211  derivaive will be aligned with corresponding XAS and XHM
1212  values. */
1213  pWF->f_inspiral_align = 0.004;
1214 
1215  // Define an int to hold status values
1216  UINT4 status;
1217 
1218  // NOTE that just below we compute the non-precessing phase parameters
1219  // BEFORE any changes are made to pWF -- SO the pWF input must not
1220  // contain any changes due to precession.
1221  IMRPhenomXPhaseCoefficients *pPhaseAS;
1222  pPhaseAS = XLALMalloc(sizeof(IMRPhenomXPhaseCoefficients));
1223  status = IMRPhenomXGetPhaseCoefficients(pWF,pPhaseAS);
1224  // Check for error
1225  XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXGetPhaseCoefficients failed.\n");
1226  // Clean up
1227 
1228  /*
1229  Below we use IMRPhenomX_FullPhase_22 to somultaneously compute
1230  the XAS phase and phase derivative at the point of interest.
1231  */
1232 
1233  /**********************************************************/
1234  // Initialize holders for the phase and phase derivative
1235  double phase, dphase;
1236  // Define the values inside of IMRPhenomX_FullPhase_22
1237  IMRPhenomX_FullPhase_22(&phase,&dphase,pWF->f_inspiral_align,pPhaseAS,pWF);
1238  // Store the phase and phase derivative for later use
1239  pWF->XAS_phase_at_f_inspiral_align = phase;
1240  pWF->XAS_dphase_at_f_inspiral_align = dphase;//full_dphase_value;
1241  /**********************************************************/
1242 
1243  /*
1244  Now, once all other model changes have been made, but before the
1245  final phase is output in IMRPhenomXASGenerateFD, we want to force
1246  the PNR CoPrecessing phase and phase derivative to be pWF->XAS_phase_at_f_inspiral_align and pWF->XAS_dphase_at_f_inspiral_align, respectively. This effort
1247  is facilitated by IMRPhenomX_PNR_EnforceXASPhaseAlignment below.
1248  */
1249 
1250  // // Printing for development
1251  // printf("##>> XAS_phase_at_f_inspiral_align = %f\n",pWF->XAS_phase_at_f_inspiral_align);
1252  // printf("##>> XAS_dphase_at_f_inspiral_align = %f\n",pWF->XAS_dphase_at_f_inspiral_align);
1253 
1254 
1255  LALFree(pPhaseAS);
1256 
1257  //
1258  return status;
1259 
1260  }
1261 
1262 
1263 
1264  /* Compute XHM phase and phase derivative a reference frequency "f_inspiral_align" */
1266  INT4 ell,
1267  INT4 emm,
1270  LALDict *lalParams
1271  ){
1272 
1273  // Define an int to hold status values
1274  UINT4 status;
1275 
1276  //
1277  double f_inspiral_align = pWF->f_inspiral_align;
1278 
1279  //
1280  double phase,dphase;
1281  status = IMRPhenomXHM_Phase_for_Initialization(&phase,&dphase,f_inspiral_align,ell,emm,pPrec->pWF22AS,lalParams);
1282 
1283  //
1284  pWF->XHM_phase_at_f_inspiral_align = phase;
1285  pWF->XHM_dphase_at_f_inspiral_align = dphase;
1286 
1287  // // Dev printing
1288  // printf("(lal)>> XHM_phase_at_f_inspiral_align = %1.8f\n",phase);
1289  // printf("(lal)>> XHM_dphase_at_f_inspiral_align = %1.8f\n",dphase);
1290 
1291  //
1292  return status;
1293 
1294  }
1295 
1296 
1297 
1298  /* Align the PNR CoPrec phase and phase derivative at
1299  "f_inspiral_align" by changing the effective value of
1300  phifRef and linb */
1302  double* linb,
1305  ){
1306 
1307  /**********************************************************/
1308  // Initialize holders for the phase and phase derivative
1309  double phi_at_f_inspiral_align;
1310  double dphi_at_f_inspiral_align;
1311  // Define the values inside of IMRPhenomX_FullPhase_22
1312  IMRPhenomX_FullPhase_22(&phi_at_f_inspiral_align,&dphi_at_f_inspiral_align,pWF->f_inspiral_align,pPhase,pWF);
1313  /**********************************************************/
1314 
1315  /* If dphi is the phase derivative at some frequency,
1316  then we want
1317 
1318  dphi = dphi - dphi_at_f_inspiral_align +
1319  pWF->XAS_dphase_at_f_inspiral_align
1320 
1321  So the constant of interest is ...
1322  */
1323  double shift_in_linb = - dphi_at_f_inspiral_align +
1325 
1326  // So the new value of linb is
1327  *linb += shift_in_linb;
1328 
1329  /* NOTE that phifRef need not be changed due to its dependence on linb.
1330  In other words, given the new value of linb, we leave the computation of
1331  phifRef to an external routine. */
1332 
1333  // /* If phi is the phase at some frequency, then we want
1334  // phi = phi - phi_at_f_inspiral_align +
1335  // pWF->XAS_phase_at_f_inspiral_align
1336  // So the constant of interest is ...
1337  // */
1338  // double shift_in_phifRef = - phi_at_f_inspiral_align +
1339  // pWF->XAS_phase_at_f_inspiral_align;
1340  // // So the new value of phifRef is
1341  // *phifRef += shift_in_phifRef;
1342 
1343  }
1344 
1345 
1346 
1347  /* Align the PNR HM CoPrec phase and phase derivative at
1348  "f_inspiral_align" by providing the needed phase and time shifts*/
1350  double* lina,
1351  double* linb,
1352  INT4 ell,
1353  INT4 emm,
1355  LALDict *lalParams
1356  ){
1357 
1358  /**********************************************************/
1359  // Initialize holders for the phase and phase derivative
1360  double phi_at_f_inspiral_align;
1361  double dphi_at_f_inspiral_align;
1362  // Define the values
1363  IMRPhenomXHM_Phase_for_Initialization(&phi_at_f_inspiral_align,&dphi_at_f_inspiral_align,pWF->f_inspiral_align,ell,emm,pWF,lalParams);
1364  /**********************************************************/
1365 
1366  // printf("** phi_at_f_inspiral_align = %f\n",phi_at_f_inspiral_align);
1367  // printf("** dphi_at_f_inspiral_align = %f\n",dphi_at_f_inspiral_align);
1368 
1369  /* If phi is the phase at some frequency, then we want
1370  phi = phi - phi_at_f_inspiral_align +
1371  pWF->XHM_phase_at_f_inspiral_align
1372  So the constant of interest is ...
1373 
1374  NOTE that at this point in the program flow,
1375  pWF->XHM_phase_at_f_inspiral_align should be the
1376  correct value for the (ell,emm) multipole moment.
1377  See
1378  */
1379  double shift_in_phase = - phi_at_f_inspiral_align +
1381 
1382  /* So the value of lina is below. But note that we are
1383  not done yet -- to get the correct answer, we must take
1384  into account the effect of adding a non-zero phase
1385  derivative shift. For this see below ... */
1386  *lina = shift_in_phase;
1387 
1388  /* If dphi is the phase derivative at some frequency,
1389  then we want
1390 
1391  dphi = dphi - dphi_at_f_inspiral_align +
1392  pWF->XHM_dphase_at_f_inspiral_align
1393 
1394  So the constant of interest is ...
1395 
1396  NOTE that at this point in the program flow,
1397  pWF->XHM_phase_at_f_inspiral_align should be the
1398  correct value for the (ell,emm) multipole moment.
1399  See
1400  */
1401  double shift_in_linb = - dphi_at_f_inspiral_align +
1403 
1404  // So the value of linb is
1405  *linb = shift_in_linb;
1406 
1407  /* With the value of linb in hand, we now want to note that
1408  given the phase, we will add a line:
1409 
1410  phase += lina + linb*Mf .
1411 
1412  when Mf=f_inspiral_align, then we want alignment to hold.
1413  For this to hapen, lina must include the negative of
1414  linb*Mf at f_inspiral_align:
1415 
1416  So the new value of lina is */
1417  *lina = shift_in_phase - shift_in_linb*pWF->f_inspiral_align;
1418 
1419  }
1420 
1421 /* Function to get and or store coprec params
1422 into pWF and pPrec */
1426  LALDict *lalParams
1427 ){
1428 
1429 
1430  //
1431  INT4 status = 0;
1432 
1433  // Get toggle for outputting coprecesing model from LAL dictionary
1435 
1436  // Get toggle for PNR coprecessing tuning
1438  pWF->IMRPhenomXPNRUseTunedCoprec = PNRUseTunedCoprec;
1439  pPrec->IMRPhenomXPNRUseTunedCoprec = PNRUseTunedCoprec;
1440  // Same as above but for 33
1443 
1444  // Throw error if preferred value of PNRUseTunedCoprec33 is not found
1445  if ( pPrec->IMRPhenomXPNRUseTunedCoprec33 ) {
1446  XLAL_ERROR(XLAL_EFUNC,"Error: Coprecessing tuning for l=|m|=3 must be off.\n");
1447  }
1448 
1449  // Get toggle for enforced use of non-precessing spin as is required during tuning of PNR's coprecessing model
1450  INT4 PNRUseInputCoprecDeviations = XLALSimInspiralWaveformParamsLookupPhenomXPNRUseInputCoprecDeviations(lalParams);
1451  pPrec->IMRPhenomXPNRUseInputCoprecDeviations = PNRUseInputCoprecDeviations;
1452 
1453  // Get toggle for forcing inspiral phase and phase derivative alignment with XHM/AS
1454  INT4 PNRForceXHMAlignment = XLALSimInspiralWaveformParamsLookupPhenomXPNRForceXHMAlignment(lalParams);
1455  pWF->IMRPhenomXPNRForceXHMAlignment = PNRForceXHMAlignment;
1456 
1457  // Throw error if preferred value of PNRForceXHMAlignment is not found
1458  if ( PNRForceXHMAlignment ) {
1459  XLAL_ERROR(XLAL_EFUNC,"Error: PNRForceXHMAlignment must be off.\n");
1460  }
1461 
1462 #if DEBUG == 1
1463  if ( PNRForceXHMAlignment ){
1464  printf("lal>> Dev toggle for XHM alignment ON in LALSimIMRPhenomX_precession.c.\n");
1465  }
1466 #endif
1467 
1468  /*-~-~-~-~-~-~-~-~-~-~-~-~-~*
1469  Validate PNR usage options
1470  *-~-~-~-~-~-~-~-~-~-~-~-~-~*/
1471  if ( PNRUseInputCoprecDeviations && PNRUseTunedCoprec ) {
1472  //
1473  XLAL_ERROR(XLAL_EDOM,"Error: PNRUseTunedCoprec and PNRUseInputCoprecDeviations must not be enabled simultaneously.\n");
1474  }
1475 
1476  // Define high-level toggle for whether to apply deviations. NOTE that this is imposed at the definition of PNR_DEV_PARAMETER, rather than in a series of IF-ELSE conditions.
1477  INT4 APPLY_PNR_DEVIATIONS = PNRUseTunedCoprec || PNRUseInputCoprecDeviations;
1478  pWF->APPLY_PNR_DEVIATIONS = APPLY_PNR_DEVIATIONS;
1479 
1480  // If applying PNR deviations, then we want to be able to refer to some non-PNR waveform properties. For that, we must compute the struct for when PNR is off (and specifically, XAS is wanted).
1481  if ( APPLY_PNR_DEVIATIONS && PNRForceXHMAlignment ) {
1482 
1483  /*<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.<-.
1484  Compute and store the value of the XAS phase derivative at pPrec->f_inspiral_align
1485  - Note that this routine also copies the current state of pWF to pPrec for use in PNR+XPHM, where we will similarly want to enforce phase alignment with XHM during inspiral.
1486  ->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.->.*/
1488 
1489  }
1490 
1491  /*-~---~---~---~---~---~---~---~---~---~---~---~---~--*/
1492  /* Define single spin parameters for fit evaluation */
1493  /*-~---~---~---~---~---~---~---~---~---~---~---~---~--*/
1494 
1495  //
1496  REAL8 a1 = pPrec->chi_singleSpin;
1497  pWF->a1 = a1;
1498  REAL8 cos_theta = pPrec->costheta_singleSpin;
1499 
1500  //
1501  double theta_LS = acos( cos_theta );
1502  pWF->theta_LS = theta_LS;
1503 
1504 
1505  // Use external function to compute window of tuning deviations. The value of the window will only differ from unity if PNRUseTunedCoprec is equipotent to True. NOTE that correct evaluation of this function requires that e,g, pWF->a1 and pWF->theta_LS be defined above.
1506  double pnr_window = 0.0; /* Making the defualt to be zero here, meaning that by default tunings will be off regardless of physical case, or other option flags.*/
1507  if (PNRUseTunedCoprec) {
1508  // Store for output in related XLAL function
1509  pnr_window = IMRPhenomX_PNR_CoprecWindow(pWF);
1510  }
1511  pWF->pnr_window = pnr_window;
1512 
1513 
1514  /* Store XCP deviation parameter: NOTE that we only want to apply the window if PNR is being used, not e.g. if we are calibrating the related coprecessing model */
1515  pWF->PNR_DEV_PARAMETER = a1 * sin( pWF->theta_LS ) * APPLY_PNR_DEVIATIONS;
1516  if ( PNRUseTunedCoprec ){
1517  pWF->PNR_DEV_PARAMETER = pnr_window * (pWF->PNR_DEV_PARAMETER);
1518  // NOTE that PNR_DEV_PARAMETER for l=m=3 is derived from (and directly proportional to) the one defined just above.
1519  }
1520 
1521  /* Store deviations to be used in PhenomXCP (PNRUseInputCoprecDeviations) */
1522  // Get them from the laldict (also used as a way to get default values)
1523  // For information about how deviations are applied, see code chunk immediately below.
1524  /* NOTE the following for the code just below:
1525  - all default values are zero
1526  - we could toggle the code chunk with PNRUseInputCoprecDeviations, but doing so would be non-orthogonal to the comment above about default values.
1527  - In any case, the user must set PNRUseInputCoprecDeviations=True, AND manually set the deviations using the LALDict interface.
1528  */
1539 
1540  //
1541  #if DEBUG == 1
1542  printf("** >>>>>>>>>>>> PhenomXCP Model domain >>>>>>>>>>> **\n");
1543  printf("theta : %f\n",theta_LS*180.0/LAL_PI);
1544  printf("eta : %f\n",pWF->eta);
1545  printf("a1 : %f\n",a1);
1546  printf("** >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> **\n\n");
1547  #endif
1548 
1549  //
1550  if( PNRUseTunedCoprec )
1551  {
1552 
1553  /* ------------------------------------------------------ >>
1554  Get them from the stored model fits that define PhenomXCP
1555  within PhenomXPNR. NOTE that most but not all
1556  modifications take place in LALSimIMRPhenomX_internals.c.
1557  For example, fRING and fDAMP are modified in this file.
1558  NOTE that each tuned parameter requires pWF->PNR_DEV_PARAMETER
1559  to be unchanged from the value used during tuning e.g. a1*sin(theta)
1560  << ------------------------------------------------------ */
1561 
1562  /* MU1 modifies pAmp->v1RD */
1563  pWF->MU1 = IMRPhenomXCP_MU1_l2m2( theta_LS, pWF->eta, a1 );
1564 
1565  // NOTE that the function for MU2 is not defined in the model
1566  /* MU2 would modify pAmp->gamma2 */
1567 
1568  /* MU2 */
1569  pWF->MU2 = IMRPhenomXCP_MU2_l2m2( theta_LS, pWF->eta, a1 );
1570 
1571  /* MU3 modifies pAmp->gamma3 */
1572  pWF->MU3 = IMRPhenomXCP_MU3_l2m2( theta_LS, pWF->eta, a1 );
1573 
1574  /* MU4 modifies V2 for the intermediate amplitude
1575  for the DEFAULT value of IMRPhenomXIntermediateAmpVersion
1576  use in IMRPhenomXPHM */
1577  // pWF->MU4 = IMRPhenomXCP_MU4_l2m2( theta_LS, pWF->eta, a1 );
1578 
1579  /* NU0 modifies the output of IMRPhenomX_TimeShift_22() */
1580  pWF->NU0 = IMRPhenomXCP_NU0_l2m2( theta_LS, pWF->eta, a1 );
1581 
1582  /* NU4 modifies pPhase->cL */
1583  pWF->NU4 = IMRPhenomXCP_NU4_l2m2( theta_LS, pWF->eta, a1 );
1584 
1585  /* NU5 modifies pWF->fRING [EXTRAP-PASS-TRUE] */
1586  pWF->NU5 = IMRPhenomXCP_NU5_l2m2( theta_LS, pWF->eta, a1 );
1587 
1588  /* NU6 modifies pWF->fDAMP [EXTRAP-PASS-TRUE] */
1589  pWF->NU6 = IMRPhenomXCP_NU6_l2m2( theta_LS, pWF->eta, a1 );
1590 
1591  /* ZETA1 modifies pPhase->b4 */
1592  pWF->ZETA1 = IMRPhenomXCP_ZETA1_l2m2( theta_LS, pWF->eta, a1 );
1593 
1594  /* ZETA2 modifies pPhase->b1 */
1595  pWF->ZETA2 = IMRPhenomXCP_ZETA2_l2m2( theta_LS, pWF->eta, a1 );
1596 
1597  }
1598 
1599  //
1600  pWF->NU0 = 0;
1601 
1602  //
1603  #if DEBUG == 1
1604  printf("** >>>>> PhenomXCP Model Parameters >>>>> **\n");
1605  printf("MU1 : %f\n",pWF->MU1);
1606  printf("MU2 : %f\n",pWF->MU2);
1607  printf("MU3 : %f\n",pWF->MU3);
1608  printf("MU4 : %f\n",pWF->MU4);
1609  printf("NU4 : %f\n",pWF->NU4);
1610  printf("NU5 : %f\n",pWF->NU5);
1611  printf("NU6 : %f\n",pWF->NU6);
1612  printf("ZETA1 : %f\n",pWF->ZETA1);
1613  printf("ZETA2 : %f\n",pWF->ZETA2);
1614  printf("** >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> **\n\n");
1615  #endif
1616 
1617  //
1618  return status;
1619 
1620 }
1621 
1622 
1623 
1624 
1625 
1626 #ifdef __cplusplus
1627 }
1628 #endif
#define LALFree(p)
const double g0
#define MAX_TOL_ATAN
Tolerance used below which numbers are treated as zero for the calculation of atan2.
double XLALSimPhenomUtilsMftoHz(REAL8 Mf, REAL8 Mtot_Msun)
Convert from geometric frequency to frequency in Hz.
double XLALSimPhenomUtilsHztoMf(REAL8 fHz, REAL8 Mtot_Msun)
Convert from frequency in Hz to geometric frequency.
int IMRPhenomX_PNR_precompute_alpha_coefficients(IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function evaluates the coefficients outlined in Sec 7A of arXiv:2107.08876 for alpha.
int IMRPhenomX_PNR_alpha_connection_parameters(IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function evaluates the connection frequencies Mf1 and Mf2 detailed in Sec.
int IMRPhenomX_PNR_precompute_beta_coefficients(IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function evaluates the Ansatz coefficients of beta outlined in Eq.
int IMRPhenomX_PNR_beta_connection_parameters(IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, IMRPhenomXWaveformStruct *pWF_SingleSpin, IMRPhenomXPrecessionStruct *pPrec_SingleSpin)
This function combines several functions together to compute the connection frequencies and the inspi...
double IMRPhenomXCP_MU3_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_MU2_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_ZETA2_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_MU1_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_NU5_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_NU4_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_NU6_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_NU0_l2m2(double theta, double eta, double a1)
double IMRPhenomXCP_ZETA1_l2m2(double theta, double eta, double a1)
REAL8 IMRPhenomX_PNR_AnglesWindow(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
void IMRPhenomX_PNR_FreeStructs(IMRPhenomXWaveformStruct **pWF_SingleSpin, IMRPhenomXPrecessionStruct **pPrec_SingleSpin, IMRPhenomX_PNR_alpha_parameters **alphaParams, IMRPhenomX_PNR_beta_parameters **betaParams)
REAL8 IMRPhenomX_PNR_alphadot_cosbeta(REAL8 f, IMRPhenomX_PNR_angle_spline *params)
Wrapper function for computing the integrand in Eq.
void IMRPhenomX_PNR_AngleParameterDebugPrint(IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomX_PNR_beta_parameters *betaParams)
Print various parameters in the angle structs.
INT4 IMRPhenomX_PNR_CheckTwoSpin(IMRPhenomXPrecessionStruct *pPrec)
This function quickly checks to see if we expect two-spin effects to be present in the inspiral prece...
REAL8 IMRPhenomX_PNR_GenerateEffectiveRingdownFreq(IMRPhenomXWaveformStruct *pWF, UINT4 ell, UINT4 emmprime, LALDict *lalParams)
This code recomputes the skymapped locations in the J-frame using the new value of beta computed from...
INT4 IMRPhenomX_PNR_PopulateStructs(IMRPhenomXWaveformStruct **pWF_SingleSpin, IMRPhenomXPrecessionStruct **pPrec_SingleSpin, IMRPhenomX_PNR_alpha_parameters **alphaParams, IMRPhenomX_PNR_beta_parameters **betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
These two functions create and populate the required parameter structs for PNR.
INT4 IMRPhenomX_PNR_GetAndSetCoPrecParams(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
REAL8 IMRPhenomX_PNR_HMInterpolationDeltaF(REAL8 f_min, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
Here we compute an appropriate deltaF to be used when generating the (2,2) angle interpolants and map...
REAL8 IMRPhenomX_PNR_CoprecWindow(IMRPhenomXWaveformStruct *pWF)
INT4 IMRPhenomXHM_PNR_SetPhaseAlignmentParams(INT4 ell, INT4 emm, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
void IMRPhenomX_PNR_EnforceXASPhaseAlignment(double *linb, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPhaseCoefficients *pPhase)
REAL8 IMRPhenomX_PNR_LinearFrequencyMap(REAL8 Mf, REAL8 ell, REAL8 emm, REAL8 Mf_lower, REAL8 Mf_upper, REAL8 Mf_RD_22, REAL8 Mf_RD_lm, UINT4 INSPIRAL)
Computes a linear frequency map for the HM PNR angles based on the linear frequency mapping used orig...
INT4 IMRPhenomX_PNR_GeneratePNRGamma(REAL8Sequence *gamma, const REAL8Sequence *freqs, const REAL8Sequence *alpha, const REAL8Sequence *beta)
This function computes the frequency integral outlined in Eq.
INT4 IMRPhenomX_PNR_RemapThetaJSF(REAL8 beta_ref, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
This code recomputes the skymapped locations in the J-frame using the new value of beta computed from...
INT4 IMRPhenomX_PNR_GetAndSetPNRVariables(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function computes the required single-spin quantities used to parameterize the MR tuned function...
INT4 IMRPhenomX_PNR_SetPhaseAlignmentParams(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
void IMRPhenomXHM_PNR_EnforceXHMPhaseAlignment(double *lina, double *linb, INT4 ell, INT4 emm, IMRPhenomXWaveformStruct *pWF, LALDict *lalParams)
REAL8 IMRPhenomX_PNR_LinearFrequencySlope(REAL8 emm, REAL8 Mf_lower, REAL8 Mf_upper, REAL8 Mf_RD_22, REAL8 Mf_RD_lm)
Computes the slope of the linear frequency interpolation mapping used for the HM PNR angles.
int IMRPhenomX_PNR_GeneratePNRGamma_FromInterpolants(REAL8Sequence *gamma, const REAL8Sequence *freqs, IMRPhenomX_PNR_angle_spline *ab_splines)
This function computes the frequency integral outlined in Eq.
REAL8 IMRPhenomX_PNR_LinearInterpolate(REAL8 a0, REAL8 a1, REAL8 f0, REAL8 f1, REAL8 feval)
Evaluates a function at two points and interpolates between them.
INT4 IMRPhenomX_PNR_LinearFrequencyMapTransitionFrequencies(REAL8 *Mf_low, REAL8 *Mf_high, REAL8 emmprime, REAL8 Mf_RD_22, REAL8 Mf_RD_lm, IMRPhenomXPrecessionStruct *pPrec)
Compute the transition frequencies for the HM PNR angle mapping.
REAL8 IMRPhenomX_PNR_AngleAtFRef(const REAL8Sequence *angle, const REAL8 f_ref, const REAL8Sequence *freqs, const REAL8 deltaF)
Evaluates a function at two points and interpolates between them.
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)
int IMRPhenomXGetPhaseCoefficients(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPhaseCoefficients *pPhase)
INT4 IMRPhenomX_FullPhase_22(double *phase, double *dphase, double Mf, IMRPhenomXPhaseCoefficients *pPhase, IMRPhenomXWaveformStruct *pWF)
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.
void IMRPhenomX_rotate_y(REAL8 angle, REAL8 *vx, REAL8 *vy, REAL8 *vz)
Function to rotate vector about y axis by given angle.
void IMRPhenomX_rotate_z(const REAL8 angle, REAL8 *vx, REAL8 *vy, REAL8 *vz)
Function to rotate vector about z axis by given angle.
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:
void IMRPhenomXHM_SetHMWaveformVariables(int ell, int emm, IMRPhenomXHMWaveformStruct *wf, IMRPhenomXWaveformStruct *wf22, QNMFits *qnms, LALDict *LALParams)
void IMRPhenomXHM_Initialize_QNMs(QNMFits *qnms)
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 XLALSimIMRPhenomXchiEff(REAL8 eta, REAL8 chi1L, REAL8 chi2L)
Effective aligned spin parameter.
static double beta(const double a, const double b, const sysq *system)
Internal function that computes the spin-orbit couplings.
@ INSPIRAL
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPZETA2(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPNU4(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRForceXHMAlignment(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPMU2(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPZETA1(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPNU6(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPConvention(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPNU5(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPMU3(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPMU1(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseTunedCoprec(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXReturnCoPrec(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseInputCoprecDeviations(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseTunedAngles(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPMU4(LALDict *params)
REAL8 XLALSimInspiralWaveformParamsLookupPhenomXCPNU0(LALDict *params)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseTunedCoprec33(LALDict *params)
#define fprintf
double i
Definition: bh_ringdown.c:118
double e
Definition: bh_ringdown.c:117
const double costheta
const double B
#define LAL_PI
double REAL8
uint32_t UINT4
int32_t INT4
void * XLALMalloc(size_t n)
INT4 IMRPhenomXHM_Phase_for_Initialization(double *phase, double *dphase, double Mf, INT4 ell, INT4 emm, IMRPhenomXWaveformStruct *pWF, LALDict *lalParams)
static const INT4 q
COMPLEX16 XLALSpinWeightedSphericalHarmonic(REAL8 theta, REAL8 phi, int s, int l, int m)
#define XLAL_ERROR(...)
#define XLAL_CHECK(assertion,...)
XLAL_SUCCESS
XLAL_EFAULT
XLAL_EFUNC
XLAL_EDOM
XLAL_EINVAL
string status
double alpha
Definition: sgwb.c:106
REAL8 derivative_alpha_upper
derivative at connection frequency
REAL8 derivative_alpha_lower
derivative at connection frequency
REAL8 alpha_interp_2
intermediate coefficient
REAL8 alpha_lower
alpha at connection frequency
REAL8 alpha_interp_0
intermediate coefficient
REAL8 Mf_alpha_lower
connection frequency
REAL8 alpha_interp_3
intermediate coefficient
REAL8 Mf_alpha_upper
connection frequency
REAL8 alpha_upper
alpha at connection frequency
REAL8 alpha_interp_1
intermediate coefficient
gsl_spline * beta_spline
beta cubic spline
gsl_interp_accel * beta_acc
beta cubic spline accelerator
gsl_interp_accel * alpha_acc
alpha cubic spline accelerator
gsl_spline * alpha_spline
alpha cubic spline
REAL8 Ny_Sf
Line-of-sight vector component in L frame.
REAL8 PNR_q_window_lower
Boundary values for PNR angle transition window.
REAL8 QArunz_Jf
Component of triad basis vector Q in J frame, arXiv:0810.5336.
REAL8 QAruny_Jf
Component of triad basis vector Q in J frame, arXiv:0810.5336.
REAL8 zeta_polarization
Angle to rotate the polarizations.
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 thetaJN
Angle between J0 and line of sight (z-direction)
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 Xx_Sf
Component of triad basis vector X in L frame.
IMRPhenomXWaveformStruct * pWF22AS
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.
UINT4 PNRInspiralScaling
Enforce inpsiral scaling for HM angles outside of calibration window.
REAL8 Nz_Jf
Line-of-sight vector component in LJ frame.
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.
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 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 costheta_final_singleSpin
Polar angle of approximate final spin, see technical document FIXME: add reference.
REAL8 * data
Definition: burst.c:245
double f_min
Definition: unicorn.c:22
double f_max
Definition: unicorn.c:23