2Class for NRSur7dq4Remnant model for the mass, spin and kick of the final BH of
3generically precessing binary black holes.
9import lalsimulation
as lalsim
12from .
import pn_spin_evolution_wrapper
13from .
import quaternion_utils
14from .nrfits
import NRFits
18 Class for NRSur7dq4Remnant model
for the mass, spin
and kick of the final BH
19 of generically precessing binary black holes.
21 Paper: arxiv:1905.09300
23 Parameter ranges
for usage: \n
25 \f$\chi_{A}, \chi_{B}\f$ = [-1, 1]
27 Training parameter ranges: \n
29 \f$\chi_{A}, \chi_{B}\f$ = [-0.81, 0.81]
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.
38 def _get_coorbital_frame_spins_at_idx(self, chiA, chiB, omega, lNhat, phi, \
40 """ Computes PN spins and dynamics at a given idx.
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.
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).
59 The inertial frame
is assumed to be aligned to the coorbital frame at
65 omega_at_idx = omega[idx]
66 chiA_at_idx = chiA[idx]
67 chiB_at_idx = chiB[idx]
68 lNhat_at_idx = lNhat[idx]
73 quat_copr_at_idx = quaternion_utils.align_vec_quat(lNhat_at_idx)
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]
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)
89 return chiA_at_idx_coorb, chiB_at_idx_coorb, quat_copr_at_idx, \
90 phi_at_idx, omega_at_idx
93 def _get_surrogate_dynamics(self, q, chiA0, chiB0, init_quat, \
94 init_orbphase, omega_ref, unlimited_extrapolation):
95 """ A wrapper for NRSur7dq4 dynamics.
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.
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
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.
121 approxTag = lalsim.SimInspiralGetApproximantFromString("NRSur7dq4")
122 LALParams = lal.CreateDict()
123 if unlimited_extrapolation:
124 lal.DictInsertUINT4Value(LALParams,
"unlimited_extrapolation", 1)
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)
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
138 return t_dyn, copr_quat, orbphase, chiA_copr, chiB_copr
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.
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.
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.
170 The time at which the PN spins are returned so that
171 surrogate dynamics can be regenerated starting
as early
173 unlimited_extrapolation:
174 If
True, Allow unlimited extrapolation to regions well
175 outside the surrogate
's training region. Else, raises
179 PN spin and frame dynamics at t_sur_switch,
as well
as the spins
180 and orbital frequency evolution.
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.
187 PN coprecessing frame quaternions at t_sur_switch.
189 PN orbital phase at t_sur_switch.
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.
199 omega_PN, phi_PN, chiA_PN, chiB_PN, lNhat_PN \
200 = pn_spin_evolution_wrapper.spin_evolution(q, chiA0, chiB0, omega0)
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 \
208 omega_PN, lNhat_PN, phi_PN, idx)
212 dyn_times, quat_sur, orbphase_sur, chiA_copr_sur, chiB_copr_sur \
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)
220 omega_init_sur = omega_sur[np.argmin(np.abs(dyn_times - t_sur_switch))]
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 \
230 omega_PN, lNhat_PN, phi_PN, idx)
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
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.
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.
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.
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
291 unlimited_extrapolation:
292 If
True, Allow unlimited extrapolation to regions well
293 outside the surrogate
's training region. Else, raises
297 Spin of BhA in the coorbital frame at t=-100M.
299 Spin of BhB
in the coorbital frame at t=-100M.
301 Coprecessing frame quaternions at t=-100M.
303 Orbital phase
in the coprecessing frame at t=-100M.
305 Dictionary containing the PN
and surrogate spin
306 evolution (
in the inertial frame)
and dynamics.
307 Default:
None, unless return_spin_evolution=
True.
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)
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
332 dyn_times, quat_sur, orbphase_sur, chiA_copr_sur, chiB_copr_sur \
334 chiB0_nrsur_coorb, quat0_nrsur_copr, phi0_nrsur, \
335 omega_init_sur, unlimited_extrapolation)
339 nodeIdx = np.argmin(np.abs(dyn_times - fitnode_time))
340 quat_fitnode = quat_sur.T[nodeIdx]
341 orbphase_fitnode = orbphase_sur[nodeIdx]
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)
349 if return_spin_evolution:
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
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,
364 'omega_init_sur': omega_init_sur,
367 spin_evolution =
None
369 return chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
370 orbphase_fitnode, spin_evolution
373 def _get_fit_params(self, m1, m2, chiA_vec, chiB_vec, f_ref,
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
383 See eval_fits.eval_nrfit()
for the definitions of the arguments of
387 unlimited_extrapolation = extra_params_dict["unlimited_extrapolation"]
396 chiA_coorb_fitnode = chiA_vec
397 chiB_coorb_fitnode = chiB_vec
401 orbphase_fitnode =
None
406 omega_ref = f_ref*np.pi*M*lal.G_SI/lal.C_SI**3
408 chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
410 chiA_vec, chiB_vec, omega_ref, \
411 unlimited_extrapolation=unlimited_extrapolation)
413 fit_params = [q, chiA_coorb_fitnode, chiB_coorb_fitnode, quat_fitnode, \
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().
423 fit_type can be one of "FinalMass",
"FinalSpin" or "RecoilKick".
425 Passing extra_params_dict = {
"unlimited_extrapolation":
True}, will
426 ignore any extrapolation errors. USE AT YOUR OWN RISK!! Default:
False.
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)
434 if fit_type ==
"FinalMass":
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",
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",
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",
452 raise ValueError(
"Invalid fit_type=%s. This model only allows "
453 "'FinalMass', 'FinalSpin' and 'RecoilKick'."%fit_type)
459 if quat_fitnode
is not None:
466 val = quaternion_utils.transform_vector_coorb_to_inertial(val, \
467 orbphase_fitnode, quat_fitnode)
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.