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

1# Licensed under an MIT style license -- see LICENSE.md 

2 

3import numpy as np 

4 

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 

9 

10__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"] 

11 

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 

22 

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} 

31 

32 

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] 

45 

46 

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] 

59 

60 

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 ] 

70 

71 

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 

75 

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) 

86 

87 

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 

97 

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 

168 

169 

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 

177 

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 

184 

185 

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 

191 

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) 

207 

208 

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. 

214 

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 ) 

238 

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 

284 

285 

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 

296 

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 

357 

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 

369 

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 ) 

512 

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 

553 

554 

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 

561 

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 

578 

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 

586 

587 

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 

592 

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 

605 

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) 

621 

622 

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"] 

637 

638 

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 

642 

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] 

651 

652 

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 

657 

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 

668 

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) 

688 

689 

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"] 

703 

704 

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. 

708 

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] 

717 

718 

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"] 

732 

733 

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 

740 

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} 

783 

784 return mass_func(*args, **kwargs) 

785 

786 

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 

793 

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} 

841 

842 return spin_func(*args, **kwargs) 

843 

844 

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 

850 

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) 

894 

895 

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 

898 

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 

916 

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)