Coverage for pesummary/gw/plots/plot.py: 81.1%

681 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-02 08:42 +0000

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

2 

3from pesummary.utils.utils import ( 

4 logger, number_of_columns_for_legend, _check_latex_install, 

5) 

6from pesummary.utils.decorators import no_latex_plot 

7from pesummary.gw.plots.bounds import default_bounds 

8from pesummary.core.plots.figure import figure, subplots, ExistingFigure 

9from pesummary.core.plots.plot import _default_legend_kwargs 

10from pesummary import conf 

11 

12import os 

13import matplotlib.style 

14import numpy as np 

15import math 

16from scipy.ndimage import gaussian_filter 

17from astropy.time import Time 

18 

19_check_latex_install() 

20 

21from lal import MSUN_SI, PC_SI 

22 

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

24try: 

25 import lalsimulation as lalsim 

26 LALSIMULATION = True 

27except ImportError: 

28 LALSIMULATION = None 

29 

30 

31def _return_bounds(param, samples, comparison=False): 

32 """Return the bounds for a given param 

33 

34 Parameters 

35 ---------- 

36 param: str 

37 name of the parameter you wish to get bounds for 

38 samples: list/np.ndarray 

39 array or list of array of posterior samples for param 

40 comparison: Bool, optional 

41 True if samples is a list of array's of posterior samples 

42 """ 

43 xlow, xhigh = None, None 

44 if param in default_bounds.keys(): 

45 bounds = default_bounds[param] 

46 if "low" in bounds.keys(): 

47 xlow = bounds["low"] 

48 if "high" in bounds.keys(): 

49 if isinstance(bounds["high"], str) and "mass_1" in bounds["high"]: 

50 if comparison: 

51 xhigh = np.max([np.max(i) for i in samples]) 

52 else: 

53 xhigh = np.max(samples) 

54 else: 

55 xhigh = bounds["high"] 

56 return xlow, xhigh 

57 

58 

59def _add_default_bounds_to_kde_kwargs_dict( 

60 kde_kwargs, param, samples, comparison=False 

61): 

62 """Add default kde bounds to the a dictionary of kwargs 

63 

64 Parameters 

65 ---------- 

66 kde_kwargs: dict 

67 dictionary of kwargs to pass to the kde class 

68 param: str 

69 name of the parameter you wish to plot 

70 samples: list 

71 list of samples for param 

72 """ 

73 from pesummary.utils.bounded_1d_kde import bounded_1d_kde 

74 

75 xlow, xhigh = _return_bounds(param, samples, comparison=comparison) 

76 kde_kwargs["xlow"] = xlow 

77 kde_kwargs["xhigh"] = xhigh 

78 kde_kwargs["kde_kernel"] = bounded_1d_kde 

79 return kde_kwargs 

80 

81 

82def _1d_histogram_plot( 

83 param, samples, *args, kde_kwargs={}, bounded=True, **kwargs 

84): 

85 """Generate the 1d histogram plot for a given parameter for a given 

86 approximant. 

87 

88 Parameters 

89 ---------- 

90 *args: tuple 

91 all args passed directly to pesummary.core.plots.plot._1d_histogram_plot 

92 function 

93 kde_kwargs: dict, optional 

94 optional kwargs passed to the kde class 

95 bounded: Bool, optional 

96 if True, pass default 'xlow' and 'xhigh' arguments to the kde class 

97 **kwargs: dict, optional 

98 all additional kwargs passed to the 

99 pesummary.core.plots.plot._1d_histogram_plot function 

100 """ 

101 from pesummary.core.plots.plot import _1d_histogram_plot 

102 

103 if bounded: 

104 kde_kwargs = _add_default_bounds_to_kde_kwargs_dict( 

105 kde_kwargs, param, samples 

106 ) 

107 return _1d_histogram_plot( 

108 param, samples, *args, kde_kwargs=kde_kwargs, **kwargs 

109 ) 

110 

111 

112def _1d_histogram_plot_mcmc( 

113 param, samples, *args, kde_kwargs={}, bounded=True, **kwargs 

114): 

115 """Generate the 1d histogram plot for a given parameter for set of 

116 mcmc chains 

117 

118 Parameters 

119 ---------- 

120 *args: tuple 

121 all args passed directly to 

122 pesummary.core.plots.plot._1d_histogram_plot_mcmc function 

123 kde_kwargs: dict, optional 

124 optional kwargs passed to the kde class 

125 bounded: Bool, optional 

126 if True, pass default 'xlow' and 'xhigh' arguments to the kde class 

127 **kwargs: dict, optional 

128 all additional kwargs passed to the 

129 pesummary.core.plots.plot._1d_histogram_plot_mcmc function 

130 """ 

131 from pesummary.core.plots.plot import _1d_histogram_plot_mcmc 

132 

133 if bounded: 

134 kde_kwargs = _add_default_bounds_to_kde_kwargs_dict( 

135 kde_kwargs, param, samples, comparison=True 

136 ) 

137 return _1d_histogram_plot_mcmc( 

138 param, samples, *args, kde_kwargs=kde_kwargs, **kwargs 

139 ) 

140 

141 

142def _1d_histogram_plot_bootstrap( 

143 param, samples, *args, kde_kwargs={}, bounded=True, **kwargs 

144): 

145 """Generate a bootstrapped 1d histogram plot for a given parameter 

146 

147 Parameters 

148 ---------- 

149 param: str 

150 name of the parameter that you wish to plot 

151 samples: np.ndarray 

152 array of samples for param 

153 args: tuple 

154 all args passed to 

155 pesummary.core.plots.plot._1d_histogram_plot_bootstrap function 

156 kde_kwargs: dict, optional 

157 optional kwargs passed to the kde class 

158 bounded: Bool, optional 

159 if True, pass default 'xlow' and 'xhigh' arguments to the kde class 

160 **kwargs: dict, optional 

161 all additional kwargs passed to the 

162 pesummary.core.plots.plot._1d_histogram_plot_bootstrap function 

163 """ 

164 from pesummary.core.plots.plot import _1d_histogram_plot_bootstrap 

165 

166 if bounded: 

167 kde_kwargs = _add_default_bounds_to_kde_kwargs_dict( 

168 kde_kwargs, param, samples 

169 ) 

170 return _1d_histogram_plot_bootstrap( 

171 param, samples, *args, kde_kwargs=kde_kwargs, **kwargs 

172 ) 

173 

174 

175def _1d_comparison_histogram_plot( 

176 param, samples, *args, kde_kwargs={}, bounded=True, max_vline=2, 

177 legend_kwargs=_default_legend_kwargs, **kwargs 

178): 

179 """Generate the a plot to compare the 1d_histogram plots for a given 

180 parameter for different approximants. 

181 

182 Parameters 

183 ---------- 

184 *args: tuple 

185 all args passed directly to 

186 pesummary.core.plots.plot._1d_comparisonhistogram_plot function 

187 kde_kwargs: dict, optional 

188 optional kwargs passed to the kde class 

189 bounded: Bool, optional 

190 if True, pass default 'xlow' and 'xhigh' arguments to the kde class 

191 max_vline: int, optional 

192 if number of peaks < max_vline draw peaks as vertical lines rather 

193 than histogramming the data 

194 **kwargs: dict, optional 

195 all additional kwargs passed to the 

196 pesummary.core.plots.plot._1d_comparison_histogram_plot function 

197 """ 

198 from pesummary.core.plots.plot import _1d_comparison_histogram_plot 

199 

200 if bounded: 

201 kde_kwargs = _add_default_bounds_to_kde_kwargs_dict( 

202 kde_kwargs, param, samples, comparison=True 

203 ) 

204 return _1d_comparison_histogram_plot( 

205 param, samples, *args, kde_kwargs=kde_kwargs, max_vline=max_vline, 

206 legend_kwargs=legend_kwargs, **kwargs 

207 ) 

208 

209 

210def _make_corner_plot(samples, latex_labels, corner_parameters=None, **kwargs): 

211 """Generate the corner plots for a given approximant 

212 

213 Parameters 

214 ---------- 

215 opts: argparse 

216 argument parser object to hold all information from the command line 

217 samples: nd list 

218 nd list of samples for each parameter for a given approximant 

219 params: list 

220 list of parameters associated with each element in samples 

221 approximant: str 

222 name of approximant that was used to generate the samples 

223 latex_labels: dict 

224 dictionary of latex labels for each parameter 

225 """ 

226 from pesummary.core.plots.plot import _make_corner_plot 

227 

228 if corner_parameters is None: 

229 corner_parameters = conf.gw_corner_parameters 

230 

231 return _make_corner_plot( 

232 samples, latex_labels, corner_parameters=corner_parameters, **kwargs 

233 ) 

234 

235 

236def _make_source_corner_plot(samples, latex_labels, **kwargs): 

237 """Generate the corner plots for a given approximant 

238 

239 Parameters 

240 ---------- 

241 opts: argparse 

242 argument parser object to hold all information from the command line 

243 samples: nd list 

244 nd list of samples for each parameter for a given approximant 

245 params: list 

246 list of parameters associated with each element in samples 

247 approximant: str 

248 name of approximant that was used to generate the samples 

249 latex_labels: dict 

250 dictionary of latex labels for each parameter 

251 """ 

252 from pesummary.core.plots.plot import _make_corner_plot 

253 

254 return _make_corner_plot( 

255 samples, latex_labels, 

256 corner_parameters=conf.gw_source_frame_corner_parameters, **kwargs 

257 )[0] 

258 

259 

260def _make_extrinsic_corner_plot(samples, latex_labels, **kwargs): 

261 """Generate the corner plots for a given approximant 

262 

263 Parameters 

264 ---------- 

265 opts: argparse 

266 argument parser object to hold all information from the command line 

267 samples: nd list 

268 nd list of samples for each parameter for a given approximant 

269 params: list 

270 list of parameters associated with each element in samples 

271 approximant: str 

272 name of approximant that was used to generate the samples 

273 latex_labels: dict 

274 dictionary of latex labels for each parameter 

275 """ 

276 from pesummary.core.plots.plot import _make_corner_plot 

277 

278 return _make_corner_plot( 

279 samples, latex_labels, 

280 corner_parameters=conf.gw_extrinsic_corner_parameters, **kwargs 

281 )[0] 

282 

283 

284def _make_comparison_corner_plot( 

285 samples, latex_labels, corner_parameters=None, colors=conf.corner_colors, 

286 **kwargs 

287): 

288 """Generate a corner plot which contains multiple datasets 

289 

290 Parameters 

291 ---------- 

292 samples: dict 

293 nested dictionary containing the label as key and SamplesDict as item 

294 for each dataset you wish to plot 

295 latex_labels: dict 

296 dictionary of latex labels for each parameter 

297 corner_parameters: list, optional 

298 corner parameters you wish to include in the plot 

299 colors: list, optional 

300 unique colors for each dataset 

301 **kwargs: dict 

302 all kwargs are passed to `corner.corner` 

303 """ 

304 from pesummary.core.plots.plot import _make_comparison_corner_plot 

305 

306 if corner_parameters is None: 

307 corner_parameters = conf.gw_corner_parameters 

308 

309 return _make_comparison_corner_plot( 

310 samples, latex_labels, corner_parameters=corner_parameters, 

311 colors=colors, **kwargs 

312 ) 

313 

314 

315def __antenna_response(name, ra, dec, psi, time_gps): 

316 """Calculate the antenna response function 

317 

318 Parameters 

319 ---------- 

320 name: str 

321 name of the detector you wish to calculate the antenna response 

322 function for 

323 ra: float 

324 right ascension of the source 

325 dec: float 

326 declination of the source 

327 psi: float 

328 polarisation of the source 

329 time_gps: float 

330 gps time of merger 

331 """ 

332 # Following 8 lines taken from pycbc.detector.Detector 

333 from astropy.units.si import sday 

334 reference_time = 1126259462.0 

335 gmst_reference = Time( 

336 reference_time, format='gps', scale='utc', location=(0, 0) 

337 ).sidereal_time('mean').rad 

338 dphase = (time_gps - reference_time) / float(sday.si.scale) * (2.0 * np.pi) 

339 gmst = (gmst_reference + dphase) % (2.0 * np.pi) 

340 corrected_ra = gmst - ra 

341 if not LALSIMULATION: 

342 raise Exception("lalsimulation could not be imported. please install " 

343 "lalsuite to be able to use all features") 

344 detector = lalsim.DetectorPrefixToLALDetector(str(name)) 

345 

346 x0 = -np.cos(psi) * np.sin(corrected_ra) - \ 

347 np.sin(psi) * np.cos(corrected_ra) * np.sin(dec) 

348 x1 = -np.cos(psi) * np.cos(corrected_ra) + \ 

349 np.sin(psi) * np.sin(corrected_ra) * np.sin(dec) 

350 x2 = np.sin(psi) * np.cos(dec) 

351 x = np.array([x0, x1, x2]) 

352 dx = detector.response.dot(x) 

353 

354 y0 = np.sin(psi) * np.sin(corrected_ra) - \ 

355 np.cos(psi) * np.cos(corrected_ra) * np.sin(dec) 

356 y1 = np.sin(psi) * np.cos(corrected_ra) + \ 

357 np.cos(psi) * np.sin(corrected_ra) * np.sin(dec) 

358 y2 = np.cos(psi) * np.cos(dec) 

359 y = np.array([y0, y1, y2]) 

360 dy = detector.response.dot(y) 

361 

362 if hasattr(dx, "shape"): 

363 fplus = (x * dx - y * dy).sum(axis=0) 

364 fcross = (x * dy + y * dx).sum(axis=0) 

365 else: 

366 fplus = (x * dx - y * dy).sum() 

367 fcross = (x * dy + y * dx).sum() 

368 

369 return fplus, fcross 

370 

371 

372@no_latex_plot 

373def _waveform_plot(detectors, maxL_params, **kwargs): 

374 """Plot the maximum likelihood waveform for a given approximant. 

375 

376 Parameters 

377 ---------- 

378 detectors: list 

379 list of detectors that you want to generate waveforms for 

380 maxL_params: dict 

381 dictionary of maximum likelihood parameter values 

382 kwargs: dict 

383 dictionary of optional keyword arguments 

384 """ 

385 from gwpy.plot.colors import GW_OBSERVATORY_COLORS 

386 if math.isnan(maxL_params["mass_1"]): 

387 return 

388 logger.debug("Generating the maximum likelihood waveform plot") 

389 if not LALSIMULATION: 

390 raise Exception("lalsimulation could not be imported. please install " 

391 "lalsuite to be able to use all features") 

392 delta_frequency = kwargs.get("delta_f", 1. / 256) 

393 minimum_frequency = kwargs.get("f_min", 5.) 

394 maximum_frequency = kwargs.get("f_max", 1000.) 

395 frequency_array = np.arange(minimum_frequency, maximum_frequency, 

396 delta_frequency) 

397 

398 approx = lalsim.GetApproximantFromString(maxL_params["approximant"]) 

399 mass_1 = maxL_params["mass_1"] * MSUN_SI 

400 mass_2 = maxL_params["mass_2"] * MSUN_SI 

401 luminosity_distance = maxL_params["luminosity_distance"] * PC_SI * 10**6 

402 if "phi_jl" in maxL_params.keys(): 

403 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

404 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

405 maxL_params["theta_jn"], maxL_params["phi_jl"], maxL_params["tilt_1"], 

406 maxL_params["tilt_2"], maxL_params["phi_12"], maxL_params["a_1"], 

407 maxL_params["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

408 maxL_params["phase"]) 

409 else: 

410 iota, S1x, S1y, S1z, S2x, S2y, S2z = maxL_params["iota"], 0., 0., 0., \ 

411 0., 0., 0. 

412 phase = maxL_params["phase"] if "phase" in maxL_params.keys() else 0.0 

413 for func in [lalsim.SimInspiralChooseFDWaveform, lalsim.SimInspiralFD]: 

414 try: 

415 h_plus, h_cross = func( 

416 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, iota, 

417 phase, 0.0, 0.0, 0.0, delta_frequency, minimum_frequency, 

418 maximum_frequency, kwargs.get("f_ref", 10.), None, approx) 

419 except Exception: 

420 continue 

421 break 

422 h_plus = h_plus.data.data 

423 h_cross = h_cross.data.data 

424 h_plus = h_plus[:len(frequency_array)] 

425 h_cross = h_cross[:len(frequency_array)] 

426 fig, ax = figure(gca=True) 

427 colors = [GW_OBSERVATORY_COLORS[i] for i in detectors] 

428 for num, i in enumerate(detectors): 

429 ar = __antenna_response(i, maxL_params["ra"], maxL_params["dec"], 

430 maxL_params["psi"], maxL_params["geocent_time"]) 

431 ax.plot(frequency_array, abs(h_plus * ar[0] + h_cross * ar[1]), 

432 color=colors[num], linewidth=1.0, label=i) 

433 ax.set_xscale("log") 

434 ax.set_yscale("log") 

435 ax.set_xlabel(r"Frequency $[Hz]$") 

436 ax.set_ylabel(r"Strain") 

437 ax.grid(visible=True) 

438 ax.legend(loc="best") 

439 fig.tight_layout() 

440 return fig 

441 

442 

443@no_latex_plot 

444def _waveform_comparison_plot(maxL_params_list, colors, labels, 

445 **kwargs): 

446 """Generate a plot which compares the maximum likelihood waveforms for 

447 each approximant. 

448 

449 Parameters 

450 ---------- 

451 maxL_params_list: list 

452 list of dictionaries containing the maximum likelihood parameter 

453 values for each approximant 

454 colors: list 

455 list of colors to be used to differentiate the different approximants 

456 approximant_labels: list, optional 

457 label to prepend the approximant in the legend 

458 kwargs: dict 

459 dictionary of optional keyword arguments 

460 """ 

461 logger.debug("Generating the maximum likelihood waveform comparison plot " 

462 "for H1") 

463 if not LALSIMULATION: 

464 raise Exception("LALSimulation could not be imported. Please install " 

465 "LALSuite to be able to use all features") 

466 delta_frequency = kwargs.get("delta_f", 1. / 256) 

467 minimum_frequency = kwargs.get("f_min", 5.) 

468 maximum_frequency = kwargs.get("f_max", 1000.) 

469 frequency_array = np.arange(minimum_frequency, maximum_frequency, 

470 delta_frequency) 

471 

472 fig, ax = figure(gca=True) 

473 for num, i in enumerate(maxL_params_list): 

474 if math.isnan(i["mass_1"]): 

475 continue 

476 approx = lalsim.GetApproximantFromString(i["approximant"]) 

477 mass_1 = i["mass_1"] * MSUN_SI 

478 mass_2 = i["mass_2"] * MSUN_SI 

479 luminosity_distance = i["luminosity_distance"] * PC_SI * 10**6 

480 if "phi_jl" in i.keys(): 

481 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

482 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

483 i["theta_jn"], i["phi_jl"], i["tilt_1"], 

484 i["tilt_2"], i["phi_12"], i["a_1"], 

485 i["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

486 i["phase"]) 

487 else: 

488 iota, S1x, S1y, S1z, S2x, S2y, S2z = i["iota"], 0., 0., 0., \ 

489 0., 0., 0. 

490 phase = i["phase"] if "phase" in i.keys() else 0.0 

491 for func in [lalsim.SimInspiralChooseFDWaveform, lalsim.SimInspiralFD]: 

492 try: 

493 h_plus, h_cross = func( 

494 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, 

495 iota, phase, 0.0, 0.0, 0.0, delta_frequency, minimum_frequency, 

496 maximum_frequency, kwargs.get("f_ref", 10.), None, approx) 

497 except Exception: 

498 continue 

499 break 

500 h_plus = h_plus.data.data 

501 h_cross = h_cross.data.data 

502 h_plus = h_plus[:len(frequency_array)] 

503 h_cross = h_cross[:len(frequency_array)] 

504 ar = __antenna_response("H1", i["ra"], i["dec"], i["psi"], 

505 i["geocent_time"]) 

506 ax.plot(frequency_array, abs(h_plus * ar[0] + h_cross * ar[1]), 

507 color=colors[num], label=labels[num], linewidth=2.0) 

508 ax.set_xscale("log") 

509 ax.set_yscale("log") 

510 ax.grid(visible=True) 

511 ax.legend(loc="best") 

512 ax.set_xlabel(r"Frequency $[Hz]$") 

513 ax.set_ylabel(r"Strain") 

514 fig.tight_layout() 

515 return fig 

516 

517 

518def _ligo_skymap_plot(ra, dec, dist=None, savedir="./", nprocess=1, 

519 downsampled=False, label="pesummary", time=None, 

520 distance_map=True, multi_resolution=True, 

521 injection=None, **kwargs): 

522 """Plot the sky location of the source for a given approximant using the 

523 ligo.skymap package 

524 

525 Parameters 

526 ---------- 

527 ra: list 

528 list of samples for right ascension 

529 dec: list 

530 list of samples for declination 

531 dist: list 

532 list of samples for the luminosity distance 

533 savedir: str 

534 path to the directory where you would like to save the output files 

535 nprocess: Bool 

536 Boolean for whether to use multithreading or not 

537 downsampled: Bool 

538 Boolean for whether the samples have been downsampled or not 

539 distance_map: Bool 

540 Boolean for whether or not to produce a distance map 

541 multi_resolution: Bool 

542 Boolean for whether or not to generate a multiresolution HEALPix map 

543 injection: list, optional 

544 List containing RA and DEC of the injection. Both must be in radians 

545 kwargs: dict 

546 optional keyword arguments 

547 """ 

548 from ligo.skymap.bayestar import rasterize 

549 from ligo.skymap import io 

550 from ligo.skymap.kde import Clustered2DSkyKDE, Clustered2Plus1DSkyKDE 

551 

552 if dist is not None and distance_map: 

553 pts = np.column_stack((ra, dec, dist)) 

554 cls = Clustered2Plus1DSkyKDE 

555 else: 

556 pts = np.column_stack((ra, dec)) 

557 cls = Clustered2DSkyKDE 

558 skypost = cls(pts, trials=5, jobs=nprocess) 

559 hpmap = skypost.as_healpix() 

560 if not multi_resolution: 

561 hpmap = rasterize(hpmap) 

562 hpmap.meta['creator'] = "pesummary" 

563 hpmap.meta['origin'] = 'LIGO/Virgo' 

564 hpmap.meta['gps_creation_time'] = Time.now().gps 

565 if dist is not None: 

566 hpmap.meta["distmean"] = float(np.mean(dist)) 

567 hpmap.meta["diststd"] = float(np.std(dist)) 

568 if time is not None: 

569 if isinstance(time, (float, int)): 

570 _time = time 

571 else: 

572 _time = np.mean(time) 

573 hpmap.meta["gps_time"] = _time 

574 

575 io.write_sky_map( 

576 os.path.join(savedir, "%s_skymap.fits" % (label)), hpmap, nest=True 

577 ) 

578 skymap, metadata = io.fits.read_sky_map( 

579 os.path.join(savedir, "%s_skymap.fits" % (label)), nest=None 

580 ) 

581 return _ligo_skymap_plot_from_array( 

582 skymap, nsamples=len(ra), downsampled=downsampled, injection=injection 

583 )[0] 

584 

585 

586def _ligo_skymap_plot_from_array( 

587 skymap, nsamples=None, downsampled=False, contour=[50, 90], 

588 annotate=True, ax=None, colors="k", injection=None 

589): 

590 """Generate a skymap with `ligo.skymap` based on an array of probabilities 

591 

592 Parameters 

593 ---------- 

594 skymap: np.array 

595 array of probabilities 

596 nsamples: int, optional 

597 number of samples used 

598 downsampled: Bool, optional 

599 If True, add a header to the skymap saying that this plot is downsampled 

600 contour: list, optional 

601 list of contours to be plotted on the skymap. Default 50, 90 

602 annotate: Bool, optional 

603 If True, annotate the figure by adding the 90% and 50% sky areas 

604 by default 

605 ax: matplotlib.axes._subplots.AxesSubplot, optional 

606 Existing axis to add the plot to 

607 colors: str/list 

608 colors to use for the contours 

609 injection: list, optional 

610 List containing RA and DEC of the injection. Both must be in radians 

611 """ 

612 import healpy as hp 

613 from ligo.skymap import plot 

614 

615 if ax is None: 

616 fig = figure(gca=False) 

617 ax = fig.add_subplot(111, projection='astro hours mollweide') 

618 ax.grid(visible=True) 

619 

620 nside = hp.npix2nside(len(skymap)) 

621 deg2perpix = hp.nside2pixarea(nside, degrees=True) 

622 probperdeg2 = skymap / deg2perpix 

623 

624 if downsampled: 

625 ax.set_title("Downsampled to %s" % (nsamples), fontdict={'fontsize': 11}) 

626 

627 vmax = probperdeg2.max() 

628 ax.imshow_hpx((probperdeg2, 'ICRS'), nested=True, vmin=0., 

629 vmax=vmax, cmap="cylon") 

630 cls, cs = _ligo_skymap_contours(ax, skymap, contour=contour, colors=colors) 

631 if annotate: 

632 text = [] 

633 pp = np.round(contour).astype(int) 

634 ii = np.round( 

635 np.searchsorted(np.sort(cls), contour) * deg2perpix).astype(int) 

636 for i, p in zip(ii, pp): 

637 text.append(u'{:d}% area: {:d} deg²'.format(p, i, grouping=True)) 

638 ax.text(1, 1.05, '\n'.join(text), transform=ax.transAxes, ha='right', 

639 fontsize=10) 

640 plot.outline_text(ax) 

641 if injection is not None and len(injection) == 2: 

642 from astropy.coordinates import SkyCoord 

643 from astropy import units as u 

644 

645 _inj = SkyCoord(*injection, unit=u.rad) 

646 ax.scatter( 

647 _inj.ra.value, _inj.dec.value, marker="*", color="orange", 

648 edgecolors='k', linewidth=1.75, s=100, zorder=100, 

649 transform=ax.get_transform('world') 

650 ) 

651 return ExistingFigure(fig), ax 

652 

653 

654def _ligo_skymap_comparion_plot_from_array( 

655 skymaps, colors, labels, contour=[50, 90], show_probability_map=False, 

656 injection=None 

657): 

658 """Generate a skymap with `ligo.skymap` based which compares arrays of 

659 probabilities 

660 

661 Parameters 

662 ---------- 

663 skymaps: list 

664 list of skymap probabilities 

665 colors: list 

666 list of colors to use for each skymap 

667 labels: list 

668 list of labels associated with each skymap 

669 contour: list, optional 

670 contours you wish to display on the comparison plot 

671 show_probability_map: int, optional 

672 the index of the skymap you wish to show the probability 

673 map for. Default False 

674 injection: list, optional 

675 List containing RA and DEC of the injection. Both must be in radians 

676 """ 

677 ncols = number_of_columns_for_legend(labels) 

678 fig = figure(gca=False) 

679 ax = fig.add_subplot(111, projection='astro hours mollweide') 

680 ax.grid(visible=True) 

681 for num, skymap in enumerate(skymaps): 

682 if isinstance(show_probability_map, int) and show_probability_map == num: 

683 _, ax = _ligo_skymap_plot_from_array( 

684 skymap, nsamples=None, downsampled=False, contour=contour, 

685 annotate=False, ax=ax, colors=colors[num], injection=injection, 

686 ) 

687 cls, cs = _ligo_skymap_contours( 

688 ax, skymap, contour=contour, colors=colors[num] 

689 ) 

690 cs.collections[0].set_label(labels[num]) 

691 ax.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, borderaxespad=0., 

692 mode="expand", ncol=ncols) 

693 return fig 

694 

695 

696def _ligo_skymap_contours(ax, skymap, contour=[50, 90], colors='k'): 

697 """Plot contours on a ligo.skymap skymap 

698 

699 Parameters 

700 ---------- 

701 ax: matplotlib.axes._subplots.AxesSubplot, optional 

702 Existing axis to add the plot to 

703 skymap: np.array 

704 array of probabilities 

705 contour: list 

706 list contours you wish to plot 

707 colors: str/list 

708 colors to use for the contours 

709 """ 

710 from ligo.skymap import postprocess 

711 

712 cls = 100 * postprocess.find_greedy_credible_levels(skymap) 

713 cs = ax.contour_hpx((cls, 'ICRS'), nested=True, colors=colors, 

714 linewidths=0.5, levels=contour) 

715 ax.clabel(cs, fmt=r'%g\%%', fontsize=6, inline=True) 

716 return cls, cs 

717 

718 

719def _default_skymap_plot(ra, dec, weights=None, injection=None, **kwargs): 

720 """Plot the default sky location of the source for a given approximant 

721 

722 Parameters 

723 ---------- 

724 ra: list 

725 list of samples for right ascension 

726 dec: list 

727 list of samples for declination 

728 injection: list, optional 

729 list containing the injected value of ra and dec 

730 kwargs: dict 

731 optional keyword arguments 

732 """ 

733 from .cmap import register_cylon, unregister_cylon 

734 # register the cylon cmap 

735 register_cylon() 

736 ra = [-i + np.pi for i in ra] 

737 logger.debug("Generating the sky map plot") 

738 fig, ax = figure(gca=True) 

739 ax = fig.add_subplot( 

740 111, projection="mollweide", 

741 facecolor=(1.0, 0.939165516411, 0.880255669068) 

742 ) 

743 ax.cla() 

744 ax.set_title("Preliminary", fontdict={'fontsize': 11}) 

745 ax.grid(visible=True) 

746 ax.set_xticklabels([ 

747 r"$2^{h}$", r"$4^{h}$", r"$6^{h}$", r"$8^{h}$", r"$10^{h}$", 

748 r"$12^{h}$", r"$14^{h}$", r"$16^{h}$", r"$18^{h}$", r"$20^{h}$", 

749 r"$22^{h}$"]) 

750 levels = [0.9, 0.5] 

751 

752 if weights is None: 

753 H, X, Y = np.histogram2d(ra, dec, bins=50) 

754 else: 

755 H, X, Y = np.histogram2d(ra, dec, bins=50, weights=weights) 

756 H = gaussian_filter(H, kwargs.get("smooth", 0.9)) 

757 Hflat = H.flatten() 

758 indicies = np.argsort(Hflat)[::-1] 

759 Hflat = Hflat[indicies] 

760 

761 CF = np.cumsum(Hflat) 

762 CF /= CF[-1] 

763 

764 V = np.empty(len(levels)) 

765 for num, i in enumerate(levels): 

766 try: 

767 V[num] = Hflat[CF <= i][-1] 

768 except Exception: 

769 V[num] = Hflat[0] 

770 V.sort() 

771 m = np.diff(V) == 0 

772 while np.any(m): 

773 V[np.where(m)[0][0]] *= 1.0 - 1e-4 

774 m = np.diff(V) == 0 

775 V.sort() 

776 X1, Y1 = 0.5 * (X[1:] + X[:-1]), 0.5 * (Y[1:] + Y[:-1]) 

777 

778 H2 = H.min() + np.zeros((H.shape[0] + 4, H.shape[1] + 4)) 

779 H2[2:-2, 2:-2] = H 

780 H2[2:-2, 1] = H[:, 0] 

781 H2[2:-2, -2] = H[:, -1] 

782 H2[1, 2:-2] = H[0] 

783 H2[-2, 2:-2] = H[-1] 

784 H2[1, 1] = H[0, 0] 

785 H2[1, -2] = H[0, -1] 

786 H2[-2, 1] = H[-1, 0] 

787 H2[-2, -2] = H[-1, -1] 

788 X2 = np.concatenate([X1[0] + np.array([-2, -1]) * np.diff(X1[:2]), X1, 

789 X1[-1] + np.array([1, 2]) * np.diff(X1[-2:]), ]) 

790 Y2 = np.concatenate([Y1[0] + np.array([-2, -1]) * np.diff(Y1[:2]), Y1, 

791 Y1[-1] + np.array([1, 2]) * np.diff(Y1[-2:]), ]) 

792 

793 ax.pcolormesh(X2, Y2, H2.T, vmin=0., vmax=H2.T.max(), cmap="cylon") 

794 cs = ax.contour(X2, Y2, H2.T, V, colors="k", linewidths=0.5) 

795 if injection is not None: 

796 ax.scatter( 

797 -injection[0] + np.pi, injection[1], marker="*", 

798 color=conf.injection_color, edgecolors='k', linewidth=1.75, s=100 

799 ) 

800 fmt = {l: s for l, s in zip(cs.levels, [r"$90\%$", r"$50\%$"])} 

801 ax.clabel(cs, fmt=fmt, fontsize=8, inline=True) 

802 text = [] 

803 for i, j in zip(cs.collections, [90, 50]): 

804 area = 0. 

805 for k in i.get_paths(): 

806 x = k.vertices[:, 0] 

807 y = k.vertices[:, 1] 

808 area += 0.5 * np.sum(y[:-1] * np.diff(x) - x[:-1] * np.diff(y)) 

809 area = int(np.abs(area) * (180 / np.pi) * (180 / np.pi)) 

810 text.append(u'{:d}% area: {:d} deg²'.format( 

811 int(j), area, grouping=True)) 

812 ax.text(1, 1.05, '\n'.join(text[::-1]), transform=ax.transAxes, ha='right', 

813 fontsize=10) 

814 xticks = np.arange(-np.pi, np.pi + np.pi / 6, np.pi / 4) 

815 ax.set_xticks(xticks) 

816 ax.set_yticks([-np.pi / 3, -np.pi / 6, 0, np.pi / 6, np.pi / 3]) 

817 labels = [r"$%s^{h}$" % (int(np.round((i + np.pi) * 3.82, 1))) for i in xticks] 

818 ax.set_xticklabels(labels[::-1], fontsize=10) 

819 ax.set_yticklabels([r"$-60^{\circ}$", r"$-30^{\circ}$", r"$0^{\circ}$", 

820 r"$30^{\circ}$", r"$60^{\circ}$"], fontsize=10) 

821 ax.grid(visible=True) 

822 # unregister the cylon cmap 

823 unregister_cylon() 

824 return fig 

825 

826 

827def _sky_map_comparison_plot(ra_list, dec_list, labels, colors, **kwargs): 

828 """Generate a plot that compares the sky location for multiple approximants 

829 

830 Parameters 

831 ---------- 

832 ra_list: 2d list 

833 list of samples for right ascension for each approximant 

834 dec_list: 2d list 

835 list of samples for declination for each approximant 

836 approximants: list 

837 list of approximants used to generate the samples 

838 colors: list 

839 list of colors to be used to differentiate the different approximants 

840 approximant_labels: list, optional 

841 label to prepend the approximant in the legend 

842 kwargs: dict 

843 optional keyword arguments 

844 """ 

845 ra_list = [[-i + np.pi for i in j] for j in ra_list] 

846 logger.debug("Generating the sky map comparison plot") 

847 fig = figure(gca=False) 

848 ax = fig.add_subplot( 

849 111, projection="mollweide", 

850 facecolor=(1.0, 0.939165516411, 0.880255669068) 

851 ) 

852 ax.cla() 

853 ax.grid(visible=True) 

854 ax.set_xticklabels([ 

855 r"$2^{h}$", r"$4^{h}$", r"$6^{h}$", r"$8^{h}$", r"$10^{h}$", 

856 r"$12^{h}$", r"$14^{h}$", r"$16^{h}$", r"$18^{h}$", r"$20^{h}$", 

857 r"$22^{h}$"]) 

858 levels = [0.9, 0.5] 

859 for num, i in enumerate(ra_list): 

860 H, X, Y = np.histogram2d(i, dec_list[num], bins=50) 

861 H = gaussian_filter(H, kwargs.get("smooth", 0.9)) 

862 Hflat = H.flatten() 

863 indicies = np.argsort(Hflat)[::-1] 

864 Hflat = Hflat[indicies] 

865 

866 CF = np.cumsum(Hflat) 

867 CF /= CF[-1] 

868 

869 V = np.empty(len(levels)) 

870 for num2, j in enumerate(levels): 

871 try: 

872 V[num2] = Hflat[CF <= j][-1] 

873 except Exception: 

874 V[num2] = Hflat[0] 

875 V.sort() 

876 m = np.diff(V) == 0 

877 while np.any(m): 

878 V[np.where(m)[0][0]] *= 1.0 - 1e-4 

879 m = np.diff(V) == 0 

880 V.sort() 

881 X1, Y1 = 0.5 * (X[1:] + X[:-1]), 0.5 * (Y[1:] + Y[:-1]) 

882 

883 H2 = H.min() + np.zeros((H.shape[0] + 4, H.shape[1] + 4)) 

884 H2[2:-2, 2:-2] = H 

885 H2[2:-2, 1] = H[:, 0] 

886 H2[2:-2, -2] = H[:, -1] 

887 H2[1, 2:-2] = H[0] 

888 H2[-2, 2:-2] = H[-1] 

889 H2[1, 1] = H[0, 0] 

890 H2[1, -2] = H[0, -1] 

891 H2[-2, 1] = H[-1, 0] 

892 H2[-2, -2] = H[-1, -1] 

893 X2 = np.concatenate([X1[0] + np.array([-2, -1]) * np.diff(X1[:2]), X1, 

894 X1[-1] + np.array([1, 2]) * np.diff(X1[-2:]), ]) 

895 Y2 = np.concatenate([Y1[0] + np.array([-2, -1]) * np.diff(Y1[:2]), Y1, 

896 Y1[-1] + np.array([1, 2]) * np.diff(Y1[-2:]), ]) 

897 CS = ax.contour(X2, Y2, H2.T, V, colors=colors[num], linewidths=2.0) 

898 CS.collections[0].set_label(labels[num]) 

899 ncols = number_of_columns_for_legend(labels) 

900 ax.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, borderaxespad=0., 

901 mode="expand", ncol=ncols) 

902 xticks = np.arange(-np.pi, np.pi + np.pi / 6, np.pi / 4) 

903 ax.set_xticks(xticks) 

904 ax.set_yticks([-np.pi / 3, -np.pi / 6, 0, np.pi / 6, np.pi / 3]) 

905 labels = [r"$%s^{h}$" % (int(np.round((i + np.pi) * 3.82, 1))) for i in xticks] 

906 ax.set_xticklabels(labels[::-1], fontsize=10) 

907 ax.set_yticklabels([r"$-60^\degree$", r"$-30^\degree$", r"$0^\degree$", 

908 r"$30^\degree$", r"$60^\degree$"], fontsize=10) 

909 ax.grid(visible=True) 

910 return fig 

911 

912 

913def __get_cutoff_indices(flow, fhigh, df, N): 

914 """ 

915 Gets the indices of a frequency series at which to stop an overlap 

916 calculation. 

917 

918 Parameters 

919 ---------- 

920 flow: float 

921 The frequency (in Hz) of the lower index. 

922 fhigh: float 

923 The frequency (in Hz) of the upper index. 

924 df: float 

925 The frequency step (in Hz) of the frequency series. 

926 N: int 

927 The number of points in the **time** series. Can be odd 

928 or even. 

929 

930 Returns 

931 ------- 

932 kmin: int 

933 kmax: int 

934 """ 

935 if flow: 

936 kmin = int(flow / df) 

937 else: 

938 kmin = 1 

939 if fhigh: 

940 kmax = int(fhigh / df) 

941 else: 

942 kmax = int((N + 1) / 2.) 

943 return kmin, kmax 

944 

945 

946@no_latex_plot 

947def _sky_sensitivity(network, resolution, maxL_params, **kwargs): 

948 """Generate the sky sensitivity for a given network 

949 

950 Parameters 

951 ---------- 

952 network: list 

953 list of detectors you want included in your sky sensitivity plot 

954 resolution: float 

955 resolution of the skymap 

956 maxL_params: dict 

957 dictionary of waveform parameters for the maximum likelihood waveform 

958 """ 

959 logger.debug("Generating the sky sensitivity for %s" % (network)) 

960 if not LALSIMULATION: 

961 raise Exception("LALSimulation could not be imported. Please install " 

962 "LALSuite to be able to use all features") 

963 delta_frequency = kwargs.get("delta_f", 1. / 256) 

964 minimum_frequency = kwargs.get("f_min", 20.) 

965 maximum_frequency = kwargs.get("f_max", 1000.) 

966 frequency_array = np.arange(minimum_frequency, maximum_frequency, 

967 delta_frequency) 

968 

969 approx = lalsim.GetApproximantFromString(maxL_params["approximant"]) 

970 mass_1 = maxL_params["mass_1"] * MSUN_SI 

971 mass_2 = maxL_params["mass_2"] * MSUN_SI 

972 luminosity_distance = maxL_params["luminosity_distance"] * PC_SI * 10**6 

973 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

974 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

975 maxL_params["iota"], maxL_params["phi_jl"], maxL_params["tilt_1"], 

976 maxL_params["tilt_2"], maxL_params["phi_12"], maxL_params["a_1"], 

977 maxL_params["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

978 maxL_params["phase"]) 

979 h_plus, h_cross = lalsim.SimInspiralChooseFDWaveform( 

980 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, iota, 

981 maxL_params["phase"], 0.0, 0.0, 0.0, delta_frequency, minimum_frequency, 

982 maximum_frequency, kwargs.get("f_ref", 10.), None, approx) 

983 h_plus = h_plus.data.data 

984 h_cross = h_cross.data.data 

985 h_plus = h_plus[:len(frequency_array)] 

986 h_cross = h_cross[:len(frequency_array)] 

987 psd = {} 

988 psd["H1"] = psd["L1"] = np.array([ 

989 lalsim.SimNoisePSDaLIGOZeroDetHighPower(i) for i in frequency_array]) 

990 psd["V1"] = np.array([lalsim.SimNoisePSDVirgo(i) for i in frequency_array]) 

991 kmin, kmax = __get_cutoff_indices(minimum_frequency, maximum_frequency, 

992 delta_frequency, (len(h_plus) - 1) * 2) 

993 ra = np.arange(-np.pi, np.pi, resolution) 

994 dec = np.arange(-np.pi, np.pi, resolution) 

995 X, Y = np.meshgrid(ra, dec) 

996 N = np.zeros([len(dec), len(ra)]) 

997 

998 indices = np.ndindex(len(ra), len(dec)) 

999 for ind in indices: 

1000 ar = {} 

1001 SNR = {} 

1002 for i in network: 

1003 ard = __antenna_response(i, ra[ind[0]], dec[ind[1]], 

1004 maxL_params["psi"], maxL_params["geocent_time"]) 

1005 ar[i] = [ard[0], ard[1]] 

1006 strain = np.array(h_plus * ar[i][0] + h_cross * ar[i][1]) 

1007 integrand = np.conj(strain[kmin:kmax]) * strain[kmin:kmax] / psd[i][kmin:kmax] 

1008 integrand = integrand[:-1] 

1009 SNR[i] = np.sqrt(4 * delta_frequency * np.sum(integrand).real) 

1010 ar[i][0] *= SNR[i] 

1011 ar[i][1] *= SNR[i] 

1012 numerator = 0.0 

1013 denominator = 0.0 

1014 for i in network: 

1015 numerator += sum(i**2 for i in ar[i]) 

1016 denominator += SNR[i]**2 

1017 N[ind[1]][ind[0]] = (((numerator / denominator)**0.5)) 

1018 fig = figure(gca=False) 

1019 ax = fig.add_subplot(111, projection="hammer") 

1020 ax.cla() 

1021 ax.grid(visible=True) 

1022 ax.pcolormesh(X, Y, N) 

1023 ax.set_xticklabels([ 

1024 r"$22^{h}$", r"$20^{h}$", r"$18^{h}$", r"$16^{h}$", r"$14^{h}$", 

1025 r"$12^{h}$", r"$10^{h}$", r"$8^{h}$", r"$6^{h}$", r"$4^{h}$", 

1026 r"$2^{h}$"]) 

1027 return fig 

1028 

1029 

1030@no_latex_plot 

1031def _time_domain_waveform(detectors, maxL_params, **kwargs): 

1032 """ 

1033 Plot the maximum likelihood waveform for a given approximant 

1034 in the time domain. 

1035 

1036 Parameters 

1037 ---------- 

1038 detectors: list 

1039 list of detectors that you want to generate waveforms for 

1040 maxL_params: dict 

1041 dictionary of maximum likelihood parameter values 

1042 kwargs: dict 

1043 dictionary of optional keyword arguments 

1044 """ 

1045 from gwpy.timeseries import TimeSeries 

1046 from gwpy.plot.colors import GW_OBSERVATORY_COLORS 

1047 if math.isnan(maxL_params["mass_1"]): 

1048 return 

1049 logger.debug("Generating the maximum likelihood waveform time domain plot") 

1050 if not LALSIMULATION: 

1051 raise Exception("lalsimulation could not be imported. please install " 

1052 "lalsuite to be able to use all features") 

1053 delta_t = 1. / 4096. 

1054 minimum_frequency = kwargs.get("f_min", 5.) 

1055 t_start = maxL_params['geocent_time'] 

1056 t_finish = maxL_params['geocent_time'] + 4. 

1057 time_array = np.arange(t_start, t_finish, delta_t) 

1058 

1059 approx = lalsim.GetApproximantFromString(maxL_params["approximant"]) 

1060 mass_1 = maxL_params["mass_1"] * MSUN_SI 

1061 mass_2 = maxL_params["mass_2"] * MSUN_SI 

1062 luminosity_distance = maxL_params["luminosity_distance"] * PC_SI * 10**6 

1063 if "phi_jl" in maxL_params.keys(): 

1064 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

1065 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

1066 maxL_params["theta_jn"], maxL_params["phi_jl"], maxL_params["tilt_1"], 

1067 maxL_params["tilt_2"], maxL_params["phi_12"], maxL_params["a_1"], 

1068 maxL_params["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

1069 maxL_params["phase"]) 

1070 else: 

1071 iota, S1x, S1y, S1z, S2x, S2y, S2z = maxL_params["iota"], 0., 0., 0., \ 

1072 0., 0., 0. 

1073 phase = maxL_params["phase"] if "phase" in maxL_params.keys() else 0.0 

1074 chirptime = lalsim.SimIMRPhenomXASDuration( 

1075 mass_1, mass_2, S1z, S2z, minimum_frequency 

1076 ) 

1077 duration = np.max([2**np.ceil(np.log2(chirptime)), 1.0]) 

1078 h_plus, h_cross = lalsim.SimInspiralChooseTDWaveform( 

1079 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, iota, 

1080 phase, 0.0, 0.0, 0.0, delta_t, minimum_frequency, 

1081 kwargs.get("f_ref", 10.), None, approx) 

1082 

1083 fig, ax = figure(gca=True) 

1084 colors = [GW_OBSERVATORY_COLORS[i] for i in detectors] 

1085 for num, i in enumerate(detectors): 

1086 ar = __antenna_response(i, maxL_params["ra"], maxL_params["dec"], 

1087 maxL_params["psi"], maxL_params["geocent_time"]) 

1088 h_t = h_plus.data.data * ar[0] + h_cross.data.data * ar[1] 

1089 h_t = TimeSeries(h_t[:], dt=h_plus.deltaT, t0=h_plus.epoch) 

1090 h_t.times = [float(np.array(i)) + t_start for i in h_t.times] 

1091 ax.plot(h_t.times, h_t, 

1092 color=colors[num], linewidth=1.0, label=i) 

1093 ax.set_xlim([t_start - 0.75 * duration, t_start + duration / 4]) 

1094 ax.set_xlabel(r"Time $[s]$") 

1095 ax.set_ylabel(r"Strain") 

1096 ax.grid(visible=True) 

1097 ax.legend(loc="best") 

1098 fig.tight_layout() 

1099 return fig 

1100 

1101 

1102@no_latex_plot 

1103def _time_domain_waveform_comparison_plot(maxL_params_list, colors, labels, 

1104 **kwargs): 

1105 """Generate a plot which compares the maximum likelihood waveforms for 

1106 each approximant. 

1107 

1108 Parameters 

1109 ---------- 

1110 maxL_params_list: list 

1111 list of dictionaries containing the maximum likelihood parameter 

1112 values for each approximant 

1113 colors: list 

1114 list of colors to be used to differentiate the different approximants 

1115 approximant_labels: list, optional 

1116 label to prepend the approximant in the legend 

1117 kwargs: dict 

1118 dictionary of optional keyword arguments 

1119 """ 

1120 from gwpy.timeseries import TimeSeries 

1121 logger.debug("Generating the maximum likelihood time domain waveform " 

1122 "comparison plot for H1") 

1123 if not LALSIMULATION: 

1124 raise Exception("LALSimulation could not be imported. Please install " 

1125 "LALSuite to be able to use all features") 

1126 delta_t = 1. / 4096. 

1127 minimum_frequency = kwargs.get("f_min", 5.) 

1128 

1129 fig, ax = figure(gca=True) 

1130 for num, i in enumerate(maxL_params_list): 

1131 if math.isnan(i["mass_1"]): 

1132 continue 

1133 t_start = i['geocent_time'] 

1134 t_finish = i['geocent_time'] + 4. 

1135 time_array = np.arange(t_start, t_finish, delta_t) 

1136 

1137 approx = lalsim.GetApproximantFromString(i["approximant"]) 

1138 mass_1 = i["mass_1"] * MSUN_SI 

1139 mass_2 = i["mass_2"] * MSUN_SI 

1140 luminosity_distance = i["luminosity_distance"] * PC_SI * 10**6 

1141 if "phi_jl" in i.keys(): 

1142 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

1143 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

1144 i["theta_jn"], i["phi_jl"], i["tilt_1"], 

1145 i["tilt_2"], i["phi_12"], i["a_1"], 

1146 i["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

1147 i["phase"]) 

1148 else: 

1149 iota, S1x, S1y, S1z, S2x, S2y, S2z = i["iota"], 0., 0., 0., \ 

1150 0., 0., 0. 

1151 phase = i["phase"] if "phase" in i.keys() else 0.0 

1152 h_plus, h_cross = lalsim.SimInspiralChooseTDWaveform( 

1153 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, 

1154 iota, phase, 0.0, 0.0, 0.0, delta_t, minimum_frequency, 

1155 kwargs.get("f_ref", 10.), None, approx) 

1156 

1157 ar = __antenna_response("H1", i["ra"], i["dec"], i["psi"], 

1158 i["geocent_time"]) 

1159 h_t = h_plus.data.data * ar[0] + h_cross.data.data * ar[1] 

1160 h_t = TimeSeries(h_t[:], dt=h_plus.deltaT, t0=h_plus.epoch) 

1161 h_t.times = [float(np.array(i)) + t_start for i in h_t.times] 

1162 

1163 ax.plot(h_t.times, h_t, 

1164 color=colors[num], label=labels[num], linewidth=2.0) 

1165 ax.set_xlabel(r"Time $[s]$") 

1166 ax.set_ylabel(r"Strain") 

1167 ax.set_xlim([t_start - 3, t_start + 0.5]) 

1168 ax.grid(visible=True) 

1169 ax.legend(loc="best") 

1170 fig.tight_layout() 

1171 return fig 

1172 

1173 

1174def _psd_plot(frequencies, strains, colors=None, labels=None, fmin=None, fmax=None): 

1175 """Superimpose all PSD plots onto a single figure. 

1176 

1177 Parameters 

1178 ---------- 

1179 frequencies: nd list 

1180 list of all frequencies used for each psd file 

1181 strains: nd list 

1182 list of all strains used for each psd file 

1183 colors: optional, list 

1184 list of colors to be used to differentiate the different PSDs 

1185 labels: optional, list 

1186 list of lavels for each PSD 

1187 fmin: optional, float 

1188 starting frequency of the plot 

1189 fmax: optional, float 

1190 maximum frequency of the plot 

1191 """ 

1192 from gwpy.plot.colors import GW_OBSERVATORY_COLORS 

1193 fig, ax = figure(gca=True) 

1194 if not colors and all(i in GW_OBSERVATORY_COLORS.keys() for i in labels): 

1195 colors = [GW_OBSERVATORY_COLORS[i] for i in labels] 

1196 elif not colors: 

1197 colors = ['r', 'b', 'orange', 'c', 'g', 'purple'] 

1198 while len(colors) <= len(labels): 

1199 colors += colors 

1200 for num, i in enumerate(frequencies): 

1201 ff = np.array(i) 

1202 ss = np.array(strains[num]) 

1203 cond = np.ones_like(strains[num], dtype=bool) 

1204 if fmin is not None: 

1205 cond *= ff >= fmin 

1206 if fmax is not None: 

1207 cond *= ff <= fmax 

1208 i = ff[cond] 

1209 strains[num] = ss[cond] 

1210 ax.loglog(i, strains[num], color=colors[num], label=labels[num]) 

1211 ax.tick_params(which="both", bottom=True, length=3, width=1) 

1212 ax.set_xlabel(r"Frequency $[\mathrm{Hz}]$") 

1213 ax.set_ylabel(r"Power Spectral Density [$\mathrm{strain}^{2}/\mathrm{Hz}$]") 

1214 ax.legend(loc="best") 

1215 fig.tight_layout() 

1216 return fig 

1217 

1218 

1219@no_latex_plot 

1220def _calibration_envelope_plot(frequency, calibration_envelopes, ifos, 

1221 colors=None, prior=[]): 

1222 """Generate a plot showing the calibration envelope 

1223 

1224 Parameters 

1225 ---------- 

1226 frequency: array 

1227 frequency bandwidth that you would like to use 

1228 calibration_envelopes: nd list 

1229 list containing the calibration envelope data for different IFOs 

1230 ifos: list 

1231 list of IFOs that are associated with the calibration envelopes 

1232 colors: list, optional 

1233 list of colors to be used to differentiate the different calibration 

1234 envelopes 

1235 prior: list, optional 

1236 list containing the prior calibration envelope data for different IFOs 

1237 """ 

1238 from gwpy.plot.colors import GW_OBSERVATORY_COLORS 

1239 

1240 def interpolate_calibration(data): 

1241 """Interpolate the calibration data using spline 

1242 

1243 Parameters 

1244 ---------- 

1245 data: np.ndarray 

1246 array containing the calibration data 

1247 """ 

1248 interp = [ 

1249 np.interp(frequency, data[:, 0], data[:, j], left=k, right=k) 

1250 for j, k in zip(range(1, 7), [1, 0, 1, 0, 1, 0]) 

1251 ] 

1252 amp_median = (interp[0] - 1) * 100 

1253 phase_median = interp[1] * 180. / np.pi 

1254 amp_lower_sigma = (interp[2] - 1) * 100 

1255 phase_lower_sigma = interp[3] * 180. / np.pi 

1256 amp_upper_sigma = (interp[4] - 1) * 100 

1257 phase_upper_sigma = interp[5] * 180. / np.pi 

1258 data_dict = { 

1259 "amplitude": { 

1260 "median": amp_median, 

1261 "lower": amp_lower_sigma, 

1262 "upper": amp_upper_sigma 

1263 }, 

1264 "phase": { 

1265 "median": phase_median, 

1266 "lower": phase_lower_sigma, 

1267 "upper": phase_upper_sigma 

1268 } 

1269 } 

1270 return data_dict 

1271 

1272 fig, (ax1, ax2) = subplots(2, 1, sharex=True, gca=False) 

1273 if not colors and all(i in GW_OBSERVATORY_COLORS.keys() for i in ifos): 

1274 colors = [GW_OBSERVATORY_COLORS[i] for i in ifos] 

1275 elif not colors: 

1276 colors = ['r', 'b', 'orange', 'c', 'g', 'purple'] 

1277 while len(colors) <= len(ifos): 

1278 colors += colors 

1279 

1280 for num, i in enumerate(calibration_envelopes): 

1281 calibration_envelopes[num] = np.array(calibration_envelopes[num]) 

1282 

1283 for num, i in enumerate(calibration_envelopes): 

1284 calibration_data = interpolate_calibration(i) 

1285 if prior != []: 

1286 prior_data = interpolate_calibration(prior[num]) 

1287 ax1.plot( 

1288 frequency, calibration_data["amplitude"]["upper"], color=colors[num], 

1289 linestyle="-", label=ifos[num] 

1290 ) 

1291 ax1.plot( 

1292 frequency, calibration_data["amplitude"]["lower"], color=colors[num], 

1293 linestyle="-" 

1294 ) 

1295 ax1.set_ylabel(r"Amplitude deviation $[\%]$", fontsize=10) 

1296 ax1.legend(loc="best") 

1297 ax2.plot( 

1298 frequency, calibration_data["phase"]["upper"], color=colors[num], 

1299 linestyle="-", label=ifos[num] 

1300 ) 

1301 ax2.plot( 

1302 frequency, calibration_data["phase"]["lower"], color=colors[num], 

1303 linestyle="-" 

1304 ) 

1305 ax2.set_ylabel(r"Phase deviation $[\degree]$", fontsize=10) 

1306 if prior != []: 

1307 ax1.fill_between( 

1308 frequency, prior_data["amplitude"]["upper"], 

1309 prior_data["amplitude"]["lower"], color=colors[num], alpha=0.2 

1310 ) 

1311 ax2.fill_between( 

1312 frequency, prior_data["phase"]["upper"], 

1313 prior_data["phase"]["lower"], color=colors[num], alpha=0.2 

1314 ) 

1315 

1316 ax1.set_xscale('log') 

1317 ax2.set_xscale('log') 

1318 ax2.set_xlabel(r"Frequency $[Hz]$") 

1319 fig.tight_layout() 

1320 return fig 

1321 

1322 

1323def _strain_plot(strain, maxL_params, **kwargs): 

1324 """Generate a plot showing the strain data and the maxL waveform 

1325 

1326 Parameters 

1327 ---------- 

1328 strain: gwpy.timeseries 

1329 timeseries containing the strain data 

1330 maxL_samples: dict 

1331 dictionary of maximum likelihood parameter values 

1332 """ 

1333 logger.debug("Generating the strain plot") 

1334 from pesummary.gw.conversions import time_in_each_ifo 

1335 from gwpy.timeseries import TimeSeries 

1336 

1337 fig, axs = subplots(nrows=len(strain.keys()), sharex=True) 

1338 time = maxL_params["geocent_time"] 

1339 delta_t = 1. / 4096. 

1340 minimum_frequency = kwargs.get("f_min", 5.) 

1341 t_start = time - 15.0 

1342 t_finish = time + 0.06 

1343 time_array = np.arange(t_start, t_finish, delta_t) 

1344 

1345 approx = lalsim.GetApproximantFromString(maxL_params["approximant"]) 

1346 mass_1 = maxL_params["mass_1"] * MSUN_SI 

1347 mass_2 = maxL_params["mass_2"] * MSUN_SI 

1348 luminosity_distance = maxL_params["luminosity_distance"] * PC_SI * 10**6 

1349 phase = maxL_params["phase"] if "phase" in maxL_params.keys() else 0.0 

1350 cartesian = [ 

1351 "iota", "spin_1x", "spin_1y", "spin_1z", "spin_2x", "spin_2y", "spin_2z" 

1352 ] 

1353 if not all(param in maxL_params.keys() for param in cartesian): 

1354 if "phi_jl" in maxL_params.keys(): 

1355 iota, S1x, S1y, S1z, S2x, S2y, S2z = \ 

1356 lalsim.SimInspiralTransformPrecessingNewInitialConditions( 

1357 maxL_params["theta_jn"], maxL_params["phi_jl"], 

1358 maxL_params["tilt_1"], maxL_params["tilt_2"], 

1359 maxL_params["phi_12"], maxL_params["a_1"], 

1360 maxL_params["a_2"], mass_1, mass_2, kwargs.get("f_ref", 10.), 

1361 phase 

1362 ) 

1363 else: 

1364 iota, S1x, S1y, S1z, S2x, S2y, S2z = maxL_params["iota"], 0., 0., \ 

1365 0., 0., 0., 0. 

1366 else: 

1367 iota, S1x, S1y, S1z, S2x, S2y, S2z = [ 

1368 maxL_params[param] for param in cartesian 

1369 ] 

1370 h_plus, h_cross = lalsim.SimInspiralChooseTDWaveform( 

1371 mass_1, mass_2, S1x, S1y, S1z, S2x, S2y, S2z, luminosity_distance, iota, 

1372 phase, 0.0, 0.0, 0.0, delta_t, minimum_frequency, 

1373 kwargs.get("f_ref", 10.), None, approx) 

1374 h_plus = TimeSeries( 

1375 h_plus.data.data[:], dt=h_plus.deltaT, t0=h_plus.epoch 

1376 ) 

1377 h_cross = TimeSeries( 

1378 h_cross.data.data[:], dt=h_cross.deltaT, t0=h_cross.epoch 

1379 ) 

1380 

1381 for num, key in enumerate(list(strain.keys())): 

1382 ifo_time = time_in_each_ifo(key, maxL_params["ra"], maxL_params["dec"], 

1383 maxL_params["geocent_time"]) 

1384 

1385 asd = strain[key].asd(8, 4, method="median") 

1386 strain_data_frequency = strain[key].fft() 

1387 asd_interp = asd.interpolate(float(np.array(strain_data_frequency.df))) 

1388 asd_interp = asd_interp[:len(strain_data_frequency)] 

1389 strain_data_time = (strain_data_frequency / asd_interp).ifft() 

1390 strain_data_time = strain_data_time.highpass(30) 

1391 strain_data_time = strain_data_time.lowpass(300) 

1392 

1393 ar = __antenna_response(key, maxL_params["ra"], maxL_params["dec"], 

1394 maxL_params["psi"], maxL_params["geocent_time"]) 

1395 

1396 h_t = ar[0] * h_plus + ar[1] * h_cross 

1397 h_t_frequency = h_t.fft() 

1398 asd_interp = asd.interpolate(float(np.array(h_t_frequency.df))) 

1399 asd_interp = asd_interp[:len(h_t_frequency)] 

1400 h_t_time = (h_t_frequency / asd_interp).ifft() 

1401 h_t_time = h_t_time.highpass(30) 

1402 h_t_time = h_t_time.lowpass(300) 

1403 h_t_time.times = [float(np.array(i)) + ifo_time for i in h_t.times] 

1404 

1405 strain_data_crop = strain_data_time.crop(ifo_time - 0.2, ifo_time + 0.06) 

1406 try: 

1407 h_t_time = h_t_time.crop(ifo_time - 0.2, ifo_time + 0.06) 

1408 except Exception: 

1409 pass 

1410 max_strain = np.max(strain_data_crop).value 

1411 

1412 axs[num].plot(strain_data_crop, color='grey', alpha=0.75, label="data") 

1413 axs[num].plot(h_t_time, color='orange', label="template") 

1414 axs[num].set_xlim([ifo_time - 0.2, ifo_time + 0.06]) 

1415 if not math.isnan(max_strain): 

1416 axs[num].set_ylim([-max_strain * 1.5, max_strain * 1.5]) 

1417 axs[num].set_ylabel("Whitened %s strain" % (key), fontsize=8) 

1418 axs[num].grid(False) 

1419 axs[num].legend(loc="best", prop={'size': 8}) 

1420 axs[-1].set_xlabel("Time $[s]$", fontsize=16) 

1421 fig.tight_layout() 

1422 return fig 

1423 

1424 

1425def _format_prob(prob): 

1426 """Format the probabilities for use with _classification_plot 

1427 """ 

1428 if prob >= 1: 

1429 return '100%' 

1430 elif prob <= 0: 

1431 return '0%' 

1432 elif prob > 0.99: 

1433 return '>99%' 

1434 elif prob < 0.01: 

1435 return '<1%' 

1436 else: 

1437 return '{}%'.format(int(np.round(100 * prob))) 

1438 

1439 

1440@no_latex_plot 

1441def _classification_plot(classification): 

1442 """Generate a bar chart showing the source classifications probabilities 

1443 

1444 Parameters 

1445 ---------- 

1446 classification: dict 

1447 dictionary of source classifications 

1448 """ 

1449 from matplotlib import rcParams 

1450 

1451 original_fontsize = rcParams["font.size"] 

1452 original_ylabel = rcParams["ytick.labelsize"] 

1453 rcParams["font.size"] = 12 

1454 rcParams["ytick.labelsize"] = 12 

1455 probs, names = zip( 

1456 *sorted(zip(classification.values(), classification.keys()))) 

1457 with matplotlib.style.context('seaborn-white'): 

1458 fig, ax = figure(figsize=(2.5, 2), gca=True) 

1459 ax.barh(names, probs) 

1460 for i, prob in enumerate(probs): 

1461 ax.annotate(_format_prob(prob), (0, i), (4, 0), 

1462 textcoords='offset points', ha='left', va='center') 

1463 ax.set_xlim(0, 1) 

1464 ax.set_xticks([]) 

1465 ax.tick_params(left=False) 

1466 for side in ['top', 'bottom', 'right']: 

1467 ax.spines[side].set_visible(False) 

1468 fig.tight_layout() 

1469 rcParams["font.size"] = original_fontsize 

1470 rcParams["ytick.labelsize"] = original_ylabel 

1471 return fig