Coverage for pesummary/gw/webpage/main.py: 77.7%

391 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-11-05 13:38 +0000

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

2 

3import os 

4import numpy as np 

5 

6from pesummary.core.webpage.main import _WebpageGeneration as _CoreWebpageGeneration 

7from pesummary.core.webpage.main import PlotCaption 

8from pesummary.gw.file.standard_names import descriptive_names 

9from pesummary.utils.utils import logger 

10from pesummary import conf 

11 

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

13 

14 

15class CommandLineCaption(object): 

16 """Class to handle generating the command line used to generate a plot and 

17 a caption to describe the plot 

18 

19 Parameters 

20 ---------- 

21 """ 

22 def __init__(self, command_line_arguments, samples=None): 

23 self.args = command_line_arguments 

24 self.samples = samples 

25 self.executable = self.get_executable("summaryplots") 

26 

27 @staticmethod 

28 def get_executable(executable): 

29 """Return the path to an executable 

30 

31 Parameters 

32 ---------- 

33 executable: str 

34 the name of the executable you wish to find 

35 """ 

36 from subprocess import check_output 

37 

38 path = check_output(["which", executable]).decode("utf-8").strip() 

39 return path 

40 

41 @property 

42 def command_line(self): 

43 """Generate the command line used to generate the plot 

44 """ 

45 return "{} {}".format(self.executable, " ".join(self.args)) 

46 

47 @property 

48 def caption(self): 

49 """Make a caption to describe the plot 

50 """ 

51 general_cli = "" 

52 if "1d_histogram" in self.args[0]: 

53 return self.histogram_caption() 

54 if "skymap" in self.args[0]: 

55 return self.skymap_caption() 

56 return general_cli 

57 

58 def histogram_caption(self): 

59 """Return a caption to describe the 1d histogram plot 

60 """ 

61 args = self.args[0].split(" ") 

62 parameter = args[args.index("--parameter") + 1] 

63 general_cli = ( 

64 "1d histogram showing the posterior distribution for " 

65 "{}.".format(parameter) 

66 ) 

67 if parameter == "chi_p": 

68 general_cli += ( 

69 " chi_p is the precession parameter and quantifies how much " 

70 "precession there is in the system. It ranges from 0 to 1 " 

71 "with 0 meaning there is no precession in the system and 1 " 

72 "meaning there is maximal precession in the system." 

73 ) 

74 if self.samples is not None: 

75 general_cli += ( 

76 " The median of the distribution is {} with 90% confidence " 

77 "interval {}".format( 

78 np.round(self.samples.average(type="median"), 3), 

79 [np.round(i, 3) for i in self.samples.credible_interval()] 

80 ) 

81 ) 

82 return general_cli 

83 

84 def skymap_caption(self): 

85 """Return a caption to describe the skymap plot 

86 """ 

87 general_cli = ( 

88 "Skymap showing the possible location of the source of the " 

89 "source of the gravitational waves" 

90 ) 

91 

92 

93class _WebpageGeneration(_CoreWebpageGeneration): 

94 """ 

95 """ 

96 def __init__( 

97 self, webdir=None, samples=None, labels=None, publication=None, 

98 user=None, config=None, same_parameters=None, base_url=None, 

99 file_versions=None, hdf5=None, colors=None, custom_plotting=None, 

100 pastro_probs=None, gracedb=None, approximant=None, key_data=None, 

101 file_kwargs=None, existing_labels=None, existing_config=None, 

102 existing_file_version=None, existing_injection_data=None, 

103 existing_samples=None, existing_metafile=None, add_to_existing=False, 

104 existing_file_kwargs=None, existing_weights=None, result_files=None, 

105 notes=None, disable_comparison=False, embright_probs=None, gwdata=None, 

106 disable_interactive=False, publication_kwargs={}, no_ligo_skymap=False, 

107 psd=None, priors=None, package_information={"packages": []}, 

108 mcmc_samples=False, external_hdf5_links=False, preliminary_pages=False, 

109 existing_plot=None, disable_expert=False, analytic_priors=None 

110 ): 

111 self.pastro_probs = pastro_probs 

112 self.embright_probs = embright_probs 

113 if self.embright_probs is None: 

114 self.embright_probs = { 

115 label: None for label in self.pastro_probs.keys() 

116 } 

117 self.gracedb = gracedb 

118 self.approximant = approximant 

119 self.file_kwargs = file_kwargs 

120 self.publication = publication 

121 self.publication_kwargs = publication_kwargs 

122 self.result_files = result_files 

123 self.gwdata = gwdata 

124 self.no_ligo_skymap = no_ligo_skymap 

125 self.psd = psd 

126 self.priors = priors 

127 

128 super(_WebpageGeneration, self).__init__( 

129 webdir=webdir, samples=samples, labels=labels, 

130 publication=publication, user=user, config=config, 

131 same_parameters=same_parameters, base_url=base_url, 

132 file_versions=file_versions, hdf5=hdf5, colors=colors, 

133 custom_plotting=custom_plotting, 

134 existing_labels=existing_labels, existing_config=existing_config, 

135 existing_file_version=existing_file_version, 

136 existing_injection_data=existing_injection_data, 

137 existing_samples=existing_samples, 

138 existing_metafile=existing_metafile, 

139 existing_file_kwargs=existing_file_kwargs, 

140 existing_weights=existing_weights, 

141 add_to_existing=add_to_existing, notes=notes, 

142 disable_comparison=disable_comparison, 

143 disable_interactive=disable_interactive, 

144 package_information=package_information, mcmc_samples=mcmc_samples, 

145 external_hdf5_links=external_hdf5_links, key_data=key_data, 

146 existing_plot=existing_plot, disable_expert=disable_expert, 

147 analytic_priors=analytic_priors 

148 ) 

149 if self.file_kwargs is None: 

150 self.file_kwargs = { 

151 label: {"sampler": {}, "meta_data": {}} for label in self.labels 

152 } 

153 if self.approximant is None: 

154 self.approximant = {label: None for label in self.labels} 

155 if self.result_files is None: 

156 self.result_files = [None] * len(self.labels) 

157 self.psd_path = {"other": os.path.join("..", "psds")} 

158 self.calibration_path = {"other": os.path.join("..", "calibration")} 

159 self.preliminary_pages = preliminary_pages 

160 if not isinstance(self.preliminary_pages, dict): 

161 if self.preliminary_pages: 

162 self.preliminary_pages = { 

163 label: True for label in self.labels 

164 } 

165 else: 

166 self.preliminary_pages = { 

167 label: False for label in self.labels 

168 } 

169 if all(value for value in self.preliminary_pages.values()): 

170 self.all_pages_preliminary = True 

171 if len(self.labels) > 1: 

172 if any(value for value in self.preliminary_pages.values()): 

173 self.preliminary_pages["Comparison"] = True 

174 

175 def categorize_parameters(self, parameters): 

176 """Categorize the parameters into common headings 

177 

178 Parameters 

179 ---------- 

180 parameters: list 

181 list of parameters that you would like to sort 

182 """ 

183 return super(_WebpageGeneration, self).categorize_parameters( 

184 parameters, starting_letter=False 

185 ) 

186 

187 def categorize_2d_parameters(self, samples): 

188 """Categorize the default 2d parameters and decide the common 

189 headings 

190 

191 Parameters 

192 ---------- 

193 parameters: list 

194 list of parameters that you would like to sort 

195 """ 

196 sort = {} 

197 for params in conf.gw_2d_plots: 

198 if not all(p in samples.keys() for p in params): 

199 continue 

200 if not all(len(np.unique(samples[p])) > 1 for p in params): 

201 continue 

202 headings = self.categorize_parameters(params) 

203 for hh in headings[::-1]: 

204 if len(hh[1]) and hh[0] not in ["others"]: 

205 heading = hh[0] 

206 break 

207 elif len(hh[1]) and "geocent_time" in params: 

208 heading = "timings" 

209 break 

210 if heading not in sort.keys(): 

211 sort[heading] = [] 

212 sort[heading] += params 

213 return sort 

214 

215 def _kde_from_same_samples(self, param, samples): 

216 """Generate KDEs for a set of samples 

217 

218 Parameters 

219 ---------- 

220 param: str 

221 The parameter that the samples belong to 

222 samples: list 

223 list of samples for each result file 

224 """ 

225 from pesummary.utils.bounded_1d_kde import ReflectionBoundedKDE 

226 from pesummary.gw.plots.plot import _return_bounds 

227 

228 xlow, xhigh = _return_bounds(param, samples, comparison=True) 

229 return super(_WebpageGeneration, self)._kde_from_same_samples( 

230 param, samples, kde=ReflectionBoundedKDE, xlow=xlow, xhigh=xhigh 

231 ) 

232 

233 def make_navbar_for_homepage(self): 

234 """Make a navbar for the homepage 

235 """ 

236 if not hasattr(self, "make_pastro"): 

237 if self.pastro_probs is not None: 

238 self.make_pastro = { 

239 label: self.pastro_probs[label] is not None and not any( 

240 _ is None for _ in self.pastro_probs[label]["default"].values() 

241 ) for label in self.pastro_probs.keys() 

242 } 

243 else: 

244 self.make_pastro = { 

245 label: False for label in self.pastro_probs.keys() 

246 } 

247 links = super(_WebpageGeneration, self).make_navbar_for_homepage() 

248 if self.gwdata is not None: 

249 links.append(["Detchar", [i for i in self.gwdata.keys()]]) 

250 return links 

251 

252 def make_navbar_for_result_page(self): 

253 """Make a navbar for the result page homepage 

254 """ 

255 links = super(_WebpageGeneration, self).make_navbar_for_result_page() 

256 for num, label in enumerate(self.labels): 

257 links[label].append( 

258 ["2d Histograms", [ 

259 {"masses": label}, {"spins": label}, 

260 {"location": label}, {"timings": label} 

261 ]] 

262 ) 

263 if self.make_pastro[label]: 

264 links[label].append({"Classification": label}) 

265 return links 

266 

267 def generate_webpages(self): 

268 """Generate all webpages for all result files passed 

269 """ 

270 super(_WebpageGeneration, self).generate_webpages() 

271 self.make_2d_histogram_pages() 

272 if self.publication: 

273 self.make_publication_pages() 

274 if self.gwdata is not None: 

275 self.make_detector_pages() 

276 self.make_classification_pages() 

277 

278 def _make_home_pages(self, pages): 

279 """Make the home pages 

280 

281 Parameters 

282 ---------- 

283 pages: list 

284 list of pages that you wish to create 

285 """ 

286 title = None if self.gracedb is None else ( 

287 "Parameter Estimation Summary Pages for {}".format(self.gracedb) 

288 ) 

289 banner = "Summary" if self.gracedb is None else ( 

290 "Summary for {}".format(self.gracedb) 

291 ) 

292 html_file = self.setup_page("home", self.navbar["home"], title=title) 

293 html_file.make_banner(approximant=banner, key="Summary") 

294 path = self.image_path["home"] 

295 

296 if self.make_comparison: 

297 if not all(self.approximant[i] is not None for i in self.labels): 

298 image_contents = [] 

299 _plot = os.path.join(path, "compare_time_domain_waveforms.png") 

300 if os.path.isfile(_plot): 

301 image_contents.append(_plot) 

302 image_contents = [image_contents] 

303 html_file = self.make_modal_carousel( 

304 html_file, image_contents, unique_id=True 

305 ) 

306 

307 for i in self.labels: 

308 html_file.make_banner(approximant=i, key=i) 

309 image_contents, captions = [], [] 

310 basic_string = os.path.join(self.webdir, "plots", "{}.png") 

311 relative_path = os.path.join(path, "{}.png") 

312 if os.path.isfile(basic_string.format("%s_strain" % (i))): 

313 image_contents.append(relative_path.format("%s_strain" % (i))) 

314 captions.append(PlotCaption("strain")) 

315 if os.path.isfile(basic_string.format("%s_psd_plot" % (i))): 

316 image_contents.append(relative_path.format("%s_psd_plot" % (i))) 

317 captions.append(PlotCaption("psd")) 

318 if os.path.isfile( 

319 basic_string.format("%s_waveform_time_domain" % (i)) 

320 ): 

321 image_contents.append( 

322 relative_path.format("%s_waveform_time_domain" % (i)) 

323 ) 

324 captions.append(PlotCaption("time_waveform")) 

325 if os.path.isfile( 

326 basic_string.format("%s_calibration_plot" % (i)) 

327 ): 

328 image_contents.append( 

329 relative_path.format("%s_calibration_plot" % (i)) 

330 ) 

331 captions.append(PlotCaption("calibration")) 

332 image_contents = [image_contents] 

333 html_file = self.make_modal_carousel( 

334 html_file, image_contents, unique_id=True, extra_div=True, 

335 captions=[captions] 

336 ) 

337 

338 for _key in ["sampler", "meta_data"]: 

339 if _key == "sampler": 

340 html_file.make_banner( 

341 approximant="Sampler kwargs", key="sampler_kwargs", 

342 _style="font-size: 26px;" 

343 ) 

344 else: 

345 html_file.make_banner( 

346 approximant="Meta data", key="meta_data", 

347 _style="font-size: 26px;" 

348 ) 

349 _style = "margin-top:3em; margin-bottom:5em; max-width:1400px" 

350 _class = "row justify-content-center" 

351 html_file.make_container(style=_style) 

352 html_file.make_div(4, _class=_class, _style=None) 

353 

354 base_label = self.labels[0] 

355 total_keys = list(self.file_kwargs[base_label][_key].keys()) 

356 if len(self.labels) > 1: 

357 for _label in self.labels[1:]: 

358 total_keys += [ 

359 key for key in self.file_kwargs[_label][_key].keys() 

360 if key not in total_keys 

361 ] 

362 _headings = ["label"] + total_keys 

363 table_contents = [ 

364 [i] + [ 

365 self.file_kwargs[i][_key][key] if key in 

366 self.file_kwargs[i][_key].keys() else "-" for key in 

367 total_keys 

368 ] for i in self.labels 

369 ] 

370 html_file.make_table( 

371 headings=_headings, format="table-hover", heading_span=1, 

372 contents=table_contents, accordian=False 

373 ) 

374 html_file.end_div(4) 

375 html_file.end_container() 

376 html_file.export("{}.csv".format(_key)) 

377 

378 html_file.make_footer(user=self.user, rundir=self.webdir) 

379 html_file.close() 

380 super(_WebpageGeneration, self)._make_home_pages(pages, make_home=False) 

381 

382 def make_2d_histogram_pages(self): 

383 """Wrapper function for _make_2d_histogram pages 

384 """ 

385 pages = [ 

386 "{}_{}_{}".format(i, i, j) for i in self.labels for j in 

387 self.categorize_2d_parameters(self.samples[i]).keys() 

388 ] 

389 self.create_blank_html_pages(pages) 

390 self._make_2d_histogram_pages(pages) 

391 

392 def _make_2d_histogram_pages(self, pages): 

393 """Make the 2d histogram pages 

394 """ 

395 for num, i in enumerate(self.labels): 

396 for j, k in self.categorize_2d_parameters(self.samples[i]).items(): 

397 html_file = self.setup_page( 

398 "{}_{}".format(i, j), self.navbar["result_page"][i], 

399 i, title="{} Posterior PDFs describing the {}".format(i, j), 

400 approximant=i, background_colour=self.colors[num], 

401 histogram_download=False, toggle=self.expert_plots 

402 ) 

403 html_file.make_banner(approximant=i, key=i) 

404 path = self.image_path["other"] 

405 contents = [ 

406 path + "{}_2d_posterior_{}_{}.png".format(i, k[l], k[l+1]) 

407 for l in range(0, len(k), 2) 

408 ] 

409 if len(contents) < 2: 

410 autoscale = False 

411 else: 

412 autoscale = True 

413 image_contents = [ 

414 contents[l:2 + l] for l in range(0, len(contents), 2) 

415 ] 

416 html_file.make_table_of_images( 

417 contents=image_contents, code="changeimage", 

418 mcmc_samples=False, autoscale=autoscale 

419 ) 

420 key_data = self.key_data 

421 contents = [] 

422 headings = [" "] + self.key_data_headings.copy() 

423 _injection = False 

424 rows = [] 

425 for param in np.unique(k): 

426 _row = [param] 

427 _row += self.key_data_table[i][param] 

428 rows.append(_row) 

429 _style = "margin-top:3em; margin-bottom:5em; max-width:1400px" 

430 _class = "row justify-content-center" 

431 html_file.make_container(style=_style) 

432 html_file.make_div(4, _class=_class, _style=None) 

433 html_file.make_table( 

434 headings=headings, contents=rows, heading_span=1, 

435 accordian=False, format="table-hover" 

436 ) 

437 html_file.end_div(4) 

438 html_file.end_container() 

439 html_file.export("summary_information_{}.csv".format(i)) 

440 html_file.make_footer(user=self.user, rundir=self.webdir) 

441 html_file.close() 

442 

443 def make_publication_pages(self): 

444 """Wrapper function for _make_publication_pages() 

445 """ 

446 pages = ["Publication"] 

447 self.create_blank_html_pages(pages) 

448 self._make_publication_pages(pages) 

449 

450 def _make_publication_pages(self, pages): 

451 """Make the publication pages 

452 

453 Parameters 

454 ---------- 

455 pages: list 

456 list of pages that you wish to create 

457 """ 

458 from glob import glob 

459 

460 executable = self.get_executable("summarypublication") 

461 general_cli = "%s --webdir %s --samples %s --labels %s --plot {}" % ( 

462 executable, os.path.join(self.webdir, "plots", "publication"), 

463 " ".join(self.result_files), " ".join(self.labels) 

464 ) 

465 if self.publication_kwargs != {}: 

466 general_cli += "--publication_kwargs %s" % ( 

467 " ".join( 

468 [ 

469 "{}:{}".format(key, value) for key, value in 

470 self.publication_kwargs.items() 

471 ] 

472 ) 

473 ) 

474 html_file = self.setup_page( 

475 "Publication", self.navbar["home"], title="Publication Plots" 

476 ) 

477 html_file.make_banner(approximant="Publication", key="Publication") 

478 path = self.image_path["other"] 

479 pub_plots = glob( 

480 os.path.join(self.webdir, "plots", "publication", "*.png") 

481 ) 

482 for num, i in enumerate(pub_plots): 

483 shortened_path = i.split("/plots/")[-1] 

484 pub_plots[num] = path + shortened_path 

485 cli = [] 

486 cap = [] 

487 posterior_name = \ 

488 lambda i: "{} ({})".format(i, descriptive_names[i]) if i in \ 

489 descriptive_names.keys() and descriptive_names[i] != "" else i 

490 for i in pub_plots: 

491 filename = i.split("/")[-1] 

492 if "violin_plot" in filename: 

493 parameter = filename.split("violin_plot_")[-1].split(".png")[0] 

494 cli.append( 

495 general_cli.format("violin") + " --parameters %s" % ( 

496 parameter 

497 ) 

498 ) 

499 cap.append( 

500 PlotCaption("violin").format(posterior_name(parameter)) 

501 ) 

502 elif "spin_disk" in filename: 

503 cli.append(general_cli.format("spin_disk")) 

504 cap.append(PlotCaption("spin_disk")) 

505 elif "2d_contour" in filename: 

506 parameters = filename.split("2d_contour_plot_")[-1].split(".png")[0] 

507 cli.append( 

508 general_cli.format("2d_contour") + " --parameters %s" % ( 

509 parameters.replace("_and_", " ") 

510 ) 

511 ) 

512 pp = parameters.split("_and_") 

513 cap.append( 

514 PlotCaption("2d_contour").format( 

515 posterior_name(pp[0]), posterior_name(pp[1]) 

516 ) 

517 ) 

518 image_contents = [ 

519 pub_plots[i:3 + i] for i in range(0, len(pub_plots), 3) 

520 ] 

521 command_lines = [ 

522 cli[i:3 + i] for i in range(0, len(cli), 3) 

523 ] 

524 captions = [cap[i:3 + i] for i in range(0, len(cap), 3)] 

525 html_file = self.make_modal_carousel( 

526 html_file, image_contents, cli=command_lines, captions=captions 

527 ) 

528 html_file.make_footer(user=self.user, rundir=self.webdir) 

529 html_file.close() 

530 

531 def make_detector_pages(self): 

532 """Wrapper function for _make_publication_pages() 

533 """ 

534 pages = [i for i in self.gwdata.keys()] 

535 self.create_blank_html_pages(pages) 

536 self._make_detector_pages(pages) 

537 

538 def _make_detector_pages(self, pages): 

539 """Make the detector characterisation pages 

540 

541 Parameters 

542 ---------- 

543 pages: list 

544 list of pages that you wish to create 

545 """ 

546 from pesummary.utils.utils import ( 

547 determine_gps_time_and_window, command_line_dict 

548 ) 

549 from astropy.time import Time 

550 

551 executable = self.get_executable("summarydetchar") 

552 try: 

553 command_line = command_line_dict() 

554 except SystemExit: 

555 command_line = {"gwdata": {}} 

556 if isinstance(command_line["gwdata"], dict): 

557 gwdata_command_line = [ 

558 "{}:{}".format(key, val) for key, val in 

559 command_line["gwdata"].items() 

560 ] 

561 else: 

562 gwdata_command_line = command_line["gwdata"] 

563 if gwdata_command_line is None: 

564 gwdata_command_line = [] 

565 general_cli = "%s --webdir %s --gwdata %s --plot {}{}" % ( 

566 executable, os.path.join(self.webdir, "plots"), 

567 " ".join(gwdata_command_line) 

568 ) 

569 path = self.image_path["other"] 

570 base = os.path.join(path, "{}_{}.png") 

571 ADD_DETCHAR_LINK = True 

572 try: 

573 maxL_samples = { 

574 i: { 

575 "geocent_time": self.key_data[i]["geocent_time"]["maxL"] 

576 } for i in self.labels 

577 } 

578 except KeyError: 

579 # trying a different name for time 

580 try: 

581 maxL_samples = { 

582 i: { 

583 "geocent_time": self.key_data[i][ 

584 "marginalized_geocent_time" 

585 ]["maxL"] 

586 } for i in self.labels 

587 } 

588 except KeyError: 

589 logger.warning( 

590 "Failed to find a time parameter to link to detchar/" 

591 "summary pages. Not adding link to webpages." 

592 ) 

593 ADD_DETCHAR_LINK = False 

594 if ADD_DETCHAR_LINK: 

595 gps_time, window = determine_gps_time_and_window(maxL_samples, self.labels) 

596 t = Time(gps_time, format='gps') 

597 t = Time(t, format='datetime') 

598 link = ( 

599 "https://ldas-jobs.ligo-wa.caltech.edu/~detchar/summary/day" 

600 "/{}{}{}/".format( 

601 t.value.year, 

602 "0{}".format(t.value.month) if t.value.month < 10 else t.value.month, 

603 "0{}".format(t.value.day) if t.value.day < 10 else t.value.day 

604 ) 

605 ) 

606 else: 

607 link = None 

608 gps_time, window = None, None 

609 for det in self.gwdata.keys(): 

610 html_file = self.setup_page( 

611 det, self.navbar["home"], title="{} Detchar".format(det) 

612 ) 

613 html_file.make_banner(approximant=det, key="detchar", link=link) 

614 image_contents = [ 

615 [base.format("spectrogram", det), base.format("omegascan", det)] 

616 ] 

617 command_lines = [ 

618 [ 

619 general_cli.format("spectrogram", ""), 

620 general_cli.format( 

621 "omegascan", "--gps %s --vmin 0 --vmax 25 --window %s" % ( 

622 gps_time, window 

623 ) 

624 ) 

625 ] 

626 ] 

627 html_file = self.make_modal_carousel( 

628 html_file, image_contents, cli=command_lines, autoscale=True, 

629 ) 

630 html_file.make_footer(user=self.user, rundir=self.webdir) 

631 html_file.close() 

632 

633 def make_classification_pages(self): 

634 """Wrapper function for _make_publication_pages() 

635 """ 

636 self._make_classification_pages([]) 

637 

638 def _make_classification_pages(self, pages): 

639 """Make the classification pages 

640 

641 Parameters 

642 ---------- 

643 pages: list 

644 list of pages that you wish to create 

645 """ 

646 executable = self.get_executable("summaryclassification") 

647 general_cli = "%s --samples {}" % (executable) 

648 for num, label in enumerate(self.labels): 

649 make_embright = self.embright_probs[label] is None or not any( 

650 _ is None for _ in self.embright_probs[label]["default"].values() 

651 ) 

652 if not self.make_pastro[label]: 

653 continue 

654 page = "{}_{}_Classification".format(label, label) 

655 self.create_blank_html_pages([page]) 

656 html_file = self.setup_page( 

657 "{}_Classification".format(label), 

658 self.navbar["result_page"][label], label, 

659 title="{} Classification".format(label), 

660 background_colour=self.colors[num], approximant=label 

661 ) 

662 html_file.make_banner(approximant=label, key="classification") 

663 if self.make_pastro[label]: 

664 html_file.make_container() 

665 _class = "row justify-content-center" 

666 html_file.make_div(4, _class=_class, _style=None) 

667 keys = list(self.pastro_probs[label]["default"].keys()) 

668 table_contents = [ 

669 ["{} prior".format(i)] + [ 

670 self.pastro_probs[label][i][j] for j in keys 

671 ] for i in ["default"] 

672 ] 

673 if self.embright_probs[label] is not None and make_embright: 

674 keys += ["HasNS"] 

675 keys += ["HasRemnant"] 

676 keys += ["HasMassGap"] 

677 table_contents[0].append( 

678 self.embright_probs[label]["default"]["HasNS"] 

679 ) 

680 table_contents[0].append( 

681 self.embright_probs[label]["default"]["HasRemnant"] 

682 ) 

683 table_contents[0].append( 

684 self.embright_probs[label]["default"]["HasMassGap"] 

685 ) 

686 html_file.make_table( 

687 headings=[" "] + keys, contents=table_contents, 

688 heading_span=1, accordian=False 

689 ) 

690 html_file.make_cli_button( 

691 general_cli.format(self.result_files[num]) 

692 ) 

693 html_file.export( 

694 "classification_{}.csv".format(label), 

695 margin_top="-1.5em", margin_bottom="0.5em", json=True 

696 ) 

697 html_file.end_div(4) 

698 html_file.end_container() 

699 path = self.image_path["other"] 

700 image_contents = [ 

701 [ 

702 os.path.join(path, "%s.pesummary.p_astro.png" % (label)), 

703 os.path.join(path, "%s.pesummary.em_bright.png" % (label)) 

704 ] 

705 ] 

706 mass_plot = os.path.join( 

707 self.webdir, "plots", 

708 f"{label}_2d_posterior_mass_1_source_mass_2_source.png" 

709 ) 

710 if os.path.isfile(mass_plot): 

711 mass_plot = os.path.join( 

712 path, 

713 f"{label}_2d_posterior_mass_1_source_mass_2_source.png" 

714 ) 

715 image_contents[0].append(mass_plot) 

716 base = ( 

717 "%s --webdir %s --labels %s --plot {} --prior {}" % ( 

718 general_cli.format(self.result_files[num]), 

719 os.path.join(self.webdir, "plots"), label 

720 ) 

721 ) 

722 command_lines = [ 

723 [ 

724 base.format("bar", "default"), 

725 base.format("bar", "population"), 

726 None 

727 ] 

728 ] 

729 captions = [ 

730 [ 

731 PlotCaption("pastro_classification_bar"), 

732 PlotCaption("embright_classification_bar"), 

733 None 

734 ] 

735 ] 

736 html_file = self.make_modal_carousel( 

737 html_file, image_contents, cli=command_lines, autoscale=False, 

738 captions=captions 

739 ) 

740 html_file.make_footer(user=self.user, rundir=self.webdir) 

741 html_file.close() 

742 

743 def _make_entry_in_downloads_table(self, html_file, label, num, base_string): 

744 """Make a label specific entry into the downloads table 

745 

746 Parameters 

747 ---------- 

748 label: str 

749 the label you wish to add to the downloads table 

750 base_string: str 

751 the download string 

752 """ 

753 table = super(_WebpageGeneration, self)._make_entry_in_downloads_table( 

754 html_file, label, num, base_string 

755 ) 

756 if not self.no_ligo_skymap: 

757 table.append( 

758 [ 

759 base_string.format( 

760 "Fits file containing skymap for this analysis", 

761 self.results_path["other"] + "%s_skymap.fits" % (label) 

762 ) 

763 ] 

764 ) 

765 if self.make_pastro[label]: 

766 table.append( 

767 [ 

768 base_string.format( 

769 "PAstro json file containing source classification " 

770 "probabilities", 

771 self.results_path["other"] + label + ".pesummary.p_astro.json" 

772 ) 

773 ] 

774 ) 

775 table.append( 

776 [ 

777 base_string.format( 

778 "PAstro bar plot showing source classification " 

779 "probabilities", 

780 self.image_path["other"] + label + ".pesummary.p_astro.png" 

781 ) 

782 ] 

783 ) 

784 if self.psd is not None and self.psd != {} and label in self.psd.keys(): 

785 for ifo in self.psd[label].keys(): 

786 if len(self.psd[label][ifo]): 

787 table.append( 

788 [ 

789 base_string.format( 

790 "%s psd file used for this analysis" % (ifo), 

791 os.path.join( 

792 self.psd_path["other"], 

793 "%s_%s_psd.dat" % (label, ifo) 

794 ) 

795 ) 

796 ] 

797 ) 

798 if self.priors is not None and "calibration" in self.priors.keys(): 

799 if label in self.priors["calibration"].keys(): 

800 for ifo in self.priors["calibration"][label].keys(): 

801 if len(self.priors["calibration"][label][ifo]): 

802 table.append( 

803 [ 

804 base_string.format( 

805 "%s calibration envelope file used for " 

806 "this analysis" % (ifo), os.path.join( 

807 self.calibration_path["other"], 

808 "%s_%s_cal.txt" % (label, ifo) 

809 ) 

810 ) 

811 ] 

812 ) 

813 return table 

814 

815 def default_images_for_result_page(self, label): 

816 """Return the default images that will be displayed on the result page 

817 """ 

818 path = self.image_path["other"] 

819 base_string = path + "%s_{}.png" % (label) 

820 image_contents = [ 

821 [ 

822 base_string.format("1d_posterior_mass_1"), 

823 base_string.format("1d_posterior_mass_2"), 

824 ], [ 

825 base_string.format("1d_posterior_a_1"), 

826 base_string.format("1d_posterior_a_2"), 

827 base_string.format("1d_posterior_chi_eff") 

828 ], [ 

829 base_string.format("1d_posterior_iota"), 

830 base_string.format("skymap"), 

831 base_string.format("waveform"), 

832 base_string.format("1d_posterior_luminosity_distance"), 

833 ] 

834 ] 

835 executable = self.get_executable("summaryplots") 

836 general_cli = ( 

837 "%s --webdir %s --samples %s --burnin %s --plot {} {} " 

838 "--labels %s" % ( 

839 executable, os.path.join(self.webdir, "plots"), 

840 self.result_files[self.labels.index(label)], conf.burnin, label 

841 ) 

842 ) 

843 cli = [ 

844 [ 

845 general_cli.format("1d_histogram", "--parameter mass_1"), 

846 general_cli.format("1d_histgram", "--parameter mass_2"), 

847 ], [ 

848 general_cli.format("1d_histogram", "--parameter a_1"), 

849 general_cli.format("1d_histogram", "--parameter a_2"), 

850 general_cli.format("1d_histogram", "--parameter chi_eff") 

851 ], [ 

852 general_cli.format("1d_histogram", "--parameter iota"), 

853 general_cli.format("skymap", ""), 

854 general_cli.format("waveform", ""), 

855 general_cli.format( 

856 "1d_histogram", "--parameter luminosity_distance" 

857 ), 

858 ] 

859 ] 

860 

861 caption_1d_histogram = PlotCaption("1d_histogram") 

862 posterior_name = \ 

863 lambda i: "{} ({})".format(i, descriptive_names[i]) if i in \ 

864 descriptive_names.keys() and descriptive_names[i] != "" else i 

865 captions = [ 

866 [ 

867 caption_1d_histogram.format(posterior_name("mass_1")), 

868 caption_1d_histogram.format(posterior_name("mass_2")), 

869 ], [ 

870 caption_1d_histogram.format(posterior_name("a_1")), 

871 caption_1d_histogram.format(posterior_name("a_2")), 

872 caption_1d_histogram.format(posterior_name("chi_eff")) 

873 ], [ 

874 caption_1d_histogram.format(posterior_name("iota")), 

875 PlotCaption("skymap"), PlotCaption("frequency_waveform"), 

876 caption_1d_histogram.format(posterior_name("luminosity_distance")) 

877 ] 

878 ] 

879 return image_contents, cli, captions 

880 

881 def default_categories(self): 

882 """Return the default categories 

883 """ 

884 categories = self.categories = { 

885 "masses": { 

886 "accept": ["mass"], 

887 "reject": ["source", "final", "torus"] 

888 }, 

889 "source": { 

890 "accept": ["source"], "reject": ["final", "torus"] 

891 }, 

892 "remnant": { 

893 "accept": ["final", "torus"], "reject": [] 

894 }, 

895 "inclination": { 

896 "accept": ["theta", "iota", "viewing"], "reject": [] 

897 }, 

898 "spins": { 

899 "accept": ["spin", "chi_p", "chi_eff", "a_1", "a_2", "precession"], 

900 "reject": ["lambda", "final", "gamma", "order"] 

901 }, 

902 "spin_angles": { 

903 "accept": ["phi", "tilt", "beta"], "reject": [] 

904 }, 

905 "tidal": { 

906 "accept": [ 

907 "lambda", "gamma_", "log_pressure", 

908 "spectral_decomposition_gamma_", "compactness_", 

909 "tidal_disruption" 

910 ], 

911 "reject": [] 

912 }, 

913 "location": { 

914 "accept": [ 

915 "ra", "dec", "psi", "luminosity_distance", "redshift", 

916 "comoving_distance" 

917 ], 

918 "reject": ["mass_ratio", "radiated", "ram", "ran", "rat", "time", "delay"] 

919 }, 

920 "timings": { 

921 "accept": ["time", "delay"], "reject": [] 

922 }, 

923 "SNR": { 

924 "accept": ["snr", "subdominant_multipoles"], "reject": [] 

925 }, 

926 "calibration": { 

927 "accept": ["spcal", "recalib", "frequency"], 

928 "reject": ["minimum", "tidal_disruption", "quasinormal"] 

929 }, 

930 "energy": { 

931 "accept": ["peak_luminosity", "radiated"], 

932 "reject": [] 

933 }, 

934 "others": { 

935 "accept": ["phase", "likelihood", "prior", "quasinormal"], 

936 "reject": ["spcal", "recalib"] 

937 } 

938 } 

939 return categories 

940 

941 def default_popular_options(self): 

942 """Return a list of popular options 

943 """ 

944 popular_options = [ 

945 "mass_1, mass_2", "luminosity_distance, theta_jn, ra, dec", 

946 "theta_jn, phi_12, phi_jl, tilt_1, tilt_2" 

947 ] 

948 return popular_options 

949 

950 def default_comparison_homepage_plots(self): 

951 """Return a list of default plots for the comparison homepage 

952 """ 

953 path = self.image_path["other"] 

954 base = os.path.join(path, "{}.png") 

955 contents = [ 

956 [ 

957 base.format("combined_skymap"), 

958 base.format("compare_time_domain_waveforms"), 

959 base.format("compare_waveforms") 

960 ] 

961 ] 

962 return contents 

963 

964 def default_corner_params(self): 

965 """Return a list of default corner parameters used by the corner 

966 plotting function 

967 """ 

968 return conf.gw_corner_parameters 

969 

970 def add_to_expert_pages(self, path, label): 

971 """Additional expert plots to add beyond the default. This returns a 

972 dictionary keyed by the parameter, with values providing the path 

973 to the additional plots you wish to add. The plots are a 2d list 

974 where each sublist represents a row in the table of images. 

975 

976 Parameters 

977 ---------- 

978 path: str 

979 path to the image directory 

980 label: str 

981 label of the plot you wish to add 

982 """ 

983 mydict = super(_WebpageGeneration, self).add_to_expert_pages( 

984 path, label 

985 ) 

986 contour_base = path + "{}_2d_contour_{}_log_likelihood.png" 

987 mydict.update({ 

988 "network_precessing_snr": [ 

989 [ 

990 contour_base.format(label, "_b_bar"), 

991 contour_base.format(label, "_precessing_harmonics_overlap"), 

992 ] 

993 ] 

994 }) 

995 return mydict 

996 

997 @property 

998 def additional_1d_pages(self): 

999 """Additional 1d histogram pages beyond one for each parameter. You may, 

1000 for instance, want a 1d histogram page which combines multiple 

1001 parameters. This returns a dictionary, keyed by the new 1d histogram 

1002 page, with values indicating the parameters you wish to include on this 

1003 page. Only the 1d marginalized histograms are shown. 

1004 """ 

1005 return conf.additional_1d_pages