Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALSimulation 6.2.0.1-00ddc7f
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
LALSimIMRPrecessingNRSur.c
Go to the documentation of this file.
1/* * Copyright (C) 2017 Jonathan Blackman, Vijay Varma.
2 * NRSur7dq2 and NRSur7dq4 NR surrogate models.
3 * Papers: https://arxiv.org/abs/1705.07089, https://arxiv.org/abs/1905.09300.
4 * Based on the python implementation found at:
5 * https://www.black-holes.org/data/surrogates/index.html
6 * which uses the same hdf5 data files.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with with program; see the file COPYING. If not, write to the
20 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301 USA
22 */
23
24#ifdef __GNUC__
25#define UNUSED __attribute__ ((unused))
26#else
27#define UNUSED
28#endif
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <math.h>
33#include <complex.h>
34#include <stdbool.h>
35#include <alloca.h>
36#include <string.h>
37#include <libgen.h>
38
39#include <gsl/gsl_errno.h>
40#include <gsl/gsl_bspline.h>
41#include <gsl/gsl_blas.h>
42#include <gsl/gsl_min.h>
43#include <gsl/gsl_spline.h>
44#include <gsl/gsl_complex_math.h>
45#include <lal/Units.h>
46#include <lal/SeqFactories.h>
47#include <lal/LALConstants.h>
48#include <lal/XLALError.h>
49#include <lal/FrequencySeries.h>
50#include <lal/Date.h>
51#include <lal/StringInput.h>
52#include <lal/Sequence.h>
53#include <lal/LALStdio.h>
54#include <lal/FileIO.h>
55#include <lal/SphericalHarmonics.h>
56#include <lal/LALSimInspiral.h>
57#include <lal/LALSimSphHarmMode.h>
58#include <lal/LALSimIMR.h>
59
60#ifdef LAL_HDF5_ENABLED
61#include <lal/H5FileIO.h>
62#endif
63
67
68#include <lal/LALConfig.h>
69#ifdef LAL_PTHREAD_LOCK
70#include <pthread.h>
71#endif
72
73
74#ifdef LAL_PTHREAD_LOCK
75UNUSED static pthread_once_t NRSur7dq2_is_initialized = PTHREAD_ONCE_INIT;
76#endif
77
78#ifdef LAL_PTHREAD_LOCK
79UNUSED static pthread_once_t NRSur7dq4_is_initialized = PTHREAD_ONCE_INIT;
80#endif
81
82
83/**
84 * Global surrogate data.
85 * This data will be loaded at most once. Any executable which calls
86 * NRSur7dq2_Init_LALDATA or NRSur7dq4_Init_LALDATA directly or by calling any
87 * XLAL function will have a memory leak according to valgrind, because we
88 * never free this memory.
89 */
92
93/**
94 * @addtogroup LALSimNRSur7dq2_c
95 * @{
96 *
97 * @name Routines for NR surrogate models "NRSur7dq2" and "NRSur7dq4"
98 * @{
99 *
100 * @author Jonathan Blackman, Vijay Varma
101 *
102 * @brief C code for NRSur7dq2 and NRSur7dq4 NR surrogate waveform models.
103 *
104 *
105 *
106 * NRSur7dq2:
107 * This is a fully precessing time domain model including all subdominant modes up to ell=4.
108 * See Blackman et al \cite Blackman:q27d for details.
109 * Any studies that use this waveform model should include a reference to that paper.
110 * Using this model requires the file lalsuite-extra/data/lalsimulation/NRSur7dq2.h5
111 * Make sure your $LAL_DATA_PATH points to lalsuite-extra/data/lalsimulation/.
112 * The lalsuite-extra commit hash at the time of review was 77613e7f5f01d5ea11829ded5677783cafc0d298
113 *
114 * @note The range of validity of the model is:
115 * * Mass ratios 1 <= q <= 2
116 * * Spin magnitudes |chi_i| <= 0.8
117 * * Total time before merger <= 4500M, which in practice leads to a parameter-dependent lower bound for fmin.
118 *
119 * @note Additional notes:
120 * * Passing in a non-trivial ModeArray controls which co-orbital frame modes are evaluated.
121 * * A python version of this model can be installed with "pip install NRSur7dq2".
122 * * This lalsimulation implementation has been verified to agree with version 1.0.3 up to
123 * very small differences due to slightly differing time interpolation methods.
124 * * Note that for conventions to agree with ChooseTDWaveform (and XLALSimInspiralNRSur7dq2Polarizations),
125 * you must pass use_lalsimulation_conventions=True when calling the pip version of NRSur7dq2.
126 *
127 * @review NRSur7dq2 model and routines reviewed by Sebastian Khan, Harald Pfeiffer, Geraint Pratten, and Michael Pürrer.
128 * Reviewees were Jonathan Blackman, Scott Field, and Vijay Varma.
129 * The review page can be found at https://git.ligo.org/waveforms/reviews/nrsur/wikis/home
130 *
131 *
132 *
133 * NRSur7dq4:
134 * This is a q=4 extension of NRSur7dq2.
135 * See Varma et al. (arxiv:1905.09300) for details.
136 * Any studies that use this waveform model should include a reference to that
137 * paper.
138 * The binary data file (NRSur7dq4_v1.0.h5) is available at:
139 * https://git.ligo.org/waveforms/software/lalsuite-waveform-data
140 * and on Zenodo at: https://zenodo.org/records/14999310.
141 * Get the lalsuite-waveform-data repo or put the data into a location in your
142 * LAL_DATA_PATH.
143 * The data is also available on CIT at /home/lalsimulation_data and via CVMFS
144 * at /cvmfs/shared.storage.igwn.org/igwn/shared/auxiliary/obs_sci/cbc/waveform/lalsimulation_data
145 * Make sure the files are in your LAL_DATA_PATH.
146 */
147
148
149/***********************************************************************************/
150/****************************** Function Definitions *******************************/
151/***********************************************************************************/
152
153
154
155#ifdef LAL_HDF5_ENABLED
156/**
157 * This needs to be called once, before __lalsim_NRSur7dq2_data is used.
158 * It finds the hdf5 data file with the NRSur7dq2 data and calls PrecessingNRSur_Init.
159 */
160static void NRSur7dq2_Init_LALDATA(void) {
161 if (NRSur7dq2_IsSetup()) return;
162
164 if (path==NULL)
165 XLAL_ERROR_VOID(XLAL_EIO, "Unable to resolve data file %s in $LAL_DATA_PATH\n", NRSUR7DQ2_DATAFILE);
166 char *dir = dirname(path);
167 size_t size = strlen(dir) + strlen(NRSUR7DQ2_DATAFILE) + 2;
168 char *file_path = XLALMalloc(size);
169 snprintf(file_path, size, "%s/%s", dir, NRSUR7DQ2_DATAFILE);
170
171 LALH5File *file = XLALH5FileOpen(file_path, "r");
172
173 // 0 is for NRSur7dq2
174 int ret = PrecessingNRSur_Init(&__lalsim_NRSur7dq2_data, file, 0);
175
176 if (ret != XLAL_SUCCESS)
177 XLAL_ERROR_VOID(XLAL_FAILURE, "Failure loading data from %s\n", file_path);
178
179 XLALFree(path);
180 XLALFree(file_path);
181}
182#endif
183
184#ifdef LAL_HDF5_ENABLED
185/**
186 * This needs to be called once, before __lalsim_NRSur7dq4_data is used.
187 * It finds the hdf5 data file with the NRSur7dq4 data and calls PrecessingNRSur_Init.
188 */
189static void NRSur7dq4_Init_LALDATA(void) {
190
191 if (NRSur7dq4_IsSetup()) return;
192
194 if (path==NULL){
196 "Unable to resolve data file '%s' in $LAL_DATA_PATH.\n"
197 "Note: LALSuite versions >= 7.25 require data files that are publicly available at:\n"
198 "https://git.ligo.org/waveforms/software/lalsuite-waveform-data\n"
199 "and on Zenodo at: https://zenodo.org/records/14999310.\n"
200 "For earlier LALSuite versions, use the files in lalsuite-extra, available at:\n"
201 "https://git.ligo.org/lscsoft/lalsuite-extra\n",
203 }
204 char *dir = dirname(path);
205 size_t size = strlen(dir) + strlen(NRSUR7DQ4_DATAFILE) + 2;
206 char *file_path = XLALMalloc(size);
207 snprintf(file_path, size, "%s/%s", dir, NRSUR7DQ4_DATAFILE);
208
209 LALH5File *file = XLALH5FileOpen(file_path, "r");
210
211 // 1 is for NRSur7dq4
212 int ret = PrecessingNRSur_Init(&__lalsim_NRSur7dq4_data, file, 1);
213
214 ret |= ROM_check_canonical_file_basename(file,NRSUR7DQ4_DATAFILE,
215 "CANONICAL_FILE_BASENAME");
216
217 if (ret != XLAL_SUCCESS)
218 XLAL_ERROR_VOID(XLAL_FAILURE, "Failure loading data from %s\n", file_path);
219
220 XLALFree(path);
221 XLALFree(file_path);
222}
223
224/**
225 * Initialize a PrecessingNRSurData structure from an open hdf5 file.
226 * This will typically only be called once, from NRSur7dq2_Init_LALDATA or
227 * NRSur7dq4_Init_LALDATA.
228 */
229static int PrecessingNRSur_Init(
230 PrecessingNRSurData *data, /**< Output: Surrogate data structure. */
231 LALH5File *file, /**< hdf5 file with surrogate data. */
232 UINT4 PrecessingNRSurVersion /**< 0 for NRSur7dq2, 1 for NRSur7dq4 */
233) {
234
235 size_t i;
236
237 if (data->setup) {
238 XLALPrintError("You tried to setup a NRSurrogate model that was already initialized. Ignoring.\n");
239 return XLAL_FAILURE;
240 }
241
242 // Get the dynamics time nodes
243 gsl_vector *t_ds_with_halves = NULL;
244 ReadHDF5RealVectorDataset(file, "t_ds", &t_ds_with_halves);
245
246 gsl_vector *t_ds = gsl_vector_alloc(t_ds_with_halves->size - 3);
247 gsl_vector *t_ds_half_times = gsl_vector_alloc(3);
248 for (i=0; i < 3; i++) {
249 gsl_vector_set(t_ds, i, gsl_vector_get(t_ds_with_halves, 2*i));
250 gsl_vector_set(t_ds_half_times, i, gsl_vector_get(t_ds_with_halves, 2*i + 1));
251 }
252 for (i=3; i < t_ds->size; i++) {
253 gsl_vector_set(t_ds, i, gsl_vector_get(t_ds_with_halves, i+3));
254 }
255 gsl_vector_free(t_ds_with_halves);
256 data->t_ds = t_ds;
257 data->t_ds_half_times = t_ds_half_times;
258
259 // Load the dynamics time node data. The nodes are stored in HDF5 groups "ds_node_%d"%(i)
260 // where i=0, 1, 2, ...
261 // These INCLUDE the half-nodes at t_1/2, t_3/2, and t_5/2, so the (node time, index) pairs are
262 // (t_0, 0), (t_1/2, 1), (t_1, 2), (t_3/2, 3), (t_2, 4), (t_5/2, 5), (t_3, 6), (t_4, 7), ...
263 // (t_n, n+3) for n >= 3
264 DynamicsNodeFitData **ds_node_data = XLALMalloc( (t_ds->size) * sizeof(*ds_node_data));
265 DynamicsNodeFitData **ds_half_node_data = XLALMalloc( 3 * sizeof(*ds_node_data) );
266 for (i=0; i < (t_ds->size); i++) ds_node_data[i] = NULL;
267 for (i=0; i < 3; i++) ds_half_node_data[i] = NULL;
268 LALH5File *sub;
269 char *sub_name = XLALMalloc(20);
270 int j;
271 for (i=0; i < (t_ds->size); i++) {
272 if (i < 3) {j = 2*i;} else {j = i+3;}
273 snprintf(sub_name, 20, "ds_node_%d", j);
274 sub = XLALH5GroupOpen(file, sub_name);
275 PrecessingNRSur_LoadDynamicsNode(ds_node_data, sub, i, PrecessingNRSurVersion);
276
277 if (i < 3) {
278 snprintf(sub_name, 20, "ds_node_%d", j+1);
279 sub = XLALH5GroupOpen(file, sub_name);
280 PrecessingNRSur_LoadDynamicsNode(ds_half_node_data, sub, i, PrecessingNRSurVersion);
281 }
282 }
283 XLALFree(sub_name);
284 data->ds_node_data = ds_node_data;
285 data->ds_half_node_data = ds_half_node_data;
286
287 // Get the coorbital time array
288 gsl_vector *t_coorb = NULL;
289 ReadHDF5RealVectorDataset(file, "t_coorb", &t_coorb);
290
291 data->t_coorb = t_coorb;
292
293 // Load coorbital waveform surrogate data
294 WaveformFixedEllModeData **coorbital_mode_data = XLALMalloc( (NRSUR_LMAX - 1) * sizeof(*coorbital_mode_data) );
295 for (int ell_idx=0; ell_idx < NRSUR_LMAX-1; ell_idx++) {
296 PrecessingNRSur_LoadCoorbitalEllModes(coorbital_mode_data, file, ell_idx);
297 }
298 data->coorbital_mode_data = coorbital_mode_data;
299
300 data->LMax = NRSUR_LMAX;
301 data->setup = 1;
302
303 return XLAL_SUCCESS;
304}
305
306/**
307 * Loads a single fit for NRSur7dq2 or NRSur7dq4.
308 */
309static void PrecessingNRSur_LoadFitData(
310 FitData **fit_data, /**< Output: Data struct for fit data. Should be NULL; Will malloc space and load data into it. */
311 LALH5File *sub, /**< Subgroup containing fit data. */
312 const char *name /**< fit name. */
313) {
314 *fit_data = XLALMalloc(sizeof(FitData));
315
316 const size_t str_size = 30;
317 char *tmp_name = XLALMalloc(str_size);
318 UNUSED size_t nwritten;
319
320 nwritten = snprintf(tmp_name, str_size, "%s_coefs", name);
321 XLAL_CHECK_ABORT(nwritten < str_size);
322 (*fit_data)->coefs = NULL;
323
324 ReadHDF5RealVectorDataset(sub, tmp_name, &((*fit_data)->coefs));
325
326 nwritten = snprintf(tmp_name, str_size, "%s_bfOrders", name);
327 XLAL_CHECK_ABORT(nwritten < str_size);
328 (*fit_data)->basisFunctionOrders = NULL;
329 ReadHDF5LongMatrixDataset(sub, tmp_name,
330 &((*fit_data)->basisFunctionOrders));
331
332 (*fit_data)->n_coefs = (*fit_data)->coefs->size;
333}
334
335/**
336 * Loads a vector fit for NRSur7dq4.
337 *
338 * For this model, vector fits are constructed as a vector of scalar fits.
339 */
340static void NRSur7dq4_LoadVectorFitData(
341 VectorFitData **vector_fit_data, /**< Output: Data struct for vector fit data. Should be NULL; Will malloc space and load data into it. */
342 LALH5File *sub, /**< Subgroup containing fit data. */
343 const char *name, /**< fit name. */
344 const size_t size /**< size of vector. */
345) {
346 const size_t str_size = 20;
347 char *tmp_name = XLALMalloc(str_size);
348 UNUSED size_t nwritten;
349
350 *vector_fit_data = XLALMalloc(sizeof(VectorFitData));
351 (*vector_fit_data)->vec_dim = size;
352 (*vector_fit_data)->fit_data = XLALMalloc(size * sizeof(FitData *) );
353
354 for (size_t i=0; i<size; i++) {
355 nwritten = snprintf(tmp_name, str_size, "%s_%zu", name, i);
356 XLAL_CHECK_ABORT(nwritten < str_size);
357 FitData *fit_data = NULL;
358 PrecessingNRSur_LoadFitData(&fit_data, sub, tmp_name);
359 (*vector_fit_data)->fit_data[i] = fit_data;
360 }
361}
362
363/**
364 * Loads the data for a single dynamics node into a DynamicsNodeFitData struct.
365 * This is only called during the initialization of the surrogate data through PrecessingNRSur_Init.
366 */
367static void PrecessingNRSur_LoadDynamicsNode(
368 DynamicsNodeFitData **ds_node_data, /**< Entry i should be NULL; Will malloc space and load data into it. */
369 LALH5File *sub, /**< Subgroup containing data for dynamics node i. */
370 int i, /**< Dynamics node index. */
371 UINT4 PrecessingNRSurVersion /**< 0 for NRSur7dq2, 1 for NRSur7dq4 */
372) {
373 ds_node_data[i] = XLALMalloc( sizeof(*ds_node_data[i]) );
374
375
376 // omega
377 FitData *omega_data = NULL;
378 PrecessingNRSur_LoadFitData(&omega_data, sub, "omega");
379 ds_node_data[i]->omega_data = omega_data;
380
381 // For NRSur7dq2 vector fits are done for omega_orb, chiA_dot and chiB_dot
382 if (PrecessingNRSurVersion == 0){
383
384 // omega_copr
385 VectorFitData *omega_copr_data = XLALMalloc(sizeof(VectorFitData));
386 omega_copr_data->coefs = NULL;
387 omega_copr_data->basisFunctionOrders = NULL;
388 omega_copr_data->componentIndices = NULL;
389 ReadHDF5RealVectorDataset(sub, "omega_orb_coefs", &(omega_copr_data->coefs));
390 ReadHDF5LongMatrixDataset(sub, "omega_orb_bfOrders", &(omega_copr_data->basisFunctionOrders));
391 ReadHDF5LongVectorDataset(sub, "omega_orb_bVecIndices", &(omega_copr_data->componentIndices));
392 omega_copr_data->n_coefs = omega_copr_data->coefs->size;
393 omega_copr_data->vec_dim = 2;
394 ds_node_data[i]->omega_copr_data = omega_copr_data;
395
396 // chiA_dot
397 VectorFitData *chiA_dot_data = XLALMalloc(sizeof(VectorFitData));
398 chiA_dot_data->coefs = NULL;
399 chiA_dot_data->basisFunctionOrders = NULL;
400 chiA_dot_data->componentIndices = NULL;
401 ReadHDF5RealVectorDataset(sub, "chiA_coefs", &(chiA_dot_data->coefs));
402 ReadHDF5LongMatrixDataset(sub, "chiA_bfOrders", &(chiA_dot_data->basisFunctionOrders));
403 ReadHDF5LongVectorDataset(sub, "chiA_bVecIndices", &(chiA_dot_data->componentIndices));
404 chiA_dot_data->n_coefs = chiA_dot_data->coefs->size;
405 chiA_dot_data->vec_dim = 3;
406 ds_node_data[i]->chiA_dot_data = chiA_dot_data;
407
408 // chiB_dot
409 // One chiB_dot node has 0 coefficients, and ReadHDF5RealVectorDataset fails.
410 VectorFitData *chiB_dot_data = XLALMalloc(sizeof(VectorFitData));
411 chiB_dot_data->coefs = NULL;
412 chiB_dot_data->basisFunctionOrders = NULL;
413 chiB_dot_data->componentIndices = NULL;
414
415 UINT4Vector *dimLength;
416 size_t n;
417 LALH5Dataset *dset;
418 dset = XLALH5DatasetRead(sub, "chiB_coefs");
419 dimLength = XLALH5DatasetQueryDims(dset);
420 n = dimLength->data[0];
421 if (n==0) {
422 chiB_dot_data->n_coefs = 0;
423 } else {
424 ReadHDF5RealVectorDataset(sub, "chiB_coefs", &(chiB_dot_data->coefs));
425 ReadHDF5LongMatrixDataset(sub, "chiB_bfOrders", &(chiB_dot_data->basisFunctionOrders));
426 ReadHDF5LongVectorDataset(sub, "chiB_bVecIndices", &(chiB_dot_data->componentIndices));
427 chiB_dot_data->n_coefs = chiB_dot_data->coefs->size;
428 }
429 chiB_dot_data->vec_dim = 3;
430 ds_node_data[i]->chiB_dot_data = chiB_dot_data;
431
432 // For NRSur7dq4 the vector fits are done simply as a vector of scalar
433 // fits, so we just need to loop over the indices.
434 } else if (PrecessingNRSurVersion == 1) {
435 // omega_copr
436 VectorFitData *omega_copr_data = NULL;
437 NRSur7dq4_LoadVectorFitData(&omega_copr_data, sub, "omega_orb", 2);
438 ds_node_data[i]->omega_copr_data = omega_copr_data;
439
440 // chiA_dot
441 VectorFitData *chiA_dot_data = NULL;
442 NRSur7dq4_LoadVectorFitData(&chiA_dot_data, sub, "chiA", 3);
443 ds_node_data[i]->chiA_dot_data = chiA_dot_data;
444
445 // chiB_dot
446 VectorFitData *chiB_dot_data = NULL;
447 NRSur7dq4_LoadVectorFitData(&chiB_dot_data, sub, "chiB", 3);
448 ds_node_data[i]->chiB_dot_data = chiB_dot_data;
449 }
450}
451
452/**
453 * Load the WaveformFixedEllModeData from file for a single value of ell.
454 * This is only called during the initialization of the surrogate data through PrecessingNRSur_Init.
455 */
456static void PrecessingNRSur_LoadCoorbitalEllModes(
457 WaveformFixedEllModeData **coorbital_mode_data, /**< Entry i should be NULL; will malloc space and load data into it.*/
458 LALH5File *file, /**< The open hdf5 file */
459 int i /**< The index of coorbital_mode_data. Equivalently, ell-2. */
460) {
461 WaveformFixedEllModeData *mode_data = XLALMalloc( sizeof(*coorbital_mode_data[i]) );
462 mode_data->ell = i+2;
463
464 LALH5File *sub;
465 int str_size = 33; // Enough for L with 15 digits...
466 char *sub_name = XLALMalloc(str_size);
467
468 // Real part of m=0 mode
469 snprintf(sub_name, str_size, "hCoorb_%d_0_real", i+2);
470 sub = XLALH5GroupOpen(file, sub_name);
471 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->m0_real_data), false);
472
473 // Imag part of m=0 mode
474 snprintf(sub_name, str_size, "hCoorb_%d_0_imag", i+2);
475 sub = XLALH5GroupOpen(file, sub_name);
476 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->m0_imag_data), false);
477
478 // NOTE:
479 // In the paper https://arxiv.org/abs/1705.07089, Eq. 16 uses
480 // h_\pm^{\ell, m} = \frac{1}{2} \left( h_\mathrm{coorb}^{\ell, m} \pm h_\mathrm{coorb}^{\ell, -m\, *} \right)
481 // and we often denote h_\pm^{\ell, m} == X_\pm^{\ell, m} in this file and other documentation,
482 // but when actually building the surrogate the (\ell, -m) mode was taken as the reference mode, so we store
483 // Y_\pm^{\ell, m} = \frac{1}{2} \left( h_\mathrm{coorb}^{\ell, -m} \pm h_\mathrm{coorb}^{\ell, m\, *} \right)
484 // Note that we still have everything we want:
485 // X_\pm^{\ell, m} = \pm Y_\pm^{\ell, m\, *}
486 // or:
487 // Re[X_+] = Re[Y_+],
488 // Im[X_+] = -Im[Y_+],
489 // Re[X_-] = -Re[Y_-],
490 // Im[X_-] = Im[Y_-]
491 // We work with X rather than Y in this file, so we need the minus signs when loading from Y.
492 mode_data->X_real_plus_data = XLALMalloc( (i+2) * sizeof(WaveformDataPiece *) );
493 mode_data->X_real_minus_data = XLALMalloc( (i+2) * sizeof(WaveformDataPiece *) );
494 mode_data->X_imag_plus_data = XLALMalloc( (i+2) * sizeof(WaveformDataPiece *) );
495 mode_data->X_imag_minus_data = XLALMalloc( (i+2) * sizeof(WaveformDataPiece *) );
496 for (int m=1; m<=(i+2); m++) {
497 snprintf(sub_name, str_size, "hCoorb_%d_%d_Re+", i+2, m);
498 sub = XLALH5GroupOpen(file, sub_name);
499 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->X_real_plus_data[m-1]), false);
500 snprintf(sub_name, str_size, "hCoorb_%d_%d_Re-", i+2, m);
501 sub = XLALH5GroupOpen(file, sub_name);
502 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->X_real_minus_data[m-1]), true);
503 snprintf(sub_name, str_size, "hCoorb_%d_%d_Im+", i+2, m);
504 sub = XLALH5GroupOpen(file, sub_name);
505 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->X_imag_plus_data[m-1]), true);
506 snprintf(sub_name, str_size, "hCoorb_%d_%d_Im-", i+2, m);
507 sub = XLALH5GroupOpen(file, sub_name);
508 PrecessingNRSur_LoadWaveformDataPiece(sub, &(mode_data->X_imag_minus_data[m-1]), false);
509 }
510 XLALFree(sub_name);
511 coorbital_mode_data[i] = mode_data;
512}
513
514/**
515 * Loads a single NRSur coorbital waveform data piece from file into a WaveformDataPiece.
516 * This is only called during the initialization of the surrogate data through PrecessingNRSur_Init.
517 */
518static void PrecessingNRSur_LoadWaveformDataPiece(
519 LALH5File *sub, /**< HDF5 group containing data for this waveform data piece */
520 WaveformDataPiece **data, /**< Output - *data should be NULL. Space will be allocated. */
521 bool invert_sign /**< If true, multiply the empirical interpolation matrix by -1. */
522) {
524
525 gsl_matrix *EI_basis = NULL;
526 ReadHDF5RealMatrixDataset(sub, "EIBasis", &EI_basis);
527 if (invert_sign) {
528 gsl_matrix_scale(EI_basis, -1);
529 }
530 (*data)->empirical_interpolant_basis = EI_basis;
531
532 gsl_vector_long *node_indices = NULL;
533 ReadHDF5LongVectorDataset(sub, "nodeIndices", &node_indices);
534 (*data)->empirical_node_indices = node_indices;
535
536 int n_nodes = (*data)->empirical_node_indices->size;
537 (*data)->n_nodes = n_nodes;
538 (*data)->fit_data = XLALMalloc( n_nodes * sizeof(FitData *) );
539
540 LALH5File *nodeModelers = XLALH5GroupOpen(sub, "nodeModelers");
541 int str_size = 20; // Enough for L with 11 digits...
542 char *sub_name = XLALMalloc(str_size);
543 for (int i=0; i<n_nodes; i++) {
544 FitData *node_data = XLALMalloc(sizeof(FitData));
545 node_data->coefs = NULL;
546 node_data->basisFunctionOrders = NULL;
547 snprintf(sub_name, str_size, "coefs_%d", i);
548 ReadHDF5RealVectorDataset(nodeModelers, sub_name, &(node_data->coefs));
549 snprintf(sub_name, str_size, "bfOrders_%d", i);
550 ReadHDF5LongMatrixDataset(nodeModelers, sub_name, &(node_data->basisFunctionOrders));
551 node_data->n_coefs = node_data->coefs->size;
552 (*data)->fit_data[i] = node_data;
553 }
554}
555#endif
556
557/**
558 * Helper function which returns whether or not the global NRSur7dq2 surrogate data has been initialized.
559 */
560UNUSED static bool NRSur7dq2_IsSetup(void) {
561 if(__lalsim_NRSur7dq2_data.setup) return true;
562 else return false;
563}
564
565/**
566 * Helper function which returns whether or not the global NRSur7dq4 surrogate data has been initialized.
567 */
568UNUSED static bool NRSur7dq4_IsSetup(void) {
569 if(__lalsim_NRSur7dq4_data.setup) return true;
570 else return false;
571}
572
573/**
574 * Helper function for integer powers
575 */
576REAL8 ipow(REAL8 base, int exponent) {
577 if (exponent == 0) return 1.0;
578 REAL8 res = base;
579 while (exponent > 1) {
580 res = res*base;
581 exponent -= 1;
582 }
583 return res;
584}
585
586
587/*
588 * Evaluate a NRSur7dq2 scalar fit.
589 * The fit result is given by
590 * \sum_{i=1}^{n} c_i * \prod_{j=1}^7 B_j(k_{i, j}; x_j)
591 * where i runs over fit coefficients, j runs over the 7 dimensional parameter
592 * space, and B_j is a basis function, taking an integer order k_{i, j} and
593 * the parameter component x_j. For this surrogate, B_j are monomials in the spin
594 * components, and monomials in an affine transformation of the mass ratio.
595 */
597 FitData *data, /**< Data for fit */
598 REAL8 *x /**< size 7, giving mass ratio q, and dimensionless spin components */
599) {
600 REAL8 x_powers[22]; // 3 per spin component, 4 for mass ratio
601 REAL8 res = 0.0;
602 REAL8 prod;
603 int i, j;
604
605 // The fits were constructed using this rather than using q directly
607
608 // Compute powers of components of x
609 for (i=0; i<22; i++) {
610 if (i%7==0) {
611 x_powers[i] = ipow(q_fit, i/7);
612 } else {
613 x_powers[i] = ipow(x[i%7], i/7);
614 }
615 }
616
617 // Sum up fit terms
618 for (i=0; i < data->n_coefs; i++) {
619 // Initialize with q basis function:
620 prod = x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, 0)];
621 // Multiply with spin basis functions:
622 for (j=1; j<7; j++) {
623 prod *= x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, j) + j];
624 }
625 res += gsl_vector_get(data->coefs, i) * prod;
626 }
627
628 return res;
629}
630
631/*
632 * This is very similar to NRSur7dq2_eval_fit except that the result is a
633 * 2d or 3d vector instead of a scalar. Each fit coefficient now applies to
634 * just a single component of the result.
635 */
637 REAL8 *res, /**< Result */
638 VectorFitData *data, /**< Data for fit */
639 REAL8 *x /**< size 7, giving mass ratio q, and dimensionless spin components */
640) {
641 REAL8 x_powers[22]; // 3 per spin component, 4 for mass ratio
642 REAL8 prod;
643 int i, j;
644
645 // Initialize the result
646 for (i=0; i < data->vec_dim; i++) {
647 res[i] = 0.0;
648 }
649
650 // The fits were constructed using this rather than using q directly
652
653 // Compute powers of components of x
654 for (i=0; i<22; i++) {
655 if (i%7==0) {
656 x_powers[i] = ipow(q_fit, i/7);
657 } else {
658 x_powers[i] = ipow(x[i%7], i/7);
659 }
660 }
661
662 // Sum up fit terms
663 for (i=0; i < data->n_coefs; i++) {
664 // Initialize with q basis function:
665 prod = x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, 0)];
666 // Multiply with spin basis functions:
667 for (j=1; j<7; j++) {
668 prod *= x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, j) + j];
669 }
670 res[gsl_vector_long_get(data->componentIndices, i)] += gsl_vector_get(data->coefs, i) * prod;
671 }
672}
673
674/*
675 * Computes effective spins chiHat and chi_a.
676 * chiHat is defined in Eq.(3) of 1508.07253.
677 * and chi_a = (chi1z - chi2z)/2.
678 */
680 REAL8 *chiHat, /**< Output: chiHat */
681 REAL8 *chi_a, /**< Output: chi_a */
682 const REAL8 q, /**< Mass ratio >= 1 */
683 const REAL8 chi1z, /**< Dimensionless z-spin of heavier BH */
684 const REAL8 chi2z /**< Dimensionless z-spin of lighter BH */
685) {
686 const REAL8 eta = q/(1.+q)/(1.+q);
687 const REAL8 chi_wtAvg = (q*chi1z+chi2z)/(1+q);
688 REAL8 chiHat_val = (chi_wtAvg - 38.*eta/113.*(chi1z
689 + chi2z))/(1. - 76.*eta/113.);
690 REAL8 chi_a_val = (chi1z - chi2z)/2.;
691 *chiHat = chiHat_val;
692 *chi_a = chi_a_val;
693 return XLAL_SUCCESS;
694}
695
696/*
697 * Evaluate a NRSur7dq4 scalar fit.
698 */
700 FitData *data, /**< Data for fit */
701 REAL8 *x /**< size 7, giving mass ratio q, and dimensionless spin components */
702) {
703 REAL8 x_powers[22]; // 3 per spin component, 4 for mass ratio
704 REAL8 res = 0.0;
705 REAL8 prod;
706 int i, j;
707
708 // get effective spins chiHat and chi_a
709 // chiHat is defined in Eq.(3) of 1508.07253.
710 // and chi_a = (chi1z - chi2z)/2.
711 REAL8 chiHat, chi_a;
712 NRSur7dq4_effective_spins(&chiHat, &chi_a, x[0], x[3], x[6]);
713
714 // Convert from [q, chi1x, chi1y, chi1z, chi2x, chi2y, chi2z]
715 // to [log(q), chi1x, chi1y, chiHat, chi2x, chi2y, chi_a]
716 REAL8 fit_params[7];
717 fit_params[0] = log(x[0]);
718 fit_params[1] = x[1];
719 fit_params[2] = x[2];
720 fit_params[3] = chiHat;
721 fit_params[4] = x[4];
722 fit_params[5] = x[5];
723 fit_params[6] = chi_a;
724
725 // The fits were constructed using this rather than using q directly
727 + NRSUR7DQ4_Q_FIT_SLOPE*fit_params[0];
728
729 // Compute powers of components of fit_params
730 for (i=0; i<22; i++) {
731 if (i%7==0) {
732 x_powers[i] = ipow(q_fit, i/7);
733 } else {
734 x_powers[i] = ipow(fit_params[i%7], i/7);
735 }
736 }
737
738 // Sum up fit terms
739 for (i=0; i < data->n_coefs; i++) {
740 // Initialize with q basis function:
741 prod = x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, 0)];
742 // Multiply with spin basis functions:
743 for (j=1; j<7; j++) {
744 prod *= x_powers[7 * gsl_matrix_long_get(data->basisFunctionOrders, i, j) + j];
745 }
746 res += gsl_vector_get(data->coefs, i) * prod;
747 }
748
749 return res;
750}
751
752/*
753 * This is very similar to NRSur7dq4_eval_fit except that the result is a
754 * 2d or 3d vector instead of a scalar. Each fit coefficient now applies to
755 * just a single component of the result.
756 */
758 REAL8 *res, /**< Result */
759 VectorFitData *data, /**< Data for fit */
760 REAL8 *x /**< size 7, giving mass ratio q, and dimensionless spin components */
761) {
762 // loop over vector indices
763 for (int i=0; i < data->vec_dim; i++) {
764 res[i] = NRSur7dq4_eval_fit((*data).fit_data[i], x);
765 }
766}
767
768
769/*
770 * Wrapper for NRSur7dq2_eval_fit and NRSur7dq4_eval_fit
771 */
773 FitData *data, /**< Data for fit */
774 REAL8 *x, /**< size 7, giving mass ratio q, and dimensionless spin components */
775 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
776) {
777 if (__sur_data->PrecessingNRSurVersion == 0) {
778 return NRSur7dq2_eval_fit(data, x);
779 } else if (__sur_data->PrecessingNRSurVersion == 1) {
780 return NRSur7dq4_eval_fit(data, x);
781 } else {
782 XLAL_ERROR_REAL8(XLAL_FAILURE, "Only 0 or 1 are currently allowed for PrecessingNRSurVersion\n");
783 }
784}
785
786/*
787 * Wrapper for NRSur7dq2_eval_vector_fit and NRSur7dq4_eval_vector_fit
788 */
790 REAL8 *res, /**< Result */
791 VectorFitData *data, /**< Data for fit */
792 REAL8 *x, /**< size 7, giving mass ratio q, and dimensionless spin components */
793 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
794) {
795 if (__sur_data->PrecessingNRSurVersion == 0) {
796 return NRSur7dq2_eval_vector_fit(res, data, x);
797 } else if (__sur_data->PrecessingNRSurVersion == 1) {
798 return NRSur7dq4_eval_vector_fit(res, data, x);
799 } else {
800 XLAL_ERROR_VOID(XLAL_FAILURE, "Only 0 or 1 are currently allowed for PrecessingNRSurVersion\n");
801 }
802}
803
804
805/* During the ODE integration, the norm of the spins will change due to
806 * integration errors and fit modeling errors. Keep them normalized.
807 * Normalizes in-place
808 */
810 REAL8 chiANorm, /**< ||vec{chi}_A|| */
811 REAL8 chiBNorm, /**< ||vec{chi}_B|| */
812 REAL8 *y /**< [q0, qx, qy, qz, phi, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] */
813) {
814
815 REAL8 nQ, nA, nB, sum;
816 int i;
817
818 // Compute current norms
819 sum = 0.0;
820 for (i=0; i<4; i++) {
821 sum += y[i]*y[i];
822 }
823 nQ = sqrt(sum);
824
825 sum = 0.0;
826 for (i=5; i<8; i++) {
827 sum += y[i]*y[i];
828 }
829 nA = sqrt(sum);
830
831 sum = 0.0;
832 for (i=8; i<11; i++) {
833 sum += y[i]*y[i];
834 }
835 nB = sqrt(sum);
836
837 // Compute normalized output
838 for (i=0; i<4; i++) {
839 y[i] = y[i] / nQ;
840 }
841 y[4] = y[4];
842 for (i=5; i<8; i++) {
843 y[i] = y[i] * chiANorm / nA;
844 }
845 for (i=8; i<11; i++) {
846 y[i] = y[i] * chiBNorm / nB;
847 }
848}
849
850/*
851 * Similar to normalize_y, but components given individually as arrays with n samples
852 */
854 REAL8 normA, /**< ||vec{chi}_A|| */
855 REAL8 normB, /**< ||vec{chi}_B|| */
856 gsl_vector **quat, /**< The four quaternion time-dependent components */
857 gsl_vector **chiA, /**< Time-dependent components of chiA */
858 gsl_vector **chiB /**< Time-dependent components of chiB */
859) {
860 REAL8 nA, nB, nQ;
861 int i;
862 int n = quat[0]->size;
863 REAL8 *chiAx = chiA[0]->data;
864 REAL8 *chiAy = chiA[1]->data;
865 REAL8 *chiAz = chiA[2]->data;
866 REAL8 *chiBx = chiB[0]->data;
867 REAL8 *chiBy = chiB[1]->data;
868 REAL8 *chiBz = chiB[2]->data;
869 REAL8 *q0 = quat[0]->data;
870 REAL8 *qx = quat[1]->data;
871 REAL8 *qy = quat[2]->data;
872 REAL8 *qz = quat[3]->data;
873
874 if (normA > 0.0) {
875 for (i=0; i<n; i++) {
876 nA = sqrt(chiAx[i]*chiAx[i] + chiAy[i]*chiAy[i] + chiAz[i]*chiAz[i]);
877 chiAx[i] *= normA / nA;
878 chiAy[i] *= normA / nA;
879 chiAz[i] *= normA / nA;
880 }
881 }
882
883 if (normB > 0.0) {
884 for (i=0; i<n; i++) {
885 nB = sqrt(chiBx[i]*chiBx[i] + chiBy[i]*chiBy[i] + chiBz[i]*chiBz[i]);
886 chiBx[i] *= normB / nB;
887 chiBy[i] *= normB / nB;
888 chiBz[i] *= normB / nB;
889 }
890 }
891
892 for (i=0; i<n; i++) {
893 nQ = sqrt(q0[i]*q0[i] + qx[i]*qx[i] + qy[i]*qy[i] + qz[i]*qz[i]);
894 q0[i] /= nQ;
895 qx[i] /= nQ;
896 qy[i] /= nQ;
897 qz[i] /= nQ;
898 }
899}
900
901/**
902 * Transforms chiA and chiB from the coprecessing frame to the coorbital frame
903 * using the orbital phase.
904 */
906 gsl_vector **chiA, /**< 3 time-dependent components of chiA in the coorbital frame */
907 gsl_vector **chiB, /**< 3 time-dependent components of chiB in the coorbital frame */
908 gsl_vector *phi_vec /**< The time-dependent orbital phase */
909) {
910 int i;
911 int n = phi_vec->size;
912 REAL8 sp, cp, tmp;
913 REAL8 *phi = phi_vec->data;
914
915 REAL8 *chix = chiA[0]->data;
916 REAL8 *chiy = chiA[1]->data;
917 for (i=0; i<n; i++) {
918 cp = cos(phi[i]);
919 sp = sin(phi[i]);
920 tmp = chix[i];
921 chix[i] = tmp*cp + chiy[i]*sp;
922 chiy[i] = -1*tmp*sp + chiy[i]*cp;
923 }
924
925 chix = chiB[0]->data;
926 chiy = chiB[1]->data;
927 for (i=0; i<n; i++) {
928 cp = cos(phi[i]);
929 sp = sin(phi[i]);
930 tmp = chix[i];
931 chix[i] = tmp*cp + chiy[i]*sp;
932 chiy[i] = -1*tmp*sp + chiy[i]*cp;
933 }
934}
935
936/*
937 * When integrating the dynamics ODE, we need to evaluate fits.
938 * This helper function computes the fit inputs from the current ODE solution.
939 * The spin components of y are in the coprecessing frame, but the spin
940 * components of x are in the coorbital frame.
941 */
943 REAL8 *x, /**< Result, length 7 */
944 REAL8 q, /**< Mass ratio */
945 REAL8 *y /**< [q0, qx, qy, qz, phi, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] */
946) {
947 REAL8 sp = sin(y[4]);
948 REAL8 cp = cos(y[4]);
949
950 // q
951 x[0] = q;
952
953 // chiA, transformed to the coorbital frame
954 x[1] = y[5]*cp + y[6]*sp;
955 x[2] = -1*y[5]*sp + y[6]*cp;
956 x[3] = y[7];
957
958 // chiB, transformed to the coorbital frame
959 x[4] = y[8]*cp + y[9]*sp;
960 x[5] = -1*y[8]*sp + y[9]*cp;
961 x[6] = y[10];
962}
963
964/*
965 * After evaluating fits, we have many time derivatives, with components in
966 * the coorbital frame. dydt has components in the coprecessing frame, so we
967 * transform them, and we also compute the time derivative of the unit
968 * quaternions using the coprecessing components of Omega
969 */
971 REAL8 *dydt, /**< Result, length 11 */
972 REAL8 *y, /**< ODE solution at the current time step */
973 REAL8 *Omega_coorb_xy, /**< a form of time derivative of the coprecessing frame */
974 REAL8 omega, /**< orbital angular frequency in the coprecessing frame */
975 REAL8 *chiA_dot, /**< chiA time derivative */
976 REAL8 *chiB_dot /**< chiB time derivative */
977) {
978 REAL8 omega_quat_x, omega_quat_y;
979
980 REAL8 cp = cos(y[4]);
981 REAL8 sp = sin(y[4]);
982
983 // Quaternion derivative:
984 // Omega = 2 * quat^{-1} * dqdt -> dqdt = 0.5 * quat * omegaQuat, where
985 // omegaQuat = [0, Omega_copr_x, Omega_copr_y, 0]
986 omega_quat_x = Omega_coorb_xy[0]*cp - Omega_coorb_xy[1]*sp;
987 omega_quat_y = Omega_coorb_xy[0]*sp + Omega_coorb_xy[1]*cp;
988 dydt[0] = (-0.5)*y[1]*omega_quat_x - 0.5*y[2]*omega_quat_y;
989 dydt[1] = (-0.5)*y[3]*omega_quat_y + 0.5*y[0]*omega_quat_x;
990 dydt[2] = 0.5*y[3]*omega_quat_x + 0.5*y[0]*omega_quat_y;
991 dydt[3] = 0.5*y[1]*omega_quat_y - 0.5*y[2]*omega_quat_x;
992
993 // Orbital phase derivative
994 dydt[4] = omega;
995
996 // Spin derivatives
997 dydt[5] = chiA_dot[0]*cp - chiA_dot[1]*sp;
998 dydt[6] = chiA_dot[0]*sp + chiA_dot[1]*cp;
999 dydt[7] = chiA_dot[2];
1000 dydt[8] = chiB_dot[0]*cp - chiB_dot[1]*sp;
1001 dydt[9] = chiB_dot[0]*sp + chiB_dot[1]*cp;
1002 dydt[10] = chiB_dot[2];
1003}
1004
1005/**
1006 * Cubic interpolation of 4 data points
1007 * This gives a much closer result to scipy.interpolate.InterpolatedUnivariateSpline than using gsl_interp_cspline
1008 * (see comment in spline_array_interp)
1009 */
1011 REAL8 xout, /**< The target x value */
1012 REAL8 *x, /**< The x values of the points to interpolate. Length 4, must be increasing. */
1013 REAL8 *y /**< The y values of the points to interpolate. Length 4. */
1014) {
1015 gsl_interp_accel *acc = gsl_interp_accel_alloc();
1016 gsl_spline *interpolant = gsl_spline_alloc(gsl_interp_polynomial, 4);
1017 gsl_spline_init(interpolant, x, y, 4);
1018 REAL8 res = gsl_spline_eval(interpolant, xout, acc);
1019 gsl_spline_free(interpolant);
1020 gsl_interp_accel_free(acc);
1021 return res;
1022}
1023
1024/**
1025 * Do cubic spline interpolation using a gsl_interp_cspline.
1026 * Results differ from scipy.interpolate.InterpolatedUnivariateSpline due to different boundary conditions.
1027 * This difference leads to small differences between this implementation and the python
1028 * implementation, especially near the start and end of the waveform.
1029 */
1030static gsl_vector *spline_array_interp(
1031 gsl_vector *xout, /**< The vector of points onto which we want to interpolate. */
1032 gsl_vector *x, /**< The x values of the data to interpolate. */
1033 gsl_vector *y /**< The y values of the data to interpolate. */
1034) {
1035 gsl_interp_accel *acc = gsl_interp_accel_alloc();
1036 gsl_spline *spline = gsl_spline_alloc(gsl_interp_cspline, x->size);
1037 gsl_spline_init(spline, x->data, y->data, x->size);
1038
1039 gsl_vector *res = gsl_vector_alloc(xout->size);
1040 REAL8 tmp;
1041 for (size_t i=0; i<xout->size; i++) {
1042 tmp = gsl_spline_eval(spline, gsl_vector_get(xout, i), acc);
1043 gsl_vector_set(res, i, tmp);
1044 }
1045 gsl_spline_free(spline);
1046 gsl_interp_accel_free(acc);
1047 return res;
1048}
1049
1050/**
1051 * Computes the orbital angular frequency at a dynamics node.
1052 */
1054 size_t node_index, /**< The index of the dynamics node. */
1055 REAL8 q, /**< The mass ratio. */
1056 REAL8 *y0, /**< The value of the ODE state y = [q0, qx, qy, qz, orbphase, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] */
1057 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1058){
1059 REAL8 x[7];
1061 FitData *data = __sur_data->ds_node_data[node_index]->omega_data;
1062 REAL8 omega = PrecessingNRSur_eval_fit(data, x, __sur_data);
1063 return omega;
1064}
1065
1066/**
1067 * Computes a reference time from a reference orbital angular frequency.
1068 */
1070 REAL8 omega_ref, /**< reference orbital angular frequency */
1071 REAL8 q, /**< mass ratio */
1072 REAL8 *chiA0, /**< chiA at reference point */
1073 REAL8 *chiB0, /**< chiB at reference point */
1074 REAL8 *init_quat, /**< coprecessing frame quaternion at reference point */
1075 REAL8 init_orbphase, /**< orbital phase at reference point */
1076 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1077) {
1078
1079 REAL8 START_TIME = 100;
1080 if (__sur_data->PrecessingNRSurVersion == 0) {
1081 START_TIME = NRSUR7DQ2_START_TIME;
1082 } else if (__sur_data->PrecessingNRSurVersion == 1) {
1083 START_TIME = NRSUR7DQ4_START_TIME;
1084 }
1085
1086 if (fabs(omega_ref) < 1.e-10) {
1087 XLAL_PRINT_WARNING("WARNING: Treating omega_ref = 0 as a flag to use t_ref = t_0 = %.2f", START_TIME);
1088 return START_TIME;
1089 }
1090
1091 // omega_ref <= 0.201 guarantees omega is smaller than the final and merger omegas for all masses and spins.
1092 if (omega_ref > 0.201) XLAL_ERROR_REAL8(XLAL_EINVAL,
1093 "Reference frequency omega_ref=%0.4f > 0.2, too large!\n", omega_ref);
1094
1095 REAL8 y0[11];
1096 int i;
1097 for (i=0; i<4; i++) y0[i] = init_quat[i];
1098 y0[4] = init_orbphase;
1099 for (i=0; i<3; i++) {
1100 y0[i+5] = chiA0[i];
1101 y0[i+8] = chiB0[i];
1102 }
1103
1104 REAL8 omega_min = PrecessingNRSur_get_omega(0, q, y0, __sur_data);
1105 if (omega_ref < omega_min) {
1107 "Got omega_ref or omega_low=%0.4f smaller than the minimum omega=%0.4f for this configuration!", omega_ref, omega_min);
1108 }
1109
1110 // i0=0 is a lower bound; find the first index where omega > omega_ref, and the previous index will have omega <= omega_ref.
1111 size_t imax = 1;
1112 REAL8 omega_max = PrecessingNRSur_get_omega(imax, q, y0, __sur_data);
1113 gsl_vector *t_ds = __sur_data->t_ds;
1114 while ((omega_max <= omega_ref) && (imax < t_ds->size)) {
1115 imax += 1;
1116 omega_min = omega_max;
1117 omega_max = PrecessingNRSur_get_omega(imax, q, y0, __sur_data);
1118 }
1119
1120 if (omega_max <= omega_ref) {
1121 XLAL_ERROR_REAL8(XLAL_FAILURE, "Tried all nodes and still have omega=%0.4f <= omega_ref=%0.4f\n", omega_min, omega_ref);
1122 }
1123
1124 // Do a linear interpolation between omega_min and omega_max
1125 REAL8 t_min = gsl_vector_get(t_ds, imax-1);
1126 REAL8 t_max = gsl_vector_get(t_ds, imax);
1127 REAL8 t_ref = (t_min * (omega_max - omega_ref) + t_max * (omega_ref - omega_min)) / (omega_max - omega_min);
1128 return t_ref;
1129}
1130
1131/**
1132 * Compute dydt at a given dynamics node, where y is the numerical solution to the dynamics ODE.
1133 */
1135 REAL8 *dydt, /**< Output: dy/dt evaluated at the ODE time node with index i0. Must have space for 11 entries. */
1136 int i0, /**< Time node index. i0=-1, -2, and -3 are used for time nodes 1/2, 3/2, and 5/2 respectively. */
1137 REAL8 q, /**< Mass ratio */
1138 REAL8 *y, /**< Current ODE state: [q0, qx, qy, qz, orbphase, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] */
1139 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1140) {
1141 // Setup fit variables
1142 REAL8 x[7];
1144
1145 // Get fit data
1146 DynamicsNodeFitData *ds_node;
1147 if (i0 >= 0) {
1148 ds_node = __sur_data->ds_node_data[i0];
1149 } else {
1150 ds_node = __sur_data->ds_half_node_data[-1*i0 - 1];
1151 }
1152
1153 // Evaluate fits
1154 REAL8 omega, Omega_coorb_xy[2], chiA_dot[3], chiB_dot[3];
1155 omega = PrecessingNRSur_eval_fit(ds_node->omega_data, x, __sur_data);
1156 PrecessingNRSur_eval_vector_fit(Omega_coorb_xy, ds_node->omega_copr_data,
1157 x, __sur_data);
1159 x, __sur_data);
1161 x, __sur_data);
1162 PrecessingNRSur_assemble_dydt(dydt, y, Omega_coorb_xy, omega, chiA_dot, chiB_dot);
1163}
1164
1165/**
1166 * Compute dydt at any time by evaluating dydt at 4 nearby dynamics nodes and
1167 * using cubic spline interpolation to evaluate at the desired time.
1168 */
1170 REAL8 *dydt, /**< Output: dy/dt evaluated at time t. Must have space for 11 entries. */
1171 REAL8 t, /**< Time at which the ODE should be evaluated. */
1172 REAL8 q, /**< Mass ratio */
1173 REAL8 *y, /**< Current ODE state */
1174 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1175) {
1176 // Make sure we are within the valid range
1177 gsl_vector *t_ds = __sur_data->t_ds;
1178 int i1 = 0;
1179 int imax = t_ds->size - 1;
1180 REAL8 t1 = gsl_vector_get(t_ds, i1);
1181 REAL8 tmax = gsl_vector_get(t_ds, imax);
1182 if (t < t1 || t > tmax) {
1183 XLAL_ERROR_VOID(XLAL_FAILURE, "Tried to get dydt at t=%0.12f, not in valid range [%0.12f, %0.12f]\n", t, t1, tmax);
1184 }
1185
1186 REAL8 t2 = gsl_vector_get(t_ds, i1+1);
1187 // Put t2 slightly larger than t
1188 while (t2 < t) {
1189 i1 += 1;
1190 t1 = t2;
1191 t2 = gsl_vector_get(t_ds, i1+1);
1192 }
1193
1194 // Do cubic spline interpolation using 4 data points, cenetered if possible
1195 int i0 = i1-1;
1196 if (i0 < 0) i0 = 0;
1197 if (i0 > imax-3) i0 = imax-3;
1198 REAL8 times[4], derivs[4], dydt0[11], dydt1[11], dydt2[11], dydt3[11];
1199 int j;
1200 for (j=0; j<4; j++) times[j] = gsl_vector_get(t_ds, i0+j);
1201 PrecessingNRSur_get_time_deriv_from_index(dydt0, i0, q, y, __sur_data);
1202 PrecessingNRSur_get_time_deriv_from_index(dydt1, i0+1, q, y, __sur_data);
1203 PrecessingNRSur_get_time_deriv_from_index(dydt2, i0+2, q, y, __sur_data);
1204 PrecessingNRSur_get_time_deriv_from_index(dydt3, i0+3, q, y, __sur_data);
1205
1206 for (j=0; j<11; j++) {
1207 derivs[0] = dydt0[j];
1208 derivs[1] = dydt1[j];
1209 derivs[2] = dydt2[j];
1210 derivs[3] = dydt3[j];
1211 dydt[j] = cubic_interp(t, times, derivs);
1212 }
1213
1214}
1215
1216/**
1217 * Initialize the dynamics ODE at a dynamics node.
1218 * Given t_ref, finds the nearest dynamics node and integrates the initial conditions at t_ref
1219 * a tiny bit to obtain the ODE state at the dynamics node.
1220 */
1222 REAL8 *dynamics_data, /**< ODE output */
1223 REAL8 t_ref, /**< reference time. t_ds[i0] will be close to t_ref. */
1224 REAL8 q, /**< mass ratio */
1225 REAL8 *chiA0, /**< chiA at t_ref. */
1226 REAL8 *chiB0, /**< chiB at t_ref. */
1227 REAL8 init_orbphase, /**< orbital phase at t_ref. */
1228 REAL8 *init_quat, /**< quaternion at t_ref. */
1229 REAL8 normA, /**< |chiA| */
1230 REAL8 normB, /**< |chiB| */
1231 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1232) {
1233 int imin, imax, i0, j;
1234 REAL8 tmin, tmax, t0, dt, y_ref[11], dydt_ref[11], *node_data;
1235 gsl_vector *t_ds;
1236
1237 // First find i0, the closest dynamics time node to time t_ref, with a binary search
1238 t_ds = __sur_data->t_ds;
1239 imin = 0;
1240 imax = t_ds->size - 1;
1241 tmin = gsl_vector_get(t_ds, imin);
1242 tmax = gsl_vector_get(t_ds, imax);
1243 while (imax - imin > 1) {
1244 i0 = (imax + imin)/2;
1245 t0 = gsl_vector_get(t_ds, i0);
1246 if (t0 > t_ref) {
1247 imax = i0;
1248 tmax = t0;
1249 } else {
1250 imin = i0;
1251 tmin = t0;
1252 }
1253 }
1254
1255 // Now we have tmin <= t_ref < tmax, and imax = imin+1.
1256 // Step towards the closest of tmin, tmax.
1257 if (fabs(t_ref - tmin) < fabs(tmax - t_ref)) {
1258 i0 = imin;
1259 dt = tmin - t_ref;
1260 } else {
1261 i0 = imax;
1262 dt = tmax - t_ref;
1263 }
1264
1265 // Setup y at t_ref
1266 for (j=0; j<4; j++) y_ref[j] = init_quat[j];
1267 y_ref[4] = init_orbphase;
1268 for (j=0; j<3; j++) {
1269 y_ref[5+j] = chiA0[j];
1270 y_ref[8+j] = chiB0[j];
1271 }
1272
1273 // Compute dydt at t_ref
1274 PrecessingNRSur_get_time_deriv(dydt_ref, t_ref, q, y_ref, __sur_data);
1275
1276 // modify y_ref to be y at t0 = t_ds[i0]
1277 for (j=0; j<11; j++) y_ref[j] += dt * dydt_ref[j];
1278 PrecessingNRSur_normalize_y(normA, normB, y_ref);
1279
1280 // transfer results to ODE output data structures
1281 node_data = dynamics_data + i0*11; // Point to the output at i0
1282 for (j=0; j<11; j++) node_data[j] = y_ref[j];
1283
1284 return i0;
1285}
1286
1287/**
1288 * Initializes the AB4 ODE system from the surrogate start time by taking 3 RK4 steps,
1289 * making use of the three additional half-time-step nodes for the RK4 substeps.
1290 * This is the recommended way to initialize the AB4 ODE system - the additional nodes
1291 * are there to increase accuracy during initialization.
1292 * The drawback is that we are forced to accept fRef to be the
1293 * surrogate start frequency, which will depend on masses and spins.
1294 * The ODE system is for the vector of 11 quantities:
1295 * [q0, qx, qy, qz, varphi, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz]
1296 * where (q0, qx, qy, qz) is the coprecessing frame quaternion, varphi is the
1297 * orbital phase, and the spin components are in the coprecessing frame.
1298 */
1300 REAL8 *dynamics_data, /**< A pointer to the start of the ODE output; the first 11 entries
1301 (for node 0) should have already been set.*/
1302 REAL8 *time_steps, /**< Output: first three time steps. Should have size 3. */
1303 REAL8 *dydt0, /**< Output: dydt at node 0. Should have size 11. */
1304 REAL8 *dydt1, /**< Output: dydt at node 1. Should have size 11. */
1305 REAL8 *dydt2, /**< Output: dydt at node 2. Should have size 11. */
1306 REAL8 *dydt3, /**< Output: dydt at node 3. Should have size 11. */
1307 REAL8 normA, /**< |chiA| */
1308 REAL8 normB, /**< |chiB| */
1309 REAL8 q, /**< mass ratio */
1310 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1311) {
1312 gsl_vector *t_ds = __sur_data->t_ds;
1313 REAL8 t1, t2;
1314 int i, j;
1315 REAL8 k2[11], k3[11], k4[11]; // dydt for all but the first RK4 substep (which will be one of dydt0, dydt1, dydt2)
1316 REAL8 *node_data;
1317 REAL8 y_tmp[11];
1318 REAL8 *dydt[3];
1319 dydt[0] = dydt0;
1320 dydt[1] = dydt1;
1321 dydt[2] = dydt2;
1322
1323 // Setup time steps
1324 t1 = gsl_vector_get(t_ds, 0);
1325 for (i=0; i<3; i++) {
1326 t2 = gsl_vector_get(t_ds, i+1);
1327 time_steps[i] = t2 - t1;
1328 t1 = t2;
1329 }
1330
1331 // Three steps of RK4.
1332 for (i=0; i<3; i++ ) {
1333
1334 // Initial substep
1335 node_data = dynamics_data + 11*i;
1336 PrecessingNRSur_get_time_deriv_from_index(dydt[i], i, q, node_data, __sur_data);
1337
1338 // Next substep: evaluate at the half node (by using -1, -2, or -3 for i=0, 1, or 2)
1339 for (j=0; j<11; j++) {
1340 y_tmp[j] = node_data[j] + 0.5*time_steps[i]*dydt[i][j];
1341 }
1342 PrecessingNRSur_get_time_deriv_from_index(k2, -1-i, q, y_tmp, __sur_data);
1343
1344 // Next substep: also at the half node, but update y_tmp
1345 for (j=0; j<11; j++) {
1346 y_tmp[j] = node_data[j] + 0.5*time_steps[i]*k2[j];
1347 }
1348 PrecessingNRSur_get_time_deriv_from_index(k3, -1-i, q, y_tmp, __sur_data);
1349
1350 // Final substep: evaluate at the next node
1351 for (j=0; j<11; j++) {
1352 y_tmp[j] = node_data[j] + time_steps[i]*k3[j];
1353 }
1354 PrecessingNRSur_get_time_deriv_from_index(k4, i+1, q, y_tmp, __sur_data);
1355
1356 // Compute the RK4 expression for the next node, normalize, and store the data
1357 for (j=0; j<11; j++) {
1358 y_tmp[j] = node_data[j] + (time_steps[i]/6.0)*(dydt[i][j] + 2*k2[j] + 2*k3[j] + k4[j]);
1359 }
1360 PrecessingNRSur_normalize_y(normA, normB, y_tmp);
1361 node_data = node_data + 11;
1362 for (j=0; j<11; j++) {
1363 node_data[j] = y_tmp[j];
1364 }
1365 }
1366
1367 // Finally, we need to compute dydt3;
1368 node_data = dynamics_data + 33;
1369 PrecessingNRSur_get_time_deriv_from_index(dydt3, 3, q, node_data, __sur_data);
1370}
1371
1372/**
1373 * Initializes the AB4 ODE system from a single arbitrary dynamics node by taking 3 RK4 steps.
1374 * Compared to PrecessingNRSur_initialize_RK4_with_half_nodes, this may be slightly less accurate
1375 * and slightly more time consuming as we must interpolate many dynamics node fit evaluations,
1376 * but this is more flexible as we can initialize from any node instead of just node 0.
1377 */
1379 REAL8 *dynamics_data, /**< A pointer to the start of the ODE output.
1380 Entries with i0*11 <= i < (i0+1)*11 should have already be computed. */
1381 REAL8 *time_steps, /**< Output: first three time steps. Should have size 3. */
1382 REAL8 *dydt0, /**< Output: dydt at node i0 + 0. Should have size 11. */
1383 REAL8 *dydt1, /**< Output: dydt at node i0 + 1. Should have size 11. */
1384 REAL8 *dydt2, /**< Output: dydt at node i0 + 2. Should have size 11. */
1385 REAL8 *dydt3, /**< Output: dydt at node i0 + 3. Should have size 11. */
1386 REAL8 normA, /**< |chiA| */
1387 REAL8 normB, /**< |chiB| */
1388 REAL8 q, /**< mass ratio */
1389 int i0, /**< the node that is already initialized */
1390 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1391) {
1392
1393 gsl_vector *t_ds = __sur_data->t_ds;
1394 REAL8 t1, t2;
1395 int i, j;
1396 REAL8 k2[11], k3[11], k4[11]; // dydt for all but the first RK4 substep (which will be one of dydt0, dydt1, dydt2, dydt3)
1397 REAL8 *node_data;
1398 REAL8 y_tmp[11];
1399 REAL8 *dydt[4];
1400 dydt[0] = dydt0;
1401 dydt[1] = dydt1;
1402 dydt[2] = dydt2;
1403 dydt[3] = dydt3;
1404 int i_start;
1405
1406 // If possible, do 3 steps backwards with RK4. Otherwise, do 3 steps forwards.
1407 if (i0 > 2) {
1408 i_start = i0 - 3;
1409
1410 // Setup time steps
1411 t1 = gsl_vector_get(t_ds, i_start);
1412 for (i=0; i<3; i++) {
1413 t2 = gsl_vector_get(t_ds, i_start+i+1);
1414 time_steps[i] = t2 - t1;
1415 t1 = t2;
1416 }
1417
1418 // Three steps of RK4 BACKWARDS, so include a minus sign beside all dydts.
1419 for (i=0; i<3; i++ ) {
1420
1421 // Initial substep
1422 node_data = dynamics_data + 11*(i0-i);
1423 PrecessingNRSur_get_time_deriv_from_index(dydt[3-i], i0-i, q, node_data, __sur_data);
1424
1425 // Next substep: evaluate halfway to the next timestep
1426 t1 = 0.5*(gsl_vector_get(t_ds, i0-i) + gsl_vector_get(t_ds, i0-i-1));
1427 for (j=0; j<11; j++) {
1428 y_tmp[j] = node_data[j] - 0.5*time_steps[2-i]*dydt[3-i][j];
1429 }
1430 PrecessingNRSur_get_time_deriv(k2, t1, q, y_tmp, __sur_data);
1431
1432 // Next substep: also halfway, but update y_tmp
1433 for (j=0; j<11; j++) {
1434 y_tmp[j] = node_data[j] - 0.5*time_steps[2-i]*k2[j];
1435 }
1436 PrecessingNRSur_get_time_deriv(k3, t1, q, y_tmp, __sur_data);
1437
1438 // Final substep: evaluate at the next node
1439 for (j=0; j<11; j++) {
1440 y_tmp[j] = node_data[j] - time_steps[2-i]*k3[j];
1441 }
1442 PrecessingNRSur_get_time_deriv_from_index(k4, i0-i-1, q, y_tmp, __sur_data);
1443
1444 // Compute the RK4 expression for the next node, normalize, and store the data
1445 for (j=0; j<11; j++) {
1446 y_tmp[j] = node_data[j] - (time_steps[2-i]/6.0)*(dydt[3-i][j] + 2*k2[j] + 2*k3[j] + k4[j]);
1447 }
1448 PrecessingNRSur_normalize_y(normA, normB, y_tmp);
1449 node_data = node_data - 11;
1450 for (j=0; j<11; j++) {
1451 node_data[j] = y_tmp[j];
1452 }
1453 }
1454 // Finally, we need to compute dydt0;
1455 node_data = dynamics_data + (i0 - 3)*11;
1456 PrecessingNRSur_get_time_deriv_from_index(dydt0, i0-3, q, node_data, __sur_data);
1457 } else {
1458 i_start = i0;
1459
1460 // Setup time steps
1461 t1 = gsl_vector_get(t_ds, i0);
1462 for (i=0; i<3; i++) {
1463 t2 = gsl_vector_get(t_ds, i0+i+1);
1464 time_steps[i] = t2 - t1;
1465 t1 = t2;
1466 }
1467
1468 // Three steps of RK4.
1469 for (i=0; i<3; i++ ) {
1470
1471 // Initial substep
1472 node_data = dynamics_data + 11*(i+i0);
1473 PrecessingNRSur_get_time_deriv_from_index(dydt[i], i0+i, q, node_data, __sur_data);
1474
1475 // Next substep: evaluate halfway to the next timestep
1476 t1 = 0.5*(gsl_vector_get(t_ds, i+i0) + gsl_vector_get(t_ds, i+i0+1));
1477 for (j=0; j<11; j++) {
1478 y_tmp[j] = node_data[j] + 0.5*time_steps[i]*dydt[i][j];
1479 }
1480 PrecessingNRSur_get_time_deriv(k2, t1, q, y_tmp, __sur_data);
1481
1482 // Next substep: also halfway, but update y_tmp
1483 for (j=0; j<11; j++) {
1484 y_tmp[j] = node_data[j] + 0.5*time_steps[i]*k2[j];
1485 }
1486 PrecessingNRSur_get_time_deriv(k3, t1, q, y_tmp, __sur_data);
1487
1488 // Final substep: evaluate at the next node
1489 for (j=0; j<11; j++) {
1490 y_tmp[j] = node_data[j] + time_steps[i]*k3[j];
1491 }
1492 PrecessingNRSur_get_time_deriv_from_index(k4, i0+i+1, q, y_tmp, __sur_data);
1493
1494 // Compute the RK4 expression for the next node, normalize, and store the data
1495 for (j=0; j<11; j++) {
1496 y_tmp[j] = node_data[j] + (time_steps[i]/6.0)*(dydt[i][j] + 2*k2[j] + 2*k3[j] + k4[j]);
1497 }
1498 PrecessingNRSur_normalize_y(normA, normB, y_tmp);
1499 node_data = node_data + 11;
1500 for (j=0; j<11; j++) {
1501 node_data[j] = y_tmp[j];
1502 }
1503 }
1504 // Finally, we need to compute dydt3;
1505 node_data = dynamics_data + (i0 + 3)*11;
1506 PrecessingNRSur_get_time_deriv_from_index(dydt3, i0+3, q, node_data, __sur_data);
1507 }
1508
1509 return i_start;
1510}
1511
1512/**
1513 * Integrates the AB4 ODE system in time forwards, and backwards if needed.
1514 * The system should have already been initialized at 4 adjacent dynamics nodes.
1515 * Output is a flattened array where entries i*11 <= j < (i+1)*11 are
1516 * [q0, qx, qy, qz, varphi, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] at dynamics node i,
1517 * where (q0, qx, qy, qz) is the coprecessing frame quaternion, varphi is the
1518 * orbital phase, and the spin components are in the coprecessing frame.
1519 */
1521 REAL8 *dynamics_data, /**< ODE output */
1522 REAL8 *time_steps, /**< The first three time steps beginning at i_start. */
1523 REAL8 *dydt0, /**< dydt at node i_start */
1524 REAL8 *dydt1, /**< dydt at node i_start + 1 */
1525 REAL8 *dydt2, /**< dydt at node i_start + 2 */
1526 REAL8 *dydt3, /**< dydt at node i_start + 3 */
1527 REAL8 normA, /**< |chiA| */
1528 REAL8 normB, /**< |chiB| */
1529 REAL8 q, /**< mass ratio */
1530 int i_start, /**< nodes i_start through i_start+3 are already initialized */
1531 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1532) {
1533 // At each step, we will use the 4 most recent nodes where we have the solution, and integrate up to the first new node.
1534 REAL8 dt1, dt2, dt3, dt4; // Time steps between the 4 known nodes, as well as up to the first new node (dt4)
1535 REAL8 k1[11], k2[11], k3[11], k4[11]; // dydt(y(t); t) evaluated at the 4 known nodes
1536 REAL8 ynext[11]; // Temporary solution
1537 REAL8 *node_data; // Pointer for writing output
1538 int i, j;
1539
1540 gsl_vector *t_ds = __sur_data->t_ds;
1541
1542 // Initialize to integrate forwards
1543 dt1 = time_steps[0];
1544 dt2 = time_steps[1];
1545 dt3 = time_steps[2];
1546 for (j=0; j<11; j++) {
1547 k1[j] = dydt0[j];
1548 k2[j] = dydt1[j];
1549 k3[j] = dydt2[j];
1550 k4[j] = dydt3[j];
1551 }
1552
1553 // Integrate forwards
1554 node_data = dynamics_data + 11*(i_start + 3); // Point to latest known solution
1555 for (i=i_start+4; i< (int)t_ds->size; i++) { // i indexes the output
1556
1557 // Compute dy = dydt*dt, write it in ynext
1558 dt4 = gsl_vector_get(t_ds, i) - gsl_vector_get(t_ds, i-1);
1559 NRSur_ab4_dy(ynext, k1, k2, k3, k4, dt1, dt2, dt3, dt4, 11);
1560
1561 // Add the latest known node, to obtain y at the next node
1562 for (j=0; j<11; j++) ynext[j] += node_data[j];
1563
1564 // Normalize and write output
1565 PrecessingNRSur_normalize_y(normA, normB, ynext);
1566 node_data += 11;
1567 for (j=0; j<11; j++) node_data[j] = ynext[j];
1568
1569 // Setup for next step
1570 if (i < (int) t_ds->size - 1) {
1571 dt1 = dt2;
1572 dt2 = dt3;
1573 dt3 = dt4;
1574 for (j=0; j<11; j++) {
1575 k1[j] = k2[j];
1576 k2[j] = k3[j];
1577 k3[j] = k4[j];
1578 }
1579 PrecessingNRSur_get_time_deriv_from_index(k4, i, q, node_data, __sur_data);
1580 }
1581 }
1582
1583 // Initialize to integrate backwards
1584 dt1 = -1 * time_steps[2];
1585 dt2 = -1 * time_steps[1];
1586 dt3 = -1 * time_steps[0];
1587 for (j=0; j<11; j++) {
1588 k1[j] = dydt3[j];
1589 k2[j] = dydt2[j];
1590 k3[j] = dydt1[j];
1591 k4[j] = dydt0[j];
1592 }
1593
1594 // Integrate backwards
1595 node_data = dynamics_data + 11*(i_start); // Point to earliest known solution
1596 for (i=i_start-1; i>=0; i--) { // i indexes the output
1597
1598 // Compute dy = dydt*dt, write it in ynext
1599 dt4 = gsl_vector_get(t_ds, i) - gsl_vector_get(t_ds, i+1);
1600 NRSur_ab4_dy(ynext, k1, k2, k3, k4, dt1, dt2, dt3, dt4, 11);
1601
1602 // Add the earliest known node, to obtain y at the previous
1603 for (j=0; j<11; j++) ynext[j] += node_data[j];
1604
1605 // Normalize and write output
1606 PrecessingNRSur_normalize_y(normA, normB, ynext);
1607 node_data -= 11;
1608 for (j=0; j<11; j++) node_data[j] = ynext[j];
1609
1610 // Setup for next step
1611 if (i > 0) {
1612 dt1 = dt2;
1613 dt2 = dt3;
1614 dt3 = dt4;
1615 for (j=0; j<11; j++) {
1616 k1[j] = k2[j];
1617 k2[j] = k3[j];
1618 k3[j] = k4[j];
1619 }
1620 PrecessingNRSur_get_time_deriv_from_index(k4, i, q, node_data, __sur_data);
1621 }
1622 }
1623}
1624
1625/**
1626 * Evaluates a single NRSur coorbital waveoform data piece.
1627 * The dynamics ODE must have already been solved, since this requires the
1628 * spins evaluated at all of the empirical nodes for this waveform data piece.
1629 */
1631 gsl_vector *result, /**< Output: Should have already been assigned space */
1632 REAL8 q, /**< Mass ratio */
1633 gsl_vector **chiA, /**< 3 gsl_vector *s, one for each (coorbital) component */
1634 gsl_vector **chiB, /**< similar to chiA */
1635 WaveformDataPiece *data, /**< The data piece to evaluate */
1636 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
1637) {
1638
1639 gsl_vector *nodes = gsl_vector_alloc(data->n_nodes);
1640 REAL8 x[7];
1641 int i, j, node_index;
1642
1643 // Evaluate the fits at the empirical nodes, using the spins at the empirical node times
1644 x[0] = q;
1645 for (i=0; i<data->n_nodes; i++) {
1646 node_index = gsl_vector_long_get(data->empirical_node_indices, i);
1647 for (j=0; j<3; j++) {
1648 x[1+j] = gsl_vector_get(chiA[j], node_index);
1649 x[4+j] = gsl_vector_get(chiB[j], node_index);
1650 }
1651 gsl_vector_set(nodes, i, PrecessingNRSur_eval_fit(data->fit_data[i], x, __sur_data));
1652 }
1653
1654 // Evaluate the empirical interpolant
1655 gsl_blas_dgemv(CblasTrans, 1.0, data->empirical_interpolant_basis, nodes, 0.0, result);
1656
1657 gsl_vector_free(nodes);
1658}
1659
1660/************************ Main Waveform Generation Routines ***********/
1661
1662/**
1663 * This is the main NRSur dynamics surrogate integration routine.
1664 * Given omega_ref and the system parameters at omega_ref, we find the corresponding t_ref
1665 * and first (if needed) take a tiny time step and evolve the system to the nearest
1666 * dynamics node.
1667 * We then initialize the AB4 ODE system at 4 consecutive dynamics nodes by taking 3 RK4 steps.
1668 * Finally, we integrate forwards (and backwards if needed) to obtain the solution at all
1669 * dynamics nodes.
1670 * Output is a flattened array where entries i*11 <= j < (i+1)*11 are
1671 * [q0, qx, qy, qz, varphi, chiAx, chiAy, chiAz, chiBx, chiBy, chiBz] at dynamics node i,
1672 * where (q0, qx, qy, qz) is the coprecessing frame quaternion, varphi is the
1673 * orbital phase, and the spin components are in the coprecessing frame.
1674 */
1676 REAL8 *dynamics_data, /**< Output: length (n_dynamics_nodes * 11) */
1677 REAL8 q, /**< Mass ratio mA / mB */
1678 REAL8 *chiA0, /**< chiA at the reference point */
1679 REAL8 *chiB0, /**< chiB at the reference point */
1680 REAL8 omega_ref, /**< orbital angular frequency at reference point */
1681 REAL8 init_orbphase, /**< orbital phase at the reference point */
1682 REAL8 *init_quat, /**< coprecessing quaterion at the reference point */
1683 LALDict* LALparams, /**< Dict with extra parameters */
1684 UINT4 PrecessingNRSurVersion /**< 0 for NRSur7dq2, 1 for NRSur7dq4 */
1685) {
1686
1687 // By default we do not allow unlimited_extrapolation
1688 UINT4 unlim_extrap = 0;
1689 if (LALparams != NULL &&
1690 XLALDictContains(LALparams, "unlimited_extrapolation")) {
1691 // Unless the user asks for it
1692 unlim_extrap
1693 = XLALDictLookupUINT4Value(LALparams, "unlimited_extrapolation");
1694 }
1695
1696 REAL8 normA = sqrt(chiA0[0]*chiA0[0] + chiA0[1]*chiA0[1] + chiA0[2]*chiA0[2]);
1697 REAL8 normB = sqrt(chiB0[0]*chiB0[0] + chiB0[1]*chiB0[1] + chiB0[2]*chiB0[2]);
1698
1699 // Sanity checks and warnings
1700 REAL8 Q_MAX = 1;
1701 REAL8 CHI_MAX = 0;
1702 REAL8 Q_MAX_WARN = 1;
1703 REAL8 CHI_MAX_WARN = 0;
1704 PrecessingNRSurData *__sur_data;
1705 if (PrecessingNRSurVersion == 0) {
1707 CHI_MAX = NRSUR7DQ2_CHI_MAX;
1708 Q_MAX_WARN = NRSUR7DQ2_Q_MAX_WARN;
1709 CHI_MAX_WARN = NRSUR7DQ2_CHI_MAX_WARN;
1710 __sur_data = &__lalsim_NRSur7dq2_data;
1711 } else if (PrecessingNRSurVersion == 1) {
1713 CHI_MAX = NRSUR7DQ4_CHI_MAX;
1714 Q_MAX_WARN = NRSUR7DQ4_Q_MAX_WARN;
1715 CHI_MAX_WARN = NRSUR7DQ4_CHI_MAX_WARN;
1716 __sur_data = &__lalsim_NRSur7dq4_data;
1717 } else {
1719 "Only 0 or 1 are currently allowed for PrecessingNRSurVersion\n");
1720 }
1721
1722 if (q < 0.999) {
1724 "Invalid mass ratio q = %0.4f < 1\n", q);
1725 }
1726 if ((q > Q_MAX) && (unlim_extrap == 0)) {
1728 "Too much extrapolation. Mass ratio q = %0.4f > %0.4f, the "
1729 "maximum allowed value.\n", q, Q_MAX);
1730 }
1731 if (q > Q_MAX_WARN) {
1733 "Extrapolating to mass ratio q = %0.4f > %0.4f, the maximum "
1734 "mass ratio used to train the surrogate.\n", q, Q_MAX_WARN);
1735 }
1736 if ((normA > CHI_MAX) && (unlim_extrap == 0)) {
1738 "Too much extrapolation. Spin magnitude |chiA| = %0.4f > %.04f "
1739 "the maximum allowed .\n", normA, CHI_MAX);
1740 }
1741 if ((normB > CHI_MAX) && (unlim_extrap == 0)) {
1743 "Too much extrapolation. Spin magnitude |chiB| = %0.4f > %.04f "
1744 "the maximum allowed value.\n", normB, CHI_MAX);
1745 }
1746 if (normA > CHI_MAX_WARN) {
1748 "Extrapolating to spin magnitude |chiA| = %0.4f > %0.4f, the "
1749 "maximum spin magnitude used to train the surrogate.\n",
1750 normA, CHI_MAX_WARN);
1751 }
1752 if (normB > CHI_MAX_WARN) {
1754 "Extrapolating to spin magnitude |chiB| = %0.4f > %0.4f, the "
1755 "maximum spin magnitude used to train the surrogate.\n",
1756 normB, CHI_MAX_WARN);
1757 }
1758
1759 REAL8 t_ref = PrecessingNRSur_get_t_ref(omega_ref, q, chiA0, chiB0,
1760 init_quat, init_orbphase, __sur_data);
1761 if (XLAL_IS_REAL8_FAIL_NAN(t_ref)) {
1762 XLAL_ERROR_REAL8(XLAL_FAILURE, "Failed to determine t_ref");
1763 }
1764
1765 // Initialize the ODE system by stepping to a dynamics node indexed by i0
1766 int i0 = PrecessingNRSur_initialize_at_dynamics_node(dynamics_data, t_ref, q, chiA0, chiB0, init_orbphase, init_quat, normA, normB, __sur_data);
1767
1768 // To initialize the RK4 ODE integration scheme, we need y to be evaluated at 4 consecutive nodes. Currently we have 1.
1769 // The original method assumed we always use t_ref=-4500M and took 3 steps using RK4, making use of the time nodes 1/2, 3/2, and 5/2.
1770 // If i0=0, we can use that scheme and obtain y at nodes 0, 1, 2, and 3. Otherwise, obtain 4 consecutive nodes starting at i_start,
1771 // where i_start = max(0, i0 - 3).
1772 int i_start;
1773 REAL8 time_steps[3];
1774 REAL8 dydt0[11], dydt1[11], dydt2[11], dydt3[11];
1775
1776 if (i0 == 0) {
1777 PrecessingNRSur_initialize_RK4_with_half_nodes(dynamics_data, time_steps, dydt0, dydt1, dydt2, dydt3, normA, normB, q, __sur_data);
1778 i_start = 0;
1779 } else {
1780 i_start = PrecessingNRSur_initialize_RK4(dynamics_data, time_steps, dydt0, dydt1, dydt2, dydt3, normA, normB, q, i0, __sur_data);
1781 }
1782
1783 // Now that we have 4 consecutive evaluated nodes beginning at i_start, we can integrate from i_start backwards to 0
1784 // with AB4 as well as from i_start+3 to the end
1785 PrecessingNRSur_integrate_AB4(dynamics_data, time_steps, dydt0, dydt1, dydt2, dydt3, normA, normB, q, i_start, __sur_data);
1786 return XLAL_SUCCESS;
1787}
1788
1789
1790/**
1791 * Wrapper for Loading NRSur7dq2 and NRSur7dq4 data.
1792 *
1793 * Calls NRSur7dq2_Init_LALDATA() or NRSur7dq4_Init_LALDATA() if surrogate data
1794 * is not already loaded, and returns loaded surrogate data. If surrogate data
1795 * is already loaded, just returns the loaded data.
1796 */
1798 UNUSED Approximant approximant /**< approximant (NRSur7dq2 or NRSur7dq4) */
1799) {
1800
1801 PrecessingNRSurData *__sur_data;
1802
1803 #if LAL_HDF5_ENABLED
1804 switch (approximant) {
1805 case NRSur7dq2:
1806 #ifdef LAL_PTHREAD_LOCK
1807 (void) pthread_once(&NRSur7dq2_is_initialized,
1808 NRSur7dq2_Init_LALDATA);
1809 #else
1810 NRSur7dq2_Init_LALDATA();
1811 #endif
1812 __sur_data = &__lalsim_NRSur7dq2_data;
1813 // 0 is for NRSur7dq2
1814 __sur_data->PrecessingNRSurVersion = 0;
1815 break;
1816
1817 case NRSur7dq4:
1818 #ifdef LAL_PTHREAD_LOCK
1819 (void) pthread_once(&NRSur7dq4_is_initialized,
1820 NRSur7dq4_Init_LALDATA);
1821 #else
1822 NRSur7dq4_Init_LALDATA();
1823 #endif
1824 __sur_data = &__lalsim_NRSur7dq4_data;
1825 // 1 is for NRSur7dq2
1826 __sur_data->PrecessingNRSurVersion = 1;
1827 break;
1828
1829 default:
1830 XLAL_ERROR_NULL(XLAL_EINVAL, "Invalid approximant, only NRSur7dq2"
1831 " and NRSur7dq4 are allowed.\n");
1832 }
1833 #else
1834 XLAL_ERROR_NULL(XLAL_EINVAL, "Cannot load approximant, HDF5 support is required.\n");
1835 #endif
1836
1837 return __sur_data;
1838}
1839
1840
1841
1842
1843/* This is the core function of the NRSur7dq2 and NRSur7dq4 models.
1844 * It evaluates the model, and returns waveform modes in the inertial frame, sampled on t_coorb.
1845 * When using a custom ModeArray the user must explicitly supply all modes, -ve and +ve m-modes.
1846 * Note that the co-orbital frame modes are specified in ModeArray, not the inertial frame modes.
1847 */
1849 MultiModalWaveform **h, /**< Output. Dimensionless waveform modes sampled on t_coorb */
1850 REAL8 q, /**< Mass ratio mA / mB */
1851 REAL8 *chiA0, /**< chiA at the reference point */
1852 REAL8 *chiB0, /**< chiB at the reference point */
1853 REAL8 omega_ref, /**< orbital angular frequency at the reference point */
1854 REAL8 init_orbphase, /**< orbital phase at the reference point */
1855 REAL8 *init_quat, /**< coprecessing quaterion at the reference point */
1856 LALValue* ModeArray, /**< Container for the ell and m co-orbital modes to generate */
1857 LALDict* LALparams, /**< Dict with extra parameters */
1858 Approximant approximant /**< approximant (NRSur7dq2 or NRSur7dq4) */
1859) {
1860
1861 // Load surrogate data if needed. If not, just access the loaded data.
1863 if (!__sur_data->setup) {
1864 XLAL_ERROR_NULL(XLAL_EFAILED, "Error loading surrogate data.\n");
1865 }
1866
1867 // Make sure we didn't request any unavailable modes
1868 int ell, m;
1869 for (ell=NRSUR_LMAX+1; ell <= LAL_SIM_L_MAX_MODE_ARRAY; ell++) {
1870 for (m=-ell; m<=ell; m++) {
1871 if (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, m) == 1) {
1872 XLAL_ERROR_NULL(XLAL_EINVAL, "A requested mode has ell larger than available");
1873 }
1874 }
1875 }
1876
1877 // time arrays
1878 gsl_vector *t_ds = __sur_data->t_ds;
1879 gsl_vector *t_coorb = __sur_data->t_coorb;
1880 int n_ds = t_ds->size;
1881 int n_coorb = t_coorb->size;
1882
1883 // Get dynamics
1884 REAL8 *dynamics_data = XLALCalloc(n_ds * 11, sizeof(REAL8));
1885
1886 int ret = PrecessingNRSur_IntegrateDynamics(dynamics_data, q, chiA0, chiB0,
1887 omega_ref, init_orbphase, init_quat, LALparams,
1888 __sur_data->PrecessingNRSurVersion);
1889 if(ret != XLAL_SUCCESS) {
1890 XLALFree(dynamics_data);
1891 XLAL_ERROR_NULL(XLAL_FAILURE, "Failed to integrate dynamics");
1892 }
1893
1894 // Put output into appropriate vectors
1895 int i, j;
1896 gsl_vector *dynamics[11];
1897 for (j=0; j<11; j++) {
1898 dynamics[j] = gsl_vector_alloc(n_ds);
1899 }
1900 for (i=0; i<n_ds; i++) {
1901 for (j=0; j<11; j++) {
1902 gsl_vector_set(dynamics[j], i, dynamics_data[11*i + j]);
1903 }
1904 }
1905
1906 XLALFree(dynamics_data);
1907
1908 // Interpolate onto the coorbital time grid
1909 // NOTE: The spins are currently in the coprecessing frame, they are called
1910 // chiA_coorb because they will be overwritten below with the coorbital frame
1911 // spins.
1912 gsl_vector *quat_coorb[4], *chiA_coorb[3], *chiB_coorb[3];
1913 quat_coorb[0] = spline_array_interp(t_coorb, t_ds, dynamics[0]);
1914 quat_coorb[1] = spline_array_interp(t_coorb, t_ds, dynamics[1]);
1915 quat_coorb[2] = spline_array_interp(t_coorb, t_ds, dynamics[2]);
1916 quat_coorb[3] = spline_array_interp(t_coorb, t_ds, dynamics[3]);
1917 gsl_vector *phi_coorb = spline_array_interp(t_coorb, t_ds, dynamics[4]);
1918 chiA_coorb[0] = spline_array_interp(t_coorb, t_ds, dynamics[5]);
1919 chiA_coorb[1] = spline_array_interp(t_coorb, t_ds, dynamics[6]);
1920 chiA_coorb[2] = spline_array_interp(t_coorb, t_ds, dynamics[7]);
1921 chiB_coorb[0] = spline_array_interp(t_coorb, t_ds, dynamics[8]);
1922 chiB_coorb[1] = spline_array_interp(t_coorb, t_ds, dynamics[9]);
1923 chiB_coorb[2] = spline_array_interp(t_coorb, t_ds, dynamics[10]);
1924
1925 for (j=0; j<11; j++) {
1926 gsl_vector_free(dynamics[j]);
1927 }
1928
1929 // Normalize spins after interpolation
1930 REAL8 normA = sqrt(chiA0[0]*chiA0[0] + chiA0[1]*chiA0[1] + chiA0[2]*chiA0[2]);
1931 REAL8 normB = sqrt(chiB0[0]*chiB0[0] + chiB0[1]*chiB0[1] + chiB0[2]*chiB0[2]);
1932 PrecessingNRSur_normalize_results(normA, normB, quat_coorb, chiA_coorb, chiB_coorb);
1933
1934 // Transform spins from coprecessing frame to coorbital frame for use in coorbital waveform surrogate
1935 PrecessingNRSur_rotate_spins(chiA_coorb, chiB_coorb, phi_coorb);
1936
1937 // Evaluate the coorbital waveform surrogate
1938 MultiModalWaveform *h_coorb = NULL;
1939 MultiModalWaveform_Init(&h_coorb, NRSUR_LMAX, n_coorb);
1940 gsl_vector *data_piece_eval = gsl_vector_alloc(n_coorb);
1941 WaveformDataPiece *data_piece_data;
1942 int i0; // for indexing the (ell, m=0) mode, such that the (ell, m) mode is index (i0 + m).
1943 WaveformFixedEllModeData *ell_data;
1944 for (ell=2; ell<=NRSUR_LMAX; ell++) {
1945 ell_data = __sur_data->coorbital_mode_data[ell - 2];
1946 i0 = ell*(ell+1) - 4;
1947
1948 // m=0
1949 if (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, 0) != 1) {
1950 }
1951 else {
1952 data_piece_data = ell_data->m0_real_data;
1953 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1954 gsl_vector_add(h_coorb->modes_real_part[i0], data_piece_eval);
1955
1956 data_piece_data = ell_data->m0_imag_data;
1957 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1958 gsl_vector_add(h_coorb->modes_imag_part[i0], data_piece_eval);
1959 }
1960
1961 // Other modes
1962 for (m=1; m<=ell; m++) {
1963 if ((XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, m) != 1) &&
1964 (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, -m) != 1)) {
1965 continue;
1966 }
1967
1968 // h^{ell, m} = X_plus + X_minus
1969 // h^{ell, -m} = (X_plus - X_minus)* <- complex conjugate
1970
1971 // Re[X_plus] gets added to both Re[h^{ell, m}] and Re[h^{ell, -m}]
1972 data_piece_data = ell_data->X_real_plus_data[m-1];
1973 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1974 gsl_vector_add(h_coorb->modes_real_part[i0+m], data_piece_eval);
1975 gsl_vector_add(h_coorb->modes_real_part[i0-m], data_piece_eval);
1976
1977 // Re[X_minus] gets added to Re[h^{ell, m}] and subtracted from Re[h^{ell, -m}]
1978 data_piece_data = ell_data->X_real_minus_data[m-1];
1979 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1980 gsl_vector_add(h_coorb->modes_real_part[i0+m], data_piece_eval);
1981 gsl_vector_sub(h_coorb->modes_real_part[i0-m], data_piece_eval);
1982
1983 // Im[X_plus] gets added to Re[h^{ell, m}] and subtracted from Re[h^{ell, -m}]
1984 data_piece_data = ell_data->X_imag_plus_data[m-1];
1985 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1986 gsl_vector_add(h_coorb->modes_imag_part[i0+m], data_piece_eval);
1987 gsl_vector_sub(h_coorb->modes_imag_part[i0-m], data_piece_eval);
1988
1989 // Im[X_minus] gets added to both Re[h^{ell, m}] and Re[h^{ell, -m}]
1990 data_piece_data = ell_data->X_imag_minus_data[m-1];
1991 PrecessingNRSur_eval_data_piece(data_piece_eval, q, chiA_coorb, chiB_coorb, data_piece_data, __sur_data);
1992 gsl_vector_add(h_coorb->modes_imag_part[i0+m], data_piece_eval);
1993 gsl_vector_add(h_coorb->modes_imag_part[i0-m], data_piece_eval);
1994 }
1995 }
1996
1997 // Rotate to the inertial frame, write results in h
1999 TransformModesCoorbitalToInertial(*h, h_coorb, quat_coorb, phi_coorb);
2000
2001 // Cleanup
2003 for (i=0; i<3; i++) {
2004 gsl_vector_free(chiA_coorb[i]);
2005 gsl_vector_free(chiB_coorb[i]);
2006 gsl_vector_free(quat_coorb[i]);
2007 }
2008 gsl_vector_free(quat_coorb[3]);
2009 gsl_vector_free(phi_coorb);
2010 gsl_vector_free(data_piece_eval);
2011
2012 return __sur_data;
2013}
2014
2015/**
2016 * Computes the starting frequency of the NRSur7dq2 or NRSur7dq4 waveform
2017 * approximant when evaluated using fRef=0 (which uses the entire surrogate
2018 * waveform starting 4500M for NRSur7dq2 and 4300M for NRSur7dq4, before the
2019 * peak amplitude).
2020 */
2022 REAL8 m1, /**< mass of companion 1 (kg) */
2023 REAL8 m2, /**< mass of companion 2 (kg) */
2024 REAL8 s1x, /**< initial value of S1x */
2025 REAL8 s1y, /**< initial value of S1y */
2026 REAL8 s1z, /**< initial value of S1z */
2027 REAL8 s2x, /**< initial value of S2x */
2028 REAL8 s2y, /**< initial value of S2y */
2029 REAL8 s2z, /**< initial value of S2z */
2030 PrecessingNRSurData *__sur_data /**< Loaded surrogate data */
2031
2032) {
2033 REAL8 Mtot = (m1 + m2) / LAL_MSUN_SI;
2034 REAL8 Mtot_sec = Mtot * LAL_MTSUN_SI; /* Total mass in seconds */
2035 REAL8 q = m1 / m2;
2036 REAL8 y0[11];
2037 y0[0] = 1.0; // Scalar component of reference quaternion
2038 int i;
2039 for (i=1; i<4; i++) y0[i] = 0.0; // Vector components of quaternion
2040 y0[4] = 0.0; // Reference orbital phase - doesn't affect frequency
2041 y0[5] = s1x;
2042 y0[6] = s1y;
2043 y0[7] = s1z;
2044 y0[8] = s2x;
2045 y0[9] = s2y;
2046 y0[10] = s2z;
2047 REAL8 omega_dimless_start = PrecessingNRSur_get_omega(0, q, y0, __sur_data);
2048 // GW freq is roughly twice the orbital freq
2049 REAL8 f_start_Hz = omega_dimless_start / (LAL_PI * Mtot_sec);
2050 return f_start_Hz;
2051}
2052
2053/**
2054 * If m1<m2, swaps the labels for the two BHs such that Bh1 is always heavier.
2055 * Then rotates the in-plane spins by pi. These two together are the same
2056 * as a rigid rotation of the system by pi. This rotation will be undone by
2057 * multiplying the odd-m modes by a minus sign after the modes are generated.
2058 */
2060 REAL8 *m1, /**< Input and Output. mass of companion 1 (kg) */
2061 REAL8 *m2, /**< Input and Output. mass of companion 2 (kg) */
2062 REAL8 *s1x, /**< Input and Output. S1x at reference epoch */
2063 REAL8 *s1y, /**< Input and Output. S1y at reference epoch */
2064 REAL8 *s1z, /**< Input and Output. S1z at reference epoch */
2065 REAL8 *s2x, /**< Input and Output. S2x at reference epoch */
2066 REAL8 *s2y, /**< Input and Output. S2y at reference epoch */
2067 REAL8 *s2z /**< Input and Output. S2z at reference epoch */
2068) {
2069
2070 // BH A is defined to be the one with the larger mass, BH B with the
2071 // smaller mass.
2072 if (*m1 < *m2) {
2073 // Switch the labels for the two BHs
2074 REAL8 tmp = *m1;
2075 *m1 = *m2;
2076 *m2 = tmp;
2077 // For the in-plane spins, also change the signs after swapping. This
2078 // is the same as roting the spins about the z-axis by pi.
2079 tmp = *s1x;
2080 *s1x = -*s2x;
2081 *s2x = -tmp;
2082 tmp = *s1y;
2083 *s1y = -*s2y;
2084 *s2y = -tmp;
2085 // No sign change for z-spins
2086 tmp = *s1z;
2087 *s1z = *s2z;
2088 *s2z = tmp;
2089
2090 return true;
2091 } else {
2092 return false;
2093 }
2094}
2095
2096
2097/**
2098 * This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and sums
2099 * over all ell <= 4 modes to obtain the + and x polarizations.
2100 * Papers:
2101 * NRSur7dq2: https://arxiv.org/abs/1705.07089
2102 * NRSur7dq4: https://arxiv.org/abs/1905.09300
2103 *
2104 * The system is initialized at a time where the orbital frequency of the
2105 * waveform in the coprecessing frame (Eq.11 of
2106 * https://arxiv.org/abs/1705.07089) is pi * fRef. At this reference point, the
2107 * system is initialized in the coorbital frame, with the z-axis along the
2108 * principal eigenvector of the angular momentum operator acting on the
2109 * waveform, the x-axis along the line of separation from the lighter BH to the
2110 * heavier BH, and the y-axis completing the triad. The polarizations of the
2111 * waveform are returned in the sky of this frame at
2112 * (inclination, pi/2 - phiRef). This agrees with the LAL convention.
2113 *
2114 * Extra options are passed through a LALDict:
2115 * dictParams = lal.CreateDict()
2116 *
2117 * 1. Mode options: When using a custom ModeArray the user must explicitly
2118 * supply all modes, -ve and +ve m-modes. Note that these are modes in the
2119 * co-orbital frame, not the inertial frame.
2120 * Examples:
2121 * # First, create the 'empty' mode array
2122 * ma=lalsim.SimInspiralCreateModeArray()
2123 *
2124 * 1a. If you want all models upto ellMax:
2125 * # add the modes (these are in the coorbital frame)
2126 * for ell in range(2, ellMax+1):
2127 * lalsim.SimInspiralModeArrayActivateAllModesAtL(ma, ell)
2128 * # then insert your ModeArray into the LALDict params with
2129 * lalsim.SimInspiralWaveformParamsInsertModeArray(dictParams, ma)
2130 *
2131 * 1b. If you want only a single mode:
2132 * # add (l,m) and (l,-m) modes (these are in the coorbital frame)
2133 * lalsim.SimInspiralModeArrayActivateMode(ma, l, m)
2134 * lalsim.SimInspiralModeArrayActivateMode(ma, l, -m)
2135 * # then insert your ModeArray into the LALDict params with
2136 * lalsim.SimInspiralWaveformParamsInsertModeArray(params, ma)
2137 *
2138 * 2. Extrapolation: The surrogate models are trained on the following ranges.
2139 * NRSur7dq2: q <= 2.01, |chi_1|, |chi_2| <= 0.81.
2140 * NRSur7dq4: q <= 4.01, |chi_1|, |chi_2| <= 0.81.
2141 * If you want a guarantee of accuracy you should stick to the above ranges.
2142 *
2143 * We allow extrapolation to the following ranges, but with a warning:
2144 * NRSur7dq2: q <= 3.01, |chi_1|, |chi_2| <= 1.
2145 * NRSur7dq4: q <= 6.01, |chi_1|, |chi_2| <= 1.
2146 * We expect the models to be reasonable when extrapolated to these ranges.
2147 *
2148 * Beyond the above ranges, we raise an error. If you want to extrapolate
2149 * beyond these limits you can specify unlimited_extrapolation = 1 in your
2150 * dictParams as follows:
2151 * # USE AT YOUR OWN RISK!!
2152 * lal.DictInsertUINT4Value(dictParams, "unlimited_extrapolation", 1)
2153 */
2155 REAL8TimeSeries **hplus, /**< OUTPUT h_+ vector */
2156 REAL8TimeSeries **hcross, /**< OUTPUT h_x vector */
2157 REAL8 phiRef, /**< azimuthal angle for Ylms */
2158 REAL8 inclination, /**< inclination angle */
2159 REAL8 deltaT, /**< sampling interval (s) */
2160 REAL8 m1, /**< mass of companion 1 (kg) */
2161 REAL8 m2, /**< mass of companion 2 (kg) */
2162 REAL8 distance, /**< distance of source (m) */
2163 REAL8 fMin, /**< start GW frequency (Hz) */
2164 REAL8 fRef, /**< reference GW frequency (Hz) */
2165 REAL8 s1x, /**< initial value of S1x */
2166 REAL8 s1y, /**< initial value of S1y */
2167 REAL8 s1z, /**< initial value of S1z */
2168 REAL8 s2x, /**< initial value of S2x */
2169 REAL8 s2y, /**< initial value of S2y */
2170 REAL8 s2z, /**< initial value of S2z */
2171 LALDict* LALparams, /**< Dict with extra parameters */
2172 Approximant approximant /**< approximant (NRSur7dq2 or NRSur7dq4) */
2173) {
2174
2175 LALValue* ModeArray
2177
2178 int ell, m;
2179 if ( ModeArray == NULL ) {
2180 // Use all available modes
2181 ModeArray = XLALSimInspiralCreateModeArray();
2182 for (ell=2; ell <= NRSUR_LMAX; ell++) {
2184 }
2185 } // Otherwise, use the specified modes.
2186
2187 // If m1 < m2, switch the BH labels so that q = m1/m2 >= 1 always.
2188 bool labels_switched = PrecessingNRSur_switch_labels_if_needed(&m1, &m2,
2189 &s1x, &s1y, &s1z, &s2x, &s2y, &s2z);
2190
2191 // Parameters
2192 REAL8 massA = m1 / LAL_MSUN_SI;
2193 REAL8 massB = m2 / LAL_MSUN_SI;
2194 REAL8 Mtot = massA+massB;
2195 REAL8 Mtot_sec = Mtot * LAL_MTSUN_SI; /* Total mass in seconds */
2196 REAL8 q = massA / massB;
2197 REAL8 chiA0[3], chiB0[3];
2198 chiA0[0] = s1x;
2199 chiA0[1] = s1y;
2200 chiA0[2] = s1z;
2201 chiB0[0] = s2x;
2202 chiB0[1] = s2y;
2203 chiB0[2] = s2z;
2204
2205 REAL8 omegaMin_dimless;
2206 REAL8 omegaRef_dimless;
2207 int ret = get_dimless_omega(&omegaMin_dimless, &omegaRef_dimless,
2208 fMin, fRef, Mtot_sec);
2209 if(ret != XLAL_SUCCESS) {
2210 if(ModeArray) XLALDestroyValue(ModeArray);
2211 XLAL_ERROR(XLAL_EFUNC, "Failed to process fMin/fRef");
2212 }
2213
2214 // When generating the modes take the inertial frame to be aligned with
2215 // the coorbital frame at the reference point. This means that the
2216 // quaternion from inertial frame to coprecessing frame is identity, and
2217 // the init_orbphase = 0 between the coprecessing frame and the coorbital
2218 // frame at the reference point. This agrees with the LAL convention, and
2219 // phiRef should only used in the Ylms when computing the polarizations.
2220 REAL8 init_quat[4];
2221 init_quat[0] = 1.0;
2222 init_quat[1] = 0.0;
2223 init_quat[2] = 0.0;
2224 init_quat[3] = 0.0;
2225 REAL8 init_orbphase = 0;
2226
2227 // Evaluate the model modes
2228 MultiModalWaveform *h_inertial_modes = NULL;
2229 PrecessingNRSurData *__sur_data = PrecessingNRSur_core(&h_inertial_modes,
2230 q, chiA0, chiB0, omegaRef_dimless, init_orbphase, init_quat,
2231 ModeArray, LALparams, approximant);
2232
2233 if (!h_inertial_modes) {
2234 if(ModeArray) XLALDestroyValue(ModeArray);
2235 return XLAL_FAILURE;
2236 }
2237
2238 // Setup the time series for h_+ and h_x evaluated on the surrogate time array
2239 size_t length = h_inertial_modes->n_times;
2240 gsl_vector *hplus_model_times = gsl_vector_calloc(length);
2241 gsl_vector *hcross_model_times = gsl_vector_calloc(length);
2242
2243 // Sum up the modes
2244 COMPLEX16 swsh_coef;// = XLALSpinWeightedSphericalHarmonic(spheretheta, spherephi, -2, swsh_L, swsh_m);
2245 REAL8 c_re, c_im;// = creal(swsh_coef);
2246 REAL8 hmre, hmim;
2247 int i, j;
2248 bool skip_ell_modes;
2249 i=0;
2250 REAL8 prefactor = 1;
2251 for (ell=2; ell <= NRSUR_LMAX; ell++) {
2252 /* If *any* co-orbital frame mode of this ell is non-zero we need to
2253 * sum over all modes of this ell, as the frame rotation will have
2254 * mixed the modes. */
2255 skip_ell_modes = true;
2256 for (m=-ell; m<=ell; m++) {
2257 if (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, m) == 1) {
2258 skip_ell_modes = false;
2259 }
2260 }
2261 if (skip_ell_modes) {
2262 i += 2*ell + 1;
2263 continue;
2264 }
2265 for (m=-ell; m<=ell; m++) {
2266 /* See Harald Pfeiffer, T18002260-v1 for frame diagram. The
2267 * surrogate frame has its z direction aligned with the orbital
2268 * angular momentum, which agrees with the lowercase source frame z
2269 * in the diagram. The surrogate x direction, however, points along
2270 * the line of ascending nodes (the funny omega with circles on the
2271 * ends). The uppercase Z, which is the direction along which we
2272 * want to evaluate the waveform, is always in the surrogate frame
2273 * (y, z) plane. Z is rotated from z towards the +ive y surrogate
2274 * axis, so we should always evaluate at
2275 * (inclination, pi/2-phiRef). */
2276 swsh_coef = XLALSpinWeightedSphericalHarmonic(inclination,
2277 0.5 * LAL_PI - phiRef, -2, ell, m);
2278 c_re = creal(swsh_coef);
2279 c_im = cimag(swsh_coef);
2280
2281 // If m1 < m2, the BH labels were switched so that m1 > m2. This
2282 // generates the requested waveform but with a rotation of pi about
2283 // the z-axis. We now undo this by multiplying all odd-m modes by
2284 // -1, which is the same as a pi rotation; the even-m modes don't
2285 // need this fix as they are insensitive to pi rotations about
2286 // z-axis.
2287 if ((m%2 != 0) && (labels_switched)) {
2288 prefactor = -1;
2289 } else {
2290 prefactor = 1;
2291 }
2292 for (j=0; j < (int)length; j++) {
2293 hmre = gsl_vector_get(h_inertial_modes->modes_real_part[i], j);
2294 hmim = gsl_vector_get(h_inertial_modes->modes_imag_part[i], j);
2295 hplus_model_times->data[j] += prefactor
2296 * (c_re * hmre - c_im * hmim);
2297 hcross_model_times->data[j] -= prefactor
2298 * (c_im * hmre + c_re * hmim); // - sign as h = h_+ - i*h_x
2299 }
2300 i += 1;
2301 }
2302 }
2303
2304 // Scale the amplitude based on the distance
2305 REAL8 a0 = Mtot_sec * LAL_C_SI / distance;
2306 gsl_vector_scale(hplus_model_times, a0);
2307 gsl_vector_scale(hcross_model_times, a0);
2308
2309 // Setup output times
2310 gsl_vector *model_times = __sur_data->t_coorb;
2311 REAL8 deltaT_dimless = deltaT / Mtot_sec;
2312 REAL8 t0 = gsl_vector_get(model_times, 0);
2313 REAL8 start_freq = PrecessingNRSur_StartFrequency(m1, m2, s1x, s1y, s1z, s2x, s2y, s2z, __sur_data);
2314 if (fMin >= start_freq) {
2315 t0 = PrecessingNRSur_get_t_ref(omegaMin_dimless, q, chiA0, chiB0,
2316 init_quat, init_orbphase, __sur_data);
2317 } else if (fMin > 0) {
2318 // Cleanup and exit
2319 if(ModeArray) XLALDestroyValue(ModeArray);
2320 gsl_vector_free(hplus_model_times);
2321 gsl_vector_free(hcross_model_times);
2322 MultiModalWaveform_Destroy(h_inertial_modes);
2323 XLAL_ERROR_REAL8(XLAL_EDOM, "fMin should be 0 or >= %0.8f for this configuration, got %0.8f", start_freq, fMin);
2324 }
2325 REAL8 tf = gsl_vector_get(model_times, length-1);
2326 int nt = (int) ceil((tf - t0) / deltaT_dimless);
2327 gsl_vector *output_times = gsl_vector_alloc(nt);
2328 for (j=0; j<nt; j++) {
2329 gsl_vector_set(output_times, j, t0 + deltaT_dimless*j);
2330 }
2331
2332 // Interpolate onto output times
2333 REAL8 t;
2335 XLALGPSAdd( &epoch, Mtot_sec * t0);
2336 *hplus = XLALCreateREAL8TimeSeries("hp: TD waveform", &epoch, 0.0, deltaT, &lalStrainUnit, nt);
2337 *hcross = XLALCreateREAL8TimeSeries("hc: TD waveform", &epoch, 0.0, deltaT, &lalStrainUnit, nt);
2338 gsl_interp_accel *acc = gsl_interp_accel_alloc();
2339 gsl_spline *spl_hplus = gsl_spline_alloc(gsl_interp_cspline, length);
2340 gsl_spline *spl_hcross = gsl_spline_alloc(gsl_interp_cspline, length);
2341 gsl_spline_init(spl_hplus, model_times->data, hplus_model_times->data, length);
2342 gsl_spline_init(spl_hcross, model_times->data, hcross_model_times->data, length);
2343 for (j=0; j<nt; j++) {
2344 t = gsl_vector_get(output_times, j);
2345 (*hplus)->data->data[j] = gsl_spline_eval(spl_hplus, t, acc);
2346 (*hcross)->data->data[j] = gsl_spline_eval(spl_hcross, t, acc);
2347 }
2348
2349 // Cleanup
2350 gsl_vector_free(hplus_model_times);
2351 gsl_vector_free(hcross_model_times);
2352 gsl_vector_free(output_times);
2353 gsl_interp_accel_free(acc);
2354 gsl_spline_free(spl_hplus);
2355 gsl_spline_free(spl_hcross);
2356 MultiModalWaveform_Destroy(h_inertial_modes);
2357
2358 if(ModeArray) {
2359 XLALDestroyValue(ModeArray);
2360 }
2361
2362 return XLAL_SUCCESS;
2363}
2364
2365/**
2366 * This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and
2367 * returns the inertial frame modes in the form of a SphHarmTimeSeries.
2368 * Papers:
2369 * NRSur7dq2: https://arxiv.org/abs/1705.07089
2370 * NRSur7dq4: https://arxiv.org/abs/1905.09300
2371 *
2372 * The system is initialized at a time where the orbital frequency of the
2373 * waveform in the coprecessing frame (Eq.11 of
2374 * https://arxiv.org/abs/1705.07089) is pi * fRef. At this reference point, the
2375 * system is initialized in the coorbital frame, with the z-axis along the
2376 * principal eigenvector of the angular momentum operator acting on the
2377 * waveform, the x-axis along the line of separation from the lighter BH to the
2378 * heavier BH, and the y-axis completing the triad. The modes are returned in
2379 * this frame, which agrees with the LAL convention. When combining the modes
2380 * to get the polarizations, the Ylms should be evaluated at
2381 * (inclination, pi/2 - phiRef), following the LAL convention.
2382 *
2383 * Extra options are passed through a LALDict:
2384 * dictParams = lal.CreateDict()
2385 *
2386 * 1. Mode options: When using a custom ModeArray the user must explicitly
2387 * supply all modes, -ve and +ve m-modes. Note that these are modes in the
2388 * co-orbital frame, not the inertial frame.
2389 * Examples:
2390 * # First, create the 'empty' mode array
2391 * ma=lalsim.SimInspiralCreateModeArray()
2392 *
2393 * 1a. If you want all models upto ellMax:
2394 * # add the modes (these are in the coorbital frame)
2395 * for ell in range(2, ellMax+1):
2396 * lalsim.SimInspiralModeArrayActivateAllModesAtL(ma, ell)
2397 * # then insert your ModeArray into the LALDict params with
2398 * lalsim.SimInspiralWaveformParamsInsertModeArray(dictParams, ma)
2399 *
2400 * 1b. If you want only a single mode:
2401 * # add (l,m) and (l,-m) modes (these are in the coorbital frame)
2402 * lalsim.SimInspiralModeArrayActivateMode(ma, l, m)
2403 * lalsim.SimInspiralModeArrayActivateMode(ma, l, -m)
2404 * # then insert your ModeArray into the LALDict params with
2405 * lalsim.SimInspiralWaveformParamsInsertModeArray(params, ma)
2406 *
2407 * 2. Extrapolation: The surrogate models are trained on the following ranges.
2408 * NRSur7dq2: q <= 2.01, |chi_1|, |chi_2| <= 0.81.
2409 * NRSur7dq4: q <= 4.01, |chi_1|, |chi_2| <= 0.81.
2410 * If you want a guarantee of accuracy you should stick to the above ranges.
2411 *
2412 * We allow extrapolation to the following ranges, but with a warning:
2413 * NRSur7dq2: q <= 3.01, |chi_1|, |chi_2| <= 1.
2414 * NRSur7dq4: q <= 6.01, |chi_1|, |chi_2| <= 1.
2415 * We expect the models to be reasonable when extrapolated to these ranges.
2416 *
2417 * Beyond the above ranges, we raise an error. If you want to extrapolate
2418 * beyond these limits you can specify unlimited_extrapolation = 1 in your
2419 * dictParams as follows:
2420 * # USE AT YOUR OWN RISK!!
2421 * lal.DictInsertUINT4Value(dictParams, "unlimited_extrapolation", 1)
2422 */
2424 REAL8 deltaT, /**< sampling interval (s) */
2425 REAL8 m1, /**< mass of companion 1 (kg) */
2426 REAL8 m2, /**< mass of companion 2 (kg) */
2427 REAL8 S1x, /**< x-component of the dimensionless spin of object 1 */
2428 REAL8 S1y, /**< y-component of the dimensionless spin of object 1 */
2429 REAL8 S1z, /**< z-component of the dimensionless spin of object 1 */
2430 REAL8 S2x, /**< x-component of the dimensionless spin of object 2 */
2431 REAL8 S2y, /**< y-component of the dimensionless spin of object 2 */
2432 REAL8 S2z, /**< z-component of the dimensionless spin of object 2 */
2433 REAL8 fMin, /**< start GW frequency (Hz) */
2434 REAL8 fRef, /**< reference GW frequency (Hz) */
2435 REAL8 distance, /**< distance of source (m) */
2436 LALDict* LALparams, /**< Dict with extra parameters */
2437 Approximant approximant /**< approximant (NRSur7dq2 or NRSur7dq4) */
2438) {
2439 SphHarmTimeSeries *hlms = NULL;
2440
2441 LALValue* ModeArray
2443
2444 int ell, m;
2445 if ( ModeArray == NULL ) {
2446 // Use all available modes
2447 ModeArray = XLALSimInspiralCreateModeArray();
2448 for (ell=2; ell <= NRSUR_LMAX; ell++) {
2450 }
2451 } // Otherwise, use the specified modes.
2452
2453 // If m1 < m2, switch the BH labels so that q = m1/m2 >= 1 always.
2454 bool labels_switched = PrecessingNRSur_switch_labels_if_needed(&m1, &m2,
2455 &S1x, &S1y, &S1z, &S2x, &S2y, &S2z);
2456
2457 // Parameters
2458 REAL8 massA = m1 / LAL_MSUN_SI;
2459 REAL8 massB = m2 / LAL_MSUN_SI;
2460 REAL8 Mtot = massA+massB;
2461 REAL8 Mtot_sec = Mtot * LAL_MTSUN_SI; /* Total mass in seconds */
2462 REAL8 q = massA / massB;
2463 REAL8 chiA0[3], chiB0[3];
2464 chiA0[0] = S1x;
2465 chiA0[1] = S1y;
2466 chiA0[2] = S1z;
2467 chiB0[0] = S2x;
2468 chiB0[1] = S2y;
2469 chiB0[2] = S2z;
2470
2471 REAL8 omegaMin_dimless;
2472 REAL8 omegaRef_dimless;
2473 int ret = get_dimless_omega(&omegaMin_dimless, &omegaRef_dimless,
2474 fMin, fRef, Mtot_sec);
2475 if(ret != XLAL_SUCCESS) {
2476 if(ModeArray) XLALDestroyValue(ModeArray);
2477 XLAL_ERROR_NULL(XLAL_EFUNC, "Failed to process fMin/fRef");
2478 }
2479
2480 // When generating the modes take the inertial frame to be aligned with
2481 // the coorbital frame at the reference point. This means that the
2482 // quaternion from inertial frame to coprecessing frame is identity, and
2483 // the init_orbphase = 0 between the coprecessing frame and the coorbital
2484 // frame at the reference point. This agrees with the LAL convention, and
2485 // phiRef should only used in the Ylms when computing the polarizations.
2486 REAL8 init_quat[4];
2487 init_quat[0] = 1.0;
2488 init_quat[1] = 0.0;
2489 init_quat[2] = 0.0;
2490 init_quat[3] = 0.0;
2491 REAL8 init_orbphase = 0;
2492
2493 // Evaluate the model modes
2494 MultiModalWaveform *h_inertial = NULL;
2495 PrecessingNRSurData *__sur_data = PrecessingNRSur_core(&h_inertial, q,
2496 chiA0, chiB0, omegaRef_dimless, init_orbphase, init_quat,
2497 ModeArray, LALparams, approximant);
2498 if (!h_inertial) {
2499 if(ModeArray) XLALDestroyValue(ModeArray);
2500 XLAL_PRINT_INFO("PrecessingNRSur_core failed!");
2501 return hlms;
2502 }
2503
2504 // Scale the amplitude based on the distance
2505 REAL8 a0 = Mtot_sec * LAL_C_SI / distance;
2506
2507 // Setup dimensionless output times
2508 gsl_vector *model_times = __sur_data->t_coorb;
2509 REAL8 deltaT_dimless = deltaT / Mtot_sec;
2510 REAL8 t0 = gsl_vector_get(model_times, 0);
2511 REAL8 start_freq = PrecessingNRSur_StartFrequency(m1, m2, S1x, S1y, S1z, S2x, S2y, S2z, __sur_data);
2512 if (fMin >= start_freq) {
2513 t0 = PrecessingNRSur_get_t_ref(omegaMin_dimless, q, chiA0, chiB0,
2514 init_quat, init_orbphase, __sur_data);
2515 } else if (fMin > 0) {
2516 // Cleanup and exit
2517 if(ModeArray) XLALDestroyValue(ModeArray);
2518 MultiModalWaveform_Destroy(h_inertial);
2519 XLAL_ERROR_NULL(XLAL_EDOM, "fMin should be 0 or >= %0.8f for this configuration, got %0.8f", start_freq, fMin);
2520 }
2521 size_t length = model_times->size;
2522 REAL8 tf = gsl_vector_get(model_times, length-1);
2523 int nt = (int) ceil((tf - t0) / deltaT_dimless);
2524 gsl_vector *output_times = gsl_vector_alloc(nt);
2525 int j;
2526 for (j=0; j<nt; j++) {
2527 gsl_vector_set(output_times, j, t0 + deltaT_dimless*j);
2528 }
2529
2530 // Setup interpolation onto dimensionless output times
2531 REAL8 t;
2533 XLALGPSAdd( &epoch, Mtot_sec * t0);
2534 gsl_interp_accel *acc = gsl_interp_accel_alloc();
2535
2536 // Create LAL modes
2537 COMPLEX16TimeSeries *tmp_mode;
2538 int i=0;
2539 bool skip_ell_modes;
2540 char mode_name[32];
2541 REAL8 prefactor = 1;
2542 for (ell=2; ell <= NRSUR_LMAX; ell++) {
2543 /* If *any* co-orbital frame mode of this ell is non-zero we need to sum over all modes of this ell,
2544 * as the frame rotation will have mixed the modes. */
2545 skip_ell_modes = true;
2546 for (m=-ell; m<=ell; m++) {
2547 if (XLALSimInspiralModeArrayIsModeActive(ModeArray, ell, m) == 1) skip_ell_modes = false;
2548 }
2549 if (skip_ell_modes) {
2550 i += 2*ell + 1;
2551 continue;
2552 }
2553 for (m=-ell; m<=ell; m++) {
2554
2555 // If m1 < m2, the BH labels were switched so that m1 > m2. This
2556 // generates the requested waveform but with a rotation of pi about
2557 // the z-axis. We now undo this by multiplying all odd-m modes by
2558 // -1, which is the same as a pi rotation; the even-m modes don't
2559 // need this fix as they are insensitive to pi rotations about
2560 // z-axis.
2561 if ((m%2 != 0) && (labels_switched)) {
2562 prefactor = -1;
2563 } else {
2564 prefactor = 1;
2565 }
2566
2567 gsl_vector_scale(h_inertial->modes_real_part[i], prefactor*a0);
2568 gsl_vector_scale(h_inertial->modes_imag_part[i], prefactor*a0);
2569 snprintf(mode_name, sizeof(mode_name), "(%d, %d) mode", ell, m);
2570 tmp_mode = XLALCreateCOMPLEX16TimeSeries(mode_name, &epoch, 0.0,
2571 deltaT, &lalStrainUnit, nt);
2572 gsl_spline *spl_re = gsl_spline_alloc(gsl_interp_cspline, length);
2573 gsl_spline *spl_im = gsl_spline_alloc(gsl_interp_cspline, length);
2574 gsl_spline_init(spl_re, model_times->data,
2575 h_inertial->modes_real_part[i]->data, length);
2576 gsl_spline_init(spl_im, model_times->data,
2577 h_inertial->modes_imag_part[i]->data, length);
2578 for (j=0; j<nt; j++) {
2579 t = gsl_vector_get(output_times, j);
2580 tmp_mode->data->data[j] = gsl_spline_eval(spl_re, t, acc);
2581 tmp_mode->data->data[j] += I * gsl_spline_eval(spl_im, t, acc);
2582 }
2583 gsl_spline_free(spl_re);
2584 gsl_spline_free(spl_im);
2585 hlms = XLALSphHarmTimeSeriesAddMode(hlms, tmp_mode, ell, m);
2587 i += 1;
2588 }
2589 }
2590
2591 // Cleanup
2592 MultiModalWaveform_Destroy(h_inertial);
2593 gsl_vector_free(output_times);
2594 gsl_interp_accel_free(acc);
2595 if(ModeArray) {
2596 XLALDestroyValue(ModeArray);
2597 }
2598
2599 return hlms;
2600}
2601
2602/**
2603 * This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and
2604 * returns the precessing frame dynamics.
2605 * Papers:
2606 * NRSur7dq2: https://arxiv.org/abs/1705.07089
2607 * NRSur7dq4: https://arxiv.org/abs/1905.09300
2608 *
2609 * Example usage:
2610 * t_dynamics, quat0, quat1, quat2, quat3, orbphase, chiAx, chiAy, chiAz,
2611 * chiBx, chiBy, chiBz = lalsim.PrecessingNRSurDynamics(
2612 * q, chiA0x, chiA0y, chiA0z, chiB0x, chiB0y, chiB0z,
2613 * omegaRef_dimless, init_quat0, init_quat1, init_quat2, init_quat3,
2614 * init_orbphase, LALparams, approxTag)
2615 *
2616 * INPUTS:
2617 * q
2618 * mass ratio, mA/mB >= 1.
2619 * chiA0x
2620 * chiA0y
2621 * chiA0z
2622 * spin of the heavier BH at the reference epoch, in the coorbital
2623 * frame, as defined in the above papers. This agrees with the LAL
2624 * convention, see LALSimInspiral.h for definition of the LAL frame.
2625 * chiB0x
2626 * chiB0y
2627 * chiB0z
2628 * spin of the lighter BH at the reference epoch, in the coorbital
2629 * frame.
2630 * omegaRef_dimless
2631 * reference dimensionless orbital frequency in the coprecessing
2632 * frame, this is used to set the reference epoch. The coprecessing
2633 * frame is defined in the above papers.
2634 * init_quat0
2635 * init_quat1
2636 * init_quat2
2637 * init_quat3
2638 * coprecessing frame quaternion at the reference epoch. To follow
2639 * the LAL convention this should be [1, 0, 0, 0], but the surrogate
2640 * allows arbitrary unit quaternions.
2641 * init_orbphase
2642 * orbital phase in the coprecessing frame at the reference epoch. To
2643 * follow the LAL convention this should be 0, but the surrogate
2644 * allows arbitrary initial orbital phase.
2645 * LALparams
2646 * LAL dictionary containing additional parameters, if required.
2647 * Initialized with: LALparams = lal.CreateDict()
2648 *
2649 * Extrapolation options:
2650 * The surrogate models are trained on the following ranges:
2651 * NRSur7dq2: q <= 2.01, |chi_1|, |chi_2| <= 0.81.
2652 * NRSur7dq4: q <= 4.01, |chi_1|, |chi_2| <= 0.81.
2653 * If you want a guarantee of accuracy you should stick to the above
2654 * ranges.
2655 *
2656 * We allow extrapolation to the following ranges, but with a warning:
2657 * NRSur7dq2: q <= 3.01, |chi_1|, |chi_2| <= 1.
2658 * NRSur7dq4: q <= 6.01, |chi_1|, |chi_2| <= 1.
2659 * We expect the models to be reasonable when extrapolated to these
2660 * ranges.
2661 *
2662 * Beyond the above ranges, we raise an error. If you want to
2663 * extrapolate beyond these limits you can specify
2664 * unlimited_extrapolation = 1 in your LALparams as follows:
2665 * USE AT YOUR OWN RISK!!
2666 * lal.DictInsertUINT4Value(LALparams,"unlimited_extrapolation",1)
2667 * approxTag
2668 * LAL object specifying the surrogate model to use. Example:
2669 * approxTag = lalsim.SimInspiralGetApproximantFromString('NRSur7dq4')
2670 * OUTPUTS:
2671 * t_dynamics
2672 * time values at which the dynamics are returned. These are the
2673 * dynamics time nodes as described in the above papers and are
2674 * nonuniform and sparse.
2675 * quat0
2676 * quat1
2677 * quat2
2678 * quat3
2679 * time series of coprecessing frame quaternions. These are used to
2680 * transform between the inertial frame and the coprecessing frames.
2681 * orbphase
2682 * orbital phase time series in the coprecessing frame. This is used
2683 * to transform between the coprecessing and the coorbital frames.
2684 * chiAx
2685 * chiAy
2686 * chiAz
2687 * time series of spin of the heavier BH in the coprecessing frame.
2688 * For convenience for the main use case with surrogate remnant fits,
2689 * these are in coprecessing frame rather then the reference frame in
2690 * which the input spins are specified.
2691 * chiBx
2692 * chiBy
2693 * chiBz
2694 * time series of spin of the heavier BH in the coprecessing frame.
2695 */
2697 gsl_vector **t_dynamics, /**< Output: Time array at which the dynamics are returned. */
2698 gsl_vector **quat0, /**< Output: Time series of 0th index of coprecessing frame quaternion. */
2699 gsl_vector **quat1, /**< Output: Time series of 1st index of coprecessing frame quaternion. */
2700 gsl_vector **quat2, /**< Output: Time series of 2nd index of coprecessing frame quaternion. */
2701 gsl_vector **quat3, /**< Output: Time series of 3rd index of coprecessing frame quaternion. */
2702 gsl_vector **orbphase, /**< Output: Time series of orbital phase in the coprecessing frame. */
2703 gsl_vector **chiAx, /**< Output: Time series of x-comp of dimensionless spin of BhA in the coprecessing frame. */
2704 gsl_vector **chiAy, /**< Output: Time series of y-comp of dimensionless spin of BhA in the coprecessing frame. */
2705 gsl_vector **chiAz, /**< Output: Time series of z-comp of dimensionless spin of BhA in the coprecessing frame. */
2706 gsl_vector **chiBx, /**< Output: Time series of x-comp of dimensionless spin of BhB in the coprecessing frame. */
2707 gsl_vector **chiBy, /**< Output: Time series of y-comp of dimensionless spin of BhB in the coprecessing frame. */
2708 gsl_vector **chiBz, /**< Output: Time series of z-comp of dimensionless spin of BhB in the coprecessing frame. */
2709 REAL8 q, /**< mass ratio m1/m2 >= 1. */
2710 REAL8 chiA0x, /**< x-comp of dimensionless spin of BhA in the coorbital frame at the reference epoch. */
2711 REAL8 chiA0y, /**< y-comp of dimensionless spin of BhA in the coorbital frame at the reference epoch. */
2712 REAL8 chiA0z, /**< z-comp of dimensionless spin of BhA in the coorbital frame at the reference epoch. */
2713 REAL8 chiB0x, /**< x-comp of dimensionless spin of BhB in the coorbital frame at the reference epoch. */
2714 REAL8 chiB0y, /**< y-comp of dimensionless spin of BhB in the coorbital frame at the reference epoch. */
2715 REAL8 chiB0z, /**< z-comp of dimensionless spin of BhB in the coorbital frame at the reference epoch. */
2716 REAL8 omegaRef_dimless, /**< Dimensionless orbital frequency (rad/M) in the coprecessing frame at the reference epoch.*/
2717 REAL8 init_quat0, /**< 0th comp of the coprecessing frame quaternion at the reference epoch.*/
2718 REAL8 init_quat1, /**< 1st comp of the coprecessing frame quaternion at the reference epoch.*/
2719 REAL8 init_quat2, /**< 2nd comp of the coprecessing frame quaternion at the reference epoch.*/
2720 REAL8 init_quat3, /**< 3rd comp of the coprecessing frame quaternion at the reference epoch.*/
2721 REAL8 init_orbphase, /**< orbital phase in the coprecessing frame at the reference epoch. */
2722 LALDict* LALparams, /**< Dict with extra parameters. */
2723 Approximant approximant /**< approximant (NRSur7dq2 or NRSur7dq4). */
2724) {
2725
2726 // Load surrogate data if needed. If not, just access the loaded data.
2728 if (!__sur_data->setup) {
2729 XLAL_ERROR(XLAL_EFAILED, "Error loading surrogate data.\n");
2730 }
2731
2732 // Input spins at reference epoch
2733 // The input values are in the coorbital frame at omegaRef_dimless, but
2734 // the surrogate wants them in the coprecessing frame, so we transform
2735 // from the coorbital frame to the coprecessing frame here.
2736 REAL8 chiA0[3], chiB0[3];
2737 chiA0[0] = chiA0x * cos(init_orbphase) - chiA0y * sin(init_orbphase);
2738 chiA0[1] = chiA0y * cos(init_orbphase) + chiA0x * sin(init_orbphase);
2739 chiA0[2] = chiA0z;
2740 chiB0[0] = chiB0x * cos(init_orbphase) - chiB0y * sin(init_orbphase);
2741 chiB0[1] = chiB0y * cos(init_orbphase) + chiB0x * sin(init_orbphase);
2742 chiB0[2] = chiB0z;
2743
2744 // Coprecessing frame quaternion at reference epoch
2745 REAL8 init_quat[4];
2746 init_quat[0] = init_quat0;
2747 init_quat[1] = init_quat1;
2748 init_quat[2] = init_quat2;
2749 init_quat[3] = init_quat3;
2750
2751 // Get surrogate dynamics. The spins are returned in the coprecessing
2752 // frame.
2753 int n_ds = (__sur_data->t_ds)->size;
2754 REAL8 *dynamics_data = XLALCalloc(n_ds * 11, sizeof(REAL8));
2755 int ret = PrecessingNRSur_IntegrateDynamics(dynamics_data, q,
2756 chiA0, chiB0, omegaRef_dimless, init_orbphase, init_quat,
2757 LALparams, __sur_data->PrecessingNRSurVersion);
2758 if(ret != XLAL_SUCCESS) {
2759 XLALFree(dynamics_data);
2760 XLAL_ERROR(XLAL_FAILURE, "Failed to integrate dynamics");
2761 }
2762
2763 // Put output into appropriate vectors
2764 int i, j;
2765 gsl_vector *dynamics[11];
2766 for (j=0; j<11; j++) {
2767 dynamics[j] = gsl_vector_alloc(n_ds);
2768 }
2769 for (i=0; i<n_ds; i++) {
2770 for (j=0; j<11; j++) {
2771 gsl_vector_set(dynamics[j], i, dynamics_data[11*i + j]);
2772 }
2773 }
2774
2775 // We want to make a copy as *t_dynamics gets destroyed after it is
2776 // returned by SWIG. So, if we just pass __sur_data->t_ds, it fails
2777 // upon a second call.
2778 *t_dynamics = gsl_vector_alloc(n_ds);
2779 gsl_vector_memcpy(*t_dynamics, __sur_data->t_ds);
2780
2781 *quat0 = dynamics[0];
2782 *quat1 = dynamics[1];
2783 *quat2 = dynamics[2];
2784 *quat3 = dynamics[3];
2785 *orbphase = dynamics[4];
2786 *chiAx = dynamics[5]; // These are in the coprecessing frame
2787 *chiAy = dynamics[6];
2788 *chiAz = dynamics[7];
2789 *chiBx = dynamics[8];
2790 *chiBy = dynamics[9];
2791 *chiBz = dynamics[10];
2792
2793 XLALFree(dynamics_data);
2794
2795 return XLAL_SUCCESS;
2796}
2797
2798/** @} */
2799/** @} */
struct tagLALH5File LALH5File
struct tagLALH5Dataset LALH5Dataset
int XLALDictContains(const LALDict *dict, const char *key)
UINT4 XLALDictLookupUINT4Value(const LALDict *dict, const char *key)
Auxiliary functions related to HDF5 waveform data files.
static UNUSED const double Q_MAX
static PrecessingNRSurData __lalsim_NRSur7dq2_data
Global surrogate data.
static PrecessingNRSurData __lalsim_NRSur7dq4_data
static const double NRSUR7DQ4_Q_MAX
static const double NRSUR7DQ4_Q_FIT_OFFSET
static const char NRSUR7DQ4_DATAFILE[]
static const double NRSUR7DQ4_START_TIME
static const double NRSUR7DQ4_Q_FIT_SLOPE
static const double NRSUR7DQ2_Q_FIT_SLOPE
static const double NRSUR7DQ2_Q_MAX
static const double NRSUR7DQ4_Q_MAX_WARN
static const double NRSUR7DQ2_CHI_MAX_WARN
static const int NRSUR_LMAX
static const double NRSUR7DQ2_CHI_MAX
static const double NRSUR7DQ2_Q_MAX_WARN
static const double NRSUR7DQ4_CHI_MAX_WARN
static const double NRSUR7DQ2_Q_FIT_OFFSET
static const double NRSUR7DQ4_CHI_MAX
static const double NRSUR7DQ2_START_TIME
static const char NRSUR7DQ2_DATAFILE[]
Auxiliary functions for SEOBNRv1/v2 reduced order modeling codes in LALSimIMRSEOBNRv2ROMDoubleSpin....
LALValue * XLALSimInspiralWaveformParamsLookupModeArray(LALDict *params)
const char * name
static void NRSur_ab4_dy(REAL8 *dy, REAL8 *k1, REAL8 *k2, REAL8 *k3, REAL8 *k4, REAL8 t12, REAL8 t23, REAL8 t34, REAL8 t45, int dim)
static void MultiModalWaveform_Destroy(MultiModalWaveform *wave)
Destroy a MultiModalWaveform structure; free all associated memory.
static void TransformModesCoorbitalToInertial(MultiModalWaveform *h, MultiModalWaveform *h_coorb, gsl_vector **quat, gsl_vector *orbphase)
Transforms modes from the coorbital frame to the inertial frame.
static void MultiModalWaveform_Init(MultiModalWaveform **wave, int LMax, int n_times)
Initialize a MultiModalWaveforme.
static UNUSED int get_dimless_omega(REAL8 *omegaMin_dimless, REAL8 *omegaRef_dimless, const REAL8 fMin, const REAL8 fRef, const REAL8 Mtot_sec)
Wrapper to get dimensionless omegaMin/omegaRef from fMin/fRef.
void XLALDestroyValue(LALValue *value)
static REAL8 sum(REAL8 *arr1, REAL8 *arr2, int k)
double dt
Definition: bh_ringdown.c:113
double i
Definition: bh_ringdown.c:118
sigmaKerr data[0]
#define XLAL_FILE_RESOLVE_PATH(fname)
LALH5File * XLALH5GroupOpen(LALH5File UNUSED *file, const char UNUSED *name)
LALH5File * XLALH5FileOpen(const char UNUSED *path, const char UNUSED *mode)
LALH5Dataset * XLALH5DatasetRead(LALH5File UNUSED *file, const char UNUSED *name)
UINT4Vector * XLALH5DatasetQueryDims(LALH5Dataset UNUSED *dset)
#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
void * XLALMalloc(size_t n)
void * XLALCalloc(size_t m, size_t n)
void XLALFree(void *p)
#define LAL_SIM_L_MAX_MODE_ARRAY
Maximum L spherical harmonic mode that is supported in Mode Array.
Approximant
Enum that specifies the PN approximant to be used in computing the waveform.
@ NRSur7dq4
q=4 extension of NRSur7dq2, arxiv: 1905.09300
@ NRSur7dq2
Time domain, fully precessing NR surrogate model with up to ell=4 modes, arxiv: 1705....
int XLALSimInspiralModeArrayIsModeActive(LALValue *modes, unsigned l, int m)
LALValue * XLALSimInspiralCreateModeArray(void)
Create a LALValue pointer to store the mode array.
LALValue * XLALSimInspiralModeArrayActivateAllModesAtL(LALValue *modes, unsigned l)
static REAL8 PrecessingNRSur_get_omega(size_t node_index, REAL8 q, REAL8 *y0, PrecessingNRSurData *__sur_data)
Computes the orbital angular frequency at a dynamics node.
static UNUSED bool NRSur7dq2_IsSetup(void)
Helper function which returns whether or not the global NRSur7dq2 surrogate data has been initialized...
static void PrecessingNRSur_normalize_y(REAL8 chiANorm, REAL8 chiBNorm, REAL8 *y)
static int PrecessingNRSur_initialize_RK4(REAL8 *dynamics_data, REAL8 *time_steps, REAL8 *dydt0, REAL8 *dydt1, REAL8 *dydt2, REAL8 *dydt3, REAL8 normA, REAL8 normB, REAL8 q, int i0, PrecessingNRSurData *__sur_data)
Initializes the AB4 ODE system from a single arbitrary dynamics node by taking 3 RK4 steps.
static void PrecessingNRSur_rotate_spins(gsl_vector **chiA, gsl_vector **chiB, gsl_vector *phi_vec)
Transforms chiA and chiB from the coprecessing frame to the coorbital frame using the orbital phase.
static void PrecessingNRSur_get_time_deriv_from_index(REAL8 *dydt, int i0, REAL8 q, REAL8 *y, PrecessingNRSurData *__sur_data)
Compute dydt at a given dynamics node, where y is the numerical solution to the dynamics ODE.
int XLALSimInspiralPrecessingNRSurPolarizations(REAL8TimeSeries **hplus, REAL8TimeSeries **hcross, REAL8 phiRef, REAL8 inclination, REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 distance, REAL8 fMin, REAL8 fRef, REAL8 s1x, REAL8 s1y, REAL8 s1z, REAL8 s2x, REAL8 s2y, REAL8 s2z, LALDict *LALparams, Approximant approximant)
This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and sums over all ell <= 4 modes t...
static void PrecessingNRSur_get_time_deriv(REAL8 *dydt, REAL8 t, REAL8 q, REAL8 *y, PrecessingNRSurData *__sur_data)
Compute dydt at any time by evaluating dydt at 4 nearby dynamics nodes and using cubic spline interpo...
static void PrecessingNRSur_assemble_dydt(REAL8 *dydt, REAL8 *y, REAL8 *Omega_coorb_xy, REAL8 omega, REAL8 *chiA_dot, REAL8 *chiB_dot)
SphHarmTimeSeries * XLALSimInspiralPrecessingNRSurModes(REAL8 deltaT, REAL8 m1, REAL8 m2, REAL8 S1x, REAL8 S1y, REAL8 S1z, REAL8 S2x, REAL8 S2y, REAL8 S2z, REAL8 fMin, REAL8 fRef, REAL8 distance, LALDict *LALparams, Approximant approximant)
This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and returns the inertial frame mod...
static void PrecessingNRSur_integrate_AB4(REAL8 *dynamics_data, REAL8 *time_steps, REAL8 *dydt0, REAL8 *dydt1, REAL8 *dydt2, REAL8 *dydt3, REAL8 normA, REAL8 normB, REAL8 q, int i_start, PrecessingNRSurData *__sur_data)
Integrates the AB4 ODE system in time forwards, and backwards if needed.
static int NRSur7dq4_effective_spins(REAL8 *chiHat, REAL8 *chi_a, const REAL8 q, const REAL8 chi1z, const REAL8 chi2z)
static PrecessingNRSurData * PrecessingNRSur_LoadData(UNUSED Approximant approximant)
Wrapper for Loading NRSur7dq2 and NRSur7dq4 data.
int XLALPrecessingNRSurDynamics(gsl_vector **t_dynamics, gsl_vector **quat0, gsl_vector **quat1, gsl_vector **quat2, gsl_vector **quat3, gsl_vector **orbphase, gsl_vector **chiAx, gsl_vector **chiAy, gsl_vector **chiAz, gsl_vector **chiBx, gsl_vector **chiBy, gsl_vector **chiBz, REAL8 q, REAL8 chiA0x, REAL8 chiA0y, REAL8 chiA0z, REAL8 chiB0x, REAL8 chiB0y, REAL8 chiB0z, REAL8 omegaRef_dimless, REAL8 init_quat0, REAL8 init_quat1, REAL8 init_quat2, REAL8 init_quat3, REAL8 init_orbphase, LALDict *LALparams, Approximant approximant)
This function evaluates the NRSur7dq2 or NRSur7dq4 surrogate model and returns the precessing frame d...
static int PrecessingNRSur_initialize_at_dynamics_node(REAL8 *dynamics_data, REAL8 t_ref, REAL8 q, REAL8 *chiA0, REAL8 *chiB0, REAL8 init_orbphase, REAL8 *init_quat, REAL8 normA, REAL8 normB, PrecessingNRSurData *__sur_data)
Initialize the dynamics ODE at a dynamics node.
static void PrecessingNRSur_normalize_results(REAL8 normA, REAL8 normB, gsl_vector **quat, gsl_vector **chiA, gsl_vector **chiB)
static int PrecessingNRSur_IntegrateDynamics(REAL8 *dynamics_data, REAL8 q, REAL8 *chiA0, REAL8 *chiB0, REAL8 omega_ref, REAL8 init_orbphase, REAL8 *init_quat, LALDict *LALparams, UINT4 PrecessingNRSurVersion)
This is the main NRSur dynamics surrogate integration routine.
static void NRSur7dq4_eval_vector_fit(REAL8 *res, VectorFitData *data, REAL8 *x)
REAL8 NRSur7dq4_eval_fit(FitData *data, REAL8 *x)
static gsl_vector * spline_array_interp(gsl_vector *xout, gsl_vector *x, gsl_vector *y)
Do cubic spline interpolation using a gsl_interp_cspline.
static PrecessingNRSurData * PrecessingNRSur_core(MultiModalWaveform **h, REAL8 q, REAL8 *chiA0, REAL8 *chiB0, REAL8 omega_ref, REAL8 init_orbphase, REAL8 *init_quat, LALValue *ModeArray, LALDict *LALparams, Approximant approximant)
static void NRSur7dq2_eval_vector_fit(REAL8 *res, VectorFitData *data, REAL8 *x)
static bool PrecessingNRSur_switch_labels_if_needed(REAL8 *m1, REAL8 *m2, REAL8 *s1x, REAL8 *s1y, REAL8 *s1z, REAL8 *s2x, REAL8 *s2y, REAL8 *s2z)
If m1<m2, swaps the labels for the two BHs such that Bh1 is always heavier.
REAL8 ipow(REAL8 base, int exponent)
Helper function for integer powers.
static void PrecessingNRSur_eval_vector_fit(REAL8 *res, VectorFitData *data, REAL8 *x, PrecessingNRSurData *__sur_data)
static UNUSED bool NRSur7dq4_IsSetup(void)
Helper function which returns whether or not the global NRSur7dq4 surrogate data has been initialized...
static REAL8 PrecessingNRSur_get_t_ref(REAL8 omega_ref, REAL8 q, REAL8 *chiA0, REAL8 *chiB0, REAL8 *init_quat, REAL8 init_orbphase, PrecessingNRSurData *__sur_data)
Computes a reference time from a reference orbital angular frequency.
REAL8 PrecessingNRSur_eval_fit(FitData *data, REAL8 *x, PrecessingNRSurData *__sur_data)
static void PrecessingNRSur_initialize_RK4_with_half_nodes(REAL8 *dynamics_data, REAL8 *time_steps, REAL8 *dydt0, REAL8 *dydt1, REAL8 *dydt2, REAL8 *dydt3, REAL8 normA, REAL8 normB, REAL8 q, PrecessingNRSurData *__sur_data)
Initializes the AB4 ODE system from the surrogate start time by taking 3 RK4 steps,...
static void PrecessingNRSur_eval_data_piece(gsl_vector *result, REAL8 q, gsl_vector **chiA, gsl_vector **chiB, WaveformDataPiece *data, PrecessingNRSurData *__sur_data)
Evaluates a single NRSur coorbital waveoform data piece.
static void PrecessingNRSur_ds_fit_x(REAL8 *x, REAL8 q, REAL8 *y)
REAL8 PrecessingNRSur_StartFrequency(REAL8 m1, REAL8 m2, REAL8 s1x, REAL8 s1y, REAL8 s1z, REAL8 s2x, REAL8 s2y, REAL8 s2z, PrecessingNRSurData *__sur_data)
Computes the starting frequency of the NRSur7dq2 or NRSur7dq4 waveform approximant when evaluated usi...
REAL8 NRSur7dq2_eval_fit(FitData *data, REAL8 *x)
static REAL8 cubic_interp(REAL8 xout, REAL8 *x, REAL8 *y)
Cubic interpolation of 4 data points This gives a much closer result to scipy.interpolate....
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)
void XLALDestroyCOMPLEX16TimeSeries(COMPLEX16TimeSeries *series)
COMPLEX16TimeSeries * XLALCreateCOMPLEX16TimeSeries(const CHAR *name, const LIGOTimeGPS *epoch, REAL8 f0, REAL8 deltaT, const LALUnit *sampleUnits, size_t length)
REAL8TimeSeries * XLALCreateREAL8TimeSeries(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_REAL8(...)
#define XLAL_PRINT_INFO(...)
#define XLAL_ERROR_NULL(...)
#define XLAL_ERROR(...)
#define XLAL_PRINT_WARNING(...)
#define XLAL_CHECK_ABORT(assertion)
int XLALPrintError(const char *fmt,...) _LAL_GCC_PRINTF_FORMAT_(1
#define XLAL_IS_REAL8_FAIL_NAN(val)
XLAL_SUCCESS
XLAL_EFUNC
XLAL_EDOM
XLAL_EIO
XLAL_EINVAL
XLAL_EFAILED
XLAL_FAILURE
LIGOTimeGPS * XLALGPSAdd(LIGOTimeGPS *epoch, REAL8 dt)
list y
path
x
COMPLEX16Sequence * data
COMPLEX16 * data
Data for a single dynamics node.
FitData * omega_data
A fit to the orbital angular frequency.
VectorFitData * chiB_dot_data
A 3d vector fit for the coorbital components of the time derivative of chiB taken in the coprecessing...
VectorFitData * chiA_dot_data
A 3d vector fit for the coorbital components of the time derivative of chiA taken in the coprecessing...
VectorFitData * omega_copr_data
A 2d vector fit for the x and y components of Omega^{coorb}(t) in arxiv 1705.07089.
Data used in a single scalar fit.
int n_coefs
Number of coefficients in the fit.
gsl_matrix_long * basisFunctionOrders
matrix of (n_coefs x 7) basis function orders giving the polynomial order in f(q),...
gsl_vector * coefs
coefficient vector of length n_coefs
Structure for a multi-modal waveform.
gsl_vector ** modes_real_part
The real part of each mode - n_modes vectors of length n_times.
gsl_vector ** modes_imag_part
The imaginary part of each mode - n_modes vectors of length n_times.
int n_times
Number of time samples in each mode.
All data needed by the full surrogate model.
gsl_vector * t_coorb
Vector of the coorbital surrogate output times.
UINT4 PrecessingNRSurVersion
One for each 2 <= ell <= LMax.
WaveformFixedEllModeData ** coorbital_mode_data
A DynamicsNodeFitData for each time in t_ds_half_times.
DynamicsNodeFitData ** ds_half_node_data
A DynamicsNodeFitData for each time in t_ds.
UINT4 setup
Indicates if this has been initialized.
DynamicsNodeFitData ** ds_node_data
Structure to carry a collection of spherical harmonic modes in COMPLEX16 time series.
UINT4 * data
Data used in a single vector fit NOTE: basisFunctionOrders, coefs, componentIndices,...
int n_coefs
Number of coefficients in the fit.
gsl_matrix_long * basisFunctionOrders
matrix of (n_coefs x 7) basis function orders giving the polynomial order in f(q),...
int vec_dim
Dimension of the vector.
gsl_vector_long * componentIndices
Each fit coefficient applies to a single component of the vector; this gives the component indices.
gsl_vector * coefs
coefficient vector of length n_coefs
Data for a single waveform data piece.
All WaveformDataPieces needed to evaluate all modes with a fixed value of ell.
WaveformDataPiece * m0_real_data
The real (ell, 0) mode data piece.
WaveformDataPiece ** X_real_minus_data
One Re[X_-] for each 1 <= m <= ell.
WaveformDataPiece ** X_imag_plus_data
One Im[X_+] for each 1 <= m <= ell.
WaveformDataPiece ** X_imag_minus_data
One Im[X_-] for each 1 <= m <= ell.
int ell
The fixed value of ell.
WaveformDataPiece * m0_imag_data
The imag (ell, 0) mode data piece.
WaveformDataPiece ** X_real_plus_data
One Re[X_+] for each 1 <= m <= ell.
LIGOTimeGPS epoch
Definition: unicorn.c:20
double deltaT
Definition: unicorn.c:24