Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALSimulation 6.1.0.1-02cf16d
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
NRSur7dq4Remnant.py
Go to the documentation of this file.
1"""
2Class for NRSur7dq4Remnant model for the mass, spin and kick of the final BH of
3generically precessing binary black holes.
4
5Vijay Varma, 2019.
6"""
7
8import numpy as np
9import lalsimulation as lalsim
10import lal
11
12from . import pn_spin_evolution_wrapper
13from . import quaternion_utils
14from .nrfits import NRFits
15
17 r"""
18 Class for NRSur7dq4Remnant model for the mass, spin and kick of the final BH
19 of generically precessing binary black holes.
20
21 Paper: arxiv:1905.09300
22
23 Parameter ranges for usage: \n
24 q = [1, 6.01] \n
25 \f$\chi_{A}, \chi_{B}\f$ = [-1, 1]
26
27 Training parameter ranges: \n
28 q = [1, 4.01] \n
29 \f$\chi_{A}, \chi_{B}\f$ = [-0.81, 0.81]
30
31 But extrapolates reasonably to the above mass ratios and spins. However,
32 if a guarantee of accuracy is required, this model should be used within
33 the training parameter range.
34 """
35
36
37 #-------------------------------------------------------------------------
38 def _get_coorbital_frame_spins_at_idx(self, chiA, chiB, omega, lNhat, phi, \
39 idx):
40 """ Computes PN spins and dynamics at a given idx.
41
42 Inputs:
43 chiA: Dimless spin evolution of BhA in inertial frame.
44 chiB: Dimless spin evolution of BhB in inertial frame.
45 omega: Orbital frequency evolution (this is total-mass times
46 the angular orbital frequency).
47 lNhat: Orbital angular momentum direction evolution.
48 phi: Orbital phase evolution.
49 idx: Index for output.
50
51 Outputs (all are time series):
52 chiA_at_idx_coorb: Spin of BhA at idx, in coorbital frame.
53 chiB_at_idx_coorb: Spin of BhB at idx, in coorbital frame.
54 quat_copr_at_idx: Coprecessing frame quaternion at idx.
55 phi_at_idx: Orbital phase in the coprecessing frame at idx.
56 omega_at_idx Orbital frequency at idx (this is total-mass
57 times the angular orbital frequency).
58
59 The inertial frame is assumed to be aligned to the coorbital frame at
60 the first index.
61 """
62
63 # Get omega, inertial spins, angular momentum direction and orbital
64 # phase at idx
65 omega_at_idx = omega[idx]
66 chiA_at_idx = chiA[idx]
67 chiB_at_idx = chiB[idx]
68 lNhat_at_idx = lNhat[idx]
69 phi_at_idx = phi[idx]
70
71 # Align the z-direction along orbital angular momentum direction
72 # at idx. This moves us into the coprecessing frame.
73 quat_copr_at_idx = quaternion_utils.align_vec_quat(lNhat_at_idx)
74 # The zeroth element is taken because transform_time_dependent_vector
75 # is designed for arrays, but here we only have one element
76 chiA_at_idx_copr = quaternion_utils.transform_time_dependent_vector(
77 np.array([quat_copr_at_idx]).T,
78 np.array([chiA_at_idx]).T, inverse=1).T[0]
79 chiB_at_idx_copr = quaternion_utils.transform_time_dependent_vector(
80 np.array([quat_copr_at_idx]).T,
81 np.array([chiB_at_idx]).T, inverse=1).T[0]
82
83 # get coorbital frame spins at idx
84 chiA_at_idx_coorb = quaternion_utils.rotate_in_plane(
85 chiA_at_idx_copr, phi_at_idx)
86 chiB_at_idx_coorb = quaternion_utils.rotate_in_plane(
87 chiB_at_idx_copr, phi_at_idx)
88
89 return chiA_at_idx_coorb, chiB_at_idx_coorb, quat_copr_at_idx, \
90 phi_at_idx, omega_at_idx
91
92 #-------------------------------------------------------------------------
93 def _get_surrogate_dynamics(self, q, chiA0, chiB0, init_quat, \
94 init_orbphase, omega_ref, unlimited_extrapolation):
95 """ A wrapper for NRSur7dq4 dynamics.
96
97 Inputs:
98 q: Mass ratio, mA/mB >= 1.
99 chiA0: Dimless spin of BhA in the coorbital frame at omega_ref.
100 chiB0: Dimless spin of BhB in the coorbital frame at omega_ref.
101 init_quat: Coprecessing frame quaternion at omega_ref.
102 init_orbphase:
103 Orbital phase in the coprecessing frame at omega_ref.
104 omega_ref: Orbital frequency in the coprecessing frame at the
105 reference epoch where the input spins are given. Note:
106 This is total-mass times the angular orbital frequency.
107 unlimited_extrapolation:
108 If True, allows unlimited extrapolation to regions well
109 outside the surrogate's training region. Else, raises
110 an error.
111
112 Outputs:
113 t_dyn: Time values at which the dynamics are returned. These
114 are nonuniform and sparse.
115 copr_quat: Time series of coprecessing frame quaternions.
116 orbphase: Orbital phase time series in the coprecessing frame.
117 chiA_copr: Time series of spin of BhA in the coprecessing frame.
118 chiB_copr: Time series of spin of BhB in the coprecessing frame.
119 """
120
121 approxTag = lalsim.SimInspiralGetApproximantFromString("NRSur7dq4")
122 LALParams = lal.CreateDict()
123 if unlimited_extrapolation:
124 lal.DictInsertUINT4Value(LALParams, "unlimited_extrapolation", 1)
125
126 t_dyn, quat0, quat1, quat2, quat3, orbphase, chiAx, chiAy, chiAz, \
127 chiBx, chiBy, chiBz = lalsim.PrecessingNRSurDynamics(q, \
128 chiA0[0], chiA0[1], chiA0[2], chiB0[0], chiB0[1], chiB0[2], \
129 omega_ref, init_quat[0], init_quat[1], init_quat[2], init_quat[3], \
130 init_orbphase, LALParams, approxTag)
131
132 t_dyn = t_dyn.data
133 orbphase = orbphase.data
134 copr_quat = np.array([quat0.data, quat1.data, quat2.data, quat3.data])
135 chiA_copr = np.array([chiAx.data, chiAy.data, chiAz.data]).T
136 chiB_copr = np.array([chiBx.data, chiBy.data, chiBz.data]).T
137
138 return t_dyn, copr_quat, orbphase, chiA_copr, chiB_copr
139
140 #-------------------------------------------------------------------------
141 def _get_pn_spins_at_surrogate_start(self, q, \
142 chiA0, chiB0, omega0, omega_switch_IG, t_sur_switch,
143 unlimited_extrapolation):
144 """ Gets PN spins and frame dynamics at a time close to the start
145 of the surrogate waveform model.
146
147 Inputs:
148 q: Mass ratio, mA/mB >= 1.
149 chiA0: Initial dimless spin of BhA in the LAL inertial frame.
150 chiB0: Initial dimless spin of BhB in the LAL inertial frame.
151 omega0: Initial orbital frequency. The spins are specified at
152 this frequency. Note: This is total-mass times the
153 angular orbital frequency.
154 omega_switch_IG:
155 Initial guess for the orbital frequency at which to
156 switch from PN to surrogate dynamics. We use PN to
157 evolve the spins until the surrogate becomes valid. The
158 surrogate is in time domain, so it is not
159 straightforward to determine the frequency at which it
160 is valid. So, we evolve with PN until omega_switch_IG.
161 However, this is chosen to be overly conservative such
162 that this initial guess works for all q<=6 cases.
163 Ideally, we would like to use as much as possible of
164 the surrogate spin evolution. So, using the PN spins at
165 omega_switch_IG, the surrogate dynamics are evolved
166 both backwards and forwards in time. This lets us get
167 surrogate orbital frequency at t_sur_switch, as well as
168 the corresponding PN spins at this time.
169 t_sur_switch:
170 The time at which the PN spins are returned so that
171 surrogate dynamics can be regenerated starting as early
172 as possible.
173 unlimited_extrapolation:
174 If True, Allow unlimited extrapolation to regions well
175 outside the surrogate's training region. Else, raises
176 an error.
177
178 Outputs:
179 PN spin and frame dynamics at t_sur_switch, as well as the spins
180 and orbital frequency evolution.
181
182 chiA_PN_at_idx_coorb:
183 PN spin of BhA at t_sur_switch, in the coorbital frame.
184 chiB_PN_at_idx_coorb:
185 PN spin of BhB at t_sur_switch, in the coorbital frame.
186 quat_PN_copr_at_idx:
187 PN coprecessing frame quaternions at t_sur_switch.
188 phi_PN_at_idx:
189 PN orbital phase at t_sur_switch.
190 omega_PN_at_idx:
191 PN orbital frequency at t_sur_switch.
192 chiA_PN: PN Spin evolution of BhA in the inertial frame.
193 chiB_PN: PN Spin evolution of BhB in the inertial frame.
194 omega_PN: PN orbital frequency evolution. Note: This is total-mass
195 times the angular orbital frequency.
196 """
197
198 # Get PN spin evolution starting at omega0
199 omega_PN, phi_PN, chiA_PN, chiB_PN, lNhat_PN \
200 = pn_spin_evolution_wrapper.spin_evolution(q, chiA0, chiB0, omega0)
201
202 # Get PN coorbital frame spins and frame dynamics at
203 # omega_PN=omega_switch_IG
204 idx = np.argmin(np.abs(omega_PN - omega_switch_IG))
205 chiA_PN_at_idx_coorb, chiB_PN_at_idx_coorb, quat_PN_copr_at_idx, \
206 phi_PN_at_idx, omega_PN_at_idx \
207 = self._get_coorbital_frame_spins_at_idx(chiA_PN, chiB_PN, \
208 omega_PN, lNhat_PN, phi_PN, idx)
209
210 # Now evaluate the surrogate dynamics (both forwards and backwards)
211 # using PN spins at omega_switch_IG
212 dyn_times, quat_sur, orbphase_sur, chiA_copr_sur, chiB_copr_sur \
213 = self._get_surrogate_dynamics(q, chiA_PN_at_idx_coorb, \
214 chiB_PN_at_idx_coorb, quat_PN_copr_at_idx, phi_PN_at_idx, \
215 omega_switch_IG, unlimited_extrapolation)
216 omega_sur = np.gradient(orbphase_sur, dyn_times)
217
218 # Get surrogate orbital frequency at t_sur_switch, which is
219 # close to the start of the surrogate data
220 omega_init_sur = omega_sur[np.argmin(np.abs(dyn_times - t_sur_switch))]
221
222 # Get PN coorbital frame spins and frame dynamics at omega_init_sur.
223 # These must be in the coorbital frame as the surrogate dynamics
224 # expects the inputs in the LAL convention which agrees with the
225 # coorbital frame.
226 idx = np.argmin(np.abs(omega_PN - omega_init_sur))
227 chiA_PN_at_idx_coorb, chiB_PN_at_idx_coorb, quat_PN_copr_at_idx, \
228 phi_PN_at_idx, omega_PN_at_idx \
229 = self._get_coorbital_frame_spins_at_idx(chiA_PN, chiB_PN, \
230 omega_PN, lNhat_PN, phi_PN, idx)
231
232 return chiA_PN_at_idx_coorb, chiB_PN_at_idx_coorb, \
233 quat_PN_copr_at_idx, phi_PN_at_idx, omega_PN_at_idx, \
234 chiA_PN, chiB_PN, omega_PN
235
236 #-------------------------------------------------------------------------
237 def _evolve_spins(self, q, chiA0, chiB0, omega0, omega_switch_IG=0.03, \
238 t_sur_switch=-4000, return_spin_evolution=False, \
239 unlimited_extrapolation=False):
240 """ Uses PN and surrogate spin evolution to evolve spins of the
241 component BHs from an initial orbital frequency = omega0 until t=-100 M
242 from the peak of the waveform.
243
244 Inputs:
245 q: Mass ratio, mA/mB >= 1.
246 chiA0: Initial dimless spin of BhA in the inertial frame.
247 chiB0: Initial dimless spin of BhB in the inertial frame. Note
248 that the LAL inertial frame coincides with the
249 coorbital frame used in the surrogate models.
250 omega0: Initial orbital frequency in the coprecessing frame.
251 The spins are specified at this frequency. Note: This
252 is total-mass times the angular orbital frequency.
253 omega_switch_IG:
254 Initial guess for orbital frequency used to determine
255 if PN spin evolution is required, and where to switch
256 from PN spin evolution to the surrogate.
257 If omega0 >= omega_switch_IG:
258 PN evolution is not required and the surrogate dynamics
259 are used to evolve the spin until t=-100M. Default
260 value for omega_switch_IG is 0.03 which is expected to
261 be large enough for q<=6. NOTE: If you get errors
262 about omega0 being too small for the surrogate, try
263 increasing omega_switch_IG.
264 If omega0 < omega_switch_IG:
265 Use PN to evolve the spins until the surrogate becomes
266 valid. The surrogate is in time domain, so it is not
267 straightforward to determine the frequency at which it
268 is valid. So, we evolve with PN until omega_switch_IG.
269 However, this is chosen to be overly conservative such
270 that this initial guess works for all q<=6 cases.
271 Ideally, we would like to use as much as possible of
272 the surrogate spin evolution. So, using the PN spins at
273 omega_switch_IG, the surrogate dynamics are evolved
274 both backwards and forwards in time. Then the PN spins
275 at omega = omega_sur[t_sur_switch] are used to
276 reevaluate the surrogate dynamics. This way,
277 omega_switch_IG is only used as an initial guess for
278 where to switch to the surrogate, and we switch to the
279 surrogate as early as possible.
280 t_sur_switch:
281 The time at which we switch to the surrogate evolution.
282 This should be slightly larger than the surrogate start
283 time of -4300M. Since we use the PN spins at
284 omega_sur[t_sur_switch] to reevaluate the surrogate
285 dynamics, it is possible that for the given PN spins at
286 omega_sur[t_sur_switch], the surrogate is not long
287 enough. Default: -4000.
288 return_spin_evolution:
289 Also return the spin evolution and dynamics as a
290 dictionary.
291 unlimited_extrapolation:
292 If True, Allow unlimited extrapolation to regions well
293 outside the surrogate's training region. Else, raises
294 an error.
295 Outputs:
296 chiA_coorb_fitnode:
297 Spin of BhA in the coorbital frame at t=-100M.
298 chiB_coorb_fitnode:
299 Spin of BhB in the coorbital frame at t=-100M.
300 quat_fitnode:
301 Coprecessing frame quaternions at t=-100M.
302 orbphase_fitnode:
303 Orbital phase in the coprecessing frame at t=-100M.
304 spin_evolution:
305 Dictionary containing the PN and surrogate spin
306 evolution (in the inertial frame) and dynamics.
307 Default: None, unless return_spin_evolution=True.
308 """
309
310 # If omega0 is below the NRSur7dq4 initial guess frequency, we use PN
311 # to evolve the spins. We get the initial PN spins and omega_init_sur
312 # that should go into the surrogate such that the initial time is
313 # t_sur_switch.
314 if omega0 < omega_switch_IG:
315 chiA0_nrsur_coorb, chiB0_nrsur_coorb, quat0_nrsur_copr, \
316 phi0_nrsur, omega_init_sur, chiA_PN, chiB_PN, omega_PN \
318 chiA0, chiB0, omega0, omega_switch_IG, t_sur_switch,
319 unlimited_extrapolation)
320
321 # If omega0 >= omega_switch_IG, we evolve spins directly with NRSur7dq4
322 # waveform model. We set the coprecessing frame quaternion to identity
323 # and orbital phase to 0 at omega=omega0, hence the coorbital frame is
324 # the same as the inertial frame here.
325 else:
326 # Note that here we set omega_init_sur to omega0
327 chiA0_nrsur_coorb, chiB0_nrsur_coorb, quat0_nrsur_copr, \
328 phi0_nrsur, omega_init_sur, chiA_PN, chiB_PN, omega_PN \
329 = chiA0, chiB0, [1,0,0,0], 0, omega0, None, None, None
330
331 # Now evaluate the surrogate dynamics using PN spins at omega_init_sur
332 dyn_times, quat_sur, orbphase_sur, chiA_copr_sur, chiB_copr_sur \
333 = self._get_surrogate_dynamics(q, chiA0_nrsur_coorb, \
334 chiB0_nrsur_coorb, quat0_nrsur_copr, phi0_nrsur, \
335 omega_init_sur, unlimited_extrapolation)
336
337 # get data at time node where remnant fits are done
338 fitnode_time = -100
339 nodeIdx = np.argmin(np.abs(dyn_times - fitnode_time))
340 quat_fitnode = quat_sur.T[nodeIdx]
341 orbphase_fitnode = orbphase_sur[nodeIdx]
342
343 # get coorbital frame spins at the time node
344 chiA_coorb_fitnode = quaternion_utils.rotate_in_plane(
345 chiA_copr_sur[nodeIdx], orbphase_fitnode)
346 chiB_coorb_fitnode = quaternion_utils.rotate_in_plane(
347 chiB_copr_sur[nodeIdx], orbphase_fitnode)
348
349 if return_spin_evolution:
350 # Transform spins to the reference inertial frame
351 chiA_sur = quaternion_utils.transform_time_dependent_vector(
352 quat_sur, chiA_copr_sur.T).T
353 chiB_sur = quaternion_utils.transform_time_dependent_vector(
354 quat_sur, chiB_copr_sur.T).T
355 spin_evolution = {
356 't_sur': dyn_times,
357 'chiA_sur': chiA_sur,
358 'chiB_sur': chiB_sur,
359 'orbphase_sur': orbphase_sur,
360 'quat_sur': quat_sur,
361 'omega_PN': omega_PN,
362 'chiA_PN': chiA_PN,
363 'chiB_PN': chiB_PN,
364 'omega_init_sur': omega_init_sur,
365 }
366 else:
367 spin_evolution = None
368
369 return chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
370 orbphase_fitnode, spin_evolution
371
372 # ------------------------------------------------------------------------
373 def _get_fit_params(self, m1, m2, chiA_vec, chiB_vec, f_ref,
374 extra_params_dict):
375 """
376 If f_ref = -1, assumes the reference epoch is at t=-100M and
377 just returns the input spins, which are expected to be in
378 the coorbital frame at t=-100M.
379 Else, takes the spins at f_ref and evolves them using a combination of
380 PN and NRSur7dq4 until t=-100M. The returned spins are in the coorbital
381 frame at t=-100M.
382
383 See eval_fits.eval_nrfit() for the definitions of the arguments of
384 this function.
385 """
386
387 unlimited_extrapolation = extra_params_dict["unlimited_extrapolation"]
388
389 q = m1/m2
390 M = m1 + m2 # m1, m2 are assumed to be in kgs
391
392 if f_ref == -1:
393 # If f_ref = -1, assume the reference epoch is at t=-100M from the
394 # total waveform amplitude peak. All input and output quantities
395 # are in the coorbital frame at this time.
396 chiA_coorb_fitnode = chiA_vec
397 chiB_coorb_fitnode = chiB_vec
398 # We set this to None, and this will be used in _eval_fit() to
399 # determine if the remnant vectors need any further transformations.
400 quat_fitnode = None
401 orbphase_fitnode = None
402 else:
403 # Else, evolve the spins from f_ref to t = -100 M from the peak.
404
405 # reference orbital angular frequency times the total mass (M*omega)
406 omega_ref = f_ref*np.pi*M*lal.G_SI/lal.C_SI**3
407
408 chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
409 orbphase_fitnode, spin_evolution = self._evolve_spins(q, \
410 chiA_vec, chiB_vec, omega_ref, \
411 unlimited_extrapolation=unlimited_extrapolation)
412
413 fit_params = [q, chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
414 orbphase_fitnode]
415
416 return fit_params
417
418 # ------------------------------------------------------------------------
419 def _eval_fit(self, fit_params, fit_type, extra_params_dict):
420 """ Evaluates a particular fit for NRSur7dq4Remnant using the fit_params
421 returned by _get_fit_params().
422
423 fit_type can be one of "FinalMass", "FinalSpin" or "RecoilKick".
424
425 Passing extra_params_dict = {"unlimited_extrapolation": True}, will
426 ignore any extrapolation errors. USE AT YOUR OWN RISK!! Default: False.
427 """
428
429 q, chiA_vec, chiB_vec, quat_fitnode, orbphase_fitnode = fit_params
430 LALParams = lal.CreateDict()
431 if extra_params_dict["unlimited_extrapolation"]:
432 lal.DictInsertUINT4Value(LALParams, "unlimited_extrapolation", 1)
433
434 if fit_type == "FinalMass":
435 # FinalMass is given as a fraction of total mass
436 val = lalsim.NRSur7dq4Remnant(q,
437 chiA_vec[0], chiA_vec[1], chiA_vec[2],
438 chiB_vec[0], chiB_vec[1], chiB_vec[2], "mf",
439 LALParams).data[0]
440 else:
441 if fit_type == "FinalSpin":
442 val = lalsim.NRSur7dq4Remnant(q,
443 chiA_vec[0], chiA_vec[1], chiA_vec[2],
444 chiB_vec[0], chiB_vec[1], chiB_vec[2], "chif",
445 LALParams).data
446 elif fit_type == "RecoilKick":
447 val = lalsim.NRSur7dq4Remnant(q,
448 chiA_vec[0], chiA_vec[1], chiA_vec[2],
449 chiB_vec[0], chiB_vec[1], chiB_vec[2], "vf",
450 LALParams).data
451 else:
452 raise ValueError("Invalid fit_type=%s. This model only allows "
453 "'FinalMass', 'FinalSpin' and 'RecoilKick'."%fit_type)
454
455 # The fits are constructed in the coorbital frame at t=-100M.
456 # If quat_fitnode is None, this means that no spin evolution
457 # was done and the return values should be in the coorbital frame
458 # at t=-100. So, we don't need any further transformations.
459 if quat_fitnode is not None:
460 # If quat_fitnode is not None, we transform the remnant vectors
461 # into the LAL inertial frame, which is the same as the
462 # coorbital frame at omega0. This is done using the
463 # coprecessing frame quaternion and orbital phase at t=-100M.
464 # These are defined w.r.t. the reference LAL inertial frame
465 # defined at omega0.
466 val = quaternion_utils.transform_vector_coorb_to_inertial(val, \
467 orbphase_fitnode, quat_fitnode)
468
469 return np.atleast_1d(val)
Class for NRSur7dq4Remnant model for the mass, spin and kick of the final BH of generically precessin...
def _get_surrogate_dynamics(self, q, chiA0, chiB0, init_quat, init_orbphase, omega_ref, unlimited_extrapolation)
def _get_coorbital_frame_spins_at_idx(self, chiA, chiB, omega, lNhat, phi, idx)
def _evolve_spins(self, q, chiA0, chiB0, omega0, omega_switch_IG=0.03, t_sur_switch=-4000, return_spin_evolution=False, unlimited_extrapolation=False)
def _get_pn_spins_at_surrogate_start(self, q, chiA0, chiB0, omega0, omega_switch_IG, t_sur_switch, unlimited_extrapolation)
Base class for Numerical Relativity fits such as remnant BH mass, spin, etc.
Definition: nrfits.py:19