LALSimulation  5.4.0.1-fe68b98
LALSimIMRNRHybSur3dq8.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2018 Vijay Varma
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 /**
21  * \author Vijay Varma
22  *
23  * \file
24  *
25  * \brief C code for NRHybSur3dq8 waveform model, an NR-hybrid surrogate model
26  * for aligned-spin BBH.
27  *
28  * The binary data file is available at https://dcc.ligo.org/LIGO-T1900034.
29  * Get the lalsuite-extra repo or put the data into a location in your
30  * LAL_DATA_PATH.
31  *
32  * **Paper**: https://arxiv.org/abs/1812.07865
33  *
34  * **Parameter ranges**:
35  *
36  * q = [1, 10.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.81, 0.81]
37  * or
38  * q = [1, 9.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.91, 0.91]
39  *
40  * modes: \f$ \ell \leq 4, m \geq 0 \f$, and (5,5), but not (4,1) or (4,0).
41  * m<0 modes are determined from the m \f$\geq0\f$ modes.
42  *
43  * \f$M \geq 2.25 M_{\odot} \f$, for fstart=20Hz, for all modes.
44  *
45  * **Training parameter ranges**:
46  *
47  * q = [1, 8]
48  *
49  * \f$\chi_{1z}, \chi_{2z}\f$ = [-0.8, 0.8]
50  *
51  * But extrapolates reasonably to the above mass ratios and spins.
52  */
53 
54 
55 #ifdef __GNUC__
56 #define UNUSED __attribute__ ((unused))
57 #else
58 #define UNUSED
59 #endif
60 
61 #include "LALSimIMRNRHybSur3dq8.h"
62 
63 
64 #include <libgen.h>
65 
66 #include <lal/FileIO.h>
67 #include <lal/SphericalHarmonics.h>
68 #include <lal/H5FileIO.h>
69 
70 
71 #ifdef LAL_PTHREAD_LOCK
72 #include <pthread.h>
73 #endif
74 
75 #ifdef LAL_PTHREAD_LOCK
76 static pthread_once_t NRHybSur3dq8_is_initialized = PTHREAD_ONCE_INIT;
77 #endif
78 
79 /**
80  * Global surrogate data.
81  * This data will be loaded at most once. Any executable which calls
82  * NRHybSur3dq8_Init_LALDATA directly or by calling any XLAL NRHybSur3dq8
83  * function will have a memory leak according to valgrind, because we never
84  * free this memory.
85  */
87 
88 //*************************************************************************/
89 //************************* function definitions **************************/
90 //*************************************************************************/
91 
92 /**
93  * Helper function to check if the NRHybSur3dq8 model has been initialized.
94  */
95 static bool NRHybSur3dq8_IsSetup(void) {
97  return true;
98  else
99  return false;
100 }
101 
102 
103 /**
104  * Surrogate initialization.
105  *
106  * This needs to be called once, before __lalsim_NRHybSur3dq8_data is used. It
107  * finds the H5 data file with the NRHybSur3dq8 data and loads the surrogate.
108  * Can be called multiple times, will just return true if surrogate is already
109  * loaded.
110  */
111 static void NRHybSur3dq8_Init_LALDATA(void) {
112 
113  if (NRHybSur3dq8_IsSetup()) return;
114 
116  if (path==NULL) {
118  "Unable to find data file %s in $LAL_DATA_PATH\n",
120  }
121 
122 
123  char *dir = dirname(path);
124  const UINT4 size = strlen(dir) + strlen(NRHybSur3dq8_DATAFILE) + 2;
125  char *file_path = XLALMalloc(size);
126  snprintf(file_path, size, "%s/%s", dir, NRHybSur3dq8_DATAFILE);
127 
128  LALH5File *file = XLALH5FileOpen(file_path, "r");
129  if (file==NULL) {
131  "Unable to load data file %s in $LAL_DATA_PATH."
132  " File may be corrupted.\n", NRHybSur3dq8_DATAFILE);
133  }
134 
137  if (ret != XLAL_SUCCESS) {
138  XLAL_ERROR_VOID(XLAL_FAILURE, "Failure loading data from %s\n",
139  file_path);
140  }
141 
142  XLALFree(path);
143  XLALFree(file_path);
144 }
145 
146 
147 /**
148  * Map from mass ratio and spins to surrogate fit parameters.
149  *
150  * The fit parameters are \f$[log_e(q), \hat{\chi}, \chi_a]\f$.
151  * \f$\hat{\chi}\f$ is defined in Eq.(46) of arxiv:1812.07865.
152  * \f$\chi_a = (\chi_{1z} - \chi_{2z})/2 \f$.
153  * These are described in Sec.VI.C.3 of arxiv:1812.07865.
154  */
156  gsl_vector* fit_params, /**< Output: mapped fit parameters. */
157  const REAL8 q, /**< Mass ratio m1 / m2 >= 1. */
158  const REAL8 chi1z, /**< Dimless spin of heavier BH. */
159  const REAL8 chi2z /**< Dimless spin of lighter BH. */
160 ) {
161 
162  const REAL8 eta = q/(1.+q)/(1.+q);
163  const REAL8 chi_wtAvg = (q*chi1z+chi2z)/(1.+q);
164  const REAL8 chiHat
165  = (chi_wtAvg - 38.*eta/113.*(chi1z + chi2z))/(1. - 76.*eta/113.);
166  const REAL8 chi_a = (chi1z - chi2z)/2.;
167 
168  XLAL_CHECK((fit_params != NULL) && (fit_params->size == 3), XLAL_EDIMS,
169  "NRHybSur3dq8_fitParams(): size of fit_params should be 3, not %zu.\n",
170  fit_params->size);
171 
172  gsl_vector_set(fit_params, 0, log(q));
173  gsl_vector_set(fit_params, 1, chiHat);
174  gsl_vector_set(fit_params, 2, chi_a);
175 
176  return XLAL_SUCCESS;
177 }
178 
179 
180 /**
181  * This is the core function of the NRHybSur3dq8 model.
182  *
183  * It evaluates all required waveform modes. For each mode, it evaluates each
184  * waveform data piece. The different data pieces are described in Sec.VI.B of
185  * arxiv:1812.07865.
186  * For the (2,2) mode the data pieces are the amplitude and phase. Note that we
187  * model the phase residual but add back the 0PN term at evaluation time.
188  * For all other modes the data pieces are the real and imaginary parts of the
189  * strain in the coorbital frame.
190  *
191  * The reference point is the time at which the (2,2) mode frequency equals
192  * fRef. If fRef=0, sets fRef=fMin. We set the orbital phase to 0 at the
193  * reference point. The orbital phase is obtained as \f$\phi_{22}/2\f$, so this
194  * leaves a pi ambiguity. But the surrogate data is already aligned such that
195  * the heavier BH is on the +ve x-axis at t=-1000M. See Sec.VI.A.4 of
196  * arxiv:1812.07865. This resolves the pi ambiguity. This means that after the
197  * realignment, the orbital phase at reference frequency fRef is 0, or Bh1 is
198  * on the +ve x-axis. Note that this is alignment is done using only waveform
199  * quantities, so this doesn't necessarily correspond to the (gauge dependent)
200  * NR Bh trajectories. The modes are returned in this frame, which agrees with
201  * the LAL convention. When combining the modes to get the polarizations, the
202  * Ylms should be evaluated at (inclination, pi/2 - phiRef), following the LAL
203  * convention.
204  *
205  * Only uses data at (2,2) mode frequencies >= fMin. This determines the start
206  * time. The start time, along with the step size deltaT, is used to determine
207  * the output_times. Uses cubic spline interpolation to interpolate from the
208  * surrogate's time array to output_times.
209  *
210  * NOTE: If mass ratio q<1, the labels of the two BHs are swapped internally.
211  *
212  * **Returned values**:
213  *
214  * phi_22: contains the phase of the (2,2) mode. This is always evaluated as
215  * this is required for other modes as well to transform from coorbital
216  * frame to inertial frame.
217  *
218  * evaluated_mode_dps: Contains all other data pieces. This is a list of size
219  * num_modes_incl, the number of modes to include. For each mode this
220  * contains the amplitude, and real and imaginary parts of the coorbital
221  * frame strain. For the (2,2) mode only the amplitude is required. For
222  * all other modes only the coorbital frame strain is required. So, we
223  * evaluate only the required data pieces of each mode. The amplitude and
224  * coorbital frame strain is in units of rh/M and needs to be rescaled to
225  * physical units.
226  *
227  * epoch: Initial time value, w.r.t. the peak (t=0 at the peak) of the total
228  * waveform amplitude, as defined in Eq.38 of arxiv:1812.07865.
229  */
231  gsl_vector **phi_22, /**< Output: phase of (2,2) mode. */
232  EvaluatedDataPieces **evaluated_mode_dps, /**< Output: All other data
233  pieces. */
234  LIGOTimeGPS *epoch, /**< Output: Initial time value, where t=0 is at
235  the peak of the total waveform amplitude. */
236  const REAL8 deltaT, /**< Sampling interval (s). */
237  const REAL8 fMin, /**< Start GW frequency (Hz). */
238  const REAL8 fRef, /**< Reference GW frequency (Hz). */
239  REAL8 q, /**< Mass ratio m1/m2. */
240  const REAL8 Mtot_sec, /**< Total mass in geometric units (s). */
241  REAL8 chi1z, /**< Dimensionless spin of Bh1. */
242  REAL8 chi2z, /**< Dimensionless spin of Bh2. */
243  LALValue* ModeArray, /**< Container for (ell, m) modes to generate. */
244  LALDict* LALparams /**< Dict with extra parameters */
245 ) {
246 
247  const NRHybSurData *NR_hybsur_data = &__lalsim_NRHybSur3dq8_data;
248 
249  REAL8 init_orbphase = 0; // In the LAL convention the larger BH should
250  // be on the +ve x-axis at fRef, this is done
251  // by setting orbphase = 0 at fRef.
252  // For the surrogate Bh1 is defined to be the one with the larger mass,
253  // Bh2 with the smaller mass. Therefore, if q<1, we swap the labels of the
254  // two Bhs
255  if (q < 1) {
256  q = 1./q;
257  REAL8 tmp = chi1z;
258  chi1z = chi2z;
259  chi2z = tmp;
260 
261  // The above swap generates the requested waveform but rotated by
262  // pi about the z-axis, we undo this by adding pi to init_orbphase.
263  init_orbphase += LAL_PI;
264  }
265 
266  const char *param_validity = "This model is valid for q <= 9.1 & "
267  "|chi1z,chi2z| <= 0.91, or q <= 10.1 & |chi1z,chi2z| <= 0.81";
268 
269  // By default we do not allow unlimited_extrapolation
270  UINT4 unlim_extrap = 0;
271  if (LALparams != NULL &&
272  XLALDictContains(LALparams, "unlimited_extrapolation")) {
273  // Unless the user asks for it
274  unlim_extrap
275  = XLALDictLookupUINT4Value(LALparams, "unlimited_extrapolation");
276  }
277 
278  // Sanity checks and warnings
279  if ((q > 10.1) && (unlim_extrap == 0)) {
281  "Too much extrapolation in mass ratio; q=%0.4f > 10.1\n%s\n", q,
282  param_validity);
283  }
284  if (q > 8.1) {
286  "Extrapolating outside training range q=%0.4f > 8.1\n", q);
287  }
288  if ((fabs(chi1z) > 0.91) && (unlim_extrap == 0)) {
290  "Too much extrapolation; |chi1z|=%0.4f > 0.91\n%s\n", fabs(chi1z),
291  param_validity);
292  }
293  if ((fabs(chi2z) > 0.91) && (unlim_extrap == 0)) {
295  "Too much extrapolation; |chi2z|=%0.4f > 0.91\n%s\n", fabs(chi2z),
296  param_validity);
297  }
298  if (fabs(chi1z) > 0.81) {
299  if ((q > 9.1) && (unlim_extrap == 0)) {
301  "Too much extrapolation; q=%0.4f > 9.1 & |chi1z|=%.04f"
302  " >0.81\n%s\n", q, fabs(chi1z), param_validity);
303  }
305  "Extrapolating outside training range |chi1z|=%0.4f > 0.81\n",
306  fabs(chi1z));
307  }
308  if (fabs(chi2z) > 0.81) {
309  if ((q > 9.1) && (unlim_extrap == 0)) {
311  "Too much extrapolation; q=%0.4f > 9.1 & |chi2z|=%.04f"
312  " >0.81\n%s\n", q, fabs(chi2z), param_validity);
313  }
315  "Extrapolating outside training range |chi2z|=%0.4f > 0.81\n",
316  fabs(chi2z));
317  }
318 
319  // Get dimensionless start and reference frequencies. Note that cases where
320  // fRef<fMin, deltaT is too small, and fRef or fMin are too
321  // small/large (i.e. outside of the surrogate range) are handled in
322  // NRHybSur_eval_phase_22().
323 
324  // dimensionless start angular frequency of (2,2) mode in rad/M
325  const REAL8 omegaM_22_min = 2*LAL_PI*fMin*Mtot_sec;
326 
327  // dimensionless reference angular frequency of (2,2) mode in rad/M
328  REAL8 omegaM_22_ref;
329  if (fRef == 0) {
330  // If fRef is 0, set it to fMin
331  omegaM_22_ref = omegaM_22_min;
332  }
333  else {
334  omegaM_22_ref = 2*LAL_PI*fRef*Mtot_sec;
335  }
336 
337  // dimensionless time step size
338  const REAL8 deltaTOverM = deltaT/Mtot_sec;
339 
340  // Compute fit_params (initialize with dimension of the surrogate)
341  gsl_vector *fit_params = gsl_vector_alloc(NR_hybsur_data->params_dim);
342  int ret = NRHybSur3dq8_fitParams(fit_params, q, chi1z, chi2z);
343  if(ret != XLAL_SUCCESS) {
344  XLAL_ERROR(XLAL_EFUNC, "Failed to evaluate fit_params.");
345  }
346 
347  // Training data parameters, required for GPR fits
348  const gsl_matrix *x_train = NR_hybsur_data->x_train;
349 
350  // sparse time array of the surrogate
351  const gsl_vector *domain = NR_hybsur_data->domain;
352 
353  // assign size to dummy_worker and dummy_dp
354  gsl_vector *dummy_worker = gsl_vector_alloc(NR_hybsur_data->params_dim);
355  gsl_vector *dummy_dp = gsl_vector_alloc(domain->size);
356 
357  gsl_vector *output_times = NULL;
358 
359  // symmetric mass ratio
360  const REAL8 eta = q/(1.+q)/(1.+q);
361 
362  // Evaluate phase of (2,2) mode
363  // The phase of the (2,2,) mode is always evaluated as this is
364  // needed for the transformation from coorbital to inertial frame for all
365  // modes
366  ret = NRHybSur_eval_phase_22(phi_22, &output_times, eta, fit_params,
367  omegaM_22_min, deltaTOverM, init_orbphase, omegaM_22_ref, dummy_dp,
368  x_train, dummy_worker, NR_hybsur_data);
369  if(ret != XLAL_SUCCESS) {
371  "Failed to evaluate phi_22 data piece");
372  }
373 
374  // set epoch to initial time. Note that t=0 is at the peak of the total
375  // waveform amplitude, as defined in Eq.38 of arxiv:1812.07865.
376  const REAL8 t0 = gsl_vector_get(output_times, 0);
377  XLALGPSAdd(epoch, Mtot_sec * t0);
378 
379  // Evaluate other data pieces for required modes
380  const gsl_matrix_long *mode_list = NR_hybsur_data->mode_list;
381  const UINT4 num_modes_modeled = NR_hybsur_data->num_modes_modeled;
382  UINT4 incl_mode_idx = 0; // This tracks the output modes
383  for (UINT4 mode_idx = 0; mode_idx < num_modes_modeled; mode_idx++){
384 
385  const UINT4 ell = gsl_matrix_long_get(mode_list, mode_idx, 0);
386  const UINT4 m = gsl_matrix_long_get(mode_list, mode_idx, 1);
387 
388  // Evaluate a mode only if it is required
389  if (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, m) == 1) {
390 
391  const ModeDataPieces *data_pieces
392  = NR_hybsur_data->mode_data_pieces[mode_idx];
393 
394  if((ell != data_pieces->ell) || (m != data_pieces->m)){
395  XLAL_ERROR(XLAL_EDATA, "Modes do not agree");
396  }
397 
398  evaluated_mode_dps[incl_mode_idx]
399  = (EvaluatedDataPieces *)
401 
403  &evaluated_mode_dps[incl_mode_idx], ell, m,
404  data_pieces, output_times, fit_params, dummy_dp,
405  x_train, dummy_worker, NR_hybsur_data);
406  if(ret != XLAL_SUCCESS) {
408  "Failed to evaluate (%u, %u) mode", ell, m);
409  }
410 
411  incl_mode_idx += 1;
412  }
413  }
414 
415  gsl_vector_free(fit_params);
416  gsl_vector_free(dummy_dp);
417  gsl_vector_free(dummy_worker);
418  gsl_vector_free(output_times);
419 
420  return XLAL_SUCCESS;
421 }
422 
423 
424 
425 
426 
427 
428 //************************ main functions ***************************/
429 
430 /**
431  * Reference: arxiv:1812.07865.
432  *
433  * Returns the plus and cross polarizations of NRHybSur3dq8 waveform model.
434  *
435  * The reference point is the time at which the (2,2) mode frequency equals
436  * fRef. If fRef=0, sets fRef=fMin. We set the orbital phase to 0 at the
437  * reference point. The orbital phase is obtained as \f$\phi_{22}/2\f$, so this
438  * leaves a pi ambiguity. But the surrogate data is already aligned such that
439  * the heavier BH is on the +ve x-axis at t=-1000M. See Sec.VI.A.4 of
440  * arxiv:1812.07865. This resolves the pi ambiguity. This means that after the
441  * realignment, the orbital phase at reference frequency fRef is 0, or Bh1 is
442  * on the +ve x-axis. Note that this is alignment is done using only waveform
443  * quantities, so this doesn't necessarily correspond to the (gauge dependent)
444  * NR Bh trajectories. The polarizations of the waveform are returned in the sky
445  * of this frame at (inclination, pi/2 - phiRef). This agrees with the LAL
446  * convention.
447  *
448  * Only uses data at (2,2) mode frequencies >= fMin. This determines the start
449  * time. The start time, along with the step size deltaT, is used to determine
450  * the output_times. Uses cubic spline interpolation to interpolate from the
451  * surrogate's time array to output_times.
452  *
453  * By default, uses all available modes of the surrogate, that is \f$ \ell \leq
454  * 4, m \geq 0 \f$, and (5,5), but not (4,1) or (4,0). For m>0 modes, the
455  * contribution from the m<0 counterparts is automatically added.
456  *
457  * If a custom ModeArray is given, only those modes are used. Note that it only
458  * looks for m>=0 modes in ModeArray, and will ignore m<0 modes even if present.
459  * The m<0 modes automatically get added.
460  *
461  * This surrogate model is trained on the following range.
462  * q <= 8, |chi_1|, |chi_2| <= 0.8
463  * If you want a guarantee of accuracy you should stick to the above ranges.
464  *
465  * We allow extrapolation to the following ranges, but with a warning:
466  * q = [1, 10.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.81, 0.81]
467  * or
468  * q = [1, 9.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.91, 0.91]
469  * We expect the model to be reasonable when extrapolated to these ranges.
470  *
471  * Beyond the above ranges, we raise an error. If you want to extrapolate
472  * beyond these limits you can specify unlimited_extrapolation = 1 in your
473  * dictParams as follows:
474  * # USE AT YOUR OWN RISK!!
475  * lal.DictInsertUINT4Value(dictParams, "unlimited_extrapolation", 1)
476  */
478  REAL8TimeSeries **hplus, /**<Output: \f$h_+\f$ polarization. */
479  REAL8TimeSeries **hcross, /**<Output: \f$h_{\times}\f$ polarization.*/
480  REAL8 phiRef, /**< azimuthal angle for Ylms */
481  REAL8 inclination, /**< Inclination angle. */
482  REAL8 deltaT, /**< Sampling interval (s). */
483  REAL8 m1, /**< Mass of Bh1 (kg). */
484  REAL8 m2, /**< Mass of Bh2 (kg). */
485  REAL8 distance, /**< Distance of source (m). */
486  REAL8 fMin, /**< Start GW frequency (Hz). */
487  REAL8 fRef, /**< Reference GW frequency (Hz). */
488  REAL8 chi1z, /**< Dimensionless spin of Bh1. */
489  REAL8 chi2z, /**< Dimensionless spin of Bh2. */
490  LALDict* LALparams /**< Dict with extra parameters */
491 )
492 {
493 #ifdef LAL_PTHREAD_LOCK
494  (void) pthread_once(&NRHybSur3dq8_is_initialized, NRHybSur3dq8_Init_LALDATA);
495 #else
497 #endif
498 
499  // Loaded surrogate data
500  const NRHybSurData *NR_hybsur_data = &__lalsim_NRHybSur3dq8_data;
501 
502  if (NR_hybsur_data->setup != 1){
503  XLAL_ERROR(XLAL_FAILURE, "Surrogate data is not loaded.");
504  }
505 
506  // If ModeArray is not specified, use all available modes
507  LALValue* ModeArray
509  if (ModeArray == NULL) {
510  ModeArray = XLALSimInspiralCreateModeArray();
511  NRHybSur_set_default_modes(ModeArray, NR_hybsur_data);
512  }
513 
514  // Make sure we didn't request any unavailable modes, and get number of
515  // modes to include
516  UINT4 num_modes_incl, max_ell;
517  int ret = NRHybSur_check_mode_array(&num_modes_incl, &max_ell, ModeArray,
518  NR_hybsur_data);
519  if (ret != XLAL_SUCCESS) {
520  XLAL_ERROR(XLAL_EFUNC, "Inappropriate ModeArray.");
521  }
522 
523  // Check Nyquist frequency is larger than the ringdow frequency
524  // of the (max_ell,max_ell,0) QNM overtone, where max_ell is the
525  // highest ell index included. Raises warning only, not error.
526  ret = NRHybSur_sanity_check_sample_rate(deltaT, m1, m2, chi1z, chi2z,
527  max_ell);
528  if (ret != XLAL_SUCCESS) {
529  XLAL_ERROR(XLAL_EFUNC, "check_sample_rate failed.");
530  }
531 
532  // Total mass in seconds
533  const REAL8 Mtot_sec = (m1 + m2)/LAL_MSUN_SI * LAL_MTSUN_SI;
534 
535  // mass ratio
536  const REAL8 q = m1/m2;
537 
538  // rescale factor for the amplitude based on the distance and total mass
539  const REAL8 a0 = Mtot_sec * LAL_C_SI / distance;
540 
541  // Initialize vector to hold phase of the (2,2,) mode
542  gsl_vector *phi_22 = NULL;
543 
544  // Initialize data pieces for amplitude of (2,2) mode, and coorbital
545  // frame strain for other modes.
546  EvaluatedDataPieces **evaluated_mode_dps
547  = XLALMalloc(num_modes_incl * sizeof(*evaluated_mode_dps));
548 
550 
551  // Evaluate the surrogate
552  ret = NRHybSur3dq8_core(&phi_22, evaluated_mode_dps,
553  &epoch, deltaT, fMin, fRef, q, Mtot_sec, chi1z, chi2z,
554  ModeArray, LALparams);
555  if(ret != XLAL_SUCCESS) {
556  XLAL_ERROR(XLAL_EFUNC, "Surrogate evaluation failed.");
557  }
558 
559  const UINT4 num_times = phi_22->size;
560 
561  // initialize the hp and hc vectors to 0
562  REAL8TimeSeries *hPlusTS =
564  num_times);
565  REAL8TimeSeries *hCrossTS =
567  num_times);
568  memset(hPlusTS->data->data, 0, hPlusTS->data->length*sizeof(REAL8));
569  memset(hCrossTS->data->data, 0, hCrossTS->data->length*sizeof(REAL8));
570 
571  // Sum up all required modes
572  COMPLEX16 swsh_coef;
573  COMPLEX16 swsh_coef_negM = 0; // some compilers complain if this is
574  // not initialized here
575  COMPLEX16 hlm;
576  COMPLEX16 hcomplex;
577  int pre_factor;
578  UINT4 ell, m;
579  for (UINT4 mode_idx = 0; mode_idx < num_modes_incl; mode_idx++){
580 
581  EvaluatedDataPieces *this_mode_eval_dp = evaluated_mode_dps[mode_idx];
582  ell = this_mode_eval_dp->ell;
583  m = this_mode_eval_dp->m;
584 
585  /* See Harald Pfeiffer, T18002260-v1 for frame diagram. The surrogate
586  * frame has its z direction aligned with the orbital angular momentum,
587  * which agrees with the lowercase source frame z in the diagram. The
588  * surrogate x direction, however, points along the line of ascending
589  * nodes (the funny omega with circles on the ends). The uppercase Z,
590  * which is the direction along which we want to evaluate the waveform,
591  * is always in the surrogate frame (y, z) plane. Z is rotated from z
592  * towards the +ive y surrogate axis, so we should always evaluate at
593  * (inclination, pi/2-phiRef). */
594  swsh_coef = XLALSpinWeightedSphericalHarmonic(inclination,
595  0.5 * LAL_PI - phiRef, -2, ell, m);
596 
597  if (m > 0) {
598  // Note: Each time a new mode is added to the strain, the value of
599  // swsh_coef_negM persists. When m>0 the old value is overwritten,
600  // while if m=0 it has a meaningless value from the previous
601  // iteration. This is not a bug, but can be confusing, so just
602  // adding this note for anyone that is debugging this code.
603  swsh_coef_negM = XLALSpinWeightedSphericalHarmonic(inclination,
604  0.5 * LAL_PI - phiRef, -2, ell, -m);
605  }
606 
607  for (UINT4 j=0; j < num_times; j++) {
608 
609  // The (2,2) phase is required for all modes; for the other modes,
610  // we use it to transform from the coorbital frame to the inertial
611  // frame
612  const REAL8 tmp_phi_22 = gsl_vector_get(phi_22, j);
613 
614  if (ell == 2 && m ==2){
615  const REAL8 tmp_Amp_22 = gsl_vector_get(
616  this_mode_eval_dp->ampl_eval, j);
617  // (2,2) mode strain in the inertial frame
618  hlm = tmp_Amp_22 * (cos(tmp_phi_22) - I*sin(tmp_phi_22));
619 
620  } else {
621 
622  // set the real and imaginary parts (in coorbital frame) to
623  // zero by default
624  REAL8 coorb_hlm_re = 0.0;
625  REAL8 coorb_hlm_im = 0.0;
626 
627  // for m=0, l=odd, the real part is zero. For all other
628  // cases get the real part
629  if (m != 0 || ell % 2 == 0) {
630  coorb_hlm_re = gsl_vector_get(
631  this_mode_eval_dp->coorb_re_eval, j);
632  }
633 
634  // for m=0, l=even, the imaginary part is zero. For all other
635  // cases get the imaginary part
636  if (m != 0 || ell % 2 == 1) {
637  coorb_hlm_im = gsl_vector_get(
638  this_mode_eval_dp->coorb_im_eval, j);
639  }
640 
641  // strain in the inertial frame for all non (2,2) modes.
642  // See Eq.(39) of arxiv:1812.07865.for definition of coorbital
643  // frame strain
644  hlm = (coorb_hlm_re + I * coorb_hlm_im)
645  * (cos(m*tmp_phi_22/2.) - I*sin(m*tmp_phi_22/2.));
646  }
647 
648  hcomplex = hlm * swsh_coef;
649  if (m > 0) {
650  // For m>0 modes, also add contribution from m<0 modes as
651  // (-1)**l * conj(hlm) * Y_{ell, -m}
652  if (ell % 2 == 0){
653  pre_factor = 1;
654  }
655  else {
656  pre_factor = -1;
657  }
658  hcomplex += pre_factor * conj(hlm) * swsh_coef_negM;
659  }
660 
661  // hcomplex = h_+ - i*h_x
662  hPlusTS->data->data[j] += creal(hcomplex) * a0;
663  hCrossTS->data->data[j] -= cimag(hcomplex) * a0;
664  }
665  }
666 
667  // Point the output pointers to the relevant time series and return
668  (*hplus) = hPlusTS;
669  (*hcross) = hCrossTS;
670 
671  // Cleanup
672  NRHybSur_DestroyEvaluatedDataPieces(phi_22, evaluated_mode_dps,
673  num_modes_incl);
674  if(ModeArray) {
675  XLALDestroyValue(ModeArray);
676  }
677 
678 
679  return XLAL_SUCCESS;
680 }
681 
682 
683 
684 /**
685  * Reference: arxiv:1812.07865.
686  *
687  * Returns the spin-weighted spherical harmonic modes of NRHybSur3dq8 waveform
688  * model.
689  *
690  * The reference point is the time at which the (2,2) mode frequency equals
691  * fRef. If fRef=0, sets fRef=fMin. We set the orbital phase to 0 at the
692  * reference point. The orbital phase is obtained as \f$\phi_{22}/2\f$, so this
693  * leaves a pi ambiguity. But the surrogate data is already aligned such that
694  * the heavier BH is on the +ve x-axis at t=-1000M. See Sec.VI.A.4 of
695  * arxiv:1812.07865. This resolves the pi ambiguity. This means that after the
696  * realignment, the orbital phase at reference frequency fRef is 0, or Bh1 is
697  * on the +ve x-axis. Note that this is alignment is done using only waveform
698  * quantities, so this doesn't necessarily correspond to the (gauge dependent)
699  * NR Bh trajectories. The modes are returned in this frame, which agrees with
700  * the LAL convention. When combining the modes to get the polarizations, the
701  * Ylms should be evaluated at (inclination, pi/2 - phiRef), following the LAL
702  * convention.
703  *
704  * Only uses data at (2,2) mode frequencies >= fMin. This determines the start
705  * time. The start time, along with the step size deltaT, is used to determine
706  * the output_times. Uses cubic spline interpolation to interpolate from the
707  * surrogate's time array to output_times.
708  *
709  * By default, returns all available modes of the surrogate, that is \f$ \ell
710  * \leq 4, m \geq 0 \f$, and (5,5), but not (4,1) or (4,0).
711  *
712  * If a custom ModeArray is given, only those modes are used. Note that it only
713  * looks for m>=0 modes in ModeArray, and will ignore m<0 modes even if present.
714  *
715  * This surrogate model is trained on the following range.
716  * q <= 8, |chi_1|, |chi_2| <= 0.8
717  * If you want a guarantee of accuracy you should stick to the above ranges.
718  *
719  * We allow extrapolation to the following ranges, but with a warning:
720  * q = [1, 10.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.81, 0.81]
721  * or
722  * q = [1, 9.1] and \f$\chi_{1z}, \chi_{2z}\f$ = [-0.91, 0.91]
723  * We expect the model to be reasonable when extrapolated to these ranges.
724  *
725  * Beyond the above ranges, we raise an error. If you want to extrapolate
726  * beyond these limits you can specify unlimited_extrapolation = 1 in your
727  * dictParams as follows:
728  * # USE AT YOUR OWN RISK!!
729  * lal.DictInsertUINT4Value(dictParams, "unlimited_extrapolation", 1)
730  */
732  REAL8 deltaT, /**< Sampling interval (s). */
733  REAL8 m1, /**< Mass of Bh1 (kg). */
734  REAL8 m2, /**< Mass of Bh2 (kg). */
735  REAL8 chi1z, /**< Dimensionless spin of Bh1. */
736  REAL8 chi2z, /**< Dimensionless spin of Bh2. */
737  REAL8 fMin, /**< Start GW frequency (Hz). */
738  REAL8 fRef, /**< Reference GW frequency (Hz). */
739  REAL8 distance, /**< Distance of source (m). */
740  LALDict* LALparams /**< Dict with extra parameters */
741 ) {
742 #ifdef LAL_PTHREAD_LOCK
743  (void) pthread_once(&NRHybSur3dq8_is_initialized, NRHybSur3dq8_Init_LALDATA);
744 #else
746 #endif
747 
748  // Loaded surrogate data
749  const NRHybSurData *NR_hybsur_data = &__lalsim_NRHybSur3dq8_data;
750 
751  if (NR_hybsur_data->setup != 1){
752  XLAL_ERROR_NULL(XLAL_FAILURE, "Surrogate data is not loaded.");
753  }
754 
755  // If ModeArray is not specified, use all available modes
756  LALValue* ModeArray
758  if (ModeArray == NULL) {
759  ModeArray = XLALSimInspiralCreateModeArray();
760  NRHybSur_set_default_modes(ModeArray, NR_hybsur_data);
761  }
762 
763  // Make sure we didn't request any unavailable modes, and get number of
764  // modes to include
765  UINT4 num_modes_incl, max_ell;
766  int ret = NRHybSur_check_mode_array(&num_modes_incl, &max_ell, ModeArray,
767  NR_hybsur_data);
768  if (ret != XLAL_SUCCESS) {
769  XLAL_ERROR_NULL(XLAL_EFUNC, "Inappropriate ModeArray.");
770  }
771 
772  // Check Nyquist frequency is larger than the ringdow frequency
773  // of the (max_ell,max_ell,0) QNM overtone, where max_ell is the
774  // highest ell index included. Raises warning only, not error.
775  ret = NRHybSur_sanity_check_sample_rate(deltaT, m1, m2, chi1z, chi2z,
776  max_ell);
777  if (ret != XLAL_SUCCESS) {
778  XLAL_ERROR_NULL(XLAL_EFUNC, "check_sample_rate failed.");
779  }
780 
781  // Total mass in seconds
782  const REAL8 Mtot_sec = (m1 + m2)/LAL_MSUN_SI * LAL_MTSUN_SI;
783 
784  // mass ratio
785  const REAL8 q = m1/m2;
786 
787  // rescale factor for the amplitude based on the distance and total mass
788  const REAL8 a0 = Mtot_sec * LAL_C_SI / distance;
789 
790  // Initialize vector to hold phase of the (2,2,) mode
791  gsl_vector *phi_22 = NULL;
792 
793  // Initialize data pieces for amplitude of (2,2) mode, and coorbital
794  // frame strain for other modes.
795  EvaluatedDataPieces **evaluated_mode_dps
796  = XLALMalloc(num_modes_incl * sizeof(*evaluated_mode_dps));
797 
799 
800  // Evaluate the surrogate
801  ret = NRHybSur3dq8_core(&phi_22, evaluated_mode_dps,
802  &epoch, deltaT, fMin, fRef, q, Mtot_sec, chi1z, chi2z,
803  ModeArray, LALparams);
804  if(ret != XLAL_SUCCESS) {
805  XLAL_ERROR_NULL(XLAL_EFUNC, "Surrogate evaluation failed.");
806  }
807 
808  const UINT4 num_times = phi_22->size;
809 
810  // Evaluate all required modes
811  SphHarmTimeSeries *hlms = NULL;
812  COMPLEX16TimeSeries *tmp_mode;
813  UINT4 ell, m;
814  char mode_name[32];
815  for (UINT4 mode_idx = 0; mode_idx < num_modes_incl; mode_idx++){
816 
817  EvaluatedDataPieces *this_mode_eval_dp = evaluated_mode_dps[mode_idx];
818  ell = this_mode_eval_dp->ell;
819  m = this_mode_eval_dp->m;
820 
821  snprintf(mode_name, sizeof(mode_name), "(%u, %u) mode", ell, m);
822  tmp_mode = XLALCreateCOMPLEX16TimeSeries(mode_name, &epoch, 0.0,
823  deltaT, &lalStrainUnit, num_times);
824 
825  for (UINT4 j=0; j < num_times; j++) {
826 
827  const REAL8 tmp_phi_22 = gsl_vector_get(phi_22, j);
828 
829  if (ell == 2 && m ==2){
830  const REAL8 tmp_Amp_22 = gsl_vector_get(
831  this_mode_eval_dp->ampl_eval, j);
832 
833  // (2,2) mode strain in the inertial frame
834  tmp_mode->data->data[j]
835  = a0* tmp_Amp_22 * (cos(tmp_phi_22) - I*sin(tmp_phi_22));
836 
837  } else {
838 
839  // set the real and imaginary parts (in coorbital frame) to
840  // zero by default
841  REAL8 coorb_hlm_re = 0.0;
842  REAL8 coorb_hlm_im = 0.0;
843 
844  // for m=0, l=odd, the real part is zero. For all other
845  // cases get the real part
846  if (m != 0 || ell % 2 == 0) {
847  coorb_hlm_re = gsl_vector_get(
848  this_mode_eval_dp->coorb_re_eval, j);
849  }
850 
851  // for m=0, l=even, the imaginary part is zero. For all other
852  // cases get the imaginary part
853  if (m != 0 || ell % 2 == 1) {
854  coorb_hlm_im = gsl_vector_get(
855  this_mode_eval_dp->coorb_im_eval, j);
856  }
857 
858  // strain in the inertial frame for all non (2,2) modes.
859  // See Eq.(39) of arxiv:1812.07865.for definition of coorbital
860  // frame strain
861  tmp_mode->data->data[j]
862  = a0 * (coorb_hlm_re + I * coorb_hlm_im)
863  * (cos(m*tmp_phi_22/2.) - I*sin(m*tmp_phi_22/2.));
864  }
865  }
866 
867  hlms = XLALSphHarmTimeSeriesAddMode(hlms, tmp_mode, ell, m);
869  }
870 
871  // Cleanup
872  NRHybSur_DestroyEvaluatedDataPieces(phi_22, evaluated_mode_dps,
873  num_modes_incl);
874  if(ModeArray) {
875  XLALDestroyValue(ModeArray);
876  }
877 
878  return hlms;
879 }
struct tagLALH5File LALH5File
int XLALDictContains(const LALDict *dict, const char *key)
UINT4 XLALDictLookupUINT4Value(LALDict *dict, const char *key)
int NRHybSur3dq8_fitParams(gsl_vector *fit_params, const REAL8 q, const REAL8 chi1z, const REAL8 chi2z)
Map from mass ratio and spins to surrogate fit parameters.
static bool NRHybSur3dq8_IsSetup(void)
Helper function to check if the NRHybSur3dq8 model has been initialized.
int NRHybSur3dq8_core(gsl_vector **phi_22, EvaluatedDataPieces **evaluated_mode_dps, LIGOTimeGPS *epoch, const REAL8 deltaT, const REAL8 fMin, const REAL8 fRef, REAL8 q, const REAL8 Mtot_sec, REAL8 chi1z, REAL8 chi2z, LALValue *ModeArray, LALDict *LALparams)
This is the core function of the NRHybSur3dq8 model.
static void NRHybSur3dq8_Init_LALDATA(void)
Surrogate initialization.
INT4 XLALSimIMRNRHybSur3dq8Polarizations(REAL8TimeSeries **hplus, REAL8TimeSeries **hcross, REAL8 phiRef, REAL8 inclination, REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 distance, REAL8 fMin, REAL8 fRef, REAL8 chi1z, REAL8 chi2z, LALDict *LALparams)
Reference: arxiv:1812.07865.
SphHarmTimeSeries * XLALSimIMRNRHybSur3dq8Modes(REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 chi1z, REAL8 chi2z, REAL8 fMin, REAL8 fRef, REAL8 distance, LALDict *LALparams)
Reference: arxiv:1812.07865.
static NRHybSurData __lalsim_NRHybSur3dq8_data
Global surrogate data.
C code for NRHybSur3dq8 waveform model, an NR-hybrid surrogate model for aligned-spin BBH.
static const char NRHybSur3dq8_DATAFILE[]
LALValue * XLALSimInspiralWaveformParamsLookupModeArray(LALDict *params)
int NRHybSur_check_mode_array(UINT4 *num_modes_incl, UINT4 *max_ell, LALValue *ModeArray, const NRHybSurData *NR_hybsur_data)
Sanity checks on ModeArray.
void NRHybSur_set_default_modes(LALValue *ModeArray, const NRHybSurData *NR_hybsur_data)
Activates all modes of an NRHybSur model.
int NRHybSur_eval_phase_22(gsl_vector **phi_22, gsl_vector **output_times, const REAL8 eta, const gsl_vector *fit_params, const REAL8 omegaM_22_min, const REAL8 deltaTOverM, const REAL8 phiRef, const REAL8 omegaM_22_ref, gsl_vector *dummy_dp, const gsl_matrix *x_train, gsl_vector *dummy_worker, const NRHybSurData *NR_hybsur_data)
Evaluate the phase of the (2,2) mode.
int NRHybSur_eval_mode_data_pieces(EvaluatedDataPieces **this_mode_eval_dp, const UINT4 ell, const UINT4 m, const ModeDataPieces *data_pieces, const gsl_vector *output_times, const gsl_vector *fit_params, gsl_vector *dummy_dp, const gsl_matrix *x_train, gsl_vector *dummy_worker, const NRHybSurData *NR_hybsur_data)
Evaluate waveform data pieces of a single mode.
int NRHybSur_sanity_check_sample_rate(REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 chi1z, REAL8 chi2z, UINT4 max_ell)
Sanity check (warning only, not error) that the sample rate is high enough to capture the ringdown fr...
void NRHybSur_DestroyEvaluatedDataPieces(gsl_vector *phi_22, EvaluatedDataPieces **evaluated_mode_dps, const UINT4 num_modes_incl)
Destroy phi_22 and an EvaluatedDataPieces structure.
int NRHybSur_Init(NRHybSurData *NR_hybsur_data, LALH5File *file)
Initialize a NRHybSurData structure from an open H5 file.
void XLALDestroyValue(LALValue *value)
#define XLAL_FILE_RESOLVE_PATH(fname)
void XLALH5FileClose(LALH5File UNUSED *file)
LALH5File * XLALH5FileOpen(const char UNUSED *path, const char UNUSED *mode)
#define LAL_C_SI
#define LAL_MSUN_SI
#define LAL_PI
#define LAL_MTSUN_SI
#define LIGOTIMEGPSZERO
double complex COMPLEX16
double REAL8
uint32_t UINT4
int32_t INT4
void * XLALMalloc(size_t n)
void XLALFree(void *p)
LALValue * XLALSimInspiralCreateModeArray(void)
Create a LALValue pointer to store the mode array.
int XLALSimInspiralModeArrayIsModeActive(LALValue *modes, unsigned l, int m)
SphHarmTimeSeries * XLALSphHarmTimeSeriesAddMode(SphHarmTimeSeries *appended, const COMPLEX16TimeSeries *inmode, UINT4 l, INT4 m)
Prepend a node to a linked list of SphHarmTimeSeries, or create a new head.
static const INT4 m
static const INT4 q
COMPLEX16 XLALSpinWeightedSphericalHarmonic(REAL8 theta, REAL8 phi, int s, int l, int m)
REAL8TimeSeries * XLALCreateREAL8TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
void XLALDestroyCOMPLEX16TimeSeries(COMPLEX16TimeSeries *series)
COMPLEX16TimeSeries * XLALCreateCOMPLEX16TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
const LALUnit lalStrainUnit
#define XLAL_ERROR_VOID(...)
#define XLAL_ERROR_NULL(...)
#define XLAL_ERROR(...)
#define XLAL_CHECK(assertion,...)
#define XLAL_PRINT_WARNING(...)
XLAL_EDATA
XLAL_SUCCESS
XLAL_ENOENT
XLAL_EFUNC
XLAL_EDOM
XLAL_EIO
XLAL_EDIMS
XLAL_FAILURE
LIGOTimeGPS * XLALGPSAdd(LIGOTimeGPS *epoch, REAL8 dt)
path
int pthread_once(pthread_once_t *once_control, void(*init_routine)(void))
COMPLEX16Sequence * data
COMPLEX16 * data
NRHybSur evaluated data for a single mode.
gsl_vector * coorb_re_eval
Coorbital frame real part evaluation.
gsl_vector * ampl_eval
Amplitude data piece evaluation.
gsl_vector * coorb_im_eval
Coorbital frame imag part evaluation.
NRHybSur data pieces of a single mode.
UINT4 m
Mode m value.
UINT4 ell
Mode value.
NRHybSur surrogate data for all modes, to be loaded from a h5 file.
ModeDataPieces ** mode_data_pieces
Data pieces of all modes, same order as mode_list.
gsl_vector * domain
Common time samples for the surrogate.
REAL8 params_dim
Dimensions of the model.
UINT4 setup
Indicates if NRHybSur has been initialized.
gsl_matrix * x_train
Training set parameters, needed for GPR fits.
UINT4 num_modes_modeled
Number of modeled modes.
gsl_matrix_long * mode_list
List of modeled modes, first element is always (2,2).
REAL8Sequence * data
REAL8 * data
Structure to carry a collection of spherical harmonic modes in COMPLEX16 time series.
LIGOTimeGPS epoch
Definition: unicorn.c:20
double deltaT
Definition: unicorn.c:24