Coverage for pesummary/gw/conversions/remnant.py: 23.0%
261 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
1# Licensed under an MIT style license -- see LICENSE.md
3import numpy as np
5from pesummary.utils.utils import logger, iterator
6from pesummary.utils.decorators import array_input
7from .spins import chi_p
8from .mass import eta_from_m1_m2
10__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"]
12try:
13 import lalsimulation
14 from lalsimulation import (
15 FLAG_SEOBNRv4P_HAMILTONIAN_DERIVATIVE_NUMERICAL,
16 FLAG_SEOBNRv4P_EULEREXT_QNM_SIMPLE_PRECESSION,
17 FLAG_SEOBNRv4P_ZFRAME_L
18 )
19 from lal import MSUN_SI
20except ImportError:
21 pass
23DEFAULT_SEOBFLAGS = {
24 "SEOBNRv4P_SpinAlignedEOBversion": 4,
25 "SEOBNRv4P_SymmetrizehPlminusm": 1,
26 "SEOBNRv4P_HamiltonianDerivative": FLAG_SEOBNRv4P_HAMILTONIAN_DERIVATIVE_NUMERICAL,
27 "SEOBNRv4P_euler_extension": FLAG_SEOBNRv4P_EULEREXT_QNM_SIMPLE_PRECESSION,
28 "SEOBNRv4P_Zframe": FLAG_SEOBNRv4P_ZFRAME_L,
29 "SEOBNRv4P_debug": 0
30}
33@array_input()
34def final_mass_of_merger_from_NSBH(
35 mass_1, mass_2, spin_1z, lambda_2, approximant="IMRPhenomNSBH"
36):
37 """Calculate the final mass resulting from an NSBH merger using NSBH
38 waveform models given samples for mass_1, mass_2, spin_1z and lambda_2.
39 mass_1 and mass_2 should be in solar mass units.
40 """
41 from .tidal import _check_NSBH_approximant
42 return _check_NSBH_approximant(
43 approximant, mass_1, mass_2, spin_1z, lambda_2
44 )[4]
47@array_input()
48def final_spin_of_merger_from_NSBH(
49 mass_1, mass_2, spin_1z, lambda_2, approximant="IMRPhenomNSBH"
50):
51 """Calculate the final spin resulting from an NSBH merger using NSBH
52 waveform models given samples for mass_1, mass_2, spin_1z and lambda_2.
53 mass_1 and mass_2 should be in solar mass units.
54 """
55 from .tidal import _check_NSBH_approximant
56 return _check_NSBH_approximant(
57 approximant, mass_1, mass_2, spin_1z, lambda_2
58 )[5]
61@array_input()
62def _final_from_initial_NSBH(*args, **kwargs):
63 """Calculate the final mass and final spin given the initial parameters
64 of the binary using the approximant directly
65 """
66 return [
67 final_mass_of_merger_from_NSBH(*args, **kwargs),
68 final_spin_of_merger_from_NSBH(*args, **kwargs)
69 ]
72def _wrapper_return_final_mass_and_final_spin_from_waveform(args):
73 """Wrapper function to calculate the remnant properties for a given waveform
74 for a pool of workers
76 Parameters
77 ----------
78 args: np.ndarray
79 2 dimensional array giving arguments to pass to
80 _return_final_mass_and_final_spin_from_waveform. The first argument
81 in each sublist is the keyword and the second argument in each sublist
82 is the item you wish to pass
83 """
84 kwargs = {arg[0]: arg[1] for arg in args}
85 return _return_final_mass_and_final_spin_from_waveform(**kwargs)
88def _return_final_mass_and_final_spin_from_waveform(
89 mass_function=None, spin_function=None, mass_function_args=[],
90 spin_function_args=[], mass_function_return_function=None,
91 mass_function_return_index=None, spin_function_return_function=None,
92 spin_function_return_index=None, mass_1_index=0, mass_2_index=1,
93 nsamples=0, approximant=None, default_SEOBNRv4P_kwargs=False,
94 mass_1=None, mass_2=None
95):
96 """Return the final mass and final spin given functions to use
98 Parameters
99 ----------
100 mass_function: func
101 function you wish to use to calculate the final mass
102 spin_function: func
103 function you wish to use to calculate the final spin
104 mass_function_args: list
105 list of arguments you wish to pass to mass_function
106 spin_function_args: list
107 list of arguments you wish to pass to spin_function
108 mass_function_return_function: str, optional
109 function used to extract the final mass from the quantity returned from
110 mass_function. For example, if mass_function returns a list and the
111 final_mass is a property of the 3 arg of this list,
112 mass_function_return_function='[3].final_mass'
113 mass_function_return_index: str, optional
114 if mass_function returns a list of parameters,
115 mass_function_return_index indicates the index of `final_mass` in the
116 list
117 spin_function_return_function: str, optional
118 function used to extract the final spin from the quantity returned from
119 spin_function. For example, if spin_function returns a list and the
120 final_spin is a property of the 3 arg of this list,
121 spin_function_return_function='[3].final_spin'
122 spin_function_return_index: str, optional
123 if spin_function returns a list of parameters,
124 spin_function_return_index indicates the index of `final_spin` in the
125 list
126 mass_1_index: int, optional
127 the index of mass_1 in mass_function_args. Default is 0
128 mass_2_index: int, optional
129 the index of mass_2 in mass_function_args. Default is 1
130 nsamples: int, optional
131 the total number of samples
132 approximant: str, optional
133 the approximant used
134 default_SEOBNRv4P_kwargs: Bool, optional
135 if True, use the default SEOBNRv4P flags
136 mass_1: str, optional
137 function used to give samples for the primary mass. If provided,
138 mass_1_index is ignored
139 mass_2: str, optional
140 function used to give samples for the secondary mass. If provided,
141 mass_2_index is ignored
142 """
143 if default_SEOBNRv4P_kwargs:
144 mode_array, seob_flags = _setup_SEOBNRv4P_args()
145 mass_function_args += [mode_array, seob_flags]
146 spin_function_args += [mode_array, seob_flags]
147 fm = mass_function(*mass_function_args)
148 if mass_function_return_function is not None:
149 fm = eval("fm{}".format(mass_function_return_function))
150 elif mass_function_return_index is not None:
151 fm = fm[mass_function_return_index]
152 fs = spin_function(*spin_function_args)
153 if spin_function_return_function is not None:
154 fs = eval("fs{}".format(spin_function_return_function))
155 elif spin_function_return_index is not None:
156 fs = fs[spin_function_return_index]
157 if mass_1 is None:
158 mass1 = mass_function_args[mass_1_index]
159 else:
160 mass1 = eval(mass_1)
161 if mass_2 is None:
162 mass2 = mass_function_args[mass_2_index]
163 else:
164 mass2 = eval(mass_2)
165 final_mass = fm * (mass1 + mass2) / MSUN_SI
166 final_spin = fs
167 return final_mass, final_spin
170def _setup_SEOBNRv4P_args(mode=[2, 2], seob_flags=DEFAULT_SEOBFLAGS):
171 """Setup the SEOBNRv4P[HM] kwargs
172 """
173 from lalsimulation import (
174 SimInspiralCreateModeArray, SimInspiralModeArrayActivateMode
175 )
176 from lal import DictInsertINT4Value, CreateDict
178 mode_array = SimInspiralCreateModeArray()
179 SimInspiralModeArrayActivateMode(mode_array, mode[0], mode[1])
180 _seob_flags = CreateDict()
181 for key, item in seob_flags.items():
182 DictInsertINT4Value(_seob_flags, key, item)
183 return mode_array, _seob_flags
186def _SimIMRPhenomXFinalMass2017Wrapper(eta, spin_1z, spin_2z, mass_1, mass2):
187 """Wrapper for SimIMRPhenomXFinalMass2017 which takes
188 2 additional arguments: mass_1 and mass_2. This is needed
189 for the downstream `_return_final_mass_and_final_spin_from_waveform`
190 function
192 Parameters
193 ----------
194 eta: np.ndarray
195 symmetric mass ratio of the binary
196 spin_1z: np.ndarray
197 component of the primary spin aligned with the orbital angular momentum
198 spin_1z: np.ndarray
199 component of the secondary spin aligned with the orbital angular momentum
200 mass_1: np.ndarray
201 primary mass of the binary
202 mass_2: np.ndarray
203 secondary mass of the binary
204 """
205 from lalsimulation import SimIMRPhenomXFinalMass2017
206 return SimIMRPhenomXFinalMass2017(eta, spin_1z, spin_2z)
209def _SimIMRPhenomXFinalSpinWrapper(
210 eta, spin_1z, spin_2z, flags={}, chi_perp_mag=None, chi_p=None, spin_1x=None
211):
212 """Wrapper for SimIMRPhenomXFinalSpin2017 and SimIMRPhenomXPrecessingFinalSpin2017
213 which accounts for the different variants of PhenomX.
215 Parameters
216 ----------
217 eta: np.ndarray
218 symmetric mass ratio of the binary
219 spin_1z: np.ndarray
220 component of the primary spin aligned with the orbital angular momentum
221 spin_2z: np.ndarray
222 component of the secondary spin aligned with the orbital angular momentum
223 flags: dict, optional
224 dictionary containing flags to specify the different variants of PhenomX
225 chi_perp_mag: np.ndarray, optional
226 perpendicular magnitude of the binaries spin angular momentum. Must be
227 provided for precessing systems
228 chi_p: np.ndarray, optional
229 precessing spin of the binary. Must be provided for precessing systems
230 spin_1x: np.ndarray, optional
231 component of the primary spin which is perpendicular to the orbital
232 angular momentum, in the x-plane. Must be provided for precessing
233 systems
234 """
235 from lalsimulation import (
236 SimIMRPhenomXPrecessingFinalSpin2017, SimIMRPhenomXFinalSpin2017
237 )
239 _spinflag = int(flags.get("PhenomXPFinalSpinMod", 4))
240 _precflag = int(flags.get("PhenomXPrecVersion", 300))
241 final_spin_parallel = SimIMRPhenomXFinalSpin2017(eta, spin_1z, spin_2z)
242 if chi_perp_mag is None:
243 return final_spin_parallel
244 if _spinflag == 0:
245 if chi_p is None:
246 raise ValueError("Please provide samples for chi_p")
247 final_spin_perp = SimIMRPhenomXPrecessingFinalSpin2017(
248 eta, spin_1z, spin_2z, chi_p
249 )
250 elif _spinflag == 1:
251 if spin_1x is None:
252 raise ValueError("Please provide samples for spin_1x")
253 final_spin_perp = SimIMRPhenomXPrecessingFinalSpin2017(
254 eta, spin_1z, spin_2z, spin_1x
255 )
256 elif _spinflag == 3:
257 if _precflag in [220, 221, 222, 223, 224]:
258 logger.warning(
259 "Computing the remnant properties for PhenomX with "
260 "precession version: {} requires variables that are "
261 "not accessible with pesummary. Defaulting to final "
262 "spin version 0 ".format(_precflag)
263 )
264 if chi_p is None:
265 raise ValueError("Please provide samples for chi_p")
266 final_spin_perp = SimIMRPhenomXPrecessingFinalSpin2017(
267 eta, spin_1z, spin_2z, chi_p
268 )
269 elif _spinflag == 4:
270 final_spin_perp = SimIMRPhenomXPrecessingFinalSpin2017(
271 eta, spin_1z, spin_2z, chi_perp_mag
272 )
273 else:
274 raise ValueError(
275 "Unable to calculate remnant properties for specified PhenomX "
276 "variant."
277 )
278 if isinstance(final_spin_perp, (list, np.ndarray)):
279 final_spin_perp[final_spin_perp > 1.] = 1.
280 else:
281 if final_spin_perp > 1.:
282 final_spin_perp = 1.
283 return final_spin_perp
286@array_input()
287def _final_from_initial_BBH(
288 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
289 approximant="SEOBNRv4", iota=None, luminosity_distance=None, f_low=20.,
290 f_ref=None, phi_ref=None, mode=[2, 2], delta_t=1. / 4096,
291 seob_flags=DEFAULT_SEOBFLAGS, xphm_flags={}, return_fits_used=False,
292 multi_process=None
293):
294 """Calculate the final mass and final spin given the initial parameters
295 of the binary using the approximant directly
297 Parameters
298 ----------
299 mass_1: float/np.ndarray
300 primary mass of the binary
301 mass_2: float/np.ndarray
302 secondary mass of the binary
303 spin_1x: float/np.ndarray
304 x component of the primary spin
305 spin_1y: float/np.ndarray
306 y component of the primary spin
307 spin_1z: float/np.ndarray
308 z component of the primary spin
309 spin_2x: float/np.ndarray
310 x component of the secondary spin
311 spin_2y: float/np.ndarray
312 y component of the secondary spin
313 spin_2z: float/np.ndarray
314 z component of the seconday spin
315 approximant: str
316 name of the approximant you wish to use for the remnant fits
317 iota: float/np.ndarray, optional
318 the angle between the total orbital angular momentum and the line of
319 sight of the source. Used when calculating the remnant fits for
320 SEOBNRv4PHM. Since we only need the EOB dynamics here it does not matter
321 what we pass
322 luminosity_distance: float/np.ndarray, optional
323 the luminosity distance of the source. Used when calculating the
324 remnant fits for SEOBNRv4PHM. Since we only need the EOB dynamics here
325 it does not matter what we pass.
326 f_low: float/np.ndarray, optional
327 the low frequency to evaluate the waveform model. Only used if
328 approximant=SEOBNRv5PHM
329 f_ref: float/np.ndarray, optional
330 the reference frequency at which the spins are defined
331 phi_ref: float/np.ndarray, optional
332 the coalescence phase of the binary
333 mode: list, optional
334 specific mode to use when calculating the remnant fits for SEOBNRv4PHM.
335 Since we only need the EOB dynamics here it does not matter what we
336 pass.
337 delta_t: float, optional
338 the sampling rate used in the analysis, Used when calculating the
339 remnant fits for SEOBNRv4PHM
340 seob_flags: dict, optional
341 dictionary containing the SEOB flags. Used when calculating the remnant
342 fits for SEOBNRv4PHM
343 xphm_flags: dict, optional
344 dictionary containing the XPHM flags used during the sampling. Used when
345 calculating the remnant fits for IMRPhenomXPHM
346 return_fits_used: Bool, optional
347 if True, return the approximant that was used.
348 multi_process: int, optional
349 the number of cores to use when calculating the remnant fits
350 """
351 from lalsimulation import (
352 SimIMREOBFinalMassSpin, SimInspiralGetSpinSupportFromApproximant,
353 SimIMRSpinPrecEOBWaveformAll, SimPhenomUtilsIMRPhenomDFinalMass,
354 SimPhenomUtilsPhenomPv2FinalSpin,
355 )
356 import multiprocessing
358 def convert_args_for_multi_processing(kwargs):
359 args = []
360 for n in range(kwargs["nsamples"]):
361 _args = []
362 for key, item in kwargs.items():
363 if key == "mass_function_args" or key == "spin_function_args":
364 _args.append([key, [arg[n] for arg in item]])
365 else:
366 _args.append([key, item])
367 args.append(_args)
368 return args
370 m1 = mass_1 * MSUN_SI
371 m2 = mass_2 * MSUN_SI
372 kwargs = {"nsamples": len(mass_1), "approximant": approximant}
373 cond1 = approximant.lower() in ["seobnrv4p", "seobnrv4phm"]
374 cond2 = "seobnrv5" in approximant.lower()
375 if cond1 or cond2:
376 if any(i is None for i in [iota, luminosity_distance, f_ref, phi_ref]):
377 raise ValueError(
378 "The approximant '{}' requires samples for iota, f_ref, "
379 "phi_ref and luminosity_distance. Please pass these "
380 "samples.".format(approximant)
381 )
382 if len(delta_t) == 1:
383 delta_t = [delta_t[0]] * len(mass_1)
384 elif len(delta_t) != len(mass_1):
385 raise ValueError(
386 "Please provide either a single 'delta_t' that is is used for "
387 "all samples, or a single 'delta_t' for each sample"
388 )
389 if approximant.lower() in ["seobnrv4p", "seobnrv4phm"]:
390 mode_array, _seob_flags = _setup_SEOBNRv4P_args(
391 mode=mode, seob_flags=seob_flags
392 )
393 args = np.array([
394 phi_ref, delta_t, m1, m2, f_ref, luminosity_distance, iota,
395 spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
396 [mode_array] * len(mass_1), [_seob_flags] * len(mass_1)
397 ])
398 kwargs.update(
399 {
400 "mass_function": SimIMRSpinPrecEOBWaveformAll,
401 "spin_function": SimIMRSpinPrecEOBWaveformAll,
402 "mass_function_args": args,
403 "spin_function_args": args,
404 "mass_function_return_function": "[21].data[6]",
405 "spin_function_return_function": "[21].data[7]",
406 "mass_1_index": 2,
407 "mass_2_index": 3,
408 }
409 )
410 else:
411 from pesummary.gw.waveform import td_waveform
412 _samples = [
413 {
414 "mass_1": mass_1[ind], "mass_2": mass_2[ind],
415 "spin_1x": spin_1x[ind], "spin_1y": spin_1y[ind],
416 "spin_1z": spin_1z[ind], "spin_2x": spin_2x[ind],
417 "spin_2y": spin_2y[ind], "spin_2z": spin_2z[ind],
418 "iota": iota[ind],
419 "luminosity_distance": luminosity_distance[ind],
420 "phase": phi_ref[ind]
421 } for ind in np.arange(len(mass_1))
422 ]
423 args = np.array([
424 _samples, [approximant] * len(mass_1), delta_t, f_low,
425 f_ref, [None] * len(mass_1), [0] * len(mass_1), [0] * len(mass_1),
426 [0] * len(mass_1), [None] * len(mass_1), [None] * len(mass_1),
427 [False] * len(mass_1), [None] * len(mass_1), [1] * len(mass_1),
428 [{}] * len(mass_1), [True] * len(mass_1)
429 ], dtype=object)
430 kwargs.update(
431 {
432 "mass_function": td_waveform,
433 "spin_function": td_waveform,
434 "mass_function_args": args,
435 "spin_function_args": args,
436 "mass_function_return_function": "[1].model.final_mass",
437 "spin_function_return_function": "[1].model.final_spin",
438 "mass_1": "mass_function_args[0]['mass_1'] * MSUN_SI",
439 "mass_2": "mass_function_args[0]['mass_2'] * MSUN_SI",
440 }
441 )
442 elif approximant.lower() in ["seobnrv4"]:
443 lal_approx = getattr(lalsimulation, approximant)
444 spin1 = np.array([spin_1x, spin_1y, spin_1z]).T
445 spin2 = np.array([spin_2x, spin_2y, spin_2z]).T
446 app = np.array([lal_approx] * len(mass_1))
447 kwargs.update(
448 {
449 "mass_function": SimIMREOBFinalMassSpin,
450 "spin_function": SimIMREOBFinalMassSpin,
451 "mass_function_args": [m1, m2, spin1, spin2, app],
452 "spin_function_args": [m1, m2, spin1, spin2, app],
453 "mass_function_return_index": 1,
454 "spin_function_return_index": 2
455 }
456 )
457 elif "phenompv3" in approximant.lower():
458 lal_approx = getattr(lalsimulation, approximant)
459 kwargs.update(
460 {
461 "mass_function": SimPhenomUtilsIMRPhenomDFinalMass,
462 "spin_function": SimPhenomUtilsPhenomPv2FinalSpin,
463 "mass_function_args": [m1, m2, spin_1z, spin_2z],
464 "spin_function_args": [m1, m2, spin_1z, spin_2z]
465 }
466 )
467 if SimInspiralGetSpinSupportFromApproximant(lal_approx) > 2:
468 # matches the waveform's internal usage as corrected in
469 # https://git.ligo.org/lscsoft/lalsuite/-/merge_requests/1270
470 _chi_p = chi_p(mass_1, mass_2, spin_1x, spin_1y, spin_2x, spin_2y)
471 kwargs["spin_function_args"].append(_chi_p)
472 else:
473 kwargs["spin_function_args"].append(np.zeros_like(mass_1))
474 elif "phenomx" in approximant.lower():
475 lal_approx = getattr(lalsimulation, approximant)
476 _eta = eta_from_m1_m2(m1, m2)
477 kwargs.update(
478 {
479 "mass_function": _SimIMRPhenomXFinalMass2017Wrapper,
480 "spin_function": _SimIMRPhenomXFinalSpinWrapper,
481 "mass_function_args": [_eta, spin_1z, spin_2z, m1, m2],
482 "mass_1_index": 3,
483 "mass_2_index": 4,
484 }
485 )
486 if SimInspiralGetSpinSupportFromApproximant(lal_approx) > 2:
487 chi1_perp = np.array([spin_1x, spin_1y])
488 chi2_perp = np.array([spin_2x, spin_2y])
489 _chi_p = chi_p(mass_1, mass_1, spin_1x, spin_1y, spin_2x, spin_2y)
490 chi_perp = np.sum([chi1_perp, chi2_perp], axis=0)
491 chi_perp_mag = np.linalg.norm(chi_perp, axis=0)
492 kwargs.update(
493 {
494 "spin_function_args": [
495 _eta, spin_1z, spin_2z, [xphm_flags] * len(_eta), chi_perp_mag,
496 _chi_p, spin_1x
497 ]
498 }
499 )
500 else:
501 kwargs.update(
502 {
503 "spin_function_args": [_eta, spin_1z, spin_2z]
504 }
505 )
506 else:
507 raise ValueError(
508 "The waveform '{}' is not support by this function.".format(
509 approximant
510 )
511 )
513 args = convert_args_for_multi_processing(kwargs)
514 if multi_process is not None and multi_process[0] != 1:
515 _multi_process = multi_process[0]
516 if approximant.lower() in ["seobnrv4p", "seobnrv4phm"]:
517 logger.warning(
518 "Ignoring passed 'mode' and 'seob_flags' options. Defaults "
519 "must be used with multiprocessing. If you wish to use custom "
520 "options, please set `multi_process=None`"
521 )
522 _kwargs = kwargs.copy()
523 _kwargs["mass_function_args"] = kwargs["mass_function_args"][:-2]
524 _kwargs["spin_function_args"] = kwargs["spin_function_args"][:-2]
525 _kwargs["default_SEOBNRv4P_kwargs"] = True
526 args = convert_args_for_multi_processing(_kwargs)
527 with multiprocessing.Pool(_multi_process) as pool:
528 data = np.array(list(
529 iterator(
530 pool.imap(
531 _wrapper_return_final_mass_and_final_spin_from_waveform,
532 args
533 ), tqdm=True, desc="Evaluating {} fit".format(approximant),
534 logger=logger, total=len(mass_1)
535 )
536 )).T
537 else:
538 final_mass, final_spin = [], []
539 _iterator = iterator(
540 range(kwargs["nsamples"]), tqdm=True, total=len(mass_1),
541 desc="Evaluating {} fit".format(approximant), logger=logger
542 )
543 for i in _iterator:
544 data = _wrapper_return_final_mass_and_final_spin_from_waveform(
545 args[i]
546 )
547 final_mass.append(data[0])
548 final_spin.append(data[1])
549 data = [final_mass, final_spin]
550 if return_fits_used:
551 return data, [approximant]
552 return data
555def final_remnant_properties_from_NRSurrogate(
556 *args, f_low=20., f_ref=20., model="NRSur7dq4Remnant", return_fits_used=False,
557 properties=["final_mass", "final_spin", "final_kick"], approximant="SEOBNRv4PHM"
558):
559 """Return the properties of the final remnant resulting from a BBH merger using
560 NRSurrogate fits
562 Parameters
563 ---------
564 f_low: float/np.ndarray
565 The low frequency cut-off used in the analysis. Default is 20Hz
566 f_ref: float/np.ndarray
567 The reference frequency used in the analysis. Default is 20Hz
568 model: str, optional
569 The name of the NRSurrogate model you wish to use
570 return_fits_used: Bool, optional
571 if True, return the approximant that was used.
572 properties: list, optional
573 The list of properties you wish to calculate
574 approximant: str, optional
575 The approximant that was used to generate the posterior samples
576 """
577 from .nrutils import NRSur_fit
579 fit = NRSur_fit(
580 *args, f_low=f_low, f_ref=f_ref, model=model, fits=properties,
581 approximant=approximant
582 )
583 if return_fits_used:
584 return fit, [model]
585 return fit
588def final_mass_of_merger_from_NR(
589 *args, NRfit="average", final_spin=None, return_fits_used=False
590):
591 """Return the final mass resulting from a BBH merger using NR fits
593 Parameters
594 ----------
595 NRfit: str
596 Name of the fit you wish to use. If you wish to use a precessing fit
597 please use the syntax 'precessing_{}'.format(fit_name). If you wish
598 to have an average NR fit, then pass 'average'
599 final_spin: float/np.ndarray, optional
600 precomputed final spin of the remnant.
601 return_fits_used: Bool, optional
602 if True, return the fits that were used. Only used when NRfit='average'
603 """
604 from pesummary.gw.conversions import nrutils
606 if NRfit.lower() == "average":
607 func = getattr(nrutils, "bbh_final_mass_average")
608 elif "panetal" in NRfit.lower():
609 func = getattr(
610 nrutils, "bbh_final_mass_non_spinning_Panetal"
611 )
612 else:
613 func = getattr(
614 nrutils, "bbh_final_mass_non_precessing_{}".format(NRfit)
615 )
616 if "healy" in NRfit.lower():
617 return func(*args, final_spin=final_spin)
618 if NRfit.lower() == "average":
619 return func(*args, return_fits_used=return_fits_used)
620 return func(*args)
623def final_mass_of_merger_from_NRSurrogate(
624 *args, model="NRSur7dq4Remnant", return_fits_used=False, approximant="SEOBNRv4PHM"
625):
626 """Return the final mass resulting from a BBH merger using NRSurrogate
627 fits
628 """
629 data = final_remnant_properties_from_NRSurrogate(
630 *args, model=model, properties=["final_mass"],
631 return_fits_used=return_fits_used,
632 approximant=approximant
633 )
634 if return_fits_used:
635 return data[0]["final_mass"], data[1]
636 return data["final_mass"]
639def final_mass_of_merger_from_waveform(*args, NSBH=False, **kwargs):
640 """Return the final mass resulting from a BBH/NSBH merger using a given
641 approximant
643 Parameters
644 ----------
645 NSBH: Bool, optional
646 if True, use NSBH waveform fits. Default False
647 """
648 if NSBH or "nsbh" in kwargs.get("approximant", "").lower():
649 return _final_from_initial_NSBH(*args, **kwargs)[1]
650 return _final_from_initial_BBH(*args, **kwargs)[0]
653def final_spin_of_merger_from_NR(
654 *args, NRfit="average", return_fits_used=False
655):
656 """Return the final spin resulting from a BBH merger using NR fits
658 Parameters
659 ----------
660 NRfit: str
661 Name of the fit you wish to use. If you wish to use a precessing fit
662 please use the syntax 'precessing_{}'.format(fit_name). If you wish
663 to have an average NR fit, then pass 'average'
664 return_fits_used: Bool, optional
665 if True, return the fits that were used. Only used when NRfit='average'
666 """
667 from pesummary.gw.conversions import nrutils
669 if NRfit.lower() == "average":
670 func = getattr(nrutils, "bbh_final_spin_average_precessing")
671 elif "pan" in NRfit.lower():
672 func = getattr(
673 nrutils, "bbh_final_spin_non_spinning_Panetal"
674 )
675 elif "precessing" in NRfit.lower():
676 func = getattr(
677 nrutils, "bbh_final_spin_precessing_{}".format(
678 NRfit.split("precessing_")[1]
679 )
680 )
681 else:
682 func = getattr(
683 nrutils, "bbh_final_spin_non_precessing_{}".format(NRfit)
684 )
685 if NRfit.lower() == "average":
686 return func(*args, return_fits_used=return_fits_used)
687 return func(*args)
690def final_spin_of_merger_from_NRSurrogate(
691 *args, model="NRSur7dq4Remnant", return_fits_used=False, approximant="SEOBNRv4PHM"
692):
693 """Return the final spin resulting from a BBH merger using NRSurrogate
694 fits
695 """
696 data = final_remnant_properties_from_NRSurrogate(
697 *args, model=model, properties=["final_spin"],
698 return_fits_used=return_fits_used, approximant=approximant
699 )
700 if return_fits_used:
701 return data[0]["final_spin"], data[1]
702 return data["final_spin"]
705def final_spin_of_merger_from_waveform(*args, NSBH=False, **kwargs):
706 """Return the final spin resulting from a BBH/NSBH merger using a given
707 approximant.
709 Parameters
710 ----------
711 NSBH: Bool, optional
712 if True, use NSBH waveform fits. Default False
713 """
714 if NSBH or "nsbh" in kwargs.get("approximant", "").lower():
715 return _final_from_initial_NSBH(*args, **kwargs)[1]
716 return _final_from_initial_BBH(*args, **kwargs)[1]
719def final_kick_of_merger_from_NRSurrogate(
720 *args, model="NRSur7dq4Remnant", return_fits_used=False, approximant="SEOBNRv4PHM"
721):
722 """Return the final kick of the remnant resulting from a BBH merger
723 using NRSurrogate fits
724 """
725 data = final_remnant_properties_from_NRSurrogate(
726 *args, model=model, properties=["final_kick"],
727 return_fits_used=return_fits_used, approximant=approximant
728 )
729 if return_fits_used:
730 return data[0]["final_kick"], data[1]
731 return data["final_kick"]
734def final_mass_of_merger(
735 *args, method="NR", approximant="SEOBNRv4", NRfit="average",
736 final_spin=None, return_fits_used=False, model="NRSur7dq4Remnant",
737 xphm_flags={}
738):
739 """Return the final mass resulting from a BBH merger
741 Parameters
742 ----------
743 mass_1: float/np.ndarray
744 float/array of masses for the primary object
745 mass_2: float/np.ndarray
746 float/array of masses for the secondary object
747 spin_1z: float/np.ndarray
748 float/array of primary spin aligned with the orbital angular momentum
749 spin_2z: float/np.ndarray
750 float/array of secondary spin aligned with the orbital angular momentum
751 method: str
752 The method you wish to use to calculate the final mass of merger. Either
753 NR, NRSurrogate or waveform
754 approximant: str
755 Name of the approximant you wish to use if the chosen method is waveform
756 or NRSurrogate
757 NRFit: str
758 Name of the NR fit you wish to use if chosen method is NR
759 return_fits_used: Bool, optional
760 if True, return the NR fits that were used. Only used when
761 NRFit='average' or when method='NRSurrogate'
762 model: str, optional
763 The NRSurrogate model to use when evaluating the fits
764 xphm_flags: dict, optional
765 List of flags used to control the variant of PhenomX during the sampling.
766 Default {}
767 """
768 if method.lower() == "nr":
769 mass_func = final_mass_of_merger_from_NR
770 kwargs = {
771 "NRfit": NRfit, "final_spin": final_spin,
772 "return_fits_used": return_fits_used
773 }
774 elif "nrsur" in method.lower():
775 mass_func = final_mass_of_merger_from_NRSurrogate
776 kwargs = {
777 "approximant": approximant, "return_fits_used": return_fits_used,
778 "model": model
779 }
780 else:
781 mass_func = final_mass_of_merger_from_waveform
782 kwargs = {"approximant": approximant, "xphm_flags": xphm_flags}
784 return mass_func(*args, **kwargs)
787def final_spin_of_merger(
788 *args, method="NR", approximant="SEOBNRv4", NRfit="average",
789 return_fits_used=False, model="NRSur7dq4Remnant",
790 xphm_flags={}
791):
792 """Return the final mass resulting from a BBH merger
794 Parameters
795 ----------
796 mass_1: float/np.ndarray
797 float/array of masses for the primary object
798 mass_2: float/np.ndarray
799 float/array of masses for the secondary object
800 a_1: float/np.ndarray
801 float/array of primary spin magnitudes
802 a_2: float/np.ndarray
803 float/array of secondary spin magnitudes
804 tilt_1: float/np.ndarray
805 float/array of primary spin tilt angle from the orbital angular momentum
806 tilt_2: float/np.ndarray
807 float/array of secondary spin tilt angle from the orbital angular
808 momentum
809 phi_12: float/np.ndarray
810 float/array of samples for the angle between the in-plane spin
811 components
812 method: str
813 The method you wish to use to calculate the final mass of merger. Either
814 NR, NRSurrogate or waveform
815 approximant: str
816 Name of the approximant you wish to use if the chosen method is waveform
817 or NRSurrogate
818 NRFit: str
819 Name of the NR fit you wish to use if chosen method is NR
820 return_fits_used: Bool, optional
821 if True, return the NR fits that were used. Only used when
822 NRFit='average' or when method='NRSurrogate'
823 model: str, optional
824 The NRSurrogate model to use when evaluating the fits
825 xphm_flags: dict, optional
826 List of flags used to control the variant of PhenomX during the sampling.
827 Default {}
828 """
829 if method.lower() == "nr":
830 spin_func = final_spin_of_merger_from_NR
831 kwargs = {"NRfit": NRfit, "return_fits_used": return_fits_used}
832 elif "nrsur" in method.lower():
833 spin_func = final_spin_of_merger_from_NRSurrogate
834 kwargs = {
835 "approximant": approximant, "return_fits_used": return_fits_used,
836 "model": model
837 }
838 else:
839 spin_func = final_spin_of_merger_from_waveform
840 kwargs = {"approximant": approximant, "xphm_flags": xphm_flags}
842 return spin_func(*args, **kwargs)
845def final_kick_of_merger(
846 *args, method="NR", approximant="SEOBNRv4", NRfit="average",
847 return_fits_used: False, model="NRSur7dq4Remnant"
848):
849 """Return the final kick velocity of the remnant resulting from a BBH merger
851 Parameters
852 ----------
853 mass_1: float/np.ndarray
854 float/array of masses for the primary object
855 mass_2: float/np.ndarray
856 float/array of masses for the secondary object
857 a_1: float/np.ndarray
858 float/array of primary spin magnitudes
859 a_2: float/np.ndarray
860 float/array of secondary spin magnitudes
861 tilt_1: float/np.ndarray
862 float/array of primary spin tilt angle from the orbital angular momentum
863 tilt_2: float/np.ndarray
864 float/array of secondary spin tilt angle from the orbital angular
865 momentum
866 phi_12: float/np.ndarray
867 float/array of samples for the angle between the in-plane spin
868 components
869 method: str
870 The method you wish to use to calculate the final kick of merger. Either
871 NR, NRSurrogate or waveform
872 approximant: str
873 Name of the approximant you wish to use if the chosen method is waveform
874 or NRSurrogate
875 NRFit: str
876 Name of the NR fit you wish to use if chosen method is NR
877 return_fits_used: Bool, optional
878 if True, return the NR fits that were used. Only used when
879 NRFit='average' or when method='NRSurrogate'
880 model: str, optional
881 The NRSurrogate model to use when evaluating the fits
882 """
883 if "nrsur" not in method.lower():
884 raise NotImplementedError(
885 "Currently you can only work out the final kick velocity using "
886 "NRSurrogate fits."
887 )
888 velocity_func = final_kick_of_merger_from_NRSurrogate
889 kwargs = {
890 "approximant": approximant, "return_fits_used": return_fits_used,
891 "model": model
892 }
893 return velocity_func(*args, **kwargs)
896def peak_luminosity_of_merger(*args, NRfit="average", return_fits_used=False):
897 """Return the peak luminosity of an aligned-spin BBH using NR fits
899 Parameters
900 ----------
901 mass_1: float/np.ndarray
902 float/array of masses for the primary object
903 mass_2: float/np.ndarray
904 float/array of masses for the secondary object
905 spin_1z: float/np.ndarray
906 float/array of primary spin aligned with the orbital angular momentum
907 spin_2z: float/np.ndarray
908 float/array of secondary spin aligned with the orbital angular momentum
909 NRFit: str
910 Name of the NR fit you wish to use if chosen method is NR
911 return_fits_used: Bool, optional
912 if True, return the NR fits that were used. Only used when
913 NRFit='average'
914 """
915 from pesummary.gw.conversions import nrutils
917 if NRfit.lower() == "average":
918 func = getattr(nrutils, "bbh_peak_luminosity_average")
919 else:
920 func = getattr(
921 nrutils, "bbh_peak_luminosity_non_precessing_{}".format(NRfit)
922 )
923 if NRfit.lower() == "average":
924 return func(*args, return_fits_used=return_fits_used)
925 return func(*args)