Coverage for /home/runner/work/nr-catalog-tools/nr-catalog-tools/nrcatalogtools/lvc.py: 47%

284 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-01 05:18 +0000

1import h5py 

2import lal 

3import lalsimulation as lalsim 

4import numpy as np 

5from pycbc.pnutils import mtotal_eta_to_mass1_mass2 

6from pycbc.types import TimeSeries 

7from scipy.interpolate import interp1d 

8 

9 

10def get_lal_mode_dictionary(mode_array): 

11 """ 

12 Get LALDict with all specified modes. 

13 

14 

15 Parameters 

16 ---------- 

17 mode_array: list of modes, eg [[2,2], [3,3]] 

18 

19 Returns 

20 ------- 

21 waveform_dictionary: LALDict with all modes included 

22 

23 """ 

24 waveform_dictionary = lal.CreateDict() 

25 mode_array_lal = lalsim.SimInspiralCreateModeArray() 

26 for mode in mode_array: 

27 lalsim.SimInspiralModeArrayActivateMode(mode_array_lal, mode[0], mode[1]) 

28 lalsim.SimInspiralWaveformParamsInsertModeArray(waveform_dictionary, mode_array_lal) 

29 

30 return waveform_dictionary 

31 

32 

33def get_lal_mode_dictionary_from_lmax(lmax): 

34 r""" 

35 Get LALDict with modes derived from `lmax`. 

36 

37 

38 Parameters 

39 ---------- 

40 lmax: max value of :math:`\ell` to use 

41 

42 Returns 

43 ------- 

44 waveform_dictionary: LALDict with all modes upto `lmax` included 

45 

46 """ 

47 

48 mode_array = [ 

49 [ell, emm] for ell in range(2, lmax + 1) for emm in range(-ell, ell + 1) 

50 ] 

51 return get_lal_mode_dictionary(mode_array) 

52 

53 

54def get_modes_from_lvcnr_file(path_to_file, Mtot, distance, srate, lmax=4, f_low=None): 

55 r""" 

56 Get individual modes from LVCNR format file. 

57 

58 

59 Parameters 

60 ========== 

61 path_to_file: string 

62 Path to LVCNR file 

63 Mtot: float 

64 Total mass (in units of MSUN) to scale the waveform to 

65 distance: float 

66 Luminosity Distance (in units of MPc) to scale the waveform to 

67 srate: int 

68 Sampling rate for the waveform 

69 lmax: int 

70 Max value of :math:`\ell` to use 

71 (Default: 4) 

72 f_low: float 

73 Value of the low frequency to start waveform generation 

74 Uses value given from the LVCNR file if `None` is provided 

75 (Default: None) 

76 

77 Returns 

78 ======= 

79 mass_ratio: float 

80 Mass ratio derived from the LVCNR file 

81 spins_args: list 

82 List of spins derived from the LVCNR file 

83 eccentricity: float 

84 Eccentricty derived from the LVCNR file. 

85 Returns `None` is eccentricity is not a number. 

86 f_low: float 

87 Low Frequency derived either from the file, or provided 

88 in the call 

89 f_ref: float 

90 Reference Frequency derived from the file 

91 modes: dict of pycbc TimeSeries objects 

92 dict containing all the read in modes 

93 """ 

94 

95 with h5py.File(path_to_file) as h5file: 

96 waveform_dict = get_lal_mode_dictionary_from_lmax(lmax) 

97 

98 f_low_in_file = h5file.attrs["f_lower_at_1MSUN"] / Mtot 

99 f_ref = f_low_in_file 

100 

101 if f_low is None: 

102 f_low = f_low_in_file 

103 

104 if h5file.attrs["Format"] < 3: 

105 spin_args = lalsim.SimInspiralNRWaveformGetSpinsFromHDF5File( 

106 -1, Mtot, path_to_file 

107 ) 

108 else: 

109 spin_args = lalsim.SimInspiralNRWaveformGetSpinsFromHDF5File( 

110 f_low, Mtot, path_to_file 

111 ) 

112 

113 mass_args = list( 

114 mtotal_eta_to_mass1_mass2(Mtot * lal.MSUN_SI, h5file.attrs["eta"]) 

115 ) 

116 

117 try: 

118 eccentricity = float(h5file.attrs["eccentricity"]) 

119 except ValueError: 

120 eccentricity = None 

121 

122 values_mode_array = lalsim.SimInspiralWaveformParamsLookupModeArray(waveform_dict) 

123 _, modes = lalsim.SimInspiralNRWaveformGetHlms( 

124 1 / srate, 

125 *mass_args, 

126 distance * 1e6 * lal.PC_SI, 

127 f_low, 

128 f_ref, 

129 *spin_args, 

130 path_to_file, 

131 values_mode_array, 

132 ) 

133 mode = modes 

134 return_dict = dict() 

135 while 1 > 0: 

136 try: 

137 l, m = mode.l, mode.m 

138 read_mode = mode.mode.data.data 

139 return_dict[(l, m)] = TimeSeries(read_mode, 1 / srate) 

140 mode = mode.next 

141 except AttributeError: 

142 break 

143 

144 return ( 

145 mass_args[1] / mass_args[0], 

146 spin_args, 

147 eccentricity, 

148 f_low, 

149 f_ref, 

150 return_dict, 

151 ) 

152 

153 

154def get_strain_from_lvcnr_file( 

155 path_to_file, Mtot, distance, inclination, phi_ref, srate, mode_array=None 

156): 

157 """ 

158 Get full strain from LVCNR format file. 

159 

160 

161 Parameters 

162 ========== 

163 path_to_file: string 

164 Path to LVCNR file 

165 Mtot: float 

166 Total mass (in units of MSUN) to scale the waveform to 

167 distance: float 

168 Luminosity Distance (in units of MPc) to scale the waveform to 

169 srate: int 

170 Sampling rate for the waveform 

171 mode_array: list 

172 list of modes to be included. `None` means all modes are included. 

173 (Default:None) 

174 

175 Returns 

176 ======= 

177 UNDER CONSTRUCTION 

178 """ 

179 

180 longAscNodes = 0 

181 eccentricity = 0 

182 meanPerAno = 0 

183 

184 fixed_args = [ 

185 distance * lal.PC_SI * 1e6, 

186 inclination, 

187 phi_ref, 

188 longAscNodes, 

189 eccentricity, 

190 meanPerAno, 

191 1 / srate, 

192 ] 

193 

194 with h5py.File(path_to_file) as h5file: 

195 if mode_array is not None: 

196 waveform_dict = get_lal_mode_dictionary(mode_array) 

197 else: 

198 waveform_dict = lal.CreateDict() 

199 

200 lalsim.SimInspiralWaveformParamsInsertNumRelData(waveform_dict, path_to_file) 

201 f_low = h5file.attrs["f_lower_at_1MSUN"] / Mtot 

202 

203 if h5file.attrs["Format"] < 3: 

204 spin_args = lalsim.SimInspiralNRWaveformGetSpinsFromHDF5File( 

205 -1, Mtot, path_to_file 

206 ) 

207 else: 

208 spin_args = lalsim.SimInspiralNRWaveformGetSpinsFromHDF5File( 

209 f_low, Mtot, path_to_file 

210 ) 

211 

212 mass_args = list( 

213 mtotal_eta_to_mass1_mass2(Mtot * lal.MSUN_SI, h5file.attrs["eta"]) 

214 ) 

215 

216 hp, hc = lalsim.SimInspiralChooseTDWaveform( 

217 *mass_args, 

218 *spin_args, 

219 *fixed_args, 

220 f_low, 

221 f_low, 

222 waveform_dict, 

223 lalsim.GetApproximantFromString("NR_hdf5"), 

224 ) 

225 

226 hp_timeseries = TimeSeries(hp.data.data, fixed_args[-1]) 

227 hc_timeseries = TimeSeries(hc.data.data, fixed_args[-1]) 

228 

229 return dict( 

230 hp=hp_timeseries, 

231 hc=hc_timeseries, 

232 f_low=f_low, 

233 mass_args=mass_args, 

234 spin_args=spin_args, 

235 fixed_args=fixed_args, 

236 ) 

237 

238 

239def check_interp_req(h5_file=None, metadata=None, ref_time=None, avail_ref_time=None): 

240 """Check if the required reference time is different from 

241 the available reference time in the NR HDF5 file or the 

242 simulation metadata. 

243 

244 Parameters 

245 ---------- 

246 h5_file : file object 

247 The waveform h5 file handle. 

248 metadata : 

249 ref_time, avail_ref_time : float 

250 The use and available nr reference time. 

251 

252 Returns 

253 ------- 

254 interp : bool 

255 Whether interpolation across time is required. 

256 avail_ref_time: float 

257 The ref_time available in the NR HDF5 file. 

258 """ 

259 

260 if avail_ref_time is None: 

261 # CCheck for ref time in h5 file 

262 if h5_file is not None: 

263 keys = list(h5_file.attrs.keys()) 

264 

265 if "reference_time" in keys: 

266 avail_ref_time = h5_file.attrs["reference_time"] 

267 elif "ref_time" in keys: 

268 avail_ref_time = h5_file.attrs["ref_time"] 

269 elif "relaxed_time" in keys: 

270 avail_ref_time = h5_file.attrs["relaxed_time"] 

271 else: 

272 print("Reference time not found in waveform h5 file.") 

273 

274 if not avail_ref_time: 

275 # If not found, continue search in metadata. 

276 if metadata is not None: 

277 keys = list(metadata.keys()) 

278 

279 if "reference_time" in keys: 

280 avail_ref_time = metadata["reference_time"] 

281 elif "relaxed_time" in keys: 

282 avail_ref_time = metadata["relaxed_time"] 

283 else: 

284 print("Reference time not found in simulation metadata file.") 

285 

286 if not avail_ref_time: 

287 # Then this is GT simulation! 

288 print( 

289 "Reference time should be computed from" 

290 "the reference orbital frequency!" 

291 ) 

292 

293 interp = True 

294 if isinstance(ref_time, float): 

295 if abs(avail_ref_time - ref_time) < 1e-5: 

296 interp = False 

297 else: 

298 return interp, avail_ref_time 

299 

300 

301def get_ref_freq_from_ref_time(h5_file, ref_time): 

302 """Get the reference frequency from reference time 

303 

304 Parameters 

305 ---------- 

306 h5_file : file object 

307 The waveform h5 file handle. 

308 ref_time : float 

309 Reference time. 

310 Returns 

311 ------- 

312 f_ref : float 

313 Reference frequency. 

314 """ 

315 

316 time, freq = h5_file.attrs["Omega-vs-time"] 

317 

318 ref_freq = interp1d(time, freq, kind="cubic")[ref_time] 

319 

320 return ref_freq 

321 

322 

323def get_ref_time_from_ref_freq(h5_file, ref_freq): 

324 """Get the reference time from reference frequency 

325 

326 Parameters 

327 ---------- 

328 h5_file : file object 

329 The waveform h5 file handle. 

330 ref_freq : float 

331 The reference frequency. 

332 Returns 

333 ------- 

334 fTime : float 

335 Reference time. 

336 """ 

337 

338 time, freq = h5_file.attrs["Omega-vs-time"] 

339 

340 ref_time = interp1d(freq, time, kind="cubic")[ref_freq] 

341 

342 return ref_time 

343 

344 

345def check_nr_attrs( 

346 sim_metadata_object, 

347 req_attrs=["LNhatx", "LNhaty", "LNhatz", "nhatx", "nhaty", "nhatz"], 

348): 

349 """Check if the NR h5 file or a simulation metadata dictionary 

350 contains all the attributes required. 

351 

352 Parameters 

353 ---------- 

354 sim_metadata_object : h5 file object, dict 

355 The NR h5py file handle or simulation metadata. 

356 req_attrs : list 

357 A list of attribute keys. 

358 Returns 

359 ------- 

360 present : bool 

361 Whether or not all specified attributes are present. 

362 absent_attrs : list 

363 The attributes that are absent. 

364 """ 

365 if isinstance(sim_metadata_object, h5py.File): 

366 all_attrs = list(sim_metadata_object.attrs.keys()) 

367 

368 elif isinstance(sim_metadata_object, dict): 

369 all_attrs = list(sim_metadata_object.keys()) 

370 else: 

371 raise TypeError("Please supply an open h5py file handle or a dictionary") 

372 

373 absent_attrs = [] 

374 present = True 

375 

376 for item in req_attrs: 

377 if item not in all_attrs: 

378 present = False 

379 absent_attrs.append(item) 

380 

381 return present, absent_attrs 

382 

383 

384def get_interp_ref_values_from_h5_file(h5_file, req_ts_attrs, ref_time): 

385 """Get the interpolated reference values at a given reference time 

386 from the NR HDF5 File 

387 

388 Parameters 

389 ---------- 

390 h5_file : file object 

391 The waveform h5 file handle. 

392 req_ts_attrs : list 

393 A list of attribute keys. 

394 ref_time : float 

395 Reference time. 

396 Returns 

397 ------- 

398 params : dict 

399 The parameter values at the reference time. 

400 """ 

401 

402 params = {} 

403 

404 for key in req_ts_attrs: 

405 time, var = h5_file.attrs[key] 

406 RefVal = interp1d(time, var, kind="cubic")[ref_time] 

407 params.update({key: RefVal}) 

408 return params 

409 

410 

411def get_ref_vals( 

412 sim_metadata_object, 

413 req_attrs=["LNhatx", "LNhaty", "LNhatz", "nhatx", "nhaty", "nhatz"], 

414): 

415 """Get the reference values from a NR HDF5 file 

416 or a simulation metadata dictionary. 

417 

418 Parameters 

419 ---------- 

420 sim_metadata_object : h5 file object, dict 

421 The NR h5py file handle or 

422 the simulation metadata. 

423 req_attrs : list 

424 A list of attribute keys. 

425 Returns 

426 ------- 

427 params : dict 

428 The parameter values at the reference time. 

429 """ 

430 if isinstance(sim_metadata_object, h5py.File): 

431 source = sim_metadata_object.attrs 

432 

433 elif isinstance(sim_metadata_object, dict): 

434 source = sim_metadata_object 

435 else: 

436 raise TypeError("Please supply an open h5py file handle or a dictionary") 

437 

438 params = {} 

439 

440 for key in req_attrs: 

441 RefVal = source[key] 

442 params.update({key: RefVal}) 

443 return params 

444 

445 

446def compute_lal_source_frame_from_sxs_metadata(sim_metadata): 

447 """Compute the LAL source frame vectors at the 

448 available reference time from the SXS simulation 

449 metadata. 

450 

451 Parameters 

452 ---------- 

453 sim_metadata : dict 

454 The NR sim_metadata. 

455 Returns 

456 ------- 

457 params : dict 

458 A dictionary containing the LAL source frame 

459 vectors. 

460 """ 

461 # Masses 

462 M1 = sim_metadata["reference_mass1"] 

463 M2 = sim_metadata["reference_mass2"] 

464 M = M1 + M2 

465 

466 # Orbital anglular frequency 

467 Omega = np.array(sim_metadata["reference_orbital_frequency"]) 

468 

469 # Positions 

470 Pos1 = np.array(sim_metadata["reference_position1"]) 

471 Pos2 = np.array(sim_metadata["reference_position2"]) 

472 

473 # CoM position 

474 CoMPos = (M1 * Pos1 + M2 * Pos2) / (M) 

475 

476 Pos1 = Pos1 - CoMPos 

477 Pos2 = Pos2 - CoMPos 

478 

479 # This should point from 

480 # smaller to larger BH 

481 # Assumed here is 1 for smaller 

482 # BH 

483 DPos = Pos2 - Pos1 

484 

485 # Oribital angular momentum, Eucledian 

486 P1 = M1 * np.cross(Omega, Pos1) 

487 P2 = M2 * np.cross(Omega, Pos2) 

488 

489 Lbar = np.cross(Pos1, P1) + np.cross(Pos2, P2) 

490 

491 # Orbital normal LNhat 

492 LNhat = Lbar / np.linalg.norm(Lbar) 

493 LNhatx, LNhaty, LNhatz = LNhat 

494 

495 # Position vector nhat 

496 nhat = (DPos) / np.linalg.norm(DPos) 

497 nhatx, nhaty, nhatz = nhat 

498 

499 params = { 

500 "LNhatx": LNhatx, 

501 "LNhaty": LNhaty, 

502 "LNhatz": LNhatz, 

503 "nhatx": nhatx, 

504 "nhaty": nhaty, 

505 "nhatz": nhatz, 

506 } 

507 

508 return params 

509 

510 

511def compute_lal_source_frame_by_interp(h5_file, req_ts_attrs, t_ref): 

512 """ 

513 Compute the LAL source frame vectors at a given reference time 

514 by interpolation of time series data. 

515 

516 Parameters 

517 ---------- 

518 h5_file : h5 file object 

519 The NR h5py file handle that contains the simulation metadata. 

520 t_ref : float 

521 The reference time. 

522 Returns 

523 ------- 

524 params : dict 

525 The LAL source frame vectors at the reference time. 

526 

527 """ 

528 # For reference: attributes required for interpolation. 

529 # req_ts_attrs = ['LNhatx-vs-time', 'LNhaty-vs-time', 'LNhatz-vs-time', \ 

530 # 'position1x-vs-time', 'position1y-vs-time', 'position1z-vs-time', \ 

531 # 'position2x-vs-time', 'position2y-vs-time', 'position2z-vs-time'] 

532 

533 ref_params = get_interp_ref_values_from_h5_file(h5_file, req_ts_attrs, t_ref) 

534 

535 # r21 vec 

536 r21_x = ref_params["position1x-vs-time"] - ref_params["position2x-vs-time"] 

537 r21_y = ref_params["position1y-vs-time"] - ref_params["position2y-vs-time"] 

538 r21_z = ref_params["position1z-vs-time"] - ref_params["position2z-vs-time"] 

539 

540 # Position unit vector nhat 

541 dPos = np.array([r21_x, r21_y, r21_z]) 

542 nhat = dPos / np.linalg.norm(dPos) 

543 nhatx, nhaty, nhatz = nhat 

544 

545 # Orbital unit normal LNhat 

546 LNhatx = ref_params["LNhatx-vs-time"] 

547 LNhaty = ref_params["LNhaty-vs-time"] 

548 LNhatz = ref_params["LNhatz-vs-time"] 

549 

550 LNhat = np.array([LNhatx, LNhaty, LNhatz]) 

551 LNhat = LNhat / np.linalg.norm(LNhat) 

552 # LNhatx, LNhaty, LNhatz = LNhat 

553 

554 # params = {"LNhat": LNhat, "nhat": nhat} 

555 params = { 

556 "LNhatx": LNhatx, 

557 "LNhaty": LNhaty, 

558 "LNhatz": LNhatz, 

559 "nhatx": nhatx, 

560 "nhaty": nhaty, 

561 "nhatz": nhatz, 

562 } 

563 

564 return params 

565 

566 

567def normalize_metadata(sim_metadata): 

568 """Ensure that the keys of the metadata are 

569 as required. 

570 

571 Parameters 

572 ---------- 

573 sim_metadata : dict 

574 The NR sim_metadata. 

575 

576 Returns 

577 ------- 

578 norm_sim_metadata : dict 

579 The normalized simulation metadata 

580 """ 

581 

582 norm_sim_metadata = {} 

583 

584 for key, val in sim_metadata.items(): 

585 norm_sim_metadata.update({key.replace("relaxed-", ""): val}) 

586 

587 return norm_sim_metadata 

588 

589 

590def get_ref_time_from_metadata(sim_metadata): 

591 """Get the reference time of definition of the LAL 

592 frame from the simulation metadata, if available. 

593 

594 Parameters 

595 ---------- 

596 sim_metadata : dict 

597 The simulation metadata. 

598 

599 Returns 

600 ------- 

601 t_ref : float 

602 The reference time 

603 """ 

604 

605 MdataKeys = list(sim_metadata.keys()) 

606 

607 if "relaxed-time" in MdataKeys: 

608 # RIT style 

609 t_ref = sim_metadata["relaxed-time"] 

610 elif "reference_time" in MdataKeys: 

611 # SXS Style 

612 t_ref = sim_metadata["reference_time"] 

613 else: 

614 t_ref = -1 

615 

616 return t_ref 

617 

618 

619def transform_spins_nr_to_lal(nrSpin1, nrSpin2, n_hat, ln_hat): 

620 """Trnasform the spins of the NR simulation from the 

621 NR frame to the frame. 

622 Parameters 

623 --------- 

624 nrSpin1, nrSpin2 : list 

625 A list of the components of the spins of the objects. 

626 nhat, ln_hat : list 

627 A list of the components of the unit vectors of the objects, 

628 against which the components of the spins are specified. 

629 Returns 

630 ------- 

631 S1, S2 : list 

632 The transformed spins in LAL frame. 

633 """ 

634 nrSpin1x, nrSpin1y, nrSpin1z = nrSpin1 

635 nrSpin2x, nrSpin2y, nrSpin2z = nrSpin2 

636 

637 n_hat_x, n_hat_y, n_hat_z = n_hat 

638 ln_hat_x, ln_hat_y, ln_hat_z = ln_hat 

639 

640 S1x = nrSpin1x * n_hat_x + nrSpin1y * n_hat_y + nrSpin1z * n_hat_z 

641 

642 S1y = ( 

643 nrSpin1x * (-ln_hat_z * n_hat_y + ln_hat_y * n_hat_z) 

644 + nrSpin1y * (ln_hat_z * n_hat_x - ln_hat_x * n_hat_z) 

645 + nrSpin1z * (-ln_hat_y * n_hat_x + ln_hat_x * n_hat_y) 

646 ) 

647 

648 S1z = nrSpin1x * ln_hat_x + nrSpin1y * ln_hat_y + nrSpin1z * ln_hat_z 

649 

650 S2x = nrSpin2x * n_hat_x + nrSpin2y * n_hat_y + nrSpin2z * n_hat_z 

651 S2y = ( 

652 nrSpin2x * (-ln_hat_z * n_hat_y + ln_hat_y * n_hat_z) 

653 + nrSpin2y * (ln_hat_z * n_hat_x - ln_hat_x * n_hat_z) 

654 + nrSpin2z * (-ln_hat_y * n_hat_x + ln_hat_x * n_hat_y) 

655 ) 

656 

657 S2z = nrSpin2x * ln_hat_x + nrSpin2y * ln_hat_y + nrSpin2z * ln_hat_z 

658 

659 S1 = [S1x, S1y, S1z] 

660 S2 = [S2x, S2y, S2z] 

661 

662 return S1, S2 

663 

664 

665def get_nr_to_lal_rotation_angles( 

666 h5_file, sim_metadata, inclination, phi_ref=0, f_ref=None, t_ref=None, tol=1e-6 

667): 

668 r"""Get the angular coordinates :math:`\theta, \phi` 

669 and the rotation angle :math:`\alpha` from the H5 file 

670 

671 Parameters 

672 ---------- 

673 h5_file : file object 

674 The waveform h5 file handle. 

675 

676 inclination : float 

677 The inclination angle. 

678 phi_ref : float 

679 The orbital phase at reference time. 

680 Fref, t_ref : float, optional 

681 The reference orbital frequency or time 

682 

683 sim_metadata : dict 

684 The sim_metadata of the waveform file. 

685 tol : float 

686 The tolerance to use to allow floating point 

687 representation errors. 

688 

689 Returns 

690 ------- 

691 angles : dict 

692 The angular corrdinates Theta, Psi, and the rotation angle Alpha. 

693 If available, this also contains the reference time and frequency. 

694 

695 Notes 

696 ----- 

697 

698 Variable definitions. 

699 

700 theta : Returned inclination angle of source in NR coordinates. 

701 psi : Returned azimuth angle of source in NR coordinates. 

702 alpha: Returned polarisation angle. 

703 h5_file: h5py object of the NR HDF5 file. 

704 inclination: inclination of source in LAL source frame. 

705 phi_ref: Orbital reference phase. 

706 t_ref : Reference time. -1 or None indicates it was not found in the sim_metadata. 

707 f_ref: Reference frequency. 

708 

709 The reference epoch is defined close to the beginning of the simulation. 

710 """ 

711 

712 # Compute the angles necessary to rotate from the intrinsic NR source frame 

713 # into the LAL frame. See DCC-T1600045 for details. 

714 

715 # Following section IV of DCC-T1600045 

716 # Step 1: Define Phi = phiref 

717 orb_phase = phi_ref 

718 

719 ########################################## 

720 # Step 2: Compute Zref 

721 # 2.1 : Check if interpolation is required in IntReq 

722 # 2.2 : Get/ Compute the basis vectors of the LAL 

723 # frame. 

724 # 2.2.1 : If IntReq=yes, given the reference time, interpolate and get 

725 # the required basis vectors in the LAL source frame. 

726 # 2.2.2 : If no, then check for the default values of the 

727 # LAL frame basis vectors in the h5_file 

728 # 2.2.3 : If the h5_file does not contain the required default 

729 # vectors, then raise an error. 

730 # 2.3 : Carryout vector math to get Zref. 

731 # 2.1: Compute LN_hat from file. LN_hat = direction of orbital ang. mom. 

732 # 2.2: Compute n_hat from file. n_hat = direction from object 2 to object 1 

733 

734 ########################################### 

735 # Cases 

736 ########################################### 

737 # req_def_attrs = ["LNhatx", "LNhaty", "LNhatz", "nhatx", "nhaty", "nhatz"] 

738 

739 # SXS default attributes required 

740 # for computing the LAL source frame. 

741 req_def_attrs_sxs = [ 

742 "reference_time", 

743 "reference_mass1", 

744 "reference_mass2", 

745 "reference_orbital_frequency", 

746 "reference_position1", 

747 "reference_position2", 

748 ] 

749 

750 # Check if interpolation is necessary 

751 # if t_ref is supplied 

752 

753 interp = False 

754 

755 if not t_ref: 

756 if not f_ref: 

757 # No interpolation required. Use available reference values. 

758 interp = False 

759 

760 else: 

761 # Try to get the reference time from orbital frequency 

762 try: 

763 t_ref = get_ref_time_from_ref_freq(h5_file, f_ref) 

764 

765 # Check if interpolation is required 

766 interp, avail_ref_time = check_interp_req(h5_file, t_ref) 

767 except Exception as excep: 

768 print( 

769 f"Could not obtain reference time from given reference frequency {f_ref}.", 

770 excep, 

771 ) 

772 print("Choosing available reference time") 

773 interp = False 

774 else: 

775 interp, avail_ref_time = check_interp_req(h5_file, t_ref) 

776 

777 if interp is False: 

778 # Then load default values from the NR data 

779 # at hard coded reference time. 

780 

781 # Get the available reference time for book keeping 

782 t_ref = get_ref_time_from_metadata(sim_metadata) 

783 

784 # Check for LAL frame in simulation metadata. 

785 # RIT and GT qualify this. 

786 # Default attributes in case of no interpolation 

787 ref_check_def_h5, absent_attrs_h5 = check_nr_attrs(h5_file) 

788 

789 if ref_check_def_h5 is False: 

790 # Then the LAL source frame information is not present in the H5 file. 

791 # Then this could be SXS or GT data. The LAL source frame need to be computed from 

792 # the H5 File or simulation metadata. 

793 

794 # Check if LAL source frame info is present in the simulation metadata. 

795 ref_check_def_meta, absent_attrs_meta = check_nr_attrs(sim_metadata) 

796 

797 if ref_check_def_meta is False: 

798 # Then this is SXS data. 

799 

800 # Check for raw information in metadata to compute the LAL source frame. 

801 ref_check_def_meta_sxs, absent_attrs_meta_sxs = check_nr_attrs( 

802 sim_metadata, req_def_attrs_sxs 

803 ) 

804 

805 if ref_check_def_meta_sxs is True: 

806 # Compute the LAL source frame from simulation metadata 

807 ref_params = compute_lal_source_frame_from_sxs_metadata( 

808 sim_metadata 

809 ) 

810 else: 

811 raise Exception( 

812 "Insufficient information to compute the LAL source frame." 

813 f"\n Missing information is {absent_attrs_meta_sxs}." 

814 ) 

815 else: 

816 # LAL source frame is present in the simulation metadata 

817 ref_params = get_ref_vals(sim_metadata) 

818 else: 

819 ref_params = get_ref_vals(h5_file) 

820 

821 elif interp is True: 

822 # Experimental; This assumes all the required atributes needed 

823 # to compute the LAL source frame at the given reference time 

824 # are present in the H5file only. 

825 

826 # Attributes required for interpolation. 

827 req_ts_attrs = [ 

828 "LNhatx-vs-time", 

829 "LNhaty-vs-time", 

830 "LNhatz-vs-time", 

831 "position1x-vs-time", 

832 "position1y-vs-time", 

833 "position1z-vs-time", 

834 "position2x-vs-time", 

835 "position2y-vs-time", 

836 "position2z-vs-time", 

837 ] 

838 

839 # Check if time series data of required reference data is present 

840 ref_check_interp_req, absent_interp_attrs = check_nr_attrs( 

841 h5_file, req_ts_attrs 

842 ) 

843 

844 if ref_check_interp_req is False: 

845 raise KeyError( 

846 "Insufficient information to compute the LAL source frame at given reference time." 

847 f"Missing information is {absent_interp_attrs}." 

848 ) 

849 else: 

850 ref_params = compute_lal_source_frame_by_interp( 

851 h5_file, req_ts_attrs, t_ref 

852 ) 

853 

854 # Warning 1 

855 # Implement this Warning 

856 # XLAL_CHECK( ref_time!=XLAL_FAILURE, XLAL_FAILURE, "Error computing reference time. 

857 # Try setting fRef equal to the f_low given by the NR simulation or to a value <=0 to deactivate 

858 # fRef for a non-precessing simulation.\n") 

859 

860 # Get the LAL source frame vectors 

861 ln_hat_x = ref_params["LNhatx"] 

862 ln_hat_y = ref_params["LNhaty"] 

863 ln_hat_z = ref_params["LNhatz"] 

864 

865 n_hat_x = ref_params["nhatx"] 

866 n_hat_y = ref_params["nhaty"] 

867 n_hat_z = ref_params["nhatz"] 

868 

869 ln_hat = np.array([ln_hat_x, ln_hat_y, ln_hat_z]) 

870 n_hat = np.array([n_hat_x, n_hat_y, n_hat_z]) 

871 

872 # 2.3: Carryout vector math to get Zref in the lal wave frame 

873 corb_phase = np.cos(orb_phase) 

874 sorb_phase = np.sin(orb_phase) 

875 sinclination = np.sin(inclination) 

876 cinclination = np.cos(inclination) 

877 

878 ln_cross_n = np.cross(ln_hat, n_hat) 

879 ln_cross_n_x, ln_cross_n_y, ln_cross_n_z = ln_cross_n 

880 

881 z_wave_x = sinclination * (sorb_phase * n_hat_x + corb_phase * ln_cross_n_x) 

882 z_wave_y = sinclination * (sorb_phase * n_hat_y + corb_phase * ln_cross_n_y) 

883 z_wave_z = sinclination * (sorb_phase * n_hat_z + corb_phase * ln_cross_n_z) 

884 

885 z_wave_x += cinclination * ln_hat_x 

886 z_wave_y += cinclination * ln_hat_y 

887 z_wave_z += cinclination * ln_hat_z 

888 

889 z_wave = np.array([z_wave_x, z_wave_y, z_wave_z]) 

890 z_wave = z_wave / np.linalg.norm(z_wave) 

891 

892 ################################################################# 

893 # Step 3.1: Extract theta and psi from Z in the lal wave frame 

894 # NOTE: Theta can only run between 0 and pi, so no problem with arccos here 

895 theta = np.arccos(z_wave_z) 

896 

897 # Degenerate if Z_wave[2] == 1. In this case just choose psi randomly, 

898 # the choice will be cancelled out by alpha correction (I hope!) 

899 

900 # If theta is very close to the poles 

901 # return a random value 

902 if abs(z_wave_z - 1.0) < tol: 

903 psi = 0.5 

904 

905 else: 

906 # psi can run between 0 and 2pi, but only one solution works for x and y */ 

907 # Possible numerical issues if z_wave_x = sin(theta) */ 

908 if abs(z_wave_x / np.sin(theta)) > 1.0: 

909 if abs(z_wave_x / np.sin(theta)) < (1 + 10 * tol): 

910 # LAL tol retained. 

911 if (z_wave_x * np.sin(theta)) < 0.0: 

912 psi = np.pi 

913 

914 else: 

915 psi = 0.0 

916 

917 else: 

918 print(f"z_wave_x = {z_wave_x}") 

919 print(f"sin(theta) = {np.sin(theta)}") 

920 raise ValueError( 

921 "Z_x cannot be bigger than sin(theta). Please contact the developers." 

922 ) 

923 

924 else: 

925 psi = np.arccos(z_wave_x / np.sin(theta)) 

926 

927 y_val = np.sin(psi) * np.sin(theta) 

928 

929 # If z_wave[1] is negative, flip psi so that sin(psi) goes negative 

930 # while preserving cos(psi) */ 

931 if z_wave_y < 0.0: 

932 psi = 2 * np.pi - psi 

933 y_val = np.sin(psi) * np.sin(theta) 

934 

935 if abs(y_val - z_wave_y) > (5e3 * tol): 

936 # LAL tol retained. 

937 print(f"orb_phase = {orb_phase}") 

938 print( 

939 f"y_val = {y_val}, z_wave_y = {z_wave_y}, abs(y_val - z_wave_y) = {abs(y_val - z_wave_y)}" 

940 ) 

941 raise ValueError("Math consistency failure! Please contact the developers.") 

942 

943 # 3.2: Compute the vectors theta_hat and psi_hat 

944 # stheta = np.sin(theta) 

945 # ctheta = np.cos(theta) 

946 

947 spsi = np.sin(psi) 

948 cpsi = np.cos(psi) 

949 

950 # theta_hat_x = cpsi * ctheta 

951 # theta_hat_y = spsi * ctheta 

952 # theta_hat_z = -stheta 

953 # theta_hat = np.array([theta_hat_x, theta_hat_y, theta_hat_z]) 

954 

955 psi_hat_x = -spsi 

956 psi_hat_y = cpsi 

957 psi_hat_z = 0.0 

958 psi_hat = np.array([psi_hat_x, psi_hat_y, psi_hat_z]) 

959 

960 # Step 4: Compute sin(alpha) and cos(alpha) 

961 # Rotation angles on the tangent plane 

962 # due to spin weight. 

963 

964 # n_dot_theta = np.dot(n_hat, theta_hat) 

965 # ln_cross_n_dot_theta = np.dot(ln_cross_n, theta_hat) 

966 

967 n_dot_psi = np.dot(n_hat, psi_hat) 

968 ln_cross_n_dot_psi = np.dot(ln_cross_n, psi_hat) 

969 

970 # salpha = corb_phase * n_dot_theta - sorb_phase * ln_cross_n_dot_theta 

971 calpha = corb_phase * n_dot_psi - sorb_phase * ln_cross_n_dot_psi 

972 

973 if abs(calpha) > 1: 

974 calpha_err = abs(calpha) - 1 

975 if calpha_err < tol: 

976 # This tol could have been much smaller.  

977 # Just resuing the default for now. 

978 print( 

979 f"Correcting the polarization angle for finite precision error {calpha_err}" 

980 ) 

981 calpha = calpha / abs(calpha) 

982 else: 

983 raise ValueError( 

984 "Seems like something is wrong with the polarization angle. Please contact the developers!" 

985 ) 

986 

987 alpha = np.arccos(calpha) 

988 

989 angles = { 

990 "theta": theta, 

991 "psi": psi, 

992 "alpha": alpha, 

993 "t_ref": t_ref, 

994 "f_ref": f_ref, 

995 } 

996 

997 return angles