Coverage for bilby/gw/conversion.py: 63%

898 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-06 04:57 +0000

1""" 

2A collection of functions to convert between parameters describing 

3gravitational-wave sources. 

4""" 

5 

6import os 

7import sys 

8import multiprocessing 

9import pickle 

10 

11import numpy as np 

12from pandas import DataFrame, Series 

13from scipy.stats import norm 

14 

15from .utils import (lalsim_SimNeutronStarEOS4ParamSDGammaCheck, 

16 lalsim_SimNeutronStarEOS4ParameterSpectralDecomposition, 

17 lalsim_SimNeutronStarEOS4ParamSDViableFamilyCheck, 

18 lalsim_SimNeutronStarEOS3PieceDynamicPolytrope, 

19 lalsim_SimNeutronStarEOS3PieceCausalAnalytic, 

20 lalsim_SimNeutronStarEOS3PDViableFamilyCheck, 

21 lalsim_CreateSimNeutronStarFamily, 

22 lalsim_SimNeutronStarEOSMaxPseudoEnthalpy, 

23 lalsim_SimNeutronStarEOSSpeedOfSoundGeometerized, 

24 lalsim_SimNeutronStarFamMinimumMass, 

25 lalsim_SimNeutronStarMaximumMass, 

26 lalsim_SimNeutronStarRadius, 

27 lalsim_SimNeutronStarLoveNumberK2) 

28 

29from ..core.likelihood import MarginalizedLikelihoodReconstructionError 

30from ..core.utils import logger, solar_mass, gravitational_constant, speed_of_light, command_line_args, safe_file_dump 

31from ..core.prior import DeltaFunction 

32from .utils import lalsim_SimInspiralTransformPrecessingNewInitialConditions 

33from .eos.eos import IntegrateTOV 

34from .cosmology import get_cosmology, z_at_value 

35 

36 

37def redshift_to_luminosity_distance(redshift, cosmology=None): 

38 cosmology = get_cosmology(cosmology) 

39 return cosmology.luminosity_distance(redshift).value 

40 

41 

42def redshift_to_comoving_distance(redshift, cosmology=None): 

43 cosmology = get_cosmology(cosmology) 

44 return cosmology.comoving_distance(redshift).value 

45 

46 

47def luminosity_distance_to_redshift(distance, cosmology=None): 

48 from astropy import units 

49 cosmology = get_cosmology(cosmology) 

50 if isinstance(distance, Series): 

51 distance = distance.values 

52 return z_at_value(cosmology.luminosity_distance, distance * units.Mpc) 

53 

54 

55def comoving_distance_to_redshift(distance, cosmology=None): 

56 from astropy import units 

57 cosmology = get_cosmology(cosmology) 

58 if isinstance(distance, Series): 

59 distance = distance.values 

60 return z_at_value(cosmology.comoving_distance, distance * units.Mpc) 

61 

62 

63def comoving_distance_to_luminosity_distance(distance, cosmology=None): 

64 cosmology = get_cosmology(cosmology) 

65 redshift = comoving_distance_to_redshift(distance, cosmology) 

66 return redshift_to_luminosity_distance(redshift, cosmology) 

67 

68 

69def luminosity_distance_to_comoving_distance(distance, cosmology=None): 

70 cosmology = get_cosmology(cosmology) 

71 redshift = luminosity_distance_to_redshift(distance, cosmology) 

72 return redshift_to_comoving_distance(redshift, cosmology) 

73 

74 

75_cosmology_docstring = """ 

76Convert from {input} to {output} 

77 

78Parameters 

79---------- 

80{input}: float 

81 The {input} to convert. 

82cosmology: astropy.cosmology.Cosmology 

83 The cosmology to use for the transformation. 

84 See :code:`bilby.gw.cosmology.get_cosmology` for details of how to 

85 specify this. 

86 

87Returns 

88------- 

89float 

90 The {output} corresponding to the provided {input}. 

91""" 

92 

93for _func in [ 

94 comoving_distance_to_luminosity_distance, 

95 comoving_distance_to_redshift, 

96 luminosity_distance_to_comoving_distance, 

97 luminosity_distance_to_redshift, 

98 redshift_to_comoving_distance, 

99 redshift_to_luminosity_distance, 

100]: 

101 input, output = _func.__name__.split("_to_") 

102 _func.__doc__ = _cosmology_docstring.format(input=input, output=output) 

103 

104 

105def bilby_to_lalsimulation_spins( 

106 theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, a_2, mass_1, mass_2, 

107 reference_frequency, phase 

108): 

109 """ 

110 Convert from Bilby spin parameters to lalsimulation ones. 

111 

112 All parameters are defined at the reference frequency and in SI units. 

113 

114 Parameters 

115 ========== 

116 theta_jn: float 

117 Inclination angle 

118 phi_jl: float 

119 Spin phase angle 

120 tilt_1: float 

121 Primary object tilt 

122 tilt_2: float 

123 Secondary object tilt 

124 phi_12: float 

125 Relative spin azimuthal angle 

126 a_1: float 

127 Primary dimensionless spin magnitude 

128 a_2: float 

129 Secondary dimensionless spin magnitude 

130 mass_1: float 

131 Primary mass in SI units 

132 mass_2: float 

133 Secondary mass in SI units 

134 reference_frequency: float 

135 phase: float 

136 Orbital phase 

137 

138 Returns 

139 ======= 

140 iota: float 

141 Transformed inclination 

142 spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z: float 

143 Cartesian spin components 

144 """ 

145 if (a_1 == 0 or tilt_1 in [0, np.pi]) and (a_2 == 0 or tilt_2 in [0, np.pi]): 

146 spin_1x = 0 

147 spin_1y = 0 

148 spin_1z = a_1 * np.cos(tilt_1) 

149 spin_2x = 0 

150 spin_2y = 0 

151 spin_2z = a_2 * np.cos(tilt_2) 

152 iota = theta_jn 

153 else: 

154 from numbers import Number 

155 args = ( 

156 theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, a_2, mass_1, 

157 mass_2, reference_frequency, phase 

158 ) 

159 float_inputs = all([isinstance(arg, Number) for arg in args]) 

160 if float_inputs: 

161 func = lalsim_SimInspiralTransformPrecessingNewInitialConditions 

162 else: 

163 func = transform_precessing_spins 

164 iota, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z = func(*args) 

165 return iota, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z 

166 

167 

168@np.vectorize 

169def transform_precessing_spins(*args): 

170 """ 

171 Vectorized wrapper for 

172 lalsimulation.SimInspiralTransformPrecessingNewInitialConditions 

173 

174 For detailed documentation see 

175 :code:`bilby.gw.conversion.bilby_to_lalsimulation_spins`. 

176 This will be removed from the public API in a future release. 

177 """ 

178 return lalsim_SimInspiralTransformPrecessingNewInitialConditions(*args) 

179 

180 

181def convert_to_lal_binary_black_hole_parameters(parameters): 

182 """ 

183 Convert parameters we have into parameters we need. 

184 

185 This is defined by the parameters of bilby.source.lal_binary_black_hole() 

186 

187 

188 Mass: mass_1, mass_2 

189 Spin: a_1, a_2, tilt_1, tilt_2, phi_12, phi_jl 

190 Extrinsic: luminosity_distance, theta_jn, phase, ra, dec, geocent_time, psi 

191 

192 This involves popping a lot of things from parameters. 

193 The keys in added_keys should be popped after evaluating the waveform. 

194 

195 Parameters 

196 ========== 

197 parameters: dict 

198 dictionary of parameter values to convert into the required parameters 

199 

200 Returns 

201 ======= 

202 converted_parameters: dict 

203 dict of the required parameters 

204 added_keys: list 

205 keys which are added to parameters during function call 

206 """ 

207 

208 converted_parameters = parameters.copy() 

209 original_keys = list(converted_parameters.keys()) 

210 if 'luminosity_distance' not in original_keys: 

211 if 'redshift' in converted_parameters.keys(): 

212 converted_parameters['luminosity_distance'] = \ 

213 redshift_to_luminosity_distance(parameters['redshift']) 

214 elif 'comoving_distance' in converted_parameters.keys(): 

215 converted_parameters['luminosity_distance'] = \ 

216 comoving_distance_to_luminosity_distance( 

217 parameters['comoving_distance']) 

218 

219 for key in original_keys: 

220 if key[-7:] == '_source': 

221 if 'redshift' not in converted_parameters.keys(): 

222 converted_parameters['redshift'] =\ 

223 luminosity_distance_to_redshift( 

224 parameters['luminosity_distance']) 

225 converted_parameters[key[:-7]] = converted_parameters[key] * ( 

226 1 + converted_parameters['redshift']) 

227 

228 # we do not require the component masses be added if no mass parameters are present 

229 converted_parameters = generate_component_masses(converted_parameters, require_add=False) 

230 

231 for idx in ['1', '2']: 

232 key = 'chi_{}'.format(idx) 

233 if key in original_keys: 

234 if "chi_{}_in_plane".format(idx) in original_keys: 

235 converted_parameters["a_{}".format(idx)] = ( 

236 converted_parameters[f"chi_{idx}"] ** 2 

237 + converted_parameters[f"chi_{idx}_in_plane"] ** 2 

238 ) ** 0.5 

239 converted_parameters[f"cos_tilt_{idx}"] = ( 

240 converted_parameters[f"chi_{idx}"] 

241 / converted_parameters[f"a_{idx}"] 

242 ) 

243 elif "a_{}".format(idx) not in original_keys: 

244 converted_parameters['a_{}'.format(idx)] = abs( 

245 converted_parameters[key]) 

246 converted_parameters['cos_tilt_{}'.format(idx)] = \ 

247 np.sign(converted_parameters[key]) 

248 else: 

249 with np.errstate(invalid="raise"): 

250 try: 

251 converted_parameters[f"cos_tilt_{idx}"] = ( 

252 converted_parameters[key] / converted_parameters[f"a_{idx}"] 

253 ) 

254 except (FloatingPointError, ZeroDivisionError): 

255 logger.debug( 

256 "Error in conversion to spherical spin tilt. " 

257 "This is often due to the spin parameters being zero. " 

258 f"Setting cos_tilt_{idx} = 1." 

259 ) 

260 converted_parameters[f"cos_tilt_{idx}"] = 1.0 

261 

262 for key in ["phi_jl", "phi_12"]: 

263 if key not in converted_parameters: 

264 converted_parameters[key] = 0.0 

265 

266 for angle in ['tilt_1', 'tilt_2', 'theta_jn']: 

267 cos_angle = str('cos_' + angle) 

268 if cos_angle in converted_parameters.keys(): 

269 with np.errstate(invalid="ignore"): 

270 converted_parameters[angle] = np.arccos(converted_parameters[cos_angle]) 

271 

272 if "delta_phase" in original_keys: 

273 with np.errstate(invalid="ignore"): 

274 converted_parameters["phase"] = np.mod( 

275 converted_parameters["delta_phase"] 

276 - np.sign(np.cos(converted_parameters["theta_jn"])) 

277 * converted_parameters["psi"], 

278 2 * np.pi) 

279 added_keys = [key for key in converted_parameters.keys() 

280 if key not in original_keys] 

281 

282 return converted_parameters, added_keys 

283 

284 

285def convert_to_lal_binary_neutron_star_parameters(parameters): 

286 """ 

287 Convert parameters we have into parameters we need. 

288 

289 This is defined by the parameters of bilby.source.lal_binary_black_hole() 

290 

291 

292 Mass: mass_1, mass_2 

293 Spin: a_1, a_2, tilt_1, tilt_2, phi_12, phi_jl 

294 Extrinsic: luminosity_distance, theta_jn, phase, ra, dec, geocent_time, psi 

295 Tidal: lambda_1, lamda_2, lambda_tilde, delta_lambda_tilde 

296 

297 This involves popping a lot of things from parameters. 

298 The keys in added_keys should be popped after evaluating the waveform. 

299 For details on tidal parameters see https://arxiv.org/pdf/1402.5156.pdf. 

300 

301 Parameters 

302 ========== 

303 parameters: dict 

304 dictionary of parameter values to convert into the required parameters 

305 

306 Returns 

307 ======= 

308 converted_parameters: dict 

309 dict of the required parameters 

310 added_keys: list 

311 keys which are added to parameters during function call 

312 """ 

313 converted_parameters = parameters.copy() 

314 original_keys = list(converted_parameters.keys()) 

315 converted_parameters, added_keys =\ 

316 convert_to_lal_binary_black_hole_parameters(converted_parameters) 

317 

318 if not any([key in converted_parameters for key in 

319 ['lambda_1', 'lambda_2', 

320 'lambda_tilde', 'delta_lambda_tilde', 'lambda_symmetric', 

321 'eos_polytrope_gamma_0', 'eos_spectral_pca_gamma_0', 'eos_v1']]): 

322 converted_parameters['lambda_1'] = 0 

323 converted_parameters['lambda_2'] = 0 

324 added_keys = added_keys + ['lambda_1', 'lambda_2'] 

325 return converted_parameters, added_keys 

326 

327 if 'delta_lambda_tilde' in converted_parameters.keys(): 

328 converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ 

329 lambda_tilde_delta_lambda_tilde_to_lambda_1_lambda_2( 

330 converted_parameters['lambda_tilde'], 

331 parameters['delta_lambda_tilde'], converted_parameters['mass_1'], 

332 converted_parameters['mass_2']) 

333 elif 'lambda_tilde' in converted_parameters.keys(): 

334 converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ 

335 lambda_tilde_to_lambda_1_lambda_2( 

336 converted_parameters['lambda_tilde'], 

337 converted_parameters['mass_1'], converted_parameters['mass_2']) 

338 if 'lambda_2' not in converted_parameters.keys() and 'lambda_1' in converted_parameters.keys(): 

339 converted_parameters['lambda_2'] =\ 

340 converted_parameters['lambda_1']\ 

341 * converted_parameters['mass_1']**5\ 

342 / converted_parameters['mass_2']**5 

343 elif 'lambda_2' in converted_parameters.keys() and converted_parameters['lambda_2'] is None: 

344 converted_parameters['lambda_2'] =\ 

345 converted_parameters['lambda_1']\ 

346 * converted_parameters['mass_1']**5\ 

347 / converted_parameters['mass_2']**5 

348 elif 'eos_spectral_pca_gamma_0' in converted_parameters.keys(): # FIXME: This is a clunky way to do this 

349 converted_parameters = generate_source_frame_parameters(converted_parameters) 

350 float_eos_params = {} 

351 max_len = 1 

352 eos_keys = ['eos_spectral_pca_gamma_0', 

353 'eos_spectral_pca_gamma_1', 

354 'eos_spectral_pca_gamma_2', 

355 'eos_spectral_pca_gamma_3', 

356 'mass_1_source', 'mass_2_source'] 

357 for key in eos_keys: 

358 try: 

359 if (len(converted_parameters[key]) > max_len): 

360 max_len = len(converted_parameters[key]) 

361 except TypeError: 

362 float_eos_params[key] = converted_parameters[key] 

363 if len(float_eos_params) == len(eos_keys): # case where all eos params are floats (pinned) 

364 g0, g1, g2, g3 = spectral_pca_to_spectral( 

365 converted_parameters['eos_spectral_pca_gamma_0'], 

366 converted_parameters['eos_spectral_pca_gamma_1'], 

367 converted_parameters['eos_spectral_pca_gamma_2'], 

368 converted_parameters['eos_spectral_pca_gamma_3']) 

369 converted_parameters['lambda_1'], converted_parameters['lambda_2'], converted_parameters['eos_check'] = \ 

370 spectral_params_to_lambda_1_lambda_2( 

371 g0, g1, g2, g3, converted_parameters['mass_1_source'], converted_parameters['mass_2_source']) 

372 elif len(float_eos_params) < len(eos_keys): # case where some or none of the eos params are floats (pinned) 

373 for key in float_eos_params.keys(): 

374 converted_parameters[key] = np.ones(max_len) * converted_parameters[key] 

375 g0pca = converted_parameters['eos_spectral_pca_gamma_0'] 

376 g1pca = converted_parameters['eos_spectral_pca_gamma_1'] 

377 g2pca = converted_parameters['eos_spectral_pca_gamma_2'] 

378 g3pca = converted_parameters['eos_spectral_pca_gamma_3'] 

379 m1s = converted_parameters['mass_1_source'] 

380 m2s = converted_parameters['mass_2_source'] 

381 all_lambda_1 = np.empty(0) 

382 all_lambda_2 = np.empty(0) 

383 all_eos_check = np.empty(0, dtype=bool) 

384 for (g_0pca, g_1pca, g_2pca, g_3pca, m1_s, m2_s) in zip(g0pca, g1pca, g2pca, g3pca, m1s, m2s): 

385 g_0, g_1, g_2, g_3 = spectral_pca_to_spectral(g_0pca, g_1pca, g_2pca, g_3pca) 

386 lambda_1, lambda_2, eos_check = \ 

387 spectral_params_to_lambda_1_lambda_2(g_0, g_1, g_2, g_3, m1_s, m2_s) 

388 all_lambda_1 = np.append(all_lambda_1, lambda_1) 

389 all_lambda_2 = np.append(all_lambda_2, lambda_2) 

390 all_eos_check = np.append(all_eos_check, eos_check) 

391 converted_parameters['lambda_1'] = all_lambda_1 

392 converted_parameters['lambda_2'] = all_lambda_2 

393 converted_parameters['eos_check'] = all_eos_check 

394 for key in float_eos_params.keys(): 

395 converted_parameters[key] = float_eos_params[key] 

396 elif 'eos_polytrope_gamma_0' and 'eos_polytrope_log10_pressure_1' in converted_parameters.keys(): 

397 converted_parameters = generate_source_frame_parameters(converted_parameters) 

398 float_eos_params = {} 

399 max_len = 1 

400 eos_keys = ['eos_polytrope_gamma_0', 

401 'eos_polytrope_gamma_1', 

402 'eos_polytrope_gamma_2', 

403 'eos_polytrope_log10_pressure_1', 

404 'eos_polytrope_log10_pressure_2', 

405 'mass_1_source', 'mass_2_source'] 

406 for key in eos_keys: 

407 try: 

408 if (len(converted_parameters[key]) > max_len): 

409 max_len = len(converted_parameters[key]) 

410 except TypeError: 

411 float_eos_params[key] = converted_parameters[key] 

412 if len(float_eos_params) == len(eos_keys): # case where all eos params are floats (pinned) 

413 converted_parameters['lambda_1'], converted_parameters['lambda_2'], converted_parameters['eos_check'] = \ 

414 polytrope_or_causal_params_to_lambda_1_lambda_2( 

415 converted_parameters['eos_polytrope_gamma_0'], 

416 converted_parameters['eos_polytrope_log10_pressure_1'], 

417 converted_parameters['eos_polytrope_gamma_1'], 

418 converted_parameters['eos_polytrope_log10_pressure_2'], 

419 converted_parameters['eos_polytrope_gamma_2'], 

420 converted_parameters['mass_1_source'], 

421 converted_parameters['mass_2_source'], 

422 causal=0) 

423 elif len(float_eos_params) < len(eos_keys): # case where some or none are floats (pinned) 

424 for key in float_eos_params.keys(): 

425 converted_parameters[key] = np.ones(max_len) * converted_parameters[key] 

426 pg0 = converted_parameters['eos_polytrope_gamma_0'] 

427 pg1 = converted_parameters['eos_polytrope_gamma_1'] 

428 pg2 = converted_parameters['eos_polytrope_gamma_2'] 

429 logp1 = converted_parameters['eos_polytrope_log10_pressure_1'] 

430 logp2 = converted_parameters['eos_polytrope_log10_pressure_2'] 

431 m1s = converted_parameters['mass_1_source'] 

432 m2s = converted_parameters['mass_2_source'] 

433 all_lambda_1 = np.empty(0) 

434 all_lambda_2 = np.empty(0) 

435 all_eos_check = np.empty(0, dtype=bool) 

436 for (pg_0, pg_1, pg_2, logp_1, logp_2, m1_s, m2_s) in zip(pg0, pg1, pg2, logp1, logp2, m1s, m2s): 

437 lambda_1, lambda_2, eos_check = \ 

438 polytrope_or_causal_params_to_lambda_1_lambda_2( 

439 pg_0, logp_1, pg_1, logp_2, pg_2, m1_s, m2_s, causal=0) 

440 all_lambda_1 = np.append(all_lambda_1, lambda_1) 

441 all_lambda_2 = np.append(all_lambda_2, lambda_2) 

442 all_eos_check = np.append(all_eos_check, eos_check) 

443 converted_parameters['lambda_1'] = all_lambda_1 

444 converted_parameters['lambda_2'] = all_lambda_2 

445 converted_parameters['eos_check'] = all_eos_check 

446 for key in float_eos_params.keys(): 

447 converted_parameters[key] = float_eos_params[key] 

448 elif 'eos_polytrope_gamma_0' and 'eos_polytrope_scaled_pressure_ratio' in converted_parameters.keys(): 

449 converted_parameters = generate_source_frame_parameters(converted_parameters) 

450 float_eos_params = {} 

451 max_len = 1 

452 eos_keys = ['eos_polytrope_gamma_0', 

453 'eos_polytrope_gamma_1', 

454 'eos_polytrope_gamma_2', 

455 'eos_polytrope_scaled_pressure_ratio', 

456 'eos_polytrope_scaled_pressure_2', 

457 'mass_1_source', 'mass_2_source'] 

458 for key in eos_keys: 

459 try: 

460 if (len(converted_parameters[key]) > max_len): 

461 max_len = len(converted_parameters[key]) 

462 except TypeError: 

463 float_eos_params[key] = converted_parameters[key] 

464 if len(float_eos_params) == len(eos_keys): # case where all eos params are floats (pinned) 

465 logp1, logp2 = log_pressure_reparameterization_conversion( 

466 converted_parameters['eos_polytrope_scaled_pressure_ratio'], 

467 converted_parameters['eos_polytrope_scaled_pressure_2']) 

468 converted_parameters['lambda_1'], converted_parameters['lambda_2'], converted_parameters['eos_check'] = \ 

469 polytrope_or_causal_params_to_lambda_1_lambda_2( 

470 converted_parameters['eos_polytrope_gamma_0'], 

471 logp1, 

472 converted_parameters['eos_polytrope_gamma_1'], 

473 logp2, 

474 converted_parameters['eos_polytrope_gamma_2'], 

475 converted_parameters['mass_1_source'], 

476 converted_parameters['mass_2_source'], 

477 causal=0) 

478 elif len(float_eos_params) < len(eos_keys): # case where some or none are floats (pinned) 

479 for key in float_eos_params.keys(): 

480 converted_parameters[key] = np.ones(max_len) * converted_parameters[key] 

481 pg0 = converted_parameters['eos_polytrope_gamma_0'] 

482 pg1 = converted_parameters['eos_polytrope_gamma_1'] 

483 pg2 = converted_parameters['eos_polytrope_gamma_2'] 

484 scaledratio = converted_parameters['eos_polytrope_scaled_pressure_ratio'] 

485 scaled_p2 = converted_parameters['eos_polytrope_scaled_pressure_2'] 

486 m1s = converted_parameters['mass_1_source'] 

487 m2s = converted_parameters['mass_2_source'] 

488 all_lambda_1 = np.empty(0) 

489 all_lambda_2 = np.empty(0) 

490 all_eos_check = np.empty(0, dtype=bool) 

491 for (pg_0, pg_1, pg_2, scaled_ratio, scaled_p_2, m1_s, 

492 m2_s) in zip(pg0, pg1, pg2, scaledratio, scaled_p2, m1s, m2s): 

493 logp_1, logp_2 = log_pressure_reparameterization_conversion(scaled_ratio, scaled_p_2) 

494 lambda_1, lambda_2, eos_check = \ 

495 polytrope_or_causal_params_to_lambda_1_lambda_2( 

496 pg_0, logp_1, pg_1, logp_2, pg_2, m1_s, m2_s, causal=0) 

497 all_lambda_1 = np.append(all_lambda_1, lambda_1) 

498 all_lambda_2 = np.append(all_lambda_2, lambda_2) 

499 all_eos_check = np.append(all_eos_check, eos_check) 

500 converted_parameters['lambda_1'] = all_lambda_1 

501 converted_parameters['lambda_2'] = all_lambda_2 

502 converted_parameters['eos_check'] = all_eos_check 

503 for key in float_eos_params.keys(): 

504 converted_parameters[key] = float_eos_params[key] 

505 elif 'eos_v1' in converted_parameters.keys(): 

506 converted_parameters = generate_source_frame_parameters(converted_parameters) 

507 float_eos_params = {} 

508 max_len = 1 

509 eos_keys = ['eos_v1', 

510 'eos_v2', 

511 'eos_v3', 

512 'eos_log10_pressure1_cgs', 

513 'eos_log10_pressure2_cgs', 

514 'mass_1_source', 'mass_2_source'] 

515 for key in eos_keys: 

516 try: 

517 if (len(converted_parameters[key]) > max_len): 

518 max_len = len(converted_parameters[key]) 

519 except TypeError: 

520 float_eos_params[key] = converted_parameters[key] 

521 if len(float_eos_params) == len(eos_keys): # case where all eos params are floats (pinned) 

522 converted_parameters['lambda_1'], converted_parameters['lambda_2'], converted_parameters['eos_check'] = \ 

523 polytrope_or_causal_params_to_lambda_1_lambda_2( 

524 converted_parameters['eos_v1'], 

525 converted_parameters['eos_log10_pressure1_cgs'], 

526 converted_parameters['eos_v2'], 

527 converted_parameters['eos_log10_pressure2_cgs'], 

528 converted_parameters['eos_v3'], 

529 converted_parameters['mass_1_source'], 

530 converted_parameters['mass_2_source'], 

531 causal=1) 

532 elif len(float_eos_params) < len(eos_keys): # case where some or none are floats (pinned) 

533 for key in float_eos_params.keys(): 

534 converted_parameters[key] = np.ones(max_len) * converted_parameters[key] 

535 v1 = converted_parameters['eos_v1'] 

536 v2 = converted_parameters['eos_v2'] 

537 v3 = converted_parameters['eos_v3'] 

538 logp1 = converted_parameters['eos_log10_pressure1_cgs'] 

539 logp2 = converted_parameters['eos_log10_pressure2_cgs'] 

540 m1s = converted_parameters['mass_1_source'] 

541 m2s = converted_parameters['mass_2_source'] 

542 all_lambda_1 = np.empty(0) 

543 all_lambda_2 = np.empty(0) 

544 all_eos_check = np.empty(0, dtype=bool) 

545 for (v_1, v_2, v_3, logp_1, logp_2, m1_s, m2_s) in zip(v1, v2, v3, logp1, logp2, m1s, m2s): 

546 lambda_1, lambda_2, eos_check = \ 

547 polytrope_or_causal_params_to_lambda_1_lambda_2( 

548 v_1, logp_1, v_2, logp_2, v_3, m1_s, m2_s, causal=1) 

549 all_lambda_1 = np.append(all_lambda_1, lambda_1) 

550 all_lambda_2 = np.append(all_lambda_2, lambda_2) 

551 all_eos_check = np.append(all_eos_check, eos_check) 

552 converted_parameters['lambda_1'] = all_lambda_1 

553 converted_parameters['lambda_2'] = all_lambda_2 

554 converted_parameters['eos_check'] = all_eos_check 

555 for key in float_eos_params.keys(): 

556 converted_parameters[key] = float_eos_params[key] 

557 elif 'lambda_symmetric' in converted_parameters.keys(): 

558 if 'lambda_antisymmetric' in converted_parameters.keys(): 

559 converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ 

560 lambda_symmetric_lambda_antisymmetric_to_lambda_1_lambda_2( 

561 converted_parameters['lambda_symmetric'], 

562 converted_parameters['lambda_antisymmetric']) 

563 elif 'mass_ratio' in converted_parameters.keys(): 

564 if 'binary_love_uniform' in converted_parameters.keys(): 

565 converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ 

566 binary_love_lambda_symmetric_to_lambda_1_lambda_2_manual_marginalisation( 

567 converted_parameters['binary_love_uniform'], 

568 converted_parameters['lambda_symmetric'], 

569 converted_parameters['mass_ratio']) 

570 else: 

571 converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ 

572 binary_love_lambda_symmetric_to_lambda_1_lambda_2_automatic_marginalisation( 

573 converted_parameters['lambda_symmetric'], 

574 converted_parameters['mass_ratio']) 

575 

576 added_keys = [key for key in converted_parameters.keys() 

577 if key not in original_keys] 

578 

579 return converted_parameters, added_keys 

580 

581 

582def log_pressure_reparameterization_conversion(scaled_pressure_ratio, scaled_pressure_2): 

583 ''' 

584 Converts the reparameterization joining pressures from 

585 (scaled_pressure_ratio,scaled_pressure_2) to (log10_pressure_1,log10_pressure_2). 

586 This reparameterization with a triangular prior (with mode = max) on scaled_pressure_2 

587 and a uniform prior on scaled_pressure_ratio 

588 mimics identical uniform priors on log10_pressure_1 and log10_pressure_2 

589 where samples with log10_pressure_2 > log10_pressure_1 are rejected. 

590 This reparameterization allows for a faster initialization. 

591 A minimum log10_pressure of 33 (in cgs units) is chosen to be slightly higher than the low-density crust EOS 

592 that is stitched to the dynamic polytrope EOS model in LALSimulation. 

593 

594 Parameters 

595 ---------- 

596 scaled_pressure_ratio, scaled_pressure_2: float 

597 reparameterizations of the dividing pressures 

598 

599 Returns 

600 ------- 

601 log10_pressure_1, log10_pressure_2: float 

602 joining pressures in the original parameterization 

603 

604 ''' 

605 minimum_pressure = 33. 

606 log10_pressure_1 = (scaled_pressure_ratio * scaled_pressure_2) + minimum_pressure 

607 log10_pressure_2 = minimum_pressure + scaled_pressure_2 

608 

609 return log10_pressure_1, log10_pressure_2 

610 

611 

612def spectral_pca_to_spectral(gamma_pca_0, gamma_pca_1, gamma_pca_2, gamma_pca_3): 

613 ''' 

614 Change of basis on parameter space 

615 from an efficient space to sample in (sample space) 

616 to the space used in spectral eos model (model space). 

617 Uses principle component analysis (PCA) to sample spectral gamma parameters more efficiently. 

618 See arxiv:2001.01747 for the PCA conversion transformation matrix, mean, and standard deviation. 

619 (Note that this transformation matrix is the inverse of the one referenced 

620 in order to perform the inverse transformation.) 

621 

622 Parameters 

623 ---------- 

624 gamma_pca_0, gamma_pca_1, gamma_pca_2, gamma_pca_3: float 

625 Spectral gamma PCA parameters to be converted to spectral gamma parameters. 

626 

627 Returns 

628 ------- 

629 converted_gamma_parameters: np.array() 

630 array of gamma_0, gamma_1, gamma_2, gamma_3 in model space 

631 

632 ''' 

633 sampled_pca_gammas = np.array([gamma_pca_0, gamma_pca_1, gamma_pca_2, gamma_pca_3]) 

634 transformation_matrix = np.array( 

635 [ 

636 [0.43801, -0.76705, 0.45143, 0.12646], 

637 [-0.53573, 0.17169, 0.67968, 0.47070], 

638 [0.52660, 0.31255, -0.19454, 0.76626], 

639 [-0.49379, -0.53336, -0.54444, 0.41868] 

640 ] 

641 ) 

642 

643 model_space_mean = np.array([0.89421, 0.33878, -0.07894, 0.00393]) 

644 model_space_standard_deviation = np.array([0.35700, 0.25769, 0.05452, 0.00312]) 

645 converted_gamma_parameters = \ 

646 model_space_mean + model_space_standard_deviation * np.dot(transformation_matrix, sampled_pca_gammas) 

647 

648 return converted_gamma_parameters 

649 

650 

651def spectral_params_to_lambda_1_lambda_2(gamma_0, gamma_1, gamma_2, gamma_3, mass_1_source, mass_2_source): 

652 ''' 

653 Converts from the 4 spectral decomposition parameters and the source masses 

654 to the tidal deformability parameters. 

655 

656 Parameters 

657 ---------- 

658 gamma_0, gamma_1, gamma_2, gamma_3: float 

659 sampled spectral decomposition parameters 

660 mass_1_source, mass_2_source: float 

661 sampled component mass parameters converted to source frame in solar masses 

662 

663 Returns 

664 ------- 

665 lambda_1, lambda_2: float 

666 component tidal deformability parameters 

667 eos_check: bool 

668 whether or not the equation of state is viable / 

669 if eos_check = False, lambdas are 0 and the sample is rejected. 

670 

671 ''' 

672 eos_check = True 

673 if lalsim_SimNeutronStarEOS4ParamSDGammaCheck(gamma_0, gamma_1, gamma_2, gamma_3) != 0: 

674 lambda_1 = 0.0 

675 lambda_2 = 0.0 

676 eos_check = False 

677 else: 

678 eos = lalsim_SimNeutronStarEOS4ParameterSpectralDecomposition(gamma_0, gamma_1, gamma_2, gamma_3) 

679 if lalsim_SimNeutronStarEOS4ParamSDViableFamilyCheck(gamma_0, gamma_1, gamma_2, gamma_3) != 0: 

680 lambda_1 = 0.0 

681 lambda_2 = 0.0 

682 eos_check = False 

683 else: 

684 lambda_1, lambda_2, eos_check = neutron_star_family_physical_check(eos, mass_1_source, mass_2_source) 

685 

686 return lambda_1, lambda_2, eos_check 

687 

688 

689def polytrope_or_causal_params_to_lambda_1_lambda_2( 

690 param1, log10_pressure1_cgs, param2, log10_pressure2_cgs, param3, mass_1_source, mass_2_source, causal): 

691 """ 

692 Converts parameters from sampled dynamic piecewise polytrope parameters 

693 to component tidal deformablity parameters. 

694 Enforces log10_pressure1_cgs < log10_pressure2_cgs. 

695 Checks number of points in the equation of state for viability. 

696 Note that subtracting 1 from the log10 pressure in cgs converts it to 

697 log10 pressure in si units. 

698 

699 Parameters 

700 ---------- 

701 param1, param2, param3: float 

702 either the sampled adiabatic indices in piecewise polytrope model 

703 or the sampled causal model params v1, v2, v3 

704 log10_pressure1_cgs, log10_pressure2_cgs: float 

705 dividing pressures in piecewise polytrope model or causal model 

706 mass_1_source, mass_2_source: float 

707 source frame component mass parameters in Msuns 

708 causal: bool 

709 whether or not to use causal polytrope model 

710 1 - causal; 0 - not causal 

711 

712 Returns 

713 ------- 

714 lambda_1: float 

715 tidal deformability parameter associated with mass 1 

716 lambda_2: float 

717 tidal deformability parameter associated with mass 2 

718 eos_check: bool 

719 whether eos is valid or not 

720 

721 """ 

722 eos_check = True 

723 if log10_pressure1_cgs >= log10_pressure2_cgs: 

724 lambda_1 = 0.0 

725 lambda_2 = 0.0 

726 eos_check = False 

727 else: 

728 if causal == 0: 

729 eos = lalsim_SimNeutronStarEOS3PieceDynamicPolytrope( 

730 param1, log10_pressure1_cgs - 1., param2, log10_pressure2_cgs - 1., param3) 

731 else: 

732 eos = lalsim_SimNeutronStarEOS3PieceCausalAnalytic( 

733 param1, log10_pressure1_cgs - 1., param2, log10_pressure2_cgs - 1., param3) 

734 if lalsim_SimNeutronStarEOS3PDViableFamilyCheck( 

735 param1, log10_pressure1_cgs - 1., param2, log10_pressure2_cgs - 1., param3, causal) != 0: 

736 lambda_1 = 0.0 

737 lambda_2 = 0.0 

738 eos_check = False 

739 else: 

740 lambda_1, lambda_2, eos_check = neutron_star_family_physical_check(eos, mass_1_source, mass_2_source) 

741 

742 return lambda_1, lambda_2, eos_check 

743 

744 

745def neutron_star_family_physical_check(eos, mass_1_source, mass_2_source): 

746 """ 

747 Takes in a lalsim eos object. Performs causal and max/min mass eos checks. 

748 Calculates component lambdas if eos object passes causality. 

749 Returns lambda = 0 if not. 

750 

751 Parameters 

752 ---------- 

753 eos: lalsim swig-wrapped eos object 

754 the neutron star equation of state 

755 mass_1_source, mass_2_source: float 

756 source frame component masses 1 and 2 in solar masses 

757 

758 Returns 

759 ------- 

760 lambda_1, lambda_2: float 

761 component tidal deformability parameters 

762 eos_check: bool 

763 whether or not the equation of state is physically allowed 

764 

765 """ 

766 eos_check = True 

767 family = lalsim_CreateSimNeutronStarFamily(eos) 

768 max_pseudo_enthalpy = lalsim_SimNeutronStarEOSMaxPseudoEnthalpy(eos) 

769 max_speed_of_sound = lalsim_SimNeutronStarEOSSpeedOfSoundGeometerized(max_pseudo_enthalpy, eos) 

770 min_mass = lalsim_SimNeutronStarFamMinimumMass(family) / solar_mass 

771 max_mass = lalsim_SimNeutronStarMaximumMass(family) / solar_mass 

772 if max_speed_of_sound <= 1.1 and min_mass <= mass_1_source <= max_mass and min_mass <= mass_2_source <= max_mass: 

773 lambda_1 = lambda_from_mass_and_family(mass_1_source, family) 

774 lambda_2 = lambda_from_mass_and_family(mass_2_source, family) 

775 else: 

776 lambda_1 = 0.0 

777 lambda_2 = 0.0 

778 eos_check = False 

779 

780 return lambda_1, lambda_2, eos_check 

781 

782 

783def lambda_from_mass_and_family(mass_i, family): 

784 """ 

785 Convert from equation of state model parameters to 

786 component tidal parameters. 

787 

788 Parameters 

789 ---------- 

790 family: lalsim family object 

791 EOS family of type lalsimulation.SimNeutronStarFamily. 

792 mass_i: Component mass of neutron star in solar masses. 

793 

794 Returns 

795 ------- 

796 lambda_1: float 

797 component tidal deformability parameter 

798 

799 """ 

800 radius = lalsim_SimNeutronStarRadius(mass_i * solar_mass, family) 

801 love_number_k2 = lalsim_SimNeutronStarLoveNumberK2(mass_i * solar_mass, family) 

802 mass_geometrized = mass_i * solar_mass * gravitational_constant / speed_of_light ** 2. 

803 compactness = mass_geometrized / radius 

804 lambda_i = (2. / 3.) * love_number_k2 / compactness ** 5. 

805 

806 return lambda_i 

807 

808 

809def eos_family_physical_check(eos): 

810 """ 

811 Function that determines if the EoS family contains 

812 sufficient number of points before maximum mass is reached. 

813 

814 e_min is chosen to be sufficiently small so that the entire 

815 EoS is captured when converting to mass-radius space. 

816 

817 Returns True if family is valid, False if not. 

818 """ 

819 e_min = 1.6e-10 

820 e_central = eos.e_pdat[-1, 1] 

821 loge_min = np.log(e_min) 

822 loge_central = np.log(e_central) 

823 logedat = np.linspace(loge_min, loge_central, num=eos.npts) 

824 edat = np.exp(logedat) 

825 

826 # Generate m, r, and k2 lists 

827 mdat = [] 

828 rdat = [] 

829 k2dat = [] 

830 for i in range(8): 

831 tov_solver = IntegrateTOV(eos, edat[i]) 

832 m, r, k2 = tov_solver.integrate_TOV() 

833 mdat.append(m) 

834 rdat.append(r) 

835 k2dat.append(k2) 

836 

837 # Check if maximum mass has been found 

838 if i > 0 and mdat[i] <= mdat[i - 1]: 

839 break 

840 

841 if len(mdat) < 8: 

842 return False 

843 else: 

844 return True 

845 

846 

847def total_mass_and_mass_ratio_to_component_masses(mass_ratio, total_mass): 

848 """ 

849 Convert total mass and mass ratio of a binary to its component masses. 

850 

851 Parameters 

852 ========== 

853 mass_ratio: float 

854 Mass ratio (mass_2/mass_1) of the binary 

855 total_mass: float 

856 Total mass of the binary 

857 

858 Returns 

859 ======= 

860 mass_1: float 

861 Mass of the heavier object 

862 mass_2: float 

863 Mass of the lighter object 

864 """ 

865 

866 mass_1 = total_mass / (1 + mass_ratio) 

867 mass_2 = mass_1 * mass_ratio 

868 return mass_1, mass_2 

869 

870 

871def chirp_mass_and_mass_ratio_to_component_masses(chirp_mass, mass_ratio): 

872 """ 

873 Convert total mass and mass ratio of a binary to its component masses. 

874 

875 Parameters 

876 ========== 

877 chirp_mass: float 

878 Chirp mass of the binary 

879 mass_ratio: float 

880 Mass ratio (mass_2/mass_1) of the binary 

881 

882 Returns 

883 ======= 

884 mass_1: float 

885 Mass of the heavier object 

886 mass_2: float 

887 Mass of the lighter object 

888 """ 

889 total_mass = chirp_mass_and_mass_ratio_to_total_mass(chirp_mass=chirp_mass, 

890 mass_ratio=mass_ratio) 

891 mass_1, mass_2 = ( 

892 total_mass_and_mass_ratio_to_component_masses( 

893 total_mass=total_mass, mass_ratio=mass_ratio) 

894 ) 

895 return mass_1, mass_2 

896 

897 

898def symmetric_mass_ratio_to_mass_ratio(symmetric_mass_ratio): 

899 """ 

900 Convert the symmetric mass ratio to the normal mass ratio. 

901 

902 Parameters 

903 ========== 

904 symmetric_mass_ratio: float 

905 Symmetric mass ratio of the binary 

906 

907 Returns 

908 ======= 

909 mass_ratio: float 

910 Mass ratio of the binary 

911 """ 

912 

913 temp = (1 / symmetric_mass_ratio / 2 - 1) 

914 return temp - (temp ** 2 - 1) ** 0.5 

915 

916 

917def chirp_mass_and_total_mass_to_symmetric_mass_ratio(chirp_mass, total_mass): 

918 """ 

919 Convert chirp mass and total mass of a binary to its symmetric mass ratio. 

920 

921 Parameters 

922 ========== 

923 chirp_mass: float 

924 Chirp mass of the binary 

925 total_mass: float 

926 Total mass of the binary 

927 

928 Returns 

929 ======= 

930 symmetric_mass_ratio: float 

931 Symmetric mass ratio of the binary 

932 """ 

933 

934 return (chirp_mass / total_mass) ** (5 / 3) 

935 

936 

937def chirp_mass_and_primary_mass_to_mass_ratio(chirp_mass, mass_1): 

938 """ 

939 Convert chirp mass and mass ratio of a binary to its total mass. 

940 

941 Rearranging the relation for chirp mass (as a function of mass_1 and 

942 mass_2) and q = mass_2 / mass_1, it can be shown that 

943 

944 (chirp_mass/mass_1)^5 = q^3 / (1 + q) 

945 

946 Solving for q, we find the relation expressed in python below for q. 

947 

948 Parameters 

949 ========== 

950 chirp_mass: float 

951 Chirp mass of the binary 

952 mass_1: float 

953 The primary mass 

954 

955 Returns 

956 ======= 

957 mass_ratio: float 

958 Mass ratio (mass_2/mass_1) of the binary 

959 """ 

960 a = (chirp_mass / mass_1) ** 5 

961 t0 = np.cbrt(9 * a + np.sqrt(3) * np.sqrt(27 * a ** 2 - 4 * a ** 3)) 

962 t1 = np.cbrt(2) * 3 ** (2 / 3) 

963 t2 = np.cbrt(2 / 3) * a 

964 return t2 / t0 + t0 / t1 

965 

966 

967def chirp_mass_and_mass_ratio_to_total_mass(chirp_mass, mass_ratio): 

968 """ 

969 Convert chirp mass and mass ratio of a binary to its total mass. 

970 

971 Parameters 

972 ========== 

973 chirp_mass: float 

974 Chirp mass of the binary 

975 mass_ratio: float 

976 Mass ratio (mass_2/mass_1) of the binary 

977 

978 Returns 

979 ======= 

980 mass_1: float 

981 Mass of the heavier object 

982 mass_2: float 

983 Mass of the lighter object 

984 """ 

985 

986 with np.errstate(invalid="ignore"): 

987 return chirp_mass * (1 + mass_ratio) ** 1.2 / mass_ratio ** 0.6 

988 

989 

990def component_masses_to_chirp_mass(mass_1, mass_2): 

991 """ 

992 Convert the component masses of a binary to its chirp mass. 

993 

994 Parameters 

995 ========== 

996 mass_1: float 

997 Mass of the heavier object 

998 mass_2: float 

999 Mass of the lighter object 

1000 

1001 Returns 

1002 ======= 

1003 chirp_mass: float 

1004 Chirp mass of the binary 

1005 """ 

1006 

1007 return (mass_1 * mass_2) ** 0.6 / (mass_1 + mass_2) ** 0.2 

1008 

1009 

1010def component_masses_to_total_mass(mass_1, mass_2): 

1011 """ 

1012 Convert the component masses of a binary to its total mass. 

1013 

1014 Parameters 

1015 ========== 

1016 mass_1: float 

1017 Mass of the heavier object 

1018 mass_2: float 

1019 Mass of the lighter object 

1020 

1021 Returns 

1022 ======= 

1023 total_mass: float 

1024 Total mass of the binary 

1025 """ 

1026 

1027 return mass_1 + mass_2 

1028 

1029 

1030def component_masses_to_symmetric_mass_ratio(mass_1, mass_2): 

1031 """ 

1032 Convert the component masses of a binary to its symmetric mass ratio. 

1033 

1034 Parameters 

1035 ========== 

1036 mass_1: float 

1037 Mass of the heavier object 

1038 mass_2: float 

1039 Mass of the lighter object 

1040 

1041 Returns 

1042 ======= 

1043 symmetric_mass_ratio: float 

1044 Symmetric mass ratio of the binary 

1045 """ 

1046 

1047 return np.minimum((mass_1 * mass_2) / (mass_1 + mass_2) ** 2, 1 / 4) 

1048 

1049 

1050def component_masses_to_mass_ratio(mass_1, mass_2): 

1051 """ 

1052 Convert the component masses of a binary to its chirp mass. 

1053 

1054 Parameters 

1055 ========== 

1056 mass_1: float 

1057 Mass of the heavier object 

1058 mass_2: float 

1059 Mass of the lighter object 

1060 

1061 Returns 

1062 ======= 

1063 mass_ratio: float 

1064 Mass ratio of the binary 

1065 """ 

1066 

1067 return mass_2 / mass_1 

1068 

1069 

1070def mass_1_and_chirp_mass_to_mass_ratio(mass_1, chirp_mass): 

1071 """ 

1072 Calculate mass ratio from mass_1 and chirp_mass. 

1073 

1074 This involves solving mc = m1 * q**(3/5) / (1 + q)**(1/5). 

1075 

1076 Parameters 

1077 ========== 

1078 mass_1: float 

1079 Mass of the heavier object 

1080 chirp_mass: float 

1081 Mass of the lighter object 

1082 

1083 Returns 

1084 ======= 

1085 mass_ratio: float 

1086 Mass ratio of the binary 

1087 """ 

1088 temp = (chirp_mass / mass_1) ** 5 

1089 mass_ratio = (2 / 3 / (3 ** 0.5 * (27 * temp ** 2 - 4 * temp ** 3) ** 0.5 + 

1090 9 * temp)) ** (1 / 3) * temp + \ 

1091 ((3 ** 0.5 * (27 * temp ** 2 - 4 * temp ** 3) ** 0.5 + 

1092 9 * temp) / (2 * 3 ** 2)) ** (1 / 3) 

1093 return mass_ratio 

1094 

1095 

1096def mass_2_and_chirp_mass_to_mass_ratio(mass_2, chirp_mass): 

1097 """ 

1098 Calculate mass ratio from mass_1 and chirp_mass. 

1099 

1100 This involves solving mc = m2 * (1/q)**(3/5) / (1 + (1/q))**(1/5). 

1101 

1102 Parameters 

1103 ========== 

1104 mass_2: float 

1105 Mass of the lighter object 

1106 chirp_mass: float 

1107 Chirp mass of the binary 

1108 

1109 Returns 

1110 ======= 

1111 mass_ratio: float 

1112 Mass ratio of the binary 

1113 """ 

1114 # Passing mass_2, the expression from the function above 

1115 # returns 1/q (because chirp mass is invariant under 

1116 # mass_1 <-> mass_2) 

1117 return 1 / mass_1_and_chirp_mass_to_mass_ratio(mass_2, chirp_mass) 

1118 

1119 

1120def lambda_1_lambda_2_to_lambda_tilde(lambda_1, lambda_2, mass_1, mass_2): 

1121 """ 

1122 Convert from individual tidal parameters to domainant tidal term. 

1123 

1124 See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. 

1125 

1126 Parameters 

1127 ========== 

1128 lambda_1: float 

1129 Tidal parameter of more massive neutron star. 

1130 lambda_2: float 

1131 Tidal parameter of less massive neutron star. 

1132 mass_1: float 

1133 Mass of more massive neutron star. 

1134 mass_2: float 

1135 Mass of less massive neutron star. 

1136 

1137 Returns 

1138 ======= 

1139 lambda_tilde: float 

1140 Dominant tidal term. 

1141 

1142 """ 

1143 eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) 

1144 lambda_plus = lambda_1 + lambda_2 

1145 lambda_minus = lambda_1 - lambda_2 

1146 lambda_tilde = 8 / 13 * ( 

1147 (1 + 7 * eta - 31 * eta**2) * lambda_plus + 

1148 (1 - 4 * eta)**0.5 * (1 + 9 * eta - 11 * eta**2) * lambda_minus) 

1149 

1150 return lambda_tilde 

1151 

1152 

1153def lambda_1_lambda_2_to_delta_lambda_tilde(lambda_1, lambda_2, mass_1, mass_2): 

1154 """ 

1155 Convert from individual tidal parameters to second domainant tidal term. 

1156 

1157 See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. 

1158 

1159 Parameters 

1160 ========== 

1161 lambda_1: float 

1162 Tidal parameter of more massive neutron star. 

1163 lambda_2: float 

1164 Tidal parameter of less massive neutron star. 

1165 mass_1: float 

1166 Mass of more massive neutron star. 

1167 mass_2: float 

1168 Mass of less massive neutron star. 

1169 

1170 Returns 

1171 ======= 

1172 delta_lambda_tilde: float 

1173 Second dominant tidal term. 

1174 """ 

1175 eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) 

1176 lambda_plus = lambda_1 + lambda_2 

1177 lambda_minus = lambda_1 - lambda_2 

1178 delta_lambda_tilde = 1 / 2 * ( 

1179 (1 - 4 * eta) ** 0.5 * (1 - 13272 / 1319 * eta + 8944 / 1319 * eta**2) * 

1180 lambda_plus + (1 - 15910 / 1319 * eta + 32850 / 1319 * eta ** 2 + 

1181 3380 / 1319 * eta ** 3) * lambda_minus) 

1182 return delta_lambda_tilde 

1183 

1184 

1185def lambda_tilde_delta_lambda_tilde_to_lambda_1_lambda_2( 

1186 lambda_tilde, delta_lambda_tilde, mass_1, mass_2): 

1187 """ 

1188 Convert from dominant tidal terms to individual tidal parameters. 

1189 

1190 See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. 

1191 

1192 Parameters 

1193 ========== 

1194 lambda_tilde: float 

1195 Dominant tidal term. 

1196 delta_lambda_tilde: float 

1197 Secondary tidal term. 

1198 mass_1: float 

1199 Mass of more massive neutron star. 

1200 mass_2: float 

1201 Mass of less massive neutron star. 

1202 

1203 Returns 

1204 ======= 

1205 lambda_1: float 

1206 Tidal parameter of more massive neutron star. 

1207 lambda_2: float 

1208 Tidal parameter of less massive neutron star. 

1209 

1210 """ 

1211 eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) 

1212 coefficient_1 = (1 + 7 * eta - 31 * eta**2) 

1213 coefficient_2 = (1 - 4 * eta)**0.5 * (1 + 9 * eta - 11 * eta**2) 

1214 coefficient_3 = (1 - 4 * eta)**0.5 *\ 

1215 (1 - 13272 / 1319 * eta + 8944 / 1319 * eta**2) 

1216 coefficient_4 = (1 - 15910 / 1319 * eta + 32850 / 1319 * eta**2 + 

1217 3380 / 1319 * eta**3) 

1218 lambda_1 =\ 

1219 (13 * lambda_tilde / 8 * (coefficient_3 - coefficient_4) - 

1220 2 * delta_lambda_tilde * (coefficient_1 - coefficient_2))\ 

1221 / ((coefficient_1 + coefficient_2) * (coefficient_3 - coefficient_4) - 

1222 (coefficient_1 - coefficient_2) * (coefficient_3 + coefficient_4)) 

1223 lambda_2 =\ 

1224 (13 * lambda_tilde / 8 * (coefficient_3 + coefficient_4) - 

1225 2 * delta_lambda_tilde * (coefficient_1 + coefficient_2)) \ 

1226 / ((coefficient_1 - coefficient_2) * (coefficient_3 + coefficient_4) - 

1227 (coefficient_1 + coefficient_2) * (coefficient_3 - coefficient_4)) 

1228 

1229 return lambda_1, lambda_2 

1230 

1231 

1232def lambda_tilde_to_lambda_1_lambda_2( 

1233 lambda_tilde, mass_1, mass_2): 

1234 """ 

1235 Convert from dominant tidal term to individual tidal parameters 

1236 assuming lambda_1 * mass_1**5 = lambda_2 * mass_2**5. 

1237 

1238 See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. 

1239 

1240 Parameters 

1241 ========== 

1242 lambda_tilde: float 

1243 Dominant tidal term. 

1244 mass_1: float 

1245 Mass of more massive neutron star. 

1246 mass_2: float 

1247 Mass of less massive neutron star. 

1248 

1249 Returns 

1250 ======= 

1251 lambda_1: float 

1252 Tidal parameter of more massive neutron star. 

1253 lambda_2: float 

1254 Tidal parameter of less massive neutron star. 

1255 """ 

1256 eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) 

1257 q = mass_2 / mass_1 

1258 lambda_1 = 13 / 8 * lambda_tilde / ( 

1259 (1 + 7 * eta - 31 * eta**2) * (1 + q**-5) + 

1260 (1 - 4 * eta)**0.5 * (1 + 9 * eta - 11 * eta**2) * (1 - q**-5)) 

1261 lambda_2 = lambda_1 / q**5 

1262 return lambda_1, lambda_2 

1263 

1264 

1265def lambda_1_lambda_2_to_lambda_symmetric(lambda_1, lambda_2): 

1266 """ 

1267 Convert from individual tidal parameters to symmetric tidal term. 

1268 

1269 See, e.g., Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1270 

1271 Parameters 

1272 ========== 

1273 lambda_1: float 

1274 Tidal parameter of more massive neutron star. 

1275 lambda_2: float 

1276 Tidal parameter of less massive neutron star. 

1277 

1278 Returns 

1279 ====== 

1280 lambda_symmetric: float 

1281 Symmetric tidal parameter. 

1282 """ 

1283 lambda_symmetric = (lambda_2 + lambda_1) / 2. 

1284 return lambda_symmetric 

1285 

1286 

1287def lambda_1_lambda_2_to_lambda_antisymmetric(lambda_1, lambda_2): 

1288 """ 

1289 Convert from individual tidal parameters to antisymmetric tidal term. 

1290 

1291 See, e.g., Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1292 

1293 Parameters 

1294 ========== 

1295 lambda_1: float 

1296 Tidal parameter of more massive neutron star. 

1297 lambda_2: float 

1298 Tidal parameter of less massive neutron star. 

1299 

1300 Returns 

1301 ====== 

1302 lambda_antisymmetric: float 

1303 Antisymmetric tidal parameter. 

1304 """ 

1305 lambda_antisymmetric = (lambda_2 - lambda_1) / 2. 

1306 return lambda_antisymmetric 

1307 

1308 

1309def lambda_symmetric_lambda_antisymmetric_to_lambda_1(lambda_symmetric, lambda_antisymmetric): 

1310 """ 

1311 Convert from symmetric and antisymmetric tidal terms to lambda_1 

1312 

1313 See, e.g., Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1314 

1315 Parameters 

1316 ========== 

1317 lambda_symmetric: float 

1318 Symmetric tidal parameter. 

1319 lambda_antisymmetric: float 

1320 Antisymmetric tidal parameter. 

1321 

1322 Returns 

1323 ====== 

1324 lambda_1: float 

1325 Tidal parameter of more massive neutron star. 

1326 """ 

1327 lambda_1 = lambda_symmetric - lambda_antisymmetric 

1328 return lambda_1 

1329 

1330 

1331def lambda_symmetric_lambda_antisymmetric_to_lambda_2(lambda_symmetric, lambda_antisymmetric): 

1332 """ 

1333 Convert from symmetric and antisymmetric tidal terms to lambda_2 

1334 

1335 See, e.g., Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1336 

1337 Parameters 

1338 ========== 

1339 lambda_symmetric: float 

1340 Symmetric tidal parameter. 

1341 lambda_antisymmetric: float 

1342 Antisymmetric tidal parameter. 

1343 

1344 Returns 

1345 ====== 

1346 lambda_2: float 

1347 Tidal parameter of less massive neutron star. 

1348 """ 

1349 lambda_2 = lambda_symmetric + lambda_antisymmetric 

1350 return lambda_2 

1351 

1352 

1353def lambda_symmetric_lambda_antisymmetric_to_lambda_1_lambda_2(lambda_symmetric, lambda_antisymmetric): 

1354 """ 

1355 Convert from symmetric and antisymmetric tidal terms to lambda_1 and lambda_2 

1356 

1357 See, e.g., Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1358 

1359 Parameters 

1360 ========== 

1361 lambda_symmetric: float 

1362 Symmetric tidal parameter. 

1363 lambda_antisymmetric: float 

1364 Antisymmetric tidal parameter. 

1365 

1366 Returns 

1367 ====== 

1368 lambda_1: float 

1369 Tidal parameter of more massive neutron star. 

1370 lambda_2: float 

1371 Tidal parameter of less massive neutron star. 

1372 """ 

1373 lambda_1 = lambda_symmetric_lambda_antisymmetric_to_lambda_1(lambda_symmetric, lambda_antisymmetric) 

1374 lambda_2 = lambda_symmetric_lambda_antisymmetric_to_lambda_2(lambda_symmetric, lambda_antisymmetric) 

1375 return lambda_1, lambda_2 

1376 

1377 

1378def binary_love_fit_lambda_symmetric_mass_ratio_to_lambda_antisymmetric(lambda_symmetric, mass_ratio): 

1379 

1380 """ 

1381 Convert from symmetric tidal terms and mass ratio to antisymmetric tidal terms 

1382 using BinaryLove relations. 

1383 This function does only the fit itself, and doesn't account for marginalisation 

1384 over the uncertanties in the fit 

1385 

1386 See Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1387 and Chatziioannou, Haster, Zimmerman, https://arxiv.org/abs/1804.03221 

1388 

1389 This function will use the implementation from the CHZ paper, which 

1390 marginalises over the uncertainties in the BinaryLove fits. 

1391 This is also implemented in LALInference through the function 

1392 LALInferenceBinaryLove. 

1393 

1394 Parameters 

1395 ========== 

1396 lambda_symmetric: float 

1397 Symmetric tidal parameter. 

1398 mass_ratio: float 

1399 Mass ratio (mass_2/mass_1, with mass_2 < mass_1) of the binary 

1400 

1401 Returns 

1402 ====== 

1403 lambda_antisymmetric: float 

1404 Antisymmetric tidal parameter. 

1405 """ 

1406 lambda_symmetric_m1o5 = np.power(lambda_symmetric, -1. / 5.) 

1407 lambda_symmetric_m2o5 = lambda_symmetric_m1o5 * lambda_symmetric_m1o5 

1408 lambda_symmetric_m3o5 = lambda_symmetric_m2o5 * lambda_symmetric_m1o5 

1409 

1410 q = mass_ratio 

1411 q2 = np.square(mass_ratio) 

1412 

1413 # Eqn.2 from CHZ, incorporating the dependence on mass ratio 

1414 

1415 n_polytropic = 0.743 # average polytropic index for the EoSs included in the fit 

1416 q_for_Fnofq = np.power(q, 10. / (3. - n_polytropic)) 

1417 Fnofq = (1. - q_for_Fnofq) / (1. + q_for_Fnofq) 

1418 

1419 # b_ij and c_ij coefficients are given in Table I of CHZ 

1420 

1421 b11 = -27.7408 

1422 b12 = 8.42358 

1423 b21 = 122.686 

1424 b22 = -19.7551 

1425 b31 = -175.496 

1426 b32 = 133.708 

1427 

1428 c11 = -25.5593 

1429 c12 = 5.58527 

1430 c21 = 92.0337 

1431 c22 = 26.8586 

1432 c31 = -70.247 

1433 c32 = -56.3076 

1434 

1435 # Eqn 1 from CHZ, giving the lambda_antisymmetric_fitOnly 

1436 # not yet accounting for the uncertainty in the fit 

1437 

1438 numerator = 1.0 + \ 

1439 (b11 * q * lambda_symmetric_m1o5) + (b12 * q2 * lambda_symmetric_m1o5) + \ 

1440 (b21 * q * lambda_symmetric_m2o5) + (b22 * q2 * lambda_symmetric_m2o5) + \ 

1441 (b31 * q * lambda_symmetric_m3o5) + (b32 * q2 * lambda_symmetric_m3o5) 

1442 

1443 denominator = 1.0 + \ 

1444 (c11 * q * lambda_symmetric_m1o5) + (c12 * q2 * lambda_symmetric_m1o5) + \ 

1445 (c21 * q * lambda_symmetric_m2o5) + (c22 * q2 * lambda_symmetric_m2o5) + \ 

1446 (c31 * q * lambda_symmetric_m3o5) + (c32 * q2 * lambda_symmetric_m3o5) 

1447 

1448 lambda_antisymmetric_fitOnly = Fnofq * lambda_symmetric * numerator / denominator 

1449 

1450 return lambda_antisymmetric_fitOnly 

1451 

1452 

1453def binary_love_lambda_symmetric_to_lambda_1_lambda_2_manual_marginalisation(binary_love_uniform, 

1454 lambda_symmetric, mass_ratio): 

1455 """ 

1456 Convert from symmetric tidal terms to lambda_1 and lambda_2 

1457 using BinaryLove relations 

1458 

1459 See Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1460 and Chatziioannou, Haster, Zimmerman, https://arxiv.org/abs/1804.03221 

1461 

1462 This function will use the implementation from the CHZ paper, which 

1463 marginalises over the uncertainties in the BinaryLove fits. 

1464 This is also implemented in LALInference through the function 

1465 LALInferenceBinaryLove. 

1466 

1467 Parameters 

1468 ========== 

1469 binary_love_uniform: float (defined in the range [0,1]) 

1470 Uniformly distributed variable used in BinaryLove uncertainty marginalisation 

1471 lambda_symmetric: float 

1472 Symmetric tidal parameter. 

1473 mass_ratio: float 

1474 Mass ratio (mass_2/mass_1, with mass_2 < mass_1) of the binary 

1475 

1476 Returns 

1477 ====== 

1478 lambda_1: float 

1479 Tidal parameter of more massive neutron star. 

1480 lambda_2: float 

1481 Tidal parameter of less massive neutron star. 

1482 """ 

1483 lambda_antisymmetric_fitOnly = binary_love_fit_lambda_symmetric_mass_ratio_to_lambda_antisymmetric(lambda_symmetric, 

1484 mass_ratio) 

1485 

1486 lambda_symmetric_sqrt = np.sqrt(lambda_symmetric) 

1487 

1488 q = mass_ratio 

1489 q2 = np.square(mass_ratio) 

1490 

1491 # mu_i and sigma_i coefficients are given in Table II of CHZ 

1492 

1493 mu_1 = 137.1252739 

1494 mu_2 = -32.8026613 

1495 mu_3 = 0.5168637 

1496 mu_4 = -11.2765281 

1497 mu_5 = 14.9499544 

1498 mu_6 = - 4.6638851 

1499 

1500 sigma_1 = -0.0000739 

1501 sigma_2 = 0.0103778 

1502 sigma_3 = 0.4581717 

1503 sigma_4 = -0.8341913 

1504 sigma_5 = -201.4323962 

1505 sigma_6 = 273.9268276 

1506 sigma_7 = -71.2342246 

1507 

1508 # Eqn 6 from CHZ, correction on fit for lambdaA caused by 

1509 # uncertainty in the mean of the lambdaS residual fit, 

1510 # using coefficients mu_1, mu_2 and mu_3 from Table II of CHZ 

1511 

1512 lambda_antisymmetric_lambda_symmetric_meanCorr = \ 

1513 (mu_1 / (lambda_symmetric * lambda_symmetric)) + \ 

1514 (mu_2 / lambda_symmetric) + mu_3 

1515 

1516 # Eqn 8 from CHZ, correction on fit for lambdaA caused by 

1517 # uncertainty in the standard deviation of lambdaS residual fit, 

1518 # using coefficients sigma_1, sigma_2, sigma_3 and sigma_4 from Table II 

1519 

1520 lambda_antisymmetric_lambda_symmetric_stdCorr = \ 

1521 (sigma_1 * lambda_symmetric * lambda_symmetric_sqrt) + \ 

1522 (sigma_2 * lambda_symmetric) + \ 

1523 (sigma_3 * lambda_symmetric_sqrt) + sigma_4 

1524 

1525 # Eqn 7, correction on fit for lambdaA caused by 

1526 # uncertainty in the mean of the q residual fit, 

1527 # using coefficients mu_4, mu_5 and mu_6 from Table II 

1528 

1529 lambda_antisymmetric_mass_ratio_meanCorr = \ 

1530 (mu_4 * q2) + (mu_5 * q) + mu_6 

1531 

1532 # Eqn 9 from CHZ, correction on fit for lambdaA caused by 

1533 # uncertainty in the standard deviation of the q residual fit, 

1534 # using coefficients sigma_5, sigma_6 and sigma_7 from Table II 

1535 

1536 lambda_antisymmetric_mass_ratio_stdCorr = \ 

1537 (sigma_5 * q2) + (sigma_6 * q) + sigma_7 

1538 

1539 # Eqn 4 from CHZ, averaging the corrections from the 

1540 # mean of the residual fits 

1541 

1542 lambda_antisymmetric_meanCorr = \ 

1543 (lambda_antisymmetric_lambda_symmetric_meanCorr + 

1544 lambda_antisymmetric_mass_ratio_meanCorr) / 2. 

1545 

1546 # Eqn 5 from CHZ, averaging the corrections from the 

1547 # standard deviations of the residual fits 

1548 

1549 lambda_antisymmetric_stdCorr = \ 

1550 np.sqrt(np.square(lambda_antisymmetric_lambda_symmetric_stdCorr) + 

1551 np.square(lambda_antisymmetric_mass_ratio_stdCorr)) 

1552 

1553 # Draw a correction on the fit from a 

1554 # Gaussian distribution with width lambda_antisymmetric_stdCorr 

1555 # this is done by sampling a percent point function (inverse cdf) 

1556 # through a U{0,1} variable called binary_love_uniform 

1557 

1558 lambda_antisymmetric_scatter = norm.ppf(binary_love_uniform, loc=0., 

1559 scale=lambda_antisymmetric_stdCorr) 

1560 

1561 # Add the correction of the residual mean 

1562 # and the Gaussian scatter to the lambda_antisymmetric_fitOnly value 

1563 

1564 lambda_antisymmetric = lambda_antisymmetric_fitOnly + \ 

1565 (lambda_antisymmetric_meanCorr + lambda_antisymmetric_scatter) 

1566 

1567 lambda_1 = lambda_symmetric_lambda_antisymmetric_to_lambda_1(lambda_symmetric, lambda_antisymmetric) 

1568 lambda_2 = lambda_symmetric_lambda_antisymmetric_to_lambda_2(lambda_symmetric, lambda_antisymmetric) 

1569 

1570 # The BinaryLove model is only physically valid where 

1571 # lambda_2 > lambda_1 as it assumes mass_1 > mass_2 

1572 # It also assumes both lambda1 and lambda2 to be positive 

1573 # This is an explicit feature of the "raw" model fit, 

1574 # but since this implementation also incorporates 

1575 # marginalisation over the fit uncertainty, there can 

1576 # be instances where those assumptions are randomly broken. 

1577 # For those cases, set lambda_1 and lambda_2 to negative 

1578 # values, which in turn will cause (effectively all) the 

1579 # waveform models in LALSimulation to fail, thus setting 

1580 # logL = -infinity 

1581 

1582 # if np.greater(lambda_1, lambda_2) or np.less(lambda_1, 0) or np.less(lambda_2, 0): 

1583 # lambda_1 = -np.inf 

1584 # lambda_2 = -np.inf 

1585 

1586 # For now set this through an explicit constraint prior instead 

1587 

1588 return lambda_1, lambda_2 

1589 

1590 

1591def binary_love_lambda_symmetric_to_lambda_1_lambda_2_automatic_marginalisation(lambda_symmetric, mass_ratio): 

1592 """ 

1593 Convert from symmetric tidal terms to lambda_1 and lambda_2 

1594 using BinaryLove relations 

1595 

1596 See Yagi & Yunes, https://arxiv.org/abs/1512.02639 

1597 and Chatziioannou, Haster, Zimmerman, https://arxiv.org/abs/1804.03221 

1598 

1599 This function will use the implementation from the CHZ paper, which 

1600 marginalises over the uncertainties in the BinaryLove fits. 

1601 This is also implemented in LALInference through the function 

1602 LALInferenceBinaryLove. 

1603 

1604 This function should be used when the BinaryLove marginalisation wasn't 

1605 explicitly active in the sampling stage. It will draw a random number in U[0,1] 

1606 here instead. 

1607 

1608 Parameters 

1609 ========== 

1610 lambda_symmetric: float 

1611 Symmetric tidal parameter. 

1612 mass_ratio: float 

1613 Mass ratio (mass_2/mass_1, with mass_2 < mass_1) of the binary 

1614 

1615 Returns 

1616 ====== 

1617 lambda_1: float 

1618 Tidal parameter of more massive neutron star. 

1619 lambda_2: float 

1620 Tidal parameter of less massive neutron star. 

1621 """ 

1622 from ..core.utils.random import rng 

1623 

1624 binary_love_uniform = rng.uniform(0, 1, len(lambda_symmetric)) 

1625 

1626 lambda_1, lambda_2 = binary_love_lambda_symmetric_to_lambda_1_lambda_2_manual_marginalisation( 

1627 binary_love_uniform, lambda_symmetric, mass_ratio) 

1628 

1629 return lambda_1, lambda_2 

1630 

1631 

1632def _generate_all_cbc_parameters(sample, defaults, base_conversion, 

1633 likelihood=None, priors=None, npool=1): 

1634 """Generate all cbc parameters, helper function for BBH/BNS""" 

1635 output_sample = sample.copy() 

1636 waveform_defaults = defaults 

1637 for key in waveform_defaults: 

1638 try: 

1639 output_sample[key] = \ 

1640 likelihood.waveform_generator.waveform_arguments[key] 

1641 except (KeyError, AttributeError): 

1642 default = waveform_defaults[key] 

1643 output_sample[key] = default 

1644 logger.debug('Assuming {} = {}'.format(key, default)) 

1645 

1646 output_sample = fill_from_fixed_priors(output_sample, priors) 

1647 output_sample, _ = base_conversion(output_sample) 

1648 if likelihood is not None: 

1649 compute_per_detector_log_likelihoods( 

1650 samples=output_sample, likelihood=likelihood, npool=npool) 

1651 

1652 marginalized_parameters = getattr(likelihood, "_marginalized_parameters", list()) 

1653 if len(marginalized_parameters) > 0: 

1654 try: 

1655 generate_posterior_samples_from_marginalized_likelihood( 

1656 samples=output_sample, likelihood=likelihood, npool=npool) 

1657 except MarginalizedLikelihoodReconstructionError as e: 

1658 logger.warning( 

1659 "Marginalised parameter reconstruction failed with message " 

1660 "{}. Some parameters may not have the intended " 

1661 "interpretation.".format(e) 

1662 ) 

1663 if priors is not None: 

1664 misnamed_marginalizations = dict( 

1665 luminosity_distance="distance", 

1666 geocent_time="time", 

1667 recalib_index="calibration", 

1668 ) 

1669 for par in marginalized_parameters: 

1670 name = misnamed_marginalizations.get(par, par) 

1671 if ( 

1672 getattr(likelihood, f'{name}_marginalization', False) 

1673 and par in likelihood.priors 

1674 ): 

1675 priors[par] = likelihood.priors[par] 

1676 

1677 if ( 

1678 not getattr(likelihood, "reference_frame", "sky") == "sky" 

1679 or not getattr(likelihood, "time_reference", "geocenter") == "geocenter" 

1680 ): 

1681 try: 

1682 generate_sky_frame_parameters( 

1683 samples=output_sample, likelihood=likelihood 

1684 ) 

1685 except TypeError: 

1686 logger.info( 

1687 "Failed to generate sky frame parameters for type {}" 

1688 .format(type(output_sample)) 

1689 ) 

1690 if likelihood is not None: 

1691 compute_snrs(output_sample, likelihood, npool=npool) 

1692 for key, func in zip(["mass", "spin", "source frame"], [ 

1693 generate_mass_parameters, generate_spin_parameters, 

1694 generate_source_frame_parameters]): 

1695 try: 

1696 output_sample = func(output_sample) 

1697 except KeyError as e: 

1698 logger.info( 

1699 "Generation of {} parameters failed with message {}".format( 

1700 key, e)) 

1701 return output_sample 

1702 

1703 

1704def generate_all_bbh_parameters(sample, likelihood=None, priors=None, npool=1): 

1705 """ 

1706 From either a single sample or a set of samples fill in all missing 

1707 BBH parameters, in place. 

1708 

1709 Parameters 

1710 ========== 

1711 sample: dict or pandas.DataFrame 

1712 Samples to fill in with extra parameters, this may be either an 

1713 injection or posterior samples. 

1714 likelihood: bilby.gw.likelihood.GravitationalWaveTransient, optional 

1715 GravitationalWaveTransient used for sampling, used for waveform and 

1716 likelihood.interferometers. 

1717 priors: dict, optional 

1718 Dictionary of prior objects, used to fill in non-sampled parameters. 

1719 """ 

1720 waveform_defaults = { 

1721 'reference_frequency': 50.0, 'waveform_approximant': 'IMRPhenomPv2', 

1722 'minimum_frequency': 20.0} 

1723 output_sample = _generate_all_cbc_parameters( 

1724 sample, defaults=waveform_defaults, 

1725 base_conversion=convert_to_lal_binary_black_hole_parameters, 

1726 likelihood=likelihood, priors=priors, npool=npool) 

1727 return output_sample 

1728 

1729 

1730def generate_all_bns_parameters(sample, likelihood=None, priors=None, npool=1): 

1731 """ 

1732 From either a single sample or a set of samples fill in all missing 

1733 BNS parameters, in place. 

1734 

1735 Since we assume BNS waveforms are aligned, component spins won't be 

1736 calculated. 

1737 

1738 Parameters 

1739 ========== 

1740 sample: dict or pandas.DataFrame 

1741 Samples to fill in with extra parameters, this may be either an 

1742 injection or posterior samples. 

1743 likelihood: bilby.gw.likelihood.GravitationalWaveTransient, optional 

1744 GravitationalWaveTransient used for sampling, used for waveform and 

1745 likelihood.interferometers. 

1746 priors: dict, optional 

1747 Dictionary of prior objects, used to fill in non-sampled parameters. 

1748 npool: int, (default=1) 

1749 If given, perform generation (where possible) using a multiprocessing pool 

1750 

1751 """ 

1752 waveform_defaults = { 

1753 'reference_frequency': 50.0, 'waveform_approximant': 'TaylorF2', 

1754 'minimum_frequency': 20.0} 

1755 output_sample = _generate_all_cbc_parameters( 

1756 sample, defaults=waveform_defaults, 

1757 base_conversion=convert_to_lal_binary_neutron_star_parameters, 

1758 likelihood=likelihood, priors=priors, npool=npool) 

1759 try: 

1760 output_sample = generate_tidal_parameters(output_sample) 

1761 except KeyError as e: 

1762 logger.debug( 

1763 "Generation of tidal parameters failed with message {}".format(e)) 

1764 return output_sample 

1765 

1766 

1767def generate_specific_parameters(sample, parameters): 

1768 """ 

1769 Generate a specific subset of parameters that can be generated. 

1770 

1771 Parameters 

1772 ---------- 

1773 sample: dict 

1774 The input sample to be converted. 

1775 parameters: list 

1776 The list of parameters to return. 

1777 

1778 Returns 

1779 ------- 

1780 output_sample: dict 

1781 The converted parameters 

1782 

1783 Notes 

1784 ----- 

1785 This is _not_ an optimized function. Under the hood, it generates all 

1786 possible parameters and then downselects. 

1787 

1788 If the passed :code:`parameters` do not include the input parameters, 

1789 those will not be returned. 

1790 """ 

1791 updated_sample = generate_all_bns_parameters(sample=sample.copy()) 

1792 output_sample = sample.__class__() 

1793 for key in parameters: 

1794 if key in updated_sample: 

1795 output_sample[key] = updated_sample[key] 

1796 else: 

1797 raise KeyError("{} not in converted sample.".format(key)) 

1798 return output_sample 

1799 

1800 

1801def fill_from_fixed_priors(sample, priors): 

1802 """Add parameters with delta function prior to the data frame/dictionary. 

1803 

1804 Parameters 

1805 ========== 

1806 sample: dict 

1807 A dictionary or data frame 

1808 priors: dict 

1809 A dictionary of priors 

1810 

1811 Returns 

1812 ======= 

1813 dict: 

1814 """ 

1815 output_sample = sample.copy() 

1816 if priors is not None: 

1817 for name in priors: 

1818 if isinstance(priors[name], DeltaFunction): 

1819 output_sample[name] = priors[name].peak 

1820 return output_sample 

1821 

1822 

1823def generate_component_masses(sample, require_add=False, source=False): 

1824 """" 

1825 Add the component masses to the dataframe/dictionary 

1826 We add: 

1827 mass_1, mass_2 

1828 Or if source=True 

1829 mass_1_source, mass_2_source 

1830 We also add any other masses which may be necessary for 

1831 intermediate steps, i.e. typically the total mass is necessary, along 

1832 with the mass ratio, so these will usually be added to the dictionary 

1833 

1834 If `require_add` is True, then having an incomplete set of mass 

1835 parameters (so that the component mass parameters cannot be added) 

1836 will throw an error, otherwise it will quietly add nothing to the 

1837 dictionary. 

1838 

1839 Parameters 

1840 ========= 

1841 sample : dict 

1842 The input dictionary with at least one 

1843 component with overall mass scaling (i.e. 

1844 chirp_mass, mass_1, mass_2, total_mass) and 

1845 then any other mass parameter. 

1846 source : bool, default False 

1847 If True, then perform the conversions for source mass parameters 

1848 i.e. mass_1_source instead of mass_1 

1849 

1850 Returns 

1851 dict : the updated dictionary 

1852 """ 

1853 def check_and_return_quietly(require_add, sample): 

1854 if require_add: 

1855 raise KeyError("Insufficient mass parameters in input dictionary") 

1856 else: 

1857 return sample 

1858 output_sample = sample.copy() 

1859 

1860 if source: 

1861 mass_1_key = "mass_1_source" 

1862 mass_2_key = "mass_2_source" 

1863 total_mass_key = "total_mass_source" 

1864 chirp_mass_key = "chirp_mass_source" 

1865 else: 

1866 mass_1_key = "mass_1" 

1867 mass_2_key = "mass_2" 

1868 total_mass_key = "total_mass" 

1869 chirp_mass_key = "chirp_mass" 

1870 

1871 if mass_1_key in sample.keys(): 

1872 if mass_2_key in sample.keys(): 

1873 return output_sample 

1874 if total_mass_key in sample.keys(): 

1875 output_sample[mass_2_key] = output_sample[total_mass_key] - ( 

1876 output_sample[mass_1_key] 

1877 ) 

1878 return output_sample 

1879 

1880 elif "mass_ratio" in sample.keys(): 

1881 pass 

1882 elif "symmetric_mass_ratio" in sample.keys(): 

1883 output_sample["mass_ratio"] = ( 

1884 symmetric_mass_ratio_to_mass_ratio( 

1885 output_sample["symmetric_mass_ratio"]) 

1886 ) 

1887 elif chirp_mass_key in sample.keys(): 

1888 output_sample["mass_ratio"] = ( 

1889 mass_1_and_chirp_mass_to_mass_ratio( 

1890 mass_1=output_sample[mass_1_key], 

1891 chirp_mass=output_sample[chirp_mass_key]) 

1892 ) 

1893 else: 

1894 return check_and_return_quietly(require_add, sample) 

1895 

1896 output_sample[mass_2_key] = ( 

1897 output_sample["mass_ratio"] * output_sample[mass_1_key] 

1898 ) 

1899 

1900 return output_sample 

1901 

1902 elif mass_2_key in sample.keys(): 

1903 # mass_1 is not in the dict 

1904 if total_mass_key in sample.keys(): 

1905 output_sample[mass_1_key] = ( 

1906 output_sample[total_mass_key] - output_sample[mass_2_key] 

1907 ) 

1908 return output_sample 

1909 elif "mass_ratio" in sample.keys(): 

1910 pass 

1911 elif "symmetric_mass_ratio" in sample.keys(): 

1912 output_sample["mass_ratio"] = ( 

1913 symmetric_mass_ratio_to_mass_ratio( 

1914 output_sample["symmetric_mass_ratio"]) 

1915 ) 

1916 elif chirp_mass_key in sample.keys(): 

1917 output_sample["mass_ratio"] = ( 

1918 mass_2_and_chirp_mass_to_mass_ratio( 

1919 mass_2=output_sample[mass_2_key], 

1920 chirp_mass=output_sample[chirp_mass_key]) 

1921 ) 

1922 else: 

1923 check_and_return_quietly(require_add, sample) 

1924 

1925 output_sample[mass_1_key] = 1 / output_sample["mass_ratio"] * ( 

1926 output_sample[mass_2_key] 

1927 ) 

1928 

1929 return output_sample 

1930 

1931 # Only if neither mass_1 or mass_2 is in the input sample 

1932 if total_mass_key in sample.keys(): 

1933 if "mass_ratio" in sample.keys(): 

1934 pass # We have everything we need already 

1935 elif "symmetric_mass_ratio" in sample.keys(): 

1936 output_sample["mass_ratio"] = ( 

1937 symmetric_mass_ratio_to_mass_ratio( 

1938 output_sample["symmetric_mass_ratio"]) 

1939 ) 

1940 elif chirp_mass_key in sample.keys(): 

1941 output_sample["symmetric_mass_ratio"] = ( 

1942 chirp_mass_and_total_mass_to_symmetric_mass_ratio( 

1943 chirp_mass=output_sample[chirp_mass_key], 

1944 total_mass=output_sample[total_mass_key]) 

1945 ) 

1946 output_sample["mass_ratio"] = ( 

1947 symmetric_mass_ratio_to_mass_ratio( 

1948 output_sample["symmetric_mass_ratio"]) 

1949 ) 

1950 else: 

1951 return check_and_return_quietly(require_add, sample) 

1952 

1953 elif chirp_mass_key in sample.keys(): 

1954 if "mass_ratio" in sample.keys(): 

1955 pass 

1956 elif "symmetric_mass_ratio" in sample.keys(): 

1957 output_sample["mass_ratio"] = ( 

1958 symmetric_mass_ratio_to_mass_ratio( 

1959 sample["symmetric_mass_ratio"]) 

1960 ) 

1961 else: 

1962 return check_and_return_quietly(require_add, sample) 

1963 

1964 output_sample[total_mass_key] = ( 

1965 chirp_mass_and_mass_ratio_to_total_mass( 

1966 chirp_mass=output_sample[chirp_mass_key], 

1967 mass_ratio=output_sample["mass_ratio"]) 

1968 ) 

1969 

1970 # We haven't matched any of the criteria 

1971 if total_mass_key not in output_sample.keys() or ( 

1972 "mass_ratio" not in output_sample.keys()): 

1973 return check_and_return_quietly(require_add, sample) 

1974 mass_1, mass_2 = ( 

1975 total_mass_and_mass_ratio_to_component_masses( 

1976 total_mass=output_sample[total_mass_key], 

1977 mass_ratio=output_sample["mass_ratio"]) 

1978 ) 

1979 output_sample[mass_1_key] = mass_1 

1980 output_sample[mass_2_key] = mass_2 

1981 

1982 return output_sample 

1983 

1984 

1985def generate_mass_parameters(sample, source=False): 

1986 """ 

1987 Add the known mass parameters to the data frame/dictionary. We do 

1988 not recompute keys already present in the dictionary 

1989 

1990 We add, potentially: 

1991 chirp_mass, total_mass, symmetric_mass_ratio, mass_ratio, mass_1, mass_2 

1992 Or if source=True: 

1993 chirp_mass_source, total_mass_source, symmetric_mass_ratio, mass_ratio, mass_1_source, mass_2_source 

1994 

1995 Parameters 

1996 ========== 

1997 sample : dict 

1998 The input dictionary with two "spanning" mass parameters 

1999 e.g. (mass_1, mass_2), or (chirp_mass, mass_ratio), but not e.g. only 

2000 (mass_ratio, symmetric_mass_ratio) 

2001 source : bool, default False 

2002 If True, then perform the conversions for source mass parameters 

2003 i.e. mass_1_source instead of mass_1 

2004 

2005 Returns 

2006 ======= 

2007 dict: The updated dictionary 

2008 

2009 """ 

2010 # Only add the parameters if they're not already present 

2011 intermediate_sample = generate_component_masses(sample, source=source) 

2012 output_sample = intermediate_sample.copy() 

2013 

2014 if source: 

2015 mass_1_key = 'mass_1_source' 

2016 mass_2_key = 'mass_2_source' 

2017 total_mass_key = 'total_mass_source' 

2018 chirp_mass_key = 'chirp_mass_source' 

2019 else: 

2020 mass_1_key = 'mass_1' 

2021 mass_2_key = 'mass_2' 

2022 total_mass_key = 'total_mass' 

2023 chirp_mass_key = 'chirp_mass' 

2024 

2025 if chirp_mass_key not in output_sample.keys(): 

2026 output_sample[chirp_mass_key] = ( 

2027 component_masses_to_chirp_mass(output_sample[mass_1_key], 

2028 output_sample[mass_2_key]) 

2029 ) 

2030 if total_mass_key not in output_sample.keys(): 

2031 output_sample[total_mass_key] = ( 

2032 component_masses_to_total_mass(output_sample[mass_1_key], 

2033 output_sample[mass_2_key]) 

2034 ) 

2035 if 'symmetric_mass_ratio' not in output_sample.keys(): 

2036 output_sample['symmetric_mass_ratio'] = ( 

2037 component_masses_to_symmetric_mass_ratio(output_sample[mass_1_key], 

2038 output_sample[mass_2_key]) 

2039 ) 

2040 if 'mass_ratio' not in output_sample.keys(): 

2041 output_sample['mass_ratio'] = ( 

2042 component_masses_to_mass_ratio(output_sample[mass_1_key], 

2043 output_sample[mass_2_key]) 

2044 ) 

2045 

2046 return output_sample 

2047 

2048 

2049def generate_spin_parameters(sample): 

2050 """ 

2051 Add all spin parameters to the data frame/dictionary. 

2052 

2053 We add: 

2054 cartesian spin components, chi_eff, chi_p cos tilt 1, cos tilt 2 

2055 

2056 Parameters 

2057 ========== 

2058 sample : dict, pandas.DataFrame 

2059 The input dictionary with some spin parameters 

2060 

2061 Returns 

2062 ======= 

2063 dict: The updated dictionary 

2064 

2065 """ 

2066 output_sample = sample.copy() 

2067 

2068 output_sample = generate_component_spins(output_sample) 

2069 

2070 output_sample['chi_eff'] = (output_sample['spin_1z'] + 

2071 output_sample['spin_2z'] * 

2072 output_sample['mass_ratio']) /\ 

2073 (1 + output_sample['mass_ratio']) 

2074 

2075 output_sample['chi_1_in_plane'] = np.sqrt( 

2076 output_sample['spin_1x'] ** 2 + output_sample['spin_1y'] ** 2 

2077 ) 

2078 output_sample['chi_2_in_plane'] = np.sqrt( 

2079 output_sample['spin_2x'] ** 2 + output_sample['spin_2y'] ** 2 

2080 ) 

2081 

2082 output_sample['chi_p'] = np.maximum( 

2083 output_sample['chi_1_in_plane'], 

2084 (4 * output_sample['mass_ratio'] + 3) / 

2085 (3 * output_sample['mass_ratio'] + 4) * output_sample['mass_ratio'] * 

2086 output_sample['chi_2_in_plane']) 

2087 

2088 try: 

2089 output_sample['cos_tilt_1'] = np.cos(output_sample['tilt_1']) 

2090 output_sample['cos_tilt_2'] = np.cos(output_sample['tilt_2']) 

2091 except KeyError: 

2092 pass 

2093 

2094 return output_sample 

2095 

2096 

2097def generate_component_spins(sample): 

2098 """ 

2099 Add the component spins to the data frame/dictionary. 

2100 

2101 This function uses a lalsimulation function to transform the spins. 

2102 

2103 Parameters 

2104 ========== 

2105 sample: A dictionary with the necessary spin conversion parameters: 

2106 'theta_jn', 'phi_jl', 'tilt_1', 'tilt_2', 'phi_12', 'a_1', 'a_2', 'mass_1', 

2107 'mass_2', 'reference_frequency', 'phase' 

2108 

2109 Returns 

2110 ======= 

2111 dict: The updated dictionary 

2112 

2113 """ 

2114 output_sample = sample.copy() 

2115 spin_conversion_parameters =\ 

2116 ['theta_jn', 'phi_jl', 'tilt_1', 'tilt_2', 'phi_12', 'a_1', 'a_2', 

2117 'mass_1', 'mass_2', 'reference_frequency', 'phase'] 

2118 if all(key in output_sample.keys() for key in spin_conversion_parameters): 

2119 ( 

2120 output_sample['iota'], output_sample['spin_1x'], 

2121 output_sample['spin_1y'], output_sample['spin_1z'], 

2122 output_sample['spin_2x'], output_sample['spin_2y'], 

2123 output_sample['spin_2z'] 

2124 ) = np.vectorize(bilby_to_lalsimulation_spins)( 

2125 output_sample['theta_jn'], output_sample['phi_jl'], 

2126 output_sample['tilt_1'], output_sample['tilt_2'], 

2127 output_sample['phi_12'], output_sample['a_1'], output_sample['a_2'], 

2128 output_sample['mass_1'] * solar_mass, 

2129 output_sample['mass_2'] * solar_mass, 

2130 output_sample['reference_frequency'], output_sample['phase'] 

2131 ) 

2132 

2133 output_sample['phi_1'] =\ 

2134 np.fmod(2 * np.pi + np.arctan2( 

2135 output_sample['spin_1y'], output_sample['spin_1x']), 2 * np.pi) 

2136 output_sample['phi_2'] =\ 

2137 np.fmod(2 * np.pi + np.arctan2( 

2138 output_sample['spin_2y'], output_sample['spin_2x']), 2 * np.pi) 

2139 

2140 elif 'chi_1' in output_sample and 'chi_2' in output_sample: 

2141 output_sample['spin_1x'] = 0 

2142 output_sample['spin_1y'] = 0 

2143 output_sample['spin_1z'] = output_sample['chi_1'] 

2144 output_sample['spin_2x'] = 0 

2145 output_sample['spin_2y'] = 0 

2146 output_sample['spin_2z'] = output_sample['chi_2'] 

2147 else: 

2148 logger.debug("Component spin extraction failed.") 

2149 

2150 return output_sample 

2151 

2152 

2153def generate_tidal_parameters(sample): 

2154 """ 

2155 Generate all tidal parameters 

2156 

2157 lambda_tilde, delta_lambda_tilde 

2158 

2159 Parameters 

2160 ========== 

2161 sample: dict, pandas.DataFrame 

2162 Should contain lambda_1, lambda_2 

2163 

2164 Returns 

2165 ======= 

2166 output_sample: dict, pandas.DataFrame 

2167 Updated sample 

2168 """ 

2169 output_sample = sample.copy() 

2170 

2171 output_sample['lambda_tilde'] =\ 

2172 lambda_1_lambda_2_to_lambda_tilde( 

2173 output_sample['lambda_1'], output_sample['lambda_2'], 

2174 output_sample['mass_1'], output_sample['mass_2']) 

2175 output_sample['delta_lambda_tilde'] = \ 

2176 lambda_1_lambda_2_to_delta_lambda_tilde( 

2177 output_sample['lambda_1'], output_sample['lambda_2'], 

2178 output_sample['mass_1'], output_sample['mass_2']) 

2179 

2180 return output_sample 

2181 

2182 

2183def generate_source_frame_parameters(sample): 

2184 """ 

2185 Generate source frame masses along with redshifts and comoving distance. 

2186 

2187 Parameters 

2188 ========== 

2189 sample: dict, pandas.DataFrame 

2190 

2191 Returns 

2192 ======= 

2193 output_sample: dict, pandas.DataFrame 

2194 """ 

2195 output_sample = sample.copy() 

2196 

2197 output_sample['redshift'] =\ 

2198 luminosity_distance_to_redshift(output_sample['luminosity_distance']) 

2199 output_sample['comoving_distance'] =\ 

2200 redshift_to_comoving_distance(output_sample['redshift']) 

2201 

2202 for key in ['mass_1', 'mass_2', 'chirp_mass', 'total_mass']: 

2203 if key in output_sample: 

2204 output_sample['{}_source'.format(key)] =\ 

2205 output_sample[key] / (1 + output_sample['redshift']) 

2206 

2207 return output_sample 

2208 

2209 

2210def compute_snrs(sample, likelihood, npool=1): 

2211 """ 

2212 Compute the optimal and matched filter snrs of all posterior samples 

2213 and print it out. 

2214 

2215 Parameters 

2216 ========== 

2217 sample: dict or array_like 

2218 

2219 likelihood: bilby.gw.likelihood.GravitationalWaveTransient 

2220 Likelihood function to be applied on the posterior 

2221 

2222 """ 

2223 if likelihood is not None: 

2224 if isinstance(sample, dict): 

2225 likelihood.parameters.update(sample) 

2226 signal_polarizations = likelihood.waveform_generator.frequency_domain_strain(likelihood.parameters.copy()) 

2227 for ifo in likelihood.interferometers: 

2228 per_detector_snr = likelihood.calculate_snrs(signal_polarizations, ifo) 

2229 sample['{}_matched_filter_snr'.format(ifo.name)] =\ 

2230 per_detector_snr.complex_matched_filter_snr 

2231 sample['{}_optimal_snr'.format(ifo.name)] = \ 

2232 per_detector_snr.optimal_snr_squared.real ** 0.5 

2233 else: 

2234 from tqdm.auto import tqdm 

2235 logger.info('Computing SNRs for every sample.') 

2236 

2237 fill_args = [(ii, row) for ii, row in sample.iterrows()] 

2238 if npool > 1: 

2239 from ..core.sampler.base_sampler import _initialize_global_variables 

2240 pool = multiprocessing.Pool( 

2241 processes=npool, 

2242 initializer=_initialize_global_variables, 

2243 initargs=(likelihood, None, None, False), 

2244 ) 

2245 logger.info( 

2246 "Using a pool with size {} for nsamples={}".format(npool, len(sample)) 

2247 ) 

2248 new_samples = pool.map(_compute_snrs, tqdm(fill_args, file=sys.stdout)) 

2249 pool.close() 

2250 pool.join() 

2251 else: 

2252 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2253 _sampling_convenience_dump.likelihood = likelihood 

2254 new_samples = [_compute_snrs(xx) for xx in tqdm(fill_args, file=sys.stdout)] 

2255 

2256 for ii, ifo in enumerate(likelihood.interferometers): 

2257 snr_updates = dict() 

2258 for key in new_samples[0][ii].snrs_as_sample.keys(): 

2259 snr_updates[f"{ifo.name}_{key}"] = list() 

2260 for new_sample in new_samples: 

2261 snr_update = new_sample[ii].snrs_as_sample 

2262 for key, val in snr_update.items(): 

2263 snr_updates[f"{ifo.name}_{key}"].append(val) 

2264 for k, v in snr_updates.items(): 

2265 sample[k] = v 

2266 else: 

2267 logger.debug('Not computing SNRs.') 

2268 

2269 

2270def _compute_snrs(args): 

2271 """A wrapper of computing the SNRs to enable multiprocessing""" 

2272 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2273 likelihood = _sampling_convenience_dump.likelihood 

2274 ii, sample = args 

2275 sample = dict(sample).copy() 

2276 likelihood.parameters.update(sample) 

2277 signal_polarizations = likelihood.waveform_generator.frequency_domain_strain( 

2278 likelihood.parameters.copy() 

2279 ) 

2280 snrs = list() 

2281 for ifo in likelihood.interferometers: 

2282 snrs.append(likelihood.calculate_snrs(signal_polarizations, ifo, return_array=False)) 

2283 return snrs 

2284 

2285 

2286def compute_per_detector_log_likelihoods(samples, likelihood, npool=1, block=10): 

2287 """ 

2288 Calculate the log likelihoods in each detector. 

2289 

2290 Parameters 

2291 ========== 

2292 samples: DataFrame 

2293 Posterior from run with a marginalised likelihood. 

2294 likelihood: bilby.gw.likelihood.GravitationalWaveTransient 

2295 Likelihood used during sampling. 

2296 npool: int, (default=1) 

2297 If given, perform generation (where possible) using a multiprocessing pool 

2298 block: int, (default=10) 

2299 Size of the blocks to use in multiprocessing 

2300 

2301 Returns 

2302 ======= 

2303 sample: DataFrame 

2304 Returns the posterior with new samples. 

2305 """ 

2306 if likelihood is not None: 

2307 if not callable(likelihood.compute_per_detector_log_likelihood): 

2308 logger.debug('Not computing per-detector log likelihoods.') 

2309 return samples 

2310 

2311 if isinstance(samples, dict): 

2312 likelihood.parameters.update(samples) 

2313 samples = likelihood.compute_per_detector_log_likelihood() 

2314 return samples 

2315 

2316 elif not isinstance(samples, DataFrame): 

2317 raise ValueError("Unable to handle input samples of type {}".format(type(samples))) 

2318 from tqdm.auto import tqdm 

2319 

2320 logger.info('Computing per-detector log likelihoods.') 

2321 

2322 # Initialize cache dict 

2323 cached_samples_dict = dict() 

2324 

2325 # Store samples to convert for checking 

2326 cached_samples_dict["_samples"] = samples 

2327 

2328 # Set up the multiprocessing 

2329 if npool > 1: 

2330 from ..core.sampler.base_sampler import _initialize_global_variables 

2331 pool = multiprocessing.Pool( 

2332 processes=npool, 

2333 initializer=_initialize_global_variables, 

2334 initargs=(likelihood, None, None, False), 

2335 ) 

2336 logger.info( 

2337 "Using a pool with size {} for nsamples={}" 

2338 .format(npool, len(samples)) 

2339 ) 

2340 else: 

2341 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2342 _sampling_convenience_dump.likelihood = likelihood 

2343 pool = None 

2344 

2345 fill_args = [(ii, row) for ii, row in samples.iterrows()] 

2346 ii = 0 

2347 pbar = tqdm(total=len(samples), file=sys.stdout) 

2348 while ii < len(samples): 

2349 if ii in cached_samples_dict: 

2350 ii += block 

2351 pbar.update(block) 

2352 continue 

2353 

2354 if pool is not None: 

2355 subset_samples = pool.map(_compute_per_detector_log_likelihoods, 

2356 fill_args[ii: ii + block]) 

2357 else: 

2358 subset_samples = [list(_compute_per_detector_log_likelihoods(xx)) 

2359 for xx in fill_args[ii: ii + block]] 

2360 

2361 cached_samples_dict[ii] = subset_samples 

2362 

2363 ii += block 

2364 pbar.update(len(subset_samples)) 

2365 pbar.close() 

2366 

2367 if pool is not None: 

2368 pool.close() 

2369 pool.join() 

2370 

2371 new_samples = np.concatenate( 

2372 [np.array(val) for key, val in cached_samples_dict.items() if key != "_samples"] 

2373 ) 

2374 

2375 for ii, key in \ 

2376 enumerate([f'{ifo.name}_log_likelihood' for ifo in likelihood.interferometers]): 

2377 samples[key] = new_samples[:, ii] 

2378 

2379 return samples 

2380 

2381 else: 

2382 logger.debug('Not computing per-detector log likelihoods.') 

2383 

2384 

2385def _compute_per_detector_log_likelihoods(args): 

2386 """A wrapper of computing the per-detector log likelihoods to enable multiprocessing""" 

2387 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2388 likelihood = _sampling_convenience_dump.likelihood 

2389 ii, sample = args 

2390 sample = dict(sample).copy() 

2391 likelihood.parameters.update(dict(sample).copy()) 

2392 new_sample = likelihood.compute_per_detector_log_likelihood() 

2393 return tuple((new_sample[key] for key in 

2394 [f'{ifo.name}_log_likelihood' for ifo in likelihood.interferometers])) 

2395 

2396 

2397def generate_posterior_samples_from_marginalized_likelihood( 

2398 samples, likelihood, npool=1, block=10, use_cache=True): 

2399 """ 

2400 Reconstruct the distance posterior from a run which used a likelihood which 

2401 explicitly marginalised over time/distance/phase. 

2402 

2403 See Eq. (C29-C32) of https://arxiv.org/abs/1809.02293 

2404 

2405 Parameters 

2406 ========== 

2407 samples: DataFrame 

2408 Posterior from run with a marginalised likelihood. 

2409 likelihood: bilby.gw.likelihood.GravitationalWaveTransient 

2410 Likelihood used during sampling. 

2411 npool: int, (default=1) 

2412 If given, perform generation (where possible) using a multiprocessing pool 

2413 block: int, (default=10) 

2414 Size of the blocks to use in multiprocessing 

2415 use_cache: bool, (default=True) 

2416 If true, cache the generation so that reconstuction can begin from the 

2417 cache on restart. 

2418 

2419 Returns 

2420 ======= 

2421 sample: DataFrame 

2422 Returns the posterior with new samples. 

2423 """ 

2424 from ..core.utils.random import generate_seeds 

2425 

2426 marginalized_parameters = getattr(likelihood, "_marginalized_parameters", list()) 

2427 if len(marginalized_parameters) == 0: 

2428 return samples 

2429 

2430 # pass through a dictionary 

2431 if isinstance(samples, dict): 

2432 return samples 

2433 elif not isinstance(samples, DataFrame): 

2434 raise ValueError("Unable to handle input samples of type {}".format(type(samples))) 

2435 from tqdm.auto import tqdm 

2436 

2437 logger.info('Reconstructing marginalised parameters.') 

2438 

2439 try: 

2440 cache_filename = f"{likelihood.outdir}/.{likelihood.label}_generate_posterior_cache.pickle" 

2441 except AttributeError: 

2442 logger.warning("Likelihood has no outdir and label attribute: caching disabled") 

2443 use_cache = False 

2444 

2445 if use_cache and os.path.exists(cache_filename) and not command_line_args.clean: 

2446 try: 

2447 with open(cache_filename, "rb") as f: 

2448 cached_samples_dict = pickle.load(f) 

2449 except EOFError: 

2450 logger.warning("Cache file is empty") 

2451 cached_samples_dict = None 

2452 

2453 # Check the samples are identical between the cache and current 

2454 if (cached_samples_dict is not None) and (cached_samples_dict["_samples"].equals(samples)): 

2455 # Calculate reconstruction percentage and print a log message 

2456 nsamples_converted = np.sum( 

2457 [len(val) for key, val in cached_samples_dict.items() if key != "_samples"] 

2458 ) 

2459 perc = 100 * nsamples_converted / len(cached_samples_dict["_samples"]) 

2460 logger.info(f'Using cached reconstruction with {perc:0.1f}% converted.') 

2461 else: 

2462 logger.info("Cached samples dict out of date, ignoring") 

2463 cached_samples_dict = dict(_samples=samples) 

2464 

2465 else: 

2466 # Initialize cache dict 

2467 cached_samples_dict = dict() 

2468 

2469 # Store samples to convert for checking 

2470 cached_samples_dict["_samples"] = samples 

2471 

2472 # Set up the multiprocessing 

2473 if npool > 1: 

2474 from ..core.sampler.base_sampler import _initialize_global_variables 

2475 pool = multiprocessing.Pool( 

2476 processes=npool, 

2477 initializer=_initialize_global_variables, 

2478 initargs=(likelihood, None, None, False), 

2479 ) 

2480 logger.info( 

2481 "Using a pool with size {} for nsamples={}" 

2482 .format(npool, len(samples)) 

2483 ) 

2484 else: 

2485 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2486 _sampling_convenience_dump.likelihood = likelihood 

2487 pool = None 

2488 

2489 seeds = generate_seeds(len(samples)) 

2490 fill_args = [(ii, row, seed) for (ii, row), seed in zip(samples.iterrows(), seeds)] 

2491 ii = 0 

2492 pbar = tqdm(total=len(samples), file=sys.stdout) 

2493 while ii < len(samples): 

2494 if ii in cached_samples_dict: 

2495 ii += block 

2496 pbar.update(block) 

2497 continue 

2498 

2499 if pool is not None: 

2500 subset_samples = pool.map(fill_sample, fill_args[ii: ii + block]) 

2501 else: 

2502 subset_samples = [list(fill_sample(xx)) for xx in fill_args[ii: ii + block]] 

2503 

2504 cached_samples_dict[ii] = subset_samples 

2505 

2506 if use_cache: 

2507 safe_file_dump(cached_samples_dict, cache_filename, "pickle") 

2508 

2509 ii += block 

2510 pbar.update(len(subset_samples)) 

2511 pbar.close() 

2512 

2513 if pool is not None: 

2514 pool.close() 

2515 pool.join() 

2516 

2517 new_samples = np.concatenate( 

2518 [np.array(val) for key, val in cached_samples_dict.items() if key != "_samples"] 

2519 ) 

2520 

2521 for ii, key in enumerate(marginalized_parameters): 

2522 samples[key] = new_samples[:, ii] 

2523 

2524 return samples 

2525 

2526 

2527def generate_sky_frame_parameters(samples, likelihood): 

2528 if isinstance(samples, dict): 

2529 likelihood.parameters.update(samples) 

2530 samples.update(likelihood.get_sky_frame_parameters()) 

2531 return 

2532 elif not isinstance(samples, DataFrame): 

2533 raise ValueError 

2534 from tqdm.auto import tqdm 

2535 

2536 logger.info('Generating sky frame parameters.') 

2537 new_samples = list() 

2538 for ii in tqdm(range(len(samples)), file=sys.stdout): 

2539 sample = dict(samples.iloc[ii]).copy() 

2540 likelihood.parameters.update(sample) 

2541 new_samples.append(likelihood.get_sky_frame_parameters()) 

2542 new_samples = DataFrame(new_samples) 

2543 for key in new_samples: 

2544 samples[key] = new_samples[key] 

2545 

2546 

2547def fill_sample(args): 

2548 from ..core.sampler.base_sampler import _sampling_convenience_dump 

2549 from ..core.utils.random import seed 

2550 

2551 ii, sample, rseed = args 

2552 seed(rseed) 

2553 likelihood = _sampling_convenience_dump.likelihood 

2554 marginalized_parameters = getattr(likelihood, "_marginalized_parameters", list()) 

2555 sample = dict(sample).copy() 

2556 likelihood.parameters.update(dict(sample).copy()) 

2557 new_sample = likelihood.generate_posterior_sample_from_marginalized_likelihood() 

2558 return tuple((new_sample[key] for key in marginalized_parameters)) 

2559 

2560 

2561def identity_map_conversion(parameters): 

2562 """An identity map conversion function that makes no changes to the parameters, 

2563 but returns the correct signature expected by other conversion functions 

2564 (e.g. convert_to_lal_binary_black_hole_parameters)""" 

2565 return parameters, [] 

2566 

2567 

2568def identity_map_generation(sample, likelihood=None, priors=None, npool=1): 

2569 """An identity map generation function that handles marginalizations, SNRs, etc. correctly, 

2570 but does not attempt e.g. conversions in mass or spins 

2571 

2572 Parameters 

2573 ========== 

2574 sample: dict or pandas.DataFrame 

2575 Samples to fill in with extra parameters, this may be either an 

2576 injection or posterior samples. 

2577 likelihood: bilby.gw.likelihood.GravitationalWaveTransient, optional 

2578 GravitationalWaveTransient used for sampling, used for waveform and 

2579 likelihood.interferometers. 

2580 priors: dict, optional 

2581 Dictionary of prior objects, used to fill in non-sampled parameters. 

2582 

2583 Returns 

2584 ======= 

2585 

2586 """ 

2587 output_sample = sample.copy() 

2588 

2589 output_sample = fill_from_fixed_priors(output_sample, priors) 

2590 

2591 if likelihood is not None: 

2592 compute_per_detector_log_likelihoods( 

2593 samples=output_sample, likelihood=likelihood, npool=npool) 

2594 

2595 marginalized_parameters = getattr(likelihood, "_marginalized_parameters", list()) 

2596 if len(marginalized_parameters) > 0: 

2597 try: 

2598 generate_posterior_samples_from_marginalized_likelihood( 

2599 samples=output_sample, likelihood=likelihood, npool=npool) 

2600 except MarginalizedLikelihoodReconstructionError as e: 

2601 logger.warning( 

2602 "Marginalised parameter reconstruction failed with message " 

2603 "{}. Some parameters may not have the intended " 

2604 "interpretation.".format(e) 

2605 ) 

2606 

2607 if ("ra" in output_sample.keys() and "dec" in output_sample.keys() and "psi" in output_sample.keys()): 

2608 compute_snrs(output_sample, likelihood, npool=npool) 

2609 else: 

2610 logger.info( 

2611 "Skipping SNR computation since samples have insufficient sky location information" 

2612 ) 

2613 

2614 return output_sample