Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALSimulation 6.2.0.1-b246709
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
LALSimIMRPhenomX_PNR.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
21extern "C"
22{
23#endif
24
25#include <lal/SphericalHarmonics.h>
26#include "LALSimIMR.h"
27
28#include "LALSimIMRPhenomX.h"
29#include "LALSimIMRPhenomXPHM.h"
33
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 * @addtogroup LALSimIMRPhenomX_c
50 * @{
51 *
52 * @name Routines for PNR angles
53 * @{
54 *
55 * @author Eleanor Hamilton, Sebastian Khan, Jonathan E. Thompson
56 *
57 * @brief C code for routines to implement PNR angles in IMRPhenomXP/IMRPhenomXPHM.
58 *
59 * This is a C-code impementation of the PNR angle model
60 * outlined in arXiv:2107.08876.
61 *
62 * Available flags:
63 * PhenomXPNRUseTunedAngles:
64 * - 0 : Disable PNR angles. Use the default NNLO/MSA precession angles in IMRPhenomXP/HM without tuning.
65 * - 1 : Enable PNR angles.
66 */
67
68 /**
69 * This is an external wrapper to generate the (2,2) PNR angles,
70 * following the prescription outlined in arXiv:2107.08876,
71 * given the standard inputs given to generate FD waveforms.
72 */
74 REAL8Sequence **alphaPNR, /**< [out] Alpha precession angle (rad) */
75 REAL8Sequence **betaPNR, /**< [out] Beta precession angle (rad) */
76 REAL8Sequence **gammaPNR, /**< [out] Gamma precession angle (rad) */
77 REAL8Sequence **freqs, /**< [out] Frequency array (Hz) */
78 REAL8 *alphaPNR_ref, /**< [out] reference value of alpha (rad) */
79 REAL8 *gammaPNR_ref, /**< [out] reference value of gamma (rad) */
80 REAL8 m1_SI, /**< mass of companion 1 (kg) */
81 REAL8 m2_SI, /**< mass of companion 2 (kg) */
82 REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
83 REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
84 REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
85 REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
86 REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
87 REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
88 REAL8 inclination, /**< Angle between orbital angular momentum and line-of-sight vector at reference frequency (rad) */
89 REAL8 deltaF, /**< Frequency spacing (Hz) */
90 REAL8 f_min, /**< Starting GW frequency (Hz) */
91 REAL8 f_max, /**< Ending GW frequency (Hz) */
92 REAL8 fRef_In, /**< Reference frequency (Hz) */
93 LALDict *lalParams /**< LAL Dictionary struct */
94 )
95 {
96 /* Simple check on masses and spins */
97 UINT4 status = 0;
98 status = XLALIMRPhenomXPCheckMassesAndSpins(&m1_SI, &m2_SI, &chi1x, &chi1y, &chi1z, &chi2x, &chi2y, &chi2z);
102 "Error: XLALIMRPhenomXPCheckMassesAndSpins failed in XLALSimIMRPhenomX_PNR_GeneratePNRAngles.\n");
103
105 XLAL_CHECK(XLAL_SUCCESS == status, status, "Failed to initialize useful powers of LAL_PI.");
106
107 /* Ensure we have a dictionary */
108 LALDict *lalParams_aux;
109 if (lalParams == NULL)
110 {
111 lalParams_aux = XLALCreateDict();
112 }
113 else
114 {
115 lalParams_aux = XLALDictDuplicate(lalParams);
116 }
117
118 /* Make sure we're calling the tuned angles */
120 if (!UsePNR)
121 {
122 UsePNR = 1;
124 }
125
126 /* Map fRef to the start frequency if need be, then make sure it's within the frequency range */
127 REAL8 fRef = (fRef_In == 0.0) ? f_min : fRef_In;
129 ((f_min <= fRef) && (fRef <= f_max)),
131 "Error: fRef needs to be within the specified minimum and maximum frequency values!\n");
132
133 /* Check that the passed deltaF is non-zero */
135 deltaF > 0,
137 "Error: deltaF needs to be a positive number!\n");
138
139 /* Generate a uniformly sampled frequency grid of spacing deltaF. */
140 /* Frequencies will be set using only the lower and upper bounds that we passed */
141 size_t iStart = (size_t)(f_min / deltaF);
142 size_t iStop = (size_t)(f_max / deltaF) + 1;
143
145 (iStart <= iStop),
146 XLAL_EDOM,
147 "Error: the starting frequency index is greater than the stopping index! Please ensure that f_min <= f_max.\n");
148
149 /* Allocate memory for frequency array and terminate if this fails */
150 *freqs = XLALCreateREAL8Sequence(iStop - iStart);
151 if (!(*freqs))
152 {
153 XLAL_ERROR(XLAL_EFUNC, "Frequency array allocation failed.");
154 }
155 *alphaPNR = XLALCreateREAL8Sequence(iStop - iStart);
156 if (!(*alphaPNR))
157 {
158 XLAL_ERROR(XLAL_EFUNC, "Alpha array allocation failed.");
159 }
160 *betaPNR = XLALCreateREAL8Sequence(iStop - iStart);
161 if (!(*betaPNR))
162 {
163 XLAL_ERROR(XLAL_EFUNC, "Beta array allocation failed.");
164 }
165 *gammaPNR = XLALCreateREAL8Sequence(iStop - iStart);
166 if (!(*gammaPNR))
167 {
168 XLAL_ERROR(XLAL_EFUNC, "Gamma array allocation failed.");
169 }
170
171 /* Populate frequency array */
172 for (UINT4 i = iStart; i < iStop; i++)
173 {
174 (*freqs)->data[i - iStart] = i * deltaF;
175 }
176
177 /* Specify arbitrary parameters for the angle generation */
178 REAL8 distance = 1.0;
179 REAL8 phiRef = 0.0;
180
181 /* Use the true minimum and maximum frequency values */
182 REAL8 f_min_eval = (*freqs)->data[0];
183 REAL8 f_max_eval = (*freqs)->data[(*freqs)->length - 1];
184
185 /* Initialize PhenomX Waveform struct and check that it initialized correctly */
188 status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, chi1z, chi2z, deltaF, fRef, phiRef, f_min_eval, f_max_eval, distance, inclination, lalParams_aux, DEBUG);
189 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
190
191 /* Initialize PhenomX Precession struct and check that it generated successfully */
193 pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
194
195 /* If user chose SpinTaylor angles, set bounds for interpolation of angles */
197 if(pflag==330)
198 pPrec->M_MIN = 2, pPrec->M_MAX = 2;
199
201 pWF,
202 pPrec,
203 m1_SI,
204 m2_SI,
205 chi1x,
206 chi1y,
207 chi1z,
208 chi2x,
209 chi2y,
210 chi2z,
211 lalParams_aux,
212 DEBUG);
213 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetPrecessionVariables failed.\n");
214
215 // overwrite the precessing version to account for fallbacks
217
218 /* If user selected SpinTaylor angles, compute all the splines for the Euler angles */
219 if(pPrec->IMRPhenomXPrecVersion==330){
220
221 status = IMRPhenomX_SpinTaylorAnglesSplinesAll(f_min_eval, f_max_eval, pWF, pPrec,lalParams_aux);
222 XLAL_CHECK(status == XLAL_SUCCESS, XLAL_EFUNC, "Error in %s: IMRPhenomX_SpinTaylorAnglesSplinesAll failed.\n",__func__);
223
224 }
225
226
227 /* See if PNR angles were turned off in pPrec check */
228 UsePNR = pPrec->IMRPhenomXPNRUseTunedAngles;
229
230 if(UsePNR){
231 /* Generate the tuned precession angles */
232 status = IMRPhenomX_PNR_GeneratePNRAngles(*alphaPNR, *betaPNR, *gammaPNR, *freqs, pWF, pPrec, lalParams_aux);
233 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomX_PNR_GeneratePNRAngles failed.\n");
234
235 /* Pass through the reference values of alpha and gamma
236 * NOTE: gamma = - epsilon */
237 *alphaPNR_ref = pPrec->alpha_offset;
238 *gammaPNR_ref = -(pPrec->epsilon_offset);
239 }
240 else{
241 /* Generate PN angles */
243 /* MSA angles generated using built-in function */
244
245 REAL8Sequence *cosBeta = XLALCreateREAL8Sequence(iStop - iStart);
246 if (!cosBeta)
247 {
248 XLAL_ERROR(XLAL_EFUNC, "cosBeta array allocation failed.");
249 }
250
252 alphaPNR, gammaPNR, &cosBeta, *freqs,
253 m1_SI,m2_SI,
254 chi1x,chi1y,chi1z,
255 chi2x,chi2y,chi2z,
256 inclination,
257 fRef_In, 2, lalParams_aux
258 );
259
260 for (UINT4 i = 0; i < (*freqs)->length; i++)
261 {
262 (*betaPNR)->data[i] = acos(cosBeta->data[i]);
263 }
264
266
267 /* Reference values already applied in function */
268 *alphaPNR_ref = 0.0;
269 *gammaPNR_ref = 0.0;
270 }
271 else{
272 /* NNLO angles */
273
274 REAL8Sequence *cosBeta = XLALCreateREAL8Sequence(iStop - iStart);
275 if (!cosBeta)
276 {
277 XLAL_ERROR(XLAL_EFUNC, "cosBeta array allocation failed.");
278 }
279
281 alphaPNR, gammaPNR, &cosBeta, *freqs,
282 m1_SI,m2_SI,
283 chi1x,chi1y,chi1z,
284 chi2x,chi2y,chi2z,
285 inclination,
286 fRef_In, 2, lalParams_aux
287 );
288
289 for (UINT4 i = 0; i < (*freqs)->length; i++)
290 {
291 (*betaPNR)->data[i] = acos(cosBeta->data[i]);
292 }
293
295
296 /* Pass through the reference values of alpha and gamma
297 * NOTE: gamma = - epsilon */
298 *alphaPNR_ref = pPrec->alpha_offset;
299 *gammaPNR_ref = -(pPrec->epsilon_offset);
300 }
301 }
302 // CHECK ME: make sure all memory is freed when calling SpinTaylor model
303 /* Clean up memory allocation */
304 LALFree(pPrec);
305 LALFree(pWF);
306 XLALDestroyDict(lalParams_aux);
307
308 return XLAL_SUCCESS;
309 }
310
311 /**
312 * This is an external wrapper to generate the (l,m) PNR angles,
313 * following the prescriptions outlined in arXiv:2107.08876
314 * and arxiv.org:2312.10025,
315 * given the standard inputs given to generate FD waveforms.
316 */
318 REAL8Sequence **alphaPNR, /**< [out] Alpha precession angle (rad) */
319 REAL8Sequence **betaPNR, /**< [out] Beta precession angle (rad) */
320 REAL8Sequence **gammaPNR, /**< [out] Gamma precession angle (rad) */
321 REAL8Sequence **freqs, /**< [out] Frequency array (Hz) */
322 REAL8 *alphaPNR_ref, /**< [out] Reference value of alpha (rad) */
323 REAL8 *gammaPNR_ref, /**< [out] Reference value of gamma (rad) */
324 REAL8 m1_SI, /**< mass of companion 1 (kg) */
325 REAL8 m2_SI, /**< mass of companion 2 (kg) */
326 REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
327 REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
328 REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
329 REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
330 REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
331 REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
332 REAL8 inclination, /**< Angle between orbital angular momentum and line-of-sight vector at reference frequency (rad) */
333 REAL8 deltaF, /**< Frequency spacing (Hz) */
334 REAL8 f_min, /**< Starting GW frequency (Hz) */
335 REAL8 f_max, /**< Ending GW frequency (Hz); Defaults to Mf = 0.3 if no f_max is specified. */
336 REAL8 fRef_In, /**< Reference frequency (Hz) */
337 INT4 ell, /**< Orbital index (int) */
338 INT4 emmprime, /**< Azimuthal index (int) */
339 LALDict *lalParams /**< LAL Dictionary struct */
340 )
341 {
342 /* Simple check on masses and spins */
343 UINT4 status = 0;
344
345 /* Initialize useful powers of pi for the higher modes internal code. */
347 XLAL_CHECK(XLAL_SUCCESS == status, status, "Failed to initialize useful powers of LAL_PI.");
349 XLAL_CHECK(XLAL_SUCCESS == status, status, "Failed to initialize useful powers of LAL_PI.");
350
351 status = XLALIMRPhenomXPCheckMassesAndSpins(&m1_SI, &m2_SI, &chi1x, &chi1y, &chi1z, &chi2x, &chi2y, &chi2z);
355 "Error: XLALIMRPhenomXPCheckMassesAndSpins failed in XLALSimIMRPhenomX_PNR_GeneratePNRAnglesHM.\n");
356
357 /* Ensure we have a dictionary */
358 LALDict *lalParams_aux;
359 if (lalParams == NULL)
360 {
361 lalParams_aux = XLALCreateDict();
362 }
363 else
364 {
365 lalParams_aux = XLALDictDuplicate(lalParams);
366 }
367
368 /* Make sure we're calling the tuned angles */
370 if (!UsePNR)
371 {
372 UsePNR = 1;
374 }
375
376 /* make sure the HM multipoles are activated
377 * we need both (2,2) and (l,m) */
378 LALValue *ModeArray = XLALSimInspiralWaveformParamsLookupModeArray(lalParams_aux);
379
380 /* If the mode array is empty, populate using a default choice of modes */
381 if (ModeArray == NULL)
382 {
383 ModeArray = XLALSimInspiralCreateModeArray();
384 }
386 XLALSimInspiralModeArrayActivateMode(ModeArray, 2, -2);
387 XLALSimInspiralModeArrayActivateMode(ModeArray, ell, emmprime);
388 XLALSimInspiralModeArrayActivateMode(ModeArray, ell, -emmprime);
389
390 XLALSimInspiralWaveformParamsInsertModeArray(lalParams_aux, ModeArray);
391 XLALDestroyValue(ModeArray);
392
393 /* Map fRef to the start frequency if need be, then make sure it's within the frequency range */
394 REAL8 fRef = (fRef_In == 0.0) ? f_min : fRef_In;
396 ((f_min <= fRef) && (fRef <= f_max)),
398 "Error: fRef needs to be within the specified minimum and maximum frequency values!\n");
399
400 /* Check that the passed deltaF is non-zero */
402 deltaF > 0,
404 "Error: deltaF needs to be a positive number!\n");
405
406 /* Specify arbitrary parameters for the angle generation */
407 REAL8 distance = 1.0;
408 REAL8 phiRef = 0.0;
409
410 /* Initialize PhenomX Waveform struct and check that it initialized correctly */
413 status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, chi1z, chi2z, deltaF, fRef, phiRef, f_min, f_max, distance, inclination, lalParams_aux, DEBUG);
414 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
415
416 /* Use the true minimum and maximum frequency values */
417 REAL8 f_min_eval = pWF->fMin;
418 REAL8 f_max_eval = pWF->f_max_prime;
419
420 /* Generate a uniformly sampled frequency grid of spacing deltaF. */
421 /* Frequencies will be set using only the lower and upper bounds that we passed */
422 REAL8 df = (pWF->deltaF == 0.0) ? deltaF : pWF->deltaF;
423 size_t iStart = (size_t)(f_min_eval / df);
424 size_t iStop = (size_t)(f_max_eval / df) + 1;
425
426 /* Allocate memory for frequency array and terminate if this fails */
427 *freqs = XLALCreateREAL8Sequence(iStop - iStart);
428 if (!(*freqs))
429 {
430 XLAL_ERROR(XLAL_EFUNC, "Frequency array allocation failed.");
431 }
432 *alphaPNR = XLALCreateREAL8Sequence(iStop - iStart);
433 if (!(*alphaPNR))
434 {
435 XLAL_ERROR(XLAL_EFUNC, "Alpha array allocation failed.");
436 }
437 *betaPNR = XLALCreateREAL8Sequence(iStop - iStart);
438 if (!(*betaPNR))
439 {
440 XLAL_ERROR(XLAL_EFUNC, "Beta array allocation failed.");
441 }
442 *gammaPNR = XLALCreateREAL8Sequence(iStop - iStart);
443 if (!(*gammaPNR))
444 {
445 XLAL_ERROR(XLAL_EFUNC, "Gamma array allocation failed.");
446 }
447
448 /* Populate frequency array */
449 for (UINT4 i = iStart; i < iStop; i++)
450 {
451 (*freqs)->data[i - iStart] = i * df;
452 }
453
454 /* Initialize PhenomX Precession struct and check that it generated successfully */
456 pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
457
458 /* If user chose SpinTaylor angles, set bounds for interpolation of inspiral angles */
460 if(pflag==330)
461 pPrec->M_MIN = 1; pPrec->M_MAX = emmprime;
462
464 pWF,
465 pPrec,
466 m1_SI,
467 m2_SI,
468 chi1x,
469 chi1y,
470 chi1z,
471 chi2x,
472 chi2y,
473 chi2z,
474 lalParams_aux,
475 DEBUG);
476 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetPrecessionVariables failed.\n");
477
478 // overwrite the precessing version to account for fallbacks
480
481 /* If user selected SpinTaylor angles, compute all the splines for the angles and the relevant offsets */
482 if(pPrec->IMRPhenomXPrecVersion==330){
483 status=IMRPhenomX_Initialize_Euler_Angles(pWF,pPrec,lalParams_aux);
484 XLAL_CHECK(status==XLAL_SUCCESS, XLAL_EDOM, "%s: Error in IMRPhenomX_Initialize_Euler_Angles.\n",__func__);
485 }
486
487 /* See if PNR angles were turned off in pPrec check */
488 UsePNR = pPrec->IMRPhenomXPNRUseTunedAngles;
489
490 if(UsePNR){
491 /* First generate (2,2)-angle interpolants */
493 status = IMRPhenomX_PNR_GeneratePNRAngleInterpolants(hm_angle_spline, pWF, pPrec, lalParams_aux);
494 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomX_PNR_GeneratePNRAngleInterpolants failed.\n");
495
496 REAL8 M = pWF->Mtot;
497
498 /* Interpolate the angles */
499 /* If the function is called with (l,m)=(2,2) then just interpolate onto the frequency grid*/
500 if ((ell == 2) && (emmprime == 2))
501 {
502 for (size_t i = 0; i < (*freqs)->length; i++)
503 {
504 if((*freqs)->data[i] <= pWF->f_max_prime){
505 (*alphaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->alpha_spline, (*freqs)->data[i], hm_angle_spline->alpha_acc);
506 (*betaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->beta_spline, (*freqs)->data[i], hm_angle_spline->beta_acc);
507 (*gammaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->gamma_spline, (*freqs)->data[i], hm_angle_spline->gamma_acc);
508 }
509 else{
510 (*alphaPNR)->data[i] = 0.0;
511 (*betaPNR)->data[i] = 0.0;
512 (*gammaPNR)->data[i] = 0.0;
513 }
514 }
515 }
516 else
517 { /* Called with l!=2 or m!=2, so we map the (2,2) angles onto a rescaled frequency grid */
518
519 /* Collect the (2,2) and (l,m) ringdown frequencies for use in the frequency map */
520 REAL8 Mf_RD_22 = pWF->fRING;
521 REAL8 Mf_RD_lm = IMRPhenomXHM_GenerateRingdownFrequency(ell, emmprime, pWF);
522
523 /* Generate interpolation transition frequencies */
524 REAL8 Mf_high = 0.0;
525 REAL8 Mf_low = 0.0;
526
527 status = IMRPhenomX_PNR_LinearFrequencyMapTransitionFrequencies(&Mf_low, &Mf_high, emmprime, Mf_RD_22, Mf_RD_lm, pPrec);
528 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomX_PNR_LinearFrequencyMapTransitionFrequencies failed.\n");
529
530 UINT4 toggleInspiralScaling = pPrec->PNRInspiralScaling;
531
532 #if DEBUG == 1
533 // Save angles into a file
534 FILE *fileangle;
535 char fileSpecII[40];
536 sprintf(fileSpecII, "interpolation_frequencies.dat");
537
538 fileangle = fopen(fileSpecII, "w");
539
540 fprintf(fileangle, "Mf_RD_22: %.16e\n", Mf_RD_22);
541 fprintf(fileangle, "Mf_RD_lm: %.16e\n", Mf_RD_lm);
542
543 fprintf(fileangle, "Mf_low: %.16e\n", Mf_low);
544 fprintf(fileangle, "Mf_high: %.16e\n", Mf_high);
545
546 fclose(fileangle);
547 #endif
548 for (size_t i = 0; i < (*freqs)->length; i++)
549 {
550 REAL8 Mf = XLALSimIMRPhenomXUtilsHztoMf((*freqs)->data[i], M);
551 if(Mf <= (pWF->f_max_prime * pWF->M_sec)){
552 /* Calculate the mapped frequency */
553 REAL8 Mf_mapped = IMRPhenomX_PNR_LinearFrequencyMap(Mf, ell, emmprime, Mf_low, Mf_high, Mf_RD_22, Mf_RD_lm, toggleInspiralScaling);
554 REAL8 f_mapped = XLALSimIMRPhenomXUtilsMftoHz(Mf_mapped, M);
555
556 (*alphaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->alpha_spline, f_mapped, hm_angle_spline->alpha_acc);
557 (*betaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->beta_spline, f_mapped, hm_angle_spline->beta_acc);
558 (*gammaPNR)->data[i] = gsl_spline_eval(hm_angle_spline->gamma_spline, f_mapped, hm_angle_spline->gamma_acc);
559 }else{
560 (*alphaPNR)->data[i] = 0.0;
561 (*betaPNR)->data[i] = 0.0;
562 (*gammaPNR)->data[i] = 0.0;
563 }
564 }
565 }
566
567 /* Here we assign the reference values of alpha and gamma to their values in the precession struct */
568 /* NOTE: the contribution from pPrec->alpha0 is assigned in IMRPhenomX_PNR_RemapThetaJSF */
569 pPrec->alpha_offset = gsl_spline_eval(hm_angle_spline->alpha_spline, pWF->fRef, hm_angle_spline->alpha_acc);
570 /* NOTE: the sign is flipped between gamma and epsilon */
571 pPrec->epsilon_offset = -gsl_spline_eval(hm_angle_spline->gamma_spline, pWF->fRef, hm_angle_spline->gamma_acc) - pPrec->epsilon0; // note the sign difference between gamma and epsilon
572
573 /* Remap the J-frame sky location to use beta instead of ThetaJN */
574 REAL8 betaPNR_ref = gsl_spline_eval(hm_angle_spline->beta_spline, pWF->fRef, hm_angle_spline->beta_acc);
575 status = IMRPhenomX_PNR_RemapThetaJSF(betaPNR_ref, pWF, pPrec, lalParams_aux);
579 "Error: IMRPhenomX_PNR_RemapThetaJSF failed in IMRPhenomX_PNR_GeneratePNRAnglesHM.");
580
581 /* Pass through the reference values of alpha and gamma */
582 *alphaPNR_ref = pPrec->alpha_offset;
583 *gammaPNR_ref = -(pPrec->epsilon_offset);
584
585 gsl_spline_free(hm_angle_spline->alpha_spline);
586 gsl_spline_free(hm_angle_spline->beta_spline);
587 gsl_spline_free(hm_angle_spline->gamma_spline);
588
589 gsl_interp_accel_free(hm_angle_spline->alpha_acc);
590 gsl_interp_accel_free(hm_angle_spline->beta_acc);
591 gsl_interp_accel_free(hm_angle_spline->gamma_acc);
592
593 LALFree(hm_angle_spline);
594 }
595 else{
596 /* Generate PN angles */
598 /* MSA angles generated using built-in function */
599
600 REAL8Sequence *cosBeta = XLALCreateREAL8Sequence(iStop - iStart);
601 if (!cosBeta)
602 {
603 XLAL_ERROR(XLAL_EFUNC, "cosBeta array allocation failed.");
604 }
605
607 alphaPNR, gammaPNR, &cosBeta, *freqs,
608 m1_SI,m2_SI,
609 chi1x,chi1y,chi1z,
610 chi2x,chi2y,chi2z,
611 inclination,
612 fRef_In, emmprime, lalParams_aux
613 );
614
615 for (UINT4 i = 0; i < (*freqs)->length; i++)
616 {
617 (*betaPNR)->data[i] = acos(cosBeta->data[i]);
618 }
619
621
622 /* Reference values already applied in function */
623 *alphaPNR_ref = 0.0;
624 *gammaPNR_ref = 0.0;
625 }
626 else{
627 /* NNLO angles */
628
629 REAL8Sequence *cosBeta = XLALCreateREAL8Sequence(iStop - iStart);
630 if (!cosBeta)
631 {
632 XLAL_ERROR(XLAL_EFUNC, "cosBeta array allocation failed.");
633 }
634
636 alphaPNR, gammaPNR, &cosBeta, *freqs,
637 m1_SI,m2_SI,
638 chi1x,chi1y,chi1z,
639 chi2x,chi2y,chi2z,
640 inclination,
641 fRef_In, emmprime, lalParams_aux
642 );
643
644 for (UINT4 i = 0; i < (*freqs)->length; i++)
645 {
646 (*betaPNR)->data[i] = acos(cosBeta->data[i]);
647 }
648
650
651 /* Pass through the reference values of alpha and gamma
652 * NOTE: gamma = - epsilon */
653 *alphaPNR_ref = pPrec->alpha_offset;
654 *gammaPNR_ref = -(pPrec->epsilon_offset);
655 }
656 }
657
658 /* Clean up memory allocation */
659 LALFree(pPrec);
660 LALFree(pWF);
661 XLALDestroyDict(lalParams_aux);
662
663 return XLAL_SUCCESS;
664 }
665
666 /**
667 * Generate the tuned precession angles outlined in arXiv:2107.08876.
668 * The general flow is as follows:
669 *
670 * - First check if deltaF > 0:
671 *
672 * --> if it is, then we generate angles sampled on a uniform frequency grid
673 * and then compute the values of the angles at fRef using linear interpolation.
674 *
675 * --> otherwise we expect non-uniform frequencies, so we generate interpolants
676 * and then evaluate them at fRef.
677 *
678 * - The reference values of alpha and gamma are stored in the pPrec struct for use later,
679 * and then we re-map the J-frame sky position using the reference value of beta.
680 *
681 * - NOTE: alpha0 is recomputed in IMRPhenomX_PNR_RemapThetaJSF, hence it is not used here
682 * unlike epsilon0. Instead it is applied inside IMRPhenomX_PNR_RemapThetaJSF.
683 *
684 */
686 REAL8Sequence *alphaPNR, /**< alpha precession angle (rad) */
687 REAL8Sequence *betaPNR, /**< beta precession angle (rad) */
688 REAL8Sequence *gammaPNR, /**< gamma precession angle (rad) */
689 const REAL8Sequence *freqs, /**< input frequency array (Hz) */
690 IMRPhenomXWaveformStruct *pWF, /**< waveform struct */
691 IMRPhenomXPrecessionStruct *pPrec, /**< precession struct */
692 LALDict *lalParams /**< LAL dictionary struct */
693 )
694 {
695 /* Make sure the incoming pointers lead to something initialized */
696 XLAL_CHECK(alphaPNR != NULL, XLAL_EFAULT);
697 XLAL_CHECK(betaPNR != NULL, XLAL_EFAULT);
698 XLAL_CHECK(gammaPNR != NULL, XLAL_EFAULT);
699 XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
700 XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
701 XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
702 XLAL_CHECK(lalParams != NULL, XLAL_EFAULT);
703
704 REAL8 f_ref = pWF->fRef;
705 REAL8 deltaF = pWF->deltaF;
706
707 /* Make sure we're supposed to be here */
708 int UsePNR = pPrec->IMRPhenomXPNRUseTunedAngles;
710 UsePNR,
712 "Error: PNR angles called without being activated!\n");
713
714 INT4 status;
715
716 /* Check for uniform frequency series */
717 if (deltaF > 0.0)
718 {
719
720 /* Generate PNR structs */
721 IMRPhenomXWaveformStruct *pWF_SingleSpin = NULL;
722 IMRPhenomXPrecessionStruct *pPrec_SingleSpin = NULL;
723 IMRPhenomX_PNR_alpha_parameters *alphaParams = NULL;
724 IMRPhenomX_PNR_beta_parameters *betaParams = NULL;
725
727 &pWF_SingleSpin,
728 &pPrec_SingleSpin,
729 &alphaParams,
730 &betaParams,
731 pWF,
732 pPrec,
733 lalParams);
737 "Error: IMRPhenomX_PNR_PopulateStructs failed!\n");
738
739 /* generate angles */
741 alphaPNR,
742 betaPNR,
743 gammaPNR,
744 freqs,
745 pWF_SingleSpin,
746 pPrec_SingleSpin,
747 alphaParams,
748 betaParams,
749 pWF,
750 pPrec,
751 lalParams);
755 "Error: IMRPhenomX_PNR_GeneratePNRAngles_UniformFrequencies failed in IMRPhenomX_PNR_GeneratePNRAngles.");
756
757
758 /* Here we assign the reference values of alpha and gamma to their values in the precession struct.
759 * For the uniformly-sampled arrays, we linearly interpolate the angles between the
760 * next-to-nearest frequencies to get the reference values. */
761
762 /* NOTE: the contribution from pPrec->alpha0 is assigned in IMRPhenomX_PNR_RemapThetaJSF */
763 pPrec->alpha_offset = IMRPhenomX_PNR_AngleAtFRef(alphaPNR, f_ref, freqs, deltaF);
764 /* NOTE: the sign is flipped between gamma and epsilon */
765 pPrec->epsilon_offset = -IMRPhenomX_PNR_AngleAtFRef(gammaPNR, f_ref, freqs, deltaF) - pPrec->epsilon0;
766
767 /* Remap the J-frame sky location to use beta instead of ThetaJN */
768 REAL8 betaPNR_ref = IMRPhenomX_PNR_AngleAtFRef(betaPNR, f_ref, freqs, deltaF);
769 status = IMRPhenomX_PNR_RemapThetaJSF(betaPNR_ref, pWF, pPrec, lalParams);
773 "Error: IMRPhenomX_PNR_RemapThetaJSF failed in IMRPhenomX_PNR_GeneratePNRAngles.");
774
775 /* clean this up */
777 &pWF_SingleSpin,
778 &pPrec_SingleSpin,
779 &alphaParams,
780 &betaParams);
781 }
782 else
783 { /* Non-uniform frequency array, so we generate interpolants to evaluate */
785 /* Generate the angle interpolants */
787 angle_spline,
788 pWF, pPrec,
789 lalParams);
793 "Error: IMRPhenomX_PNR_GeneratePNRAngleInterpolants failed in IMRPhenomX_PNR_GeneratePNRAngles");
794
795 /* Fill the angle arrays with the values of the angles at the frequncy points */
796 REAL8 fval = 0;
797 for (UINT4 i = 0; i < freqs->length; i++)
798 {
799 fval = freqs->data[i];
800 alphaPNR->data[i] = gsl_spline_eval(angle_spline->alpha_spline, fval, angle_spline->alpha_acc);
801 betaPNR->data[i] = gsl_spline_eval(angle_spline->beta_spline, fval, angle_spline->beta_acc);
802 gammaPNR->data[i] = gsl_spline_eval(angle_spline->gamma_spline, fval, angle_spline->gamma_acc);
803 }
804
805 /* Here we assign the reference values of alpha and gamma to their values in the precession struct */
806 /* NOTE: the contribution from pPrec->alpha0 is assigned in IMRPhenomX_PNR_RemapThetaJSF */
807 pPrec->alpha_offset = gsl_spline_eval(angle_spline->alpha_spline, f_ref, angle_spline->alpha_acc);
808 /* NOTE: the sign is flipped between gamma and epsilon */
809 pPrec->epsilon_offset = -gsl_spline_eval(angle_spline->gamma_spline, f_ref, angle_spline->gamma_acc) - pPrec->epsilon0;
810
811 /* Remap the J-frame sky location to use beta instead of ThetaJN */
812 REAL8 betaPNR_ref = gsl_spline_eval(angle_spline->beta_spline, f_ref, angle_spline->beta_acc);
813 status = IMRPhenomX_PNR_RemapThetaJSF(betaPNR_ref, pWF, pPrec, lalParams);
817 "Error: IMRPhenomX_PNR_RemapThetaJSF failed in IMRPhenomX_PNR_GeneratePNRAngles.");
818
819 /* Free up memory */
820 gsl_spline_free(angle_spline->alpha_spline);
821 gsl_spline_free(angle_spline->beta_spline);
822 gsl_spline_free(angle_spline->gamma_spline);
823
824 gsl_interp_accel_free(angle_spline->alpha_acc);
825 gsl_interp_accel_free(angle_spline->beta_acc);
826 gsl_interp_accel_free(angle_spline->gamma_acc);
827
828 LALFree(angle_spline);
829 }
830
831 return XLAL_SUCCESS;
832 }
833
834 /**
835 * Generate the tuned precession angles outlined in arXiv:2107.08876
836 * specifically on a uniform frequency grid.
837 *
838 * - First, we populate the required alpha and beta parameter structs,
839 * along with the two-spin structs if needed.
840 *
841 * - Next we store the two connection frequencies possibly needed for the
842 * HM angle frequency mapping.
843 *
844 * - Check if we should be attaching the MR contributions to beta: this is
845 * determined by calling the function IMRPhenomX_PNR_AttachMRBeta.
846 *
847 * --> If yes, loop through the frequencies and generate full alpha and beta
848 *
849 * --> If no, loop through the frequencies and generate full alpha but only
850 * the inspiral beta
851 *
852 * - Finally, if an initialized struct is passed for gamma, compute it.
853 *
854 */
856 REAL8Sequence *alphaPNR,
857 REAL8Sequence *betaPNR,
858 REAL8Sequence *gammaPNR,
859 const REAL8Sequence *freqs,
860 IMRPhenomXWaveformStruct *pWF_SingleSpin,
861 IMRPhenomXPrecessionStruct *pPrec_SingleSpin,
866 LALDict *lalParams)
867 {
868 /* Make sure the incoming pointers lead to something initialized */
869 /* Gamma could be a NULL pointer, so we don't check here */
870 XLAL_CHECK(alphaPNR != NULL, XLAL_EFAULT);
871 XLAL_CHECK(betaPNR != NULL, XLAL_EFAULT);
872 XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
873 XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
874 XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
875 XLAL_CHECK(lalParams != NULL, XLAL_EFAULT);
876
877 REAL8 M = pWF->Mtot;
878
879 /* Make sure we're supposed to be here */
880 int UsePNR = pPrec->IMRPhenomXPNRUseTunedAngles;
882 UsePNR,
884 "Error: PNR angles called without being activated!\n");
885
886 INT4 status;
887
888 #if DEBUG == 1
889 IMRPhenomX_PNR_AngleParameterDebugPrint(alphaParams, betaParams);
890 #endif
891
892 REAL8 Mf = 0.0;
893 /* generate PNR angles */
894 REAL8 q = pWF->q;
895 REAL8 chi = pPrec->chi_singleSpin;
896 /* inside calibration region */
897 if ((q <= pPrec->PNR_q_window_lower) && (chi <= pPrec->PNR_chi_window_lower))
898 {
899 /* First check to see if we attach the MR tuning to beta */
900 UINT4 attach_MR_beta = IMRPhenomX_PNR_AttachMRBeta(betaParams);
901 if (attach_MR_beta) /* yes we do! */
902 {
903 pPrec->UseMRbeta = 1;
904 for (size_t i = 0; i < freqs->length; i++)
905 {
906 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
907 /* generate alpha and beta with MR tuning */
908 alphaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
909 betaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRBetaAtMf(Mf, betaParams, pWF, pPrec, pWF_SingleSpin, pPrec_SingleSpin);
910 }
911 }
912 else /* don't attach MR tuning to beta */
913 {
914 pPrec->UseMRbeta = 0;
915 for (size_t i = 0; i < freqs->length; i++)
916 {
917 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
918
919 /* generate alpha, generate beta with no MR tuning */
920 alphaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
921 betaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRBetaNoMR(Mf, betaParams, pWF, pPrec);
922 }
923 }
924 }
925 /* inside transition region */
926 else if ((q <= pPrec->PNR_q_window_upper) && (chi <= pPrec->PNR_chi_window_upper))
927 {
928 /* First check to see if we attach the MR tuning to beta */
929 UINT4 attach_MR_beta = IMRPhenomX_PNR_AttachMRBeta(betaParams);
930 if (attach_MR_beta) /* yes we do! */
931 {
932 for (size_t i = 0; i < freqs->length; i++)
933 {
934 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
935 /* generate alpha and beta with MR tuning */
936 alphaPNR->data[i] = IMRPhenomX_PNR_GenerateMergedPNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
937 betaPNR->data[i] = IMRPhenomX_PNR_GenerateMergedPNRBetaAtMf(Mf, betaParams, pWF, pPrec, pWF_SingleSpin, pPrec_SingleSpin);
938 }
939 }
940 else /* don't attach MR tuning to beta */
941 {
942 pPrec->UseMRbeta = 0;
943 for (size_t i = 0; i < freqs->length; i++)
944 {
945 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
946
947 /* generate alpha, generate beta with no MR tuning */
948 alphaPNR->data[i] = IMRPhenomX_PNR_GenerateMergedPNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
949 betaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRBetaNoMR(Mf, betaParams, pWF, pPrec);
950 }
951 }
952
953 }
954 /* fully in outside calibration region */
955 else{
956 pPrec->UseMRbeta = 0;
957 for (size_t i = 0; i < freqs->length; i++)
958 {
959 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
960
961 /* generate MSA alpha, generate beta with no MR tuning */
962 alphaPNR->data[i] = IMRPhenomX_PNR_GetPNAlphaAtFreq(Mf, pWF, pPrec);
963 betaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRBetaNoMR(Mf, betaParams, pWF, pPrec);
964 }
965 }
966
967 /* If gamma is a NULL pointer, we assume that we don't want to
968 * compute it. This allows us to skip computing gamma for the
969 * interpolation code to reduce redundant steps */
970 if (gammaPNR != NULL)
971 {
972 /* generate gamma */
973 status = IMRPhenomX_PNR_GeneratePNRGamma(gammaPNR, freqs, alphaPNR, betaPNR);
977 "Error: IMRPhenomX_PNR_GeneratePNRGamma failed");
978 }
979
980 return XLAL_SUCCESS;
981 }
982
983 /**
984 * Internal helper function to generate PNR alpha for the
985 * antisymmetric waveform.
986 */
988 REAL8Sequence *alphaPNR,
989 const REAL8Sequence *freqs,
992 LALDict *lalParams)
993 {
994 /* Make sure the incoming pointers lead to something initialized */
995 /* Gamma could be a NULL pointer, so we don't check here */
996 XLAL_CHECK(alphaPNR != NULL, XLAL_EFAULT);
997 XLAL_CHECK(freqs != NULL, XLAL_EFAULT);
998 XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
999 XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
1000 XLAL_CHECK(lalParams != NULL, XLAL_EFAULT);
1001
1002 REAL8 M = pWF->Mtot;
1003
1004 INT4 status;
1005
1006 /* generate alpha parameters */
1008 status = IMRPhenomX_PNR_precompute_alpha_coefficients(alphaParams, pWF, pPrec);
1009 XLAL_CHECK(
1011 XLAL_EFUNC,
1012 "Error: IMRPhenomX_PNR_precompute_alpha_coefficients failed.\n");
1013
1014 status = IMRPhenomX_PNR_alpha_connection_parameters(alphaParams, pWF, pPrec);
1015 XLAL_CHECK(
1017 XLAL_EFUNC,
1018 "Error: IMRPhenomX_PNR_alpha_connection_parameters failed.\n");
1019
1020 REAL8 Mf = 0.0;
1021 /* generate PNR angles */
1022 REAL8 q = pWF->q;
1023 REAL8 chi = pPrec->chi_singleSpin;
1024 /* inside calibration region */
1025 if ((q <= pPrec->PNR_q_window_lower) && (chi <= pPrec->PNR_chi_window_lower))
1026 {
1027 for (size_t i = 0; i < freqs->length; i++)
1028 {
1029 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
1030
1031 /* generate alpha */
1032 alphaPNR->data[i] = IMRPhenomX_PNR_GeneratePNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
1033 }
1034 }
1035 /* inside transition region */
1036 else if ((q <= pPrec->PNR_q_window_upper) && (chi <= pPrec->PNR_chi_window_upper))
1037 {
1038 for (size_t i = 0; i < freqs->length; i++)
1039 {
1040 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
1041
1042 /* generate alpha */
1043 alphaPNR->data[i] = IMRPhenomX_PNR_GenerateMergedPNRAlphaAtMf(Mf, alphaParams, pWF, pPrec);
1044 }
1045 }
1046 /* fully in outside calibration region */
1047 else
1048 {
1049 for (size_t i = 0; i < freqs->length; i++)
1050 {
1051 Mf = XLALSimIMRPhenomXUtilsHztoMf(freqs->data[i], M);
1052
1053 /* generate MSA or NNLO alpha */
1054 alphaPNR->data[i] = IMRPhenomX_PNR_GetPNAlphaAtFreq(Mf, pWF, pPrec);
1055 }
1056 }
1057
1058 LALFree(alphaParams);
1059
1060 return XLAL_SUCCESS;
1061 }
1062
1063 /**
1064 * Generate the tuned precession angles outlined in arXiv:2107.08876
1065 * specifically populate the angle interpolant struct.
1066 *
1067 * - First, we check for an activated mode array.
1068 *
1069 * --> If it exists, then we're likely computing HM angles with these interpolants
1070 * and we need to extend the frequency range to capture the HM frequency
1071 * map. Use the activated modes to determine this.
1072 *
1073 * --> Otherwise we use the default f_min and f_max.
1074 *
1075 * - Get the required deltaF from IMRPhenomX_PNR_HMInterpolationDeltaF.
1076 *
1077 * - Compute uniform alpha and beta
1078 *
1079 * - Populate interpolants for alpha and beta, then compute gamma using these interpolants
1080 *
1081 */
1083 IMRPhenomX_PNR_angle_spline *angle_spline,
1086 LALDict *lalparams)
1087 {
1088 /* check for initialization */
1089 XLAL_CHECK(angle_spline != NULL, XLAL_EFAULT);
1090 XLAL_CHECK(pWF != NULL, XLAL_EFAULT);
1091 XLAL_CHECK(pPrec != NULL, XLAL_EFAULT);
1092 XLAL_CHECK(lalparams != NULL, XLAL_EFAULT);
1093
1094 /* Generate PNR structs */
1095 IMRPhenomXWaveformStruct *pWF_SingleSpin = NULL;
1096 IMRPhenomXPrecessionStruct *pPrec_SingleSpin = NULL;
1097 IMRPhenomX_PNR_alpha_parameters *alphaParams = NULL;
1098 IMRPhenomX_PNR_beta_parameters *betaParams = NULL;
1099
1100 UINT4 status = 0;
1102 &pWF_SingleSpin,
1103 &pPrec_SingleSpin,
1104 &alphaParams,
1105 &betaParams,
1106 pWF,
1107 pPrec,
1108 lalparams);
1109 XLAL_CHECK(
1111 XLAL_EFUNC,
1112 "Error: IMRPhenomX_PNR_PopulateStructs failed!\n");
1113
1114 /* Store connection frequencies */
1115 pPrec->PNR_HM_Mfhigh = betaParams->Mf_beta_lower;
1116 pPrec->PNR_HM_Mflow = alphaParams->Mf_alpha_lower;
1117
1118 /* Check for connection frequencies potentially being poorly defined for frequency interpolation */
1119 /* Check that the lower and upper connection frequencies are well behaved relative to fCut, fRING, and each other.
1120 * fCutDef is Mf = 0.3 is except for large chiEff. */
1121 if((pPrec->PNR_HM_Mfhigh > pWF->fCutDef) || (pPrec->PNR_HM_Mfhigh < 0.1 * pWF->fRING)){
1122 pPrec->PNR_HM_Mfhigh = pWF->fRING;
1123 }
1124 if((pPrec->PNR_HM_Mflow > pWF->fCutDef) || (pPrec->PNR_HM_Mfhigh < pPrec->PNR_HM_Mflow)){
1125 pPrec->PNR_HM_Mflow = pPrec->PNR_HM_Mfhigh / 2.0;
1126 }
1127
1128 REAL8 f_min_22 = pWF->fMin;
1129 REAL8 f_max_22 = pWF->f_max_prime;
1130
1131 if(pWF->deltaF != 0){
1132 /* need to account for finite frequency spacing in fMin */
1133 size_t iStart = (size_t) (f_min_22 / pWF->deltaF);
1134
1135 REAL8 fMinFromDeltaF = iStart*pWF->deltaF;
1136 f_min_22 = (fMinFromDeltaF < f_min_22) ? fMinFromDeltaF : f_min_22;
1137 }
1138 else{
1139 f_max_22 = pWF->fMax;
1140 }
1141
1142 /* Grab the mode array to find the minimum and maximum
1143 * frequency values that need to be interpolated. */
1144 LALValue *ModeArray = XLALSimInspiralWaveformParamsLookupModeArray(lalparams);
1145
1146 REAL8 f_min = f_min_22;
1147 REAL8 f_max = f_max_22;
1148
1149 if (ModeArray != NULL)
1150 {
1151 /* Assume we possibly have higher multipoles. */
1152 for (UINT4 ell = 2; ell <= 4; ell++)
1153 {
1154 for (UINT4 emmprime = 1; emmprime <= ell; emmprime++)
1155 {
1156 /* First check for (2,2) and multibanding */
1157 if(XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, emmprime) && ((ell == 2)&&(emmprime == 2)))
1158 {
1159 if(pPrec->MBandPrecVersion != 0)
1160 {
1161 /* For (2,2) with angle MB, find max (2,2) frequency */
1162 REAL8Sequence *coarseFreqs;
1163 XLALSimIMRPhenomXPHMMultibandingGrid(&coarseFreqs, ell, emmprime, pWF, lalparams);
1164
1165 REAL8 MfCoarseFMax = coarseFreqs->data[coarseFreqs->length-1];
1166 REAL8 coarseFMax = XLALSimIMRPhenomXUtilsMftoHz(MfCoarseFMax, pWF->Mtot);
1167 if(coarseFMax > f_max){
1168 f_max = coarseFMax;
1169 }
1170
1171 XLALDestroyREAL8Sequence(coarseFreqs);
1172 }
1173 }
1174 else if(XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, emmprime) && !((ell == 2)&&(emmprime == 2)))
1175 {
1176 if((pWF->q == 1) && (pWF->chi1L == pWF->chi2L) && (emmprime % 2 != 0))
1177 {
1178 continue;
1179 }
1180 REAL8 Mf_RD_22 = pWF->fRING;
1181 REAL8 Mf_RD_lm = IMRPhenomXHM_GenerateRingdownFrequency(ell, emmprime, pWF);
1182
1183 REAL8 Mf_low = 0.0;
1184 REAL8 Mf_high = 0.0;
1185
1186 IMRPhenomX_PNR_LinearFrequencyMapTransitionFrequencies(&Mf_low, &Mf_high, emmprime, Mf_RD_22, Mf_RD_lm, pPrec);
1187
1188 UINT4 toggleInspiralScaling = pPrec->PNRInspiralScaling;
1189
1190 if(pPrec->MBandPrecVersion != 0)
1191 {
1192 /* For (l,m) with angle MB, find max mapped (2,2) frequency */
1193 REAL8Sequence *coarseFreqs;
1194 XLALSimIMRPhenomXPHMMultibandingGrid(&coarseFreqs, ell, emmprime, pWF, lalparams);
1195
1196 REAL8 MfCoarseFMin = coarseFreqs->data[0];
1197 REAL8 MfCoarseFMax = coarseFreqs->data[coarseFreqs->length-1];
1198
1199 REAL8 MfMinMapped = IMRPhenomX_PNR_LinearFrequencyMap(MfCoarseFMin, ell, emmprime, Mf_low, Mf_high, Mf_RD_22, Mf_RD_lm, toggleInspiralScaling);
1200 REAL8 MfMaxMapped = IMRPhenomX_PNR_LinearFrequencyMap(MfCoarseFMax, ell, emmprime, Mf_low, Mf_high, Mf_RD_22, Mf_RD_lm, toggleInspiralScaling);
1201
1202 REAL8 coarseFMin = XLALSimIMRPhenomXUtilsMftoHz(MfMinMapped, pWF->Mtot);
1203 REAL8 coarseFMax = XLALSimIMRPhenomXUtilsMftoHz(MfMaxMapped, pWF->Mtot);
1204
1205 if(coarseFMin < f_min)
1206 {
1207 f_min = coarseFMin;
1208 }
1209
1210 if(coarseFMax > f_max)
1211 {
1212 f_max = coarseFMax;
1213 }
1214
1215 XLALDestroyREAL8Sequence(coarseFreqs);
1216 }
1217 else
1218 {
1219 REAL8 MfFMin = XLALSimIMRPhenomXUtilsHztoMf(f_min_22,pWF->Mtot);
1220 REAL8 MfFMax = XLALSimIMRPhenomXUtilsHztoMf(f_max_22,pWF->Mtot);
1221
1222 REAL8 MfMinMapped = IMRPhenomX_PNR_LinearFrequencyMap(MfFMin, ell, emmprime, Mf_low, Mf_high, Mf_RD_22, Mf_RD_lm, toggleInspiralScaling);
1223 REAL8 MfMaxMapped = IMRPhenomX_PNR_LinearFrequencyMap(MfFMax, ell, emmprime, Mf_low, Mf_high, Mf_RD_22, Mf_RD_lm, toggleInspiralScaling);
1224
1225 REAL8 FMin = XLALSimIMRPhenomXUtilsMftoHz(MfMinMapped, pWF->Mtot);
1226 REAL8 FMax = XLALSimIMRPhenomXUtilsMftoHz(MfMaxMapped, pWF->Mtot);
1227
1228 if(FMin < f_min)
1229 {
1230 f_min = FMin;
1231 }
1232
1233 if(FMax > f_max)
1234 {
1235 f_max = FMax;
1236 }
1237 }
1238 }
1239 }
1240 }
1241 } /* Otherwise f_min and f_max are already set to the (2,2) values */
1242
1243 /* Get appropriate frequency spacing */
1244 REAL8 deltaF_int = IMRPhenomX_PNR_HMInterpolationDeltaF(f_min, pWF, pPrec);
1245
1246 /* guarantee additional space to ensure no extrapolation and to reduce effects
1247 * of natural boundary conditions */
1248 f_min = (f_min - 2.0 * deltaF_int < 0) ? f_min / 2.0 : f_min - 2.0 * deltaF_int;
1249 f_max += 2.0 * deltaF_int;
1250
1251 /* Frequencies will be set using the lower and upper bounds and computed deltaF */
1252 size_t iStart = (size_t)(f_min / deltaF_int);
1253 size_t iStop = (size_t)(f_max / deltaF_int) + 1;
1254
1255#if DEBUG == 1
1256 /* Save interpolation parameters into a file */
1257 FILE *fileangle;
1258 char fileSpecII[40];
1259 sprintf(fileSpecII, "interpolation_parameters.dat");
1260
1261 fileangle = fopen(fileSpecII, "w");
1262
1263 fprintf(fileangle, "f_min_22: %.16e\n", f_min_22);
1264 fprintf(fileangle, "f_max_22: %.16e\n", f_max_22);
1265 fprintf(fileangle, "f_min: %.16e\n", f_min);
1266 fprintf(fileangle, "f_max: %.16e\n", f_max);
1267 fprintf(fileangle, "fCut: %.16e\n", pWF->fCut);
1268 fprintf(fileangle, "f_min from df: %.16e\n", iStart * deltaF_int);
1269 fprintf(fileangle, "f_max from df: %.16e\n", iStop * deltaF_int);
1270 fprintf(fileangle, "deltaF: %.16e\n\n", deltaF_int);
1271
1272 fclose(fileangle);
1273#endif
1274
1275 XLAL_CHECK((iStart <= iStop), XLAL_EDOM,
1276 "minimum freq index %zu and maximum freq index %zu do not fulfill 0<=ind_min<=ind_max.\n", iStart, iStop);
1277
1278 /* Allocate memory for frequency array and angles, and terminate if this fails */
1279 REAL8Sequence *alphaPNR_22 = NULL;
1280 REAL8Sequence *betaPNR_22 = NULL;
1281 REAL8Sequence *gammaPNR_22 = NULL;
1282 REAL8Sequence *freqs_22 = NULL;
1283
1284 freqs_22 = XLALCreateREAL8Sequence(iStop - iStart);
1285 if (!freqs_22)
1286 {
1287 XLAL_ERROR(XLAL_EFUNC, "freqs_22 array allocation failed.");
1288 }
1289 alphaPNR_22 = XLALCreateREAL8Sequence(iStop - iStart);
1290 if (!alphaPNR_22)
1291 {
1292 XLAL_ERROR(XLAL_EFUNC, "alphaPNR_22 array allocation failed.");
1293 }
1294 betaPNR_22 = XLALCreateREAL8Sequence(iStop - iStart);
1295 if (!betaPNR_22)
1296 {
1297 XLAL_ERROR(XLAL_EFUNC, "gammaPNR_22 array allocation failed.");
1298 }
1299 gammaPNR_22 = XLALCreateREAL8Sequence(iStop - iStart);
1300 if (!gammaPNR_22)
1301 {
1302 XLAL_ERROR(XLAL_EFUNC, "gammaPNR_22 array allocation failed.");
1303 }
1304
1305 /* Populate frequency array */
1306 for (UINT4 i = iStart; i < iStop; i++)
1307 {
1308 freqs_22->data[i - iStart] = i * deltaF_int;
1309 }
1310
1311 /*
1312 If using SpinTaylor angles in the inspiral, we check whether the starting frequency of the inspiral angles is above the one requested by the PNR model
1313 In a limited number of cases, this will not be true, so we reintegrate the spin-precession equations accordingly.
1314 */
1315
1316 if((pPrec->IMRPhenomXPrecVersion==330)&& pPrec->fmin_integration>freqs_22->data[0]){
1317
1318 pPrec->PNarrays = XLALMalloc(sizeof(PhenomXPInspiralArrays));
1319 int success = IMRPhenomX_InspiralAngles_SpinTaylor(pPrec->PNarrays, &pPrec->fmin_integration, pPrec->chi1x,pPrec->chi1y,pPrec->chi1z,pPrec->chi2x,pPrec->chi2y,pPrec->chi2z,freqs_22->data[0],pPrec->IMRPhenomXPrecVersion,pWF,lalparams);
1320
1321 if(success==XLAL_FAILURE) {
1322 XLALFree(pPrec->PNarrays);
1323 XLAL_PRINT_WARNING("Warning: due to a failure in the SpinTaylor routines, the model will default to MSA angles.");
1324 pPrec->IMRPhenomXPrecVersion=223;
1325 }
1326
1327 LALFree(pPrec->alpha_params);
1328 LALFree(pPrec->beta_params);
1329
1330 gsl_spline_free(pPrec->alpha_spline);
1331 gsl_spline_free(pPrec->cosbeta_spline);
1332 gsl_spline_free(pPrec->gamma_spline);
1333
1334 gsl_interp_accel_free(pPrec->alpha_acc);
1335 gsl_interp_accel_free(pPrec->gamma_acc);
1336 gsl_interp_accel_free(pPrec->cosbeta_acc);
1337
1338 success=IMRPhenomX_Initialize_Euler_Angles(pWF,pPrec,lalparams);
1339 XLAL_CHECK(success==XLAL_SUCCESS, XLAL_EDOM, "%s: Error in IMRPhenomX_Initialize_Euler_Angles.\n",__func__);
1340
1342
1344 &pWF_SingleSpin,
1345 &pPrec_SingleSpin,
1346 &alphaParams,
1347 &betaParams);
1348
1349 pWF_SingleSpin = NULL;
1350 pPrec_SingleSpin = NULL;
1351 alphaParams = NULL;
1352 betaParams = NULL;
1353
1355 &pWF_SingleSpin,
1356 &pPrec_SingleSpin,
1357 &alphaParams,
1358 &betaParams,
1359 pWF,
1360 pPrec,
1361 lalparams);
1363 XLAL_EFUNC,"Error: IMRPhenomX_PNR_PopulateStructs failed!\n");
1364
1365 }
1366
1367 /* Generate the (2,2) angles on this uniform frequency grid.
1368 * Pass gamma as a NULL pointer, since we can save a step by
1369 * re-using the gsl splines constructed to compute gamma later on */
1371 alphaPNR_22,
1372 betaPNR_22,
1373 NULL,
1374 freqs_22,
1375 pWF_SingleSpin,
1376 pPrec_SingleSpin,
1377 alphaParams,
1378 betaParams,
1379 pWF,
1380 pPrec,
1381 lalparams);
1382 XLAL_CHECK(
1384 XLAL_EFUNC,
1385 "Error: IMRPhenomX_PNR_GeneratePNRAngles_UniformFrequencies failed in IMRPhenomX_PNR_GeneratePNRAngleInterpolants.\n");
1386
1387 /* Construct the splines for interpolation and populate the alpha and beta splines */
1388 UINT4 flen = freqs_22->length;
1389
1390 gsl_spline *a_spline = gsl_spline_alloc(gsl_interp_cspline, flen);
1391 gsl_spline *b_spline = gsl_spline_alloc(gsl_interp_cspline, flen);
1392 gsl_spline *g_spline = gsl_spline_alloc(gsl_interp_cspline, flen);
1393
1394 gsl_interp_accel *a_acc = gsl_interp_accel_alloc();
1395 gsl_interp_accel *b_acc = gsl_interp_accel_alloc();
1396 gsl_interp_accel *g_acc = gsl_interp_accel_alloc();
1397
1398 gsl_spline_init(a_spline, freqs_22->data, alphaPNR_22->data, flen);
1399 gsl_spline_init(b_spline, freqs_22->data, betaPNR_22->data, flen);
1400
1401 angle_spline->alpha_spline = a_spline;
1402 angle_spline->beta_spline = b_spline;
1403
1404 angle_spline->alpha_acc = a_acc;
1405 angle_spline->beta_acc = b_acc;
1406
1407 /* Now use the alpha and beta splines to compute gamma */
1409 gammaPNR_22,
1410 freqs_22,
1411 angle_spline);
1412 XLAL_CHECK(
1414 XLAL_EFUNC,
1415 "Error: IMRPhenomX_PNR_GeneratePNRGamma_FromInterpolants failed in IMRPhenomX_PNR_GeneratePNRAngleInterpolants.\n");
1416
1417 /* Reset the gsl accelerators for alpha and beta
1418 * Not sure if this is helpful =\_(~~)_/= */
1419 gsl_interp_accel_reset(angle_spline->alpha_acc);
1420 gsl_interp_accel_reset(angle_spline->beta_acc);
1421
1422 /* Populate the gamma spline */
1423 gsl_spline_init(g_spline, freqs_22->data, gammaPNR_22->data, flen);
1424
1425 angle_spline->gamma_spline = g_spline;
1426 angle_spline->gamma_acc = g_acc;
1427
1428 /* Clean up memory allocation
1429 * In particular, the splines copy over the data
1430 * so we can get rid of the 22 angle arrays */
1431 XLALDestroyValue(ModeArray);
1432
1433 XLALDestroyREAL8Sequence(alphaPNR_22);
1434 XLALDestroyREAL8Sequence(betaPNR_22);
1435 XLALDestroyREAL8Sequence(gammaPNR_22);
1436 XLALDestroyREAL8Sequence(freqs_22);
1437
1438 /* clean this up */
1440 &pWF_SingleSpin,
1441 &pPrec_SingleSpin,
1442 &alphaParams,
1443 &betaParams);
1444
1445 return XLAL_SUCCESS;
1446 }
1447
1448 /**
1449 * External wrapper for IMRPhenomX_PNR_GenerateEffectiveRingdownFreq
1450 *
1451 * Computes the effective ringdown frequency for a given (l,m) multipole
1452 * following the prescription given in Thompson et al., arXiv:2312.10025
1453 *
1454 */
1456 REAL8 m1_SI, /**< mass of companion 1 (kg) */
1457 REAL8 m2_SI, /**< mass of companion 2 (kg) */
1458 REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1459 REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1460 REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1461 REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1462 REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1463 REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1464 REAL8 f_min, /**< Starting GW frequency (Hz) */
1465 REAL8 f_max, /**< Ending GW frequency (Hz) */
1466 REAL8 fRef, /**< Reference frequency (Hz) */
1467 UINT4 ell, /**< Orbital index */
1468 UINT4 emmprime, /**< azimuthal index */
1469 LALDict *lalParams /**< LAL Dictionary struct */
1470 )
1471 {
1472 /* Ensure we have a dictionary */
1473 LALDict *lalParams_aux;
1474 if (lalParams == NULL)
1475 {
1476 lalParams_aux = XLALCreateDict();
1477 }
1478 else
1479 {
1480 lalParams_aux = XLALDictDuplicate(lalParams);
1481 }
1482
1483 UINT4 status = 0;
1484 status = XLALIMRPhenomXPCheckMassesAndSpins(&m1_SI, &m2_SI, &chi1x, &chi1y, &chi1z, &chi2x, &chi2y, &chi2z);
1485 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: XLALIMRPhenomXPCheckMassesAndSpins failed.\n");
1486
1488 pWF = XLALMalloc(sizeof(IMRPhenomXWaveformStruct));
1489 status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, chi1z, chi2z, 0.0, fRef, 0.0, f_min, f_max, 1.0, 0.0, lalParams_aux, 0);
1490 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
1491
1492 /* Initialize IMR PhenomX Precession struct and check that it generated successfully */
1494 pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
1496 pWF,
1497 pPrec,
1498 m1_SI,
1499 m2_SI,
1500 chi1x,
1501 chi1y,
1502 chi1z,
1503 chi2x,
1504 chi2y,
1505 chi2z,
1506 lalParams_aux,
1507 DEBUG);
1508 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetPrecessionVariables failed.\n");
1509
1510 /* get effective ringdown frequency */
1511 REAL8 effRD = IMRPhenomX_PNR_GenerateEffectiveRingdownFreq(pWF, ell, emmprime, lalParams_aux);
1512
1513 /* clean up memory allocation */
1514 LALFree(pWF);
1515 LALFree(pPrec);
1516 XLALDestroyDict(lalParams_aux);
1517
1518 return effRD;
1519 }
1520
1521 /**
1522 * External wrapper for IMRPhenomX_PNR_GenerateRingdownPNRBeta.
1523 *
1524 * Generate PNR beta as described in Eq. 60 or arXiv:2107.08876 and
1525 * evaluate at the upper connection frequency f_f described in Sec. 8C.
1526 */
1528 REAL8 m1_SI, /**< mass of companion 1 (kg) */
1529 REAL8 m2_SI, /**< mass of companion 2 (kg) */
1530 REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1531 REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1532 REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1533 REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1534 REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1535 REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1536 REAL8 deltaF, /**< Sampling Frequency (Hz) */
1537 REAL8 f_min, /**< Starting GW frequency (Hz) */
1538 REAL8 f_max, /**< Ending GW frequency (Hz) */
1539 REAL8 fRef_In, /**< Reference frequency (Hz) */
1540 LALDict *lalParams /**< LAL Dictionary struct */
1541 )
1542 {
1543 /* Ensure we have a dictionary */
1544 LALDict *lalParams_aux;
1545 if (lalParams == NULL)
1546 {
1547 lalParams_aux = XLALCreateDict();
1548 }
1549 else
1550 {
1551 lalParams_aux = XLALDictDuplicate(lalParams);
1552 }
1553
1555 if (!UsePNR)
1556 {
1557 UsePNR = 1;
1559 }
1560
1561 REAL8 fRef = (fRef_In == 0.0) ? f_min : fRef_In;
1562
1563 UINT4 status;
1564
1565 REAL8 distance = 1.0;
1566 REAL8 inclination = 0.0;
1567 REAL8 phiRef = 0.0;
1568
1569 /* Initialize IMR PhenomX Waveform struct and check that it initialized correctly */
1571 pWF = XLALMalloc(sizeof(IMRPhenomXWaveformStruct));
1572 status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, chi1z, chi2z, deltaF, fRef, phiRef, f_min, f_max, distance, inclination, lalParams_aux, DEBUG);
1573 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
1574
1575 /* Initialize IMR PhenomX Precession struct and check that it generated successfully */
1577 pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
1579 pWF,
1580 pPrec,
1581 m1_SI,
1582 m2_SI,
1583 chi1x,
1584 chi1y,
1585 chi1z,
1586 chi2x,
1587 chi2y,
1588 chi2z,
1589 lalParams_aux,
1590 DEBUG);
1591 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetPrecessionVariables failed.\n");
1592
1593 /* get the RD beta */
1594 REAL8 finalBeta = IMRPhenomX_PNR_GenerateRingdownPNRBeta(pWF, pPrec);
1595
1596 /* free up memory allocation */
1597 LALFree(pPrec);
1598 LALFree(pWF);
1599 XLALDestroyDict(lalParams_aux);
1600
1601 return finalBeta;
1602 }
1603
1604 /* Function for testing deltaF spacing for interpolation */
1606 INT4 *twospin, /**< [out] integer taking a value of 1 if two-spin effects are present, 0 otherwise */
1607 INT4 *precversion, /**< [out] one of the precession prescription described in IMRPhenomX.c */
1608 REAL8 *deltaF, /**< [out] frequency spacing to be used when generating the angles interpolants */
1609 REAL8 m1_SI, /**< mass of companion 1 (kg) */
1610 REAL8 m2_SI, /**< mass of companion 2 (kg) */
1611 REAL8 chi1x, /**< x-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1612 REAL8 chi1y, /**< y-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1613 REAL8 chi1z, /**< z-component of the dimensionless spin of object 1 w.r.t. Lhat = (0,0,1) */
1614 REAL8 chi2x, /**< x-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1615 REAL8 chi2y, /**< y-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1616 REAL8 chi2z, /**< z-component of the dimensionless spin of object 2 w.r.t. Lhat = (0,0,1) */
1617 REAL8 f_min, /**< Starting GW frequency (Hz) */
1618 REAL8 f_max, /**< Ending GW frequency (Hz); Defaults to Mf = 0.3 if no f_max is specified. */
1619 REAL8 fRef, /**< Reference frequency (Hz) */
1620 LALDict *lalParams /**< LAL Dictionary struct */)
1621 {
1622 /* Ensure we have a dictionary */
1623 LALDict *lalParams_aux;
1624 if (lalParams == NULL)
1625 {
1626 lalParams_aux = XLALCreateDict();
1627 }
1628 else
1629 {
1630 lalParams_aux = XLALDictDuplicate(lalParams);
1631 }
1632
1633 UINT4 status = 0;
1634 status = XLALIMRPhenomXPCheckMassesAndSpins(&m1_SI, &m2_SI, &chi1x, &chi1y, &chi1z, &chi2x, &chi2y, &chi2z);
1635 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: XLALIMRPhenomXPCheckMassesAndSpins failed.\n");
1636
1638 pWF = XLALMalloc(sizeof(IMRPhenomXWaveformStruct));
1639 status = IMRPhenomXSetWaveformVariables(pWF, m1_SI, m2_SI, chi1z, chi2z, 0.0, fRef, 0.0, f_min, f_max, 1.0, 0.0, lalParams_aux, 0);
1640 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetWaveformVariables failed.\n");
1641
1642 /* Initialize IMR PhenomX Precession struct and check that it generated successfully */
1644 pPrec = XLALMalloc(sizeof(IMRPhenomXPrecessionStruct));
1645
1646 status = IMRPhenomXGetAndSetPrecessionVariables( // needed to adjust ringdown frequency in pWF
1647 pWF,
1648 pPrec,
1649 m1_SI,
1650 m2_SI,
1651 chi1x,
1652 chi1y,
1653 chi1z,
1654 chi2x,
1655 chi2y,
1656 chi2z,
1657 lalParams_aux,
1658 DEBUG);
1659 XLAL_CHECK(XLAL_SUCCESS == status, XLAL_EFUNC, "Error: IMRPhenomXSetPrecessionVariables failed.\n");
1660
1661 *deltaF = IMRPhenomX_PNR_HMInterpolationDeltaF(f_min, pWF, pPrec);
1662 *twospin = IMRPhenomX_PNR_CheckTwoSpin(pPrec);
1663 *precversion = pPrec->IMRPhenomXPrecVersion;
1664
1665 LALFree(pWF);
1666 LALFree(pPrec);
1667 XLALDestroyDict(lalParams_aux);
1668
1669 return XLAL_SUCCESS;
1670 }
1671
1672 /** @} */
1673 /** @} */
1674
1675#ifdef __cplusplus
1676}
1677#endif
void XLALDestroyDict(LALDict *dict)
LALDict * XLALDictDuplicate(const LALDict *orig)
LALDict * XLALCreateDict(void)
#define LALFree(p)
IMRPhenomX_UsefulPowers powers_of_lalpi
#define DEBUG
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.
REAL8 IMRPhenomX_PNR_GetPNAlphaAtFreq(REAL8 Mf, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
Here we write a wrapper function to produce either MSA or NNLO alpha for use in IMRPhenomX_PNR_Genera...
REAL8 IMRPhenomX_PNR_GeneratePNRAlphaAtMf(REAL8 Mf, const IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function evaluates Eq.
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.
REAL8 IMRPhenomX_PNR_GenerateMergedPNRAlphaAtMf(REAL8 Mf, const IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function generates the blended PNR and PN expressions for alpha for the transition region of par...
REAL8 IMRPhenomX_PNR_GeneratePNRBetaNoMR(REAL8 Mf, const IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec)
This function evaluates only the rescaled inspiral beta given in Eq.
UINT4 IMRPhenomX_PNR_AttachMRBeta(const IMRPhenomX_PNR_beta_parameters *betaParams)
Determine whether to attach the MR contributions to beta.
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...
REAL8 IMRPhenomX_PNR_GenerateMergedPNRBetaAtMf(REAL8 Mf, const IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, IMRPhenomXWaveformStruct *pWF_SingleSpin, IMRPhenomXPrecessionStruct *pPrec_SingleSpin)
This function generates beta with the tuned angles and PN expressions blended during merger-ringdown.
REAL8 IMRPhenomX_PNR_GeneratePNRBetaAtMf(REAL8 Mf, const IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, IMRPhenomXWaveformStruct *pWF_SingleSpin, IMRPhenomXPrecessionStruct *pPrec_SingleSpin)
This function evaluates Eqs.
void IMRPhenomX_PNR_FreeStructs(IMRPhenomXWaveformStruct **pWF_SingleSpin, IMRPhenomXPrecessionStruct **pPrec_SingleSpin, IMRPhenomX_PNR_alpha_parameters **alphaParams, IMRPhenomX_PNR_beta_parameters **betaParams)
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.
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_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...
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.
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 IMRPhenomX_Initialize_Powers(IMRPhenomX_UsefulPowers *p, REAL8 number)
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,...
int IMRPhenomX_InspiralAngles_SpinTaylor(PhenomXPInspiralArrays *arrays, double *fmin_PN, 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...
int IMRPhenomX_Initialize_Euler_Angles(IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
Wrapper of IMRPhenomX_SpinTaylorAnglesSplinesAll: fmin and fmax are determined by the function based ...
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:
IMRPhenomX_UsefulPowers powers_of_lalpiHM
REAL8 IMRPhenomXHM_GenerateRingdownFrequency(UINT4 ell, UINT4 emm, IMRPhenomXWaveformStruct *wf22)
INT4 XLALSimIMRPhenomXPHMMultibandingGrid(REAL8Sequence **coarseFreqs, UINT4 ell, UINT4 emmprime, IMRPhenomXWaveformStruct *pWF, LALDict *lalParams)
INT4 XLALIMRPhenomXPCheckMassesAndSpins(REAL8 *m1, REAL8 *m2, REAL8 *chi1x, REAL8 *chi1y, REAL8 *chi1z, REAL8 *chi2x, REAL8 *chi2y, REAL8 *chi2z)
Check if m1 > m2.
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.
LALValue * XLALSimInspiralWaveformParamsLookupModeArray(LALDict *params)
int XLALSimInspiralWaveformParamsInsertModeArray(LALDict *params, LALValue *value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPrecVersion(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXPNRUseTunedAngles(LALDict *params, INT4 value)
INT4 XLALSimInspiralWaveformParamsLookupPhenomXPNRUseTunedAngles(LALDict *params)
int XLALSimInspiralWaveformParamsInsertPhenomXPrecVersion(LALDict *params, INT4 value)
void XLALDestroyValue(LALValue *value)
#define fprintf
REAL8 M
Definition: bh_qnmode.c:133
double i
Definition: bh_ringdown.c:118
#define LAL_PI
double REAL8
uint32_t UINT4
int32_t INT4
void * XLALMalloc(size_t n)
void XLALFree(void *p)
int XLALSimIMRPhenomX_PNR_GeneratePNRAnglesHM(REAL8Sequence **alphaPNR, REAL8Sequence **betaPNR, REAL8Sequence **gammaPNR, REAL8Sequence **freqs, REAL8 *alphaPNR_ref, REAL8 *gammaPNR_ref, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 inclination, REAL8 deltaF, REAL8 f_min, REAL8 f_max, REAL8 fRef_In, INT4 ell, INT4 emmprime, LALDict *lalParams)
This is an external wrapper to generate the (l,m) PNR angles, following the prescriptions outlined in...
int XLALSimIMRPhenomX_PNR_GeneratePNRAngles(REAL8Sequence **alphaPNR, REAL8Sequence **betaPNR, REAL8Sequence **gammaPNR, REAL8Sequence **freqs, REAL8 *alphaPNR_ref, REAL8 *gammaPNR_ref, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 inclination, REAL8 deltaF, REAL8 f_min, REAL8 f_max, REAL8 fRef_In, LALDict *lalParams)
This is an external wrapper to generate the (2,2) PNR angles, following the prescription outlined in ...
REAL8 XLALSimIMRPhenomX_PNR_GenerateEffectiveRingdownFreq(REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 f_min, REAL8 f_max, REAL8 fRef, UINT4 ell, UINT4 emmprime, LALDict *lalParams)
External wrapper for IMRPhenomX_PNR_GenerateEffectiveRingdownFreq.
int IMRPhenomX_PNR_GeneratePNRAlphaForAntisymmetry(REAL8Sequence *alphaPNR, const REAL8Sequence *freqs, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
Internal helper function to generate PNR alpha for the antisymmetric waveform.
int XLALSimIMRPhenomXPMSAAngles(REAL8Sequence **alpha_of_f, REAL8Sequence **gamma_of_f, REAL8Sequence **cosbeta_of_f, const REAL8Sequence *freqs, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 inclination, REAL8 fRef_In, INT4 mprime, LALDict *lalParams)
int IMRPhenomX_PNR_GeneratePNRAngles_UniformFrequencies(REAL8Sequence *alphaPNR, REAL8Sequence *betaPNR, REAL8Sequence *gammaPNR, const REAL8Sequence *freqs, IMRPhenomXWaveformStruct *pWF_SingleSpin, IMRPhenomXPrecessionStruct *pPrec_SingleSpin, IMRPhenomX_PNR_alpha_parameters *alphaParams, IMRPhenomX_PNR_beta_parameters *betaParams, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
Generate the tuned precession angles outlined in arXiv:2107.08876 specifically on a uniform frequency...
int XLALSimIMRPhenomX_PNR_InterpolationDeltaF(INT4 *twospin, INT4 *precversion, REAL8 *deltaF, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 f_min, REAL8 f_max, REAL8 fRef, LALDict *lalParams)
int IMRPhenomX_PNR_GeneratePNRAngles(REAL8Sequence *alphaPNR, REAL8Sequence *betaPNR, REAL8Sequence *gammaPNR, const REAL8Sequence *freqs, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalParams)
Generate the tuned precession angles outlined in arXiv:2107.08876.
int XLALSimIMRPhenomXPPNAngles(REAL8Sequence **alpha_of_f, REAL8Sequence **gamma_of_f, REAL8Sequence **cosbeta_of_f, const REAL8Sequence *freqs, REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 inclination, REAL8 fRef_In, INT4 mprime, LALDict *lalParams)
REAL8 XLALSimIMRPhenomX_PNR_GenerateRingdownPNRBeta(REAL8 m1_SI, REAL8 m2_SI, REAL8 chi1x, REAL8 chi1y, REAL8 chi1z, REAL8 chi2x, REAL8 chi2y, REAL8 chi2z, REAL8 deltaF, REAL8 f_min, REAL8 f_max, REAL8 fRef_In, LALDict *lalParams)
External wrapper for IMRPhenomX_PNR_GenerateRingdownPNRBeta.
int IMRPhenomX_PNR_GeneratePNRAngleInterpolants(IMRPhenomX_PNR_angle_spline *angle_spline, IMRPhenomXWaveformStruct *pWF, IMRPhenomXPrecessionStruct *pPrec, LALDict *lalparams)
Generate the tuned precession angles outlined in arXiv:2107.08876 specifically populate the angle int...
int XLALSimInspiralModeArrayIsModeActive(LALValue *modes, unsigned l, int m)
LALValue * XLALSimInspiralModeArrayActivateMode(LALValue *modes, unsigned l, int m)
LALValue * XLALSimInspiralCreateModeArray(void)
Create a LALValue pointer to store the mode array.
static const INT4 q
void XLALDestroyREAL8Sequence(REAL8Sequence *sequence)
REAL8Sequence * XLALCreateREAL8Sequence(size_t length)
#define XLAL_ERROR(...)
#define XLAL_CHECK(assertion,...)
#define XLAL_PRINT_WARNING(...)
XLAL_SUCCESS
XLAL_EFAULT
XLAL_EFUNC
XLAL_EDOM
XLAL_FAILURE
string status
REAL8 Mf_alpha_lower
connection frequency
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_interp_accel * gamma_acc
gamma cubic spline accelerator
gsl_spline * gamma_spline
gamma cubic spline
gsl_spline * alpha_spline
alpha cubic spline
PhenomXPbetaMRD * beta_params
Parameters needed for analytical MRD continuation of cosbeta.
REAL8 Mfmin_integration
Minimum frequency covered by the integration of PN spin-precessing equations for SpinTaylor models
REAL8 PNR_HM_Mflow
Mf_alpha_lower stored from alphaParams struct, 2 A4 / 7 from arXiv:2107.08876.
INT4 MBandPrecVersion
Flag to control multibanding for Euler angles.
REAL8 fmin_integration
Minimum frequency covered by the integration of PN spin-precessing equations for SpinTaylor models
INT4 IMRPhenomXPrecVersion
Flag to set version of Euler angles used.
UINT4 PNRInspiralScaling
Enforce inpsiral scaling for HM angles outside of calibration window.
PhenomXPalphaMRD * alpha_params
Parameters needed for analytical MRD continuation of alpha.
REAL8 chi_singleSpin
Magnitude of effective single spin used for tapering two-spin angles, Eq.
REAL8 PNR_HM_Mfhigh
Mf_beta_lower stored from betaParams struct, Eq.
REAL8 * data
double f_min
Definition: unicorn.c:22
double f_max
Definition: unicorn.c:23