Coverage for pesummary/core/cli/parser.py: 95.7%

116 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 

3import sys 

4import argparse 

5from ... import conf 

6from ...utils.utils import logger 

7from .actions import ( 

8 ConfigAction, CheckFilesExistAction, DictionaryAction, 

9 DeprecatedStoreFalseAction 

10) 

11 

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

13 

14 

15class ArgumentParser(argparse.ArgumentParser): 

16 """Extension of the argparse.ArgumentParser object to handle pesummary 

17 command line arguments. 

18 

19 Properties 

20 ---------- 

21 fallback_options: dict 

22 dictionary of default kwargs 

23 pesummary_options: dict 

24 dictionary giving pesummary options 

25 command_line: str 

26 command line run 

27 command_line_arguments: list 

28 list giving command line arguments 

29 dynamic_argparse: list 

30 list of dynamic argparse functions that you wish to add to the 

31 argparse.Namespace object 

32 """ 

33 @property 

34 def fallback_options(self): 

35 return { 

36 "default": None, 

37 "nargs": None, 

38 "dest": None, 

39 "type": str, 

40 "action": argparse._StoreAction, 

41 "choices": None 

42 } 

43 

44 @property 

45 def pesummary_options(self): 

46 return self._pesummary_options() 

47 

48 def update_default_pesummary_option(self, option, key, value): 

49 """Update the default dictionary of pesummary option 

50 

51 Parameters 

52 ---------- 

53 option: str 

54 Name of pesummary option to change. 

55 key: str 

56 Name of kwarg you wish to change. 

57 value: str/float/func 

58 str/float/func you with to change kwarg to 

59 """ 

60 if option not in self._pesummary_options().keys(): 

61 raise ValueError("Unknown option '{}'".format(option)) 

62 self._pesummary_options()[option][key] = value 

63 return self._pesummary_options() 

64 

65 def _pesummary_options(self): 

66 return { 

67 "pesummary": { 

68 "nargs": "?", 

69 "action": ConfigAction, 

70 "help": "Configuration file containing command line arguments", 

71 "key": "core", 

72 }, 

73 "--webdir": { 

74 "dest": "webdir", 

75 "metavar": "DIR", 

76 "help": "make page and plots in DIR", 

77 "short": "-w", 

78 "key": "core", 

79 }, 

80 "--baseurl": { 

81 "dest": "baseurl", 

82 "metavar": "DIR", 

83 "help": "make the page at this url", 

84 "short": "-b", 

85 "key": "core", 

86 }, 

87 "--labels": { 

88 "dest": "labels", 

89 "help": "labels used to distinguish runs", 

90 "nargs": "+", 

91 "key": "core", 

92 }, 

93 "--samples": { 

94 "dest": "samples", 

95 "nargs": "+", 

96 "action": CheckFilesExistAction, 

97 "help": ( 

98 "Path to posterior samples file(s). See documentation for " 

99 "allowed formats. If path is on a remote server, add " 

100 "username and servername in the form " 

101 "{username}@{servername}:{path}. If path is on a public " 

102 "webpage, ensure the path starts with https://. You may " 

103 "also pass a string such as posterior_samples*.dat and " 

104 "all matching files will be used" 

105 ), 

106 "short": "-s", 

107 "key": "core", 

108 }, 

109 "--config": { 

110 "dest": "config", 

111 "nargs": "+", 

112 "action": CheckFilesExistAction, 

113 "help": "configuration file associcated with each samples file.", 

114 "short": "-c", 

115 "key": "core", 

116 }, 

117 "--email": { 

118 "action": "store", 

119 "help": ( 

120 "send an e-mail to the given address with a link to the " 

121 "finished page." 

122 ), 

123 "key": "core", 

124 }, 

125 "--inj_file": { 

126 "dest": "inj_file", 

127 "nargs": "+", 

128 "action": CheckFilesExistAction, 

129 "help": "path to injetcion file", 

130 "short": "-i", 

131 "key": "core", 

132 }, 

133 "--user": { 

134 "dest": "user", 

135 "help": argparse.SUPPRESS, 

136 "default": conf.user, 

137 "key": "core", 

138 }, 

139 "--testing": { 

140 "action": "store_true", 

141 "help": argparse.SUPPRESS, 

142 "default": False, 

143 "key": "core", 

144 }, 

145 "--add_to_existing": { 

146 "action": "store_true", 

147 "default": False, 

148 "help": "add new results to an existing html page", 

149 "key": "core", 

150 }, 

151 "--existing_webdir": { 

152 "dest": "existing", 

153 "help": "web directory of existing output", 

154 "short": "-e", 

155 "key": "core", 

156 }, 

157 "--seed": { 

158 "dest": "seed", 

159 "default": 123456789, 

160 "type": int, 

161 "help": "Random seed to used through the analysis.", 

162 "key": "core", 

163 }, 

164 "--verbose": { 

165 "action": "store_true", 

166 "help": "print useful information for debugging purposes", 

167 "short": "-v", 

168 "key": "core", 

169 }, 

170 "--preferred": { 

171 "dest": "preferred", 

172 "help": ( 

173 "label of the preferred run. If only one result file is " 

174 "passed this label is the preferred analysis by default" 

175 ), 

176 "key": "core", 

177 }, 

178 "--ignore_parameters": { 

179 "dest": "ignore_parameters", 

180 "nargs": "+", 

181 "help": ( 

182 "Parameters that you wish to not include in the " 

183 "summarypages. You may list them or use wildcards " 

184 "('recalib*')" 

185 ), 

186 "key": "samples", 

187 }, 

188 "--nsamples": { 

189 "dest": "nsamples", 

190 "help": ( 

191 "The number of samples to use and store in the PESummary " 

192 "metafile. These samples will be randomly drawn from the " 

193 "posterior distributions" 

194 ), 

195 "key": "samples", 

196 }, 

197 "--keep_nan_likelihood_samples": { 

198 "dest": "keep_nan_likelihood_samples", 

199 "action": "store_true", 

200 "default": False, 

201 "help": ( 

202 "Do not remove posterior samples where the likelihood=" 

203 "'nan'. Without this option, posterior samples where the " 

204 "likelihood='nan' are removed by default." 

205 ), 

206 "key": "samples", 

207 }, 

208 "--burnin": { 

209 "dest": "burnin", 

210 "help": "Number of samples to remove as burnin", 

211 "key": "samples", 

212 }, 

213 "--burnin_method": { 

214 "dest": "burnin_method", 

215 "help": ( 

216 "The algorithm to use to remove mcmc samples as burnin. " 

217 "This is only used when `--mcmc_samples` also used" 

218 ), 

219 "key": "samples", 

220 }, 

221 "--regenerate": { 

222 "dest": "regenerate", 

223 "nargs": "+", 

224 "help": ( 

225 "List of posterior distributions that you wish to " 

226 "regenerate if possible" 

227 ), 

228 "key": "samples", 

229 }, 

230 "--mcmc_samples": { 

231 "action": "store_true", 

232 "default": False, 

233 "help": ( 

234 "treat the passed samples as seperate mcmc chains for the " 

235 "same analysis" 

236 ), 

237 "key": "samples", 

238 }, 

239 "--path_to_samples": { 

240 "nargs": "+", 

241 "help": ( 

242 "Path to the posterior samples stored in the result file. " 

243 "If None, pesummary will search for a 'posterior' or " 

244 "'posterior_samples' group. If more than one result file " 

245 "is passed, and only the third file requires a " 

246 "path_to_samples provide --path_to_samples None None " 

247 "path/to/samples" 

248 ), 

249 "key": "samples", 

250 }, 

251 "--pe_algorithm": { 

252 "nargs": "+", 

253 "help": "Name of the algorithm used to generate the result file", 

254 "key": "samples", 

255 }, 

256 "--reweight_samples": { 

257 "default": False, 

258 "help": ( 

259 "Method to use when reweighting posterior and/or prior " 

260 "samples. Default do not reweight samples." 

261 ), 

262 "key": "samples", 

263 }, 

264 "--descriptions": { 

265 "default": {}, 

266 "action": DictionaryAction, 

267 "nargs": "+", 

268 "help": ( 

269 "JSON file containing a set of descriptions for each " 

270 "analysis or dictionary giving descriptions for each " 

271 "analysis directly from the command line (e.g. " 

272 "`--descriptions label1:'description'`). These " 

273 "descriptions are then saved in the output." 

274 ), 

275 "key": "samples", 

276 }, 

277 "--custom_plotting": { 

278 "dest": "custom_plotting", 

279 "help": "Python file containing functions for custom plotting", 

280 "key": "plot", 

281 }, 

282 "--publication": { 

283 "action": "store_true", 

284 "help": "generate production quality plots", 

285 "key": "plot", 

286 }, 

287 "--publication_kwargs": { 

288 "action": DictionaryAction, 

289 "nargs": "+", 

290 "default": {}, 

291 "help": "Optional kwargs for publication plots", 

292 "key": "plot", 

293 }, 

294 "--kde_plot": { 

295 "action": "store_true", 

296 "default": False, 

297 "help": "plot a kde rather than a histogram", 

298 "key": "plot", 

299 }, 

300 "--colors": { 

301 "dest": "colors", 

302 "nargs": "+", 

303 "help": "Colors you wish to use to distinguish result files", 

304 "key": "plot", 

305 }, 

306 "--palette": { 

307 "dest": "palette", 

308 "default": "colorblind", 

309 "help": "Color palette to use to distinguish result files", 

310 "key": "plot", 

311 }, 

312 "--linestyles": { 

313 "dest": "linestyles", 

314 "nargs": "+", 

315 "help": "Linestyles you wish to use to distinguish result files", 

316 "key": "plot", 

317 }, 

318 "--include_prior": { 

319 "action": "store_true", 

320 "default": False, 

321 "help": "Plot the prior on the same plot as the posterior", 

322 "key": "plot", 

323 }, 

324 "--style_file": { 

325 "dest": "style_file", 

326 "action": CheckFilesExistAction, 

327 "help": "Style file you wish to use when generating plots", 

328 "key": "plot", 

329 }, 

330 "--add_to_corner": { 

331 "dest": "add_to_corner", 

332 "nargs": "+", 

333 "help": "Parameters you wish to include in the corner plot", 

334 "key": "plot", 

335 }, 

336 "--add_existing_plot": { 

337 "dest": "existing_plot", 

338 "nargs": "+", 

339 "action": DictionaryAction, 

340 "help": ( 

341 "Path(s) to existing plots that you wish to add to the " 

342 "summarypages. Should be of the form {label}:{path}" 

343 ), 

344 "key": "plot", 

345 }, 

346 "--dump": { 

347 "action": "store_true", 

348 "default": False, 

349 "help": "dump all information onto a single html page", 

350 "key": "webpage", 

351 }, 

352 "--notes": { 

353 "dest": "notes", 

354 "help": ( 

355 "Single file containing notes that you wish to put on " 

356 "summarypages" 

357 ), 

358 "key": "webpage", 

359 }, 

360 "--prior_file": { 

361 "dest": "prior_file", 

362 "nargs": "+", 

363 "action": CheckFilesExistAction, 

364 "help": "File containing for the prior samples for a given label", 

365 "key": "prior", 

366 }, 

367 "--nsamples_for_prior": { 

368 "dest": "nsamples_for_prior", 

369 "default": 5000, 

370 "type": int, 

371 "help": ( 

372 "The number of prior samples to extract from a bilby prior " 

373 "file or a bilby result file" 

374 ), 

375 "key": "prior", 

376 }, 

377 "--disable_prior_sampling": { 

378 "action": "store_true", 

379 "default": False, 

380 "help": "Skip generating prior samples using bilby", 

381 "key": "prior" 

382 }, 

383 "--disable_comparison": { 

384 "action": "store_true", 

385 "default": False, 

386 "help": ( 

387 "Whether to make a comparison webpage if multple results " 

388 "are present" 

389 ), 

390 "key": "performance", 

391 }, 

392 "--disable_interactive": { 

393 "action": "store_true", 

394 "default": False, 

395 "help": "Whether to make interactive plots or not", 

396 "key": "performance", 

397 }, 

398 "--disable_corner": { 

399 "action": "store_true", 

400 "default": False, 

401 "help": "Whether to make a corner plot or not", 

402 "key": "performance", 

403 }, 

404 "--enable_expert": { 

405 "action": "store_true", 

406 "default": False, 

407 "help": "Whether to generate extra diagnostic plots or not", 

408 "key": "performance", 

409 }, 

410 "--disable_expert": { 

411 "action": DeprecatedStoreFalseAction( 

412 new_option="--enable_expert" 

413 ), 

414 "dest": "enable_expert", 

415 "default": False, 

416 "help": "Whether to generate extra diagnostic plots or not", 

417 "key": "performance", 

418 }, 

419 "--multi_process": { 

420 "dest": "multi_process", 

421 "default": 1, 

422 "type": int, 

423 "help": "The number of cores to use when generating plots", 

424 "key": "performance", 

425 }, 

426 "--file_format": { 

427 "dest": "file_format", 

428 "nargs": "+", 

429 "help": "The file format of each result file.", 

430 "key": "performance", 

431 }, 

432 "--restart_from_checkpoint": { 

433 "action": "store_true", 

434 "default": False, 

435 "help": ( 

436 "Restart from checkpoint if a checkpoint file can be found " 

437 "in webdir" 

438 ), 

439 "key": "performance", 

440 }, 

441 "--compare_results": { 

442 "dest": "compare_results", 

443 "nargs": "+", 

444 "help": ( 

445 "labels for events stored in the posterior_samples.json " 

446 "that you wish to compare" 

447 ), 

448 "key": "metafile", 

449 }, 

450 "--save_to_json": { 

451 "action": "store_true", 

452 "default": False, 

453 "help": "save the meta file in json format", 

454 "key": "metafile", 

455 }, 

456 "--posterior_samples_filename": { 

457 "dest": "filename", 

458 "help": "name of the posterior samples metafile that is produced", 

459 "key": "metafile", 

460 }, 

461 "--external_hdf5_links": { 

462 "action": "store_true", 

463 "default": False, 

464 "help": ( 

465 "save each analysis as a seperate hdf5 file and connect " 

466 "them to the meta file through external links" 

467 ), 

468 "key": "metafile", 

469 }, 

470 "--hdf5_compression": { 

471 "dest": "hdf5_compression", 

472 "type": int, 

473 "help": ( 

474 "compress each dataset with a particular compression " 

475 "filter. Filter must be integer between 0 and 9. Only " 

476 "applies to meta files stored in hdf5 format. Default, no " 

477 "compression applied" 

478 ), 

479 "key": "metafile", 

480 }, 

481 "--disable_injection": { 

482 "action": "store_true", 

483 "default": False, 

484 "help": ( 

485 "whether or not to extract stored injection data from the " 

486 "meta file" 

487 ), 

488 "key": "metafile", 

489 }, 

490 } 

491 

492 def add_known_options_to_parser_from_key(self, parser, key): 

493 options = [ 

494 option for option, _dict in self.pesummary_options.items() if 

495 _dict["key"] == key 

496 ] 

497 return self.add_known_options_to_parser(options, parser=parser) 

498 

499 def add_core_group(self): 

500 core_group = self.add_argument_group( 

501 "Core command line options\n" 

502 "-------------------------" 

503 ) 

504 return self.add_known_options_to_parser_from_key(core_group, "core") 

505 

506 def add_samples_group(self): 

507 sample_group = self.add_argument_group( 

508 "Options specific to the samples you wish to input\n" 

509 "-------------------------------------------------" 

510 ) 

511 return self.add_known_options_to_parser_from_key(sample_group, "samples") 

512 

513 def add_plot_group(self): 

514 plot_group = self.add_argument_group( 

515 "Options specific to the plots you wish to make\n" 

516 "----------------------------------------------" 

517 ) 

518 return self.add_known_options_to_parser_from_key(plot_group, "plot") 

519 

520 def add_webpage_group(self): 

521 webpage_group = self.add_argument_group( 

522 "Options specific to the webpages you wish to produce\n" 

523 "----------------------------------------------------" 

524 ) 

525 return self.add_known_options_to_parser_from_key(webpage_group, "webpage") 

526 

527 def add_prior_group(self): 

528 prior_group = self.add_argument_group( 

529 "Options specific for passing prior files\n" 

530 "----------------------------------------" 

531 ) 

532 return self.add_known_options_to_parser_from_key(prior_group, "prior") 

533 

534 def add_performance_group(self): 

535 performance_group = self.add_argument_group( 

536 "Options specific for enhancing the performance of the code\n" 

537 "----------------------------------------------------------" 

538 ) 

539 return self.add_known_options_to_parser_from_key( 

540 performance_group, "performance" 

541 ) 

542 

543 def add_metafile_group(self): 

544 metafile_group = self.add_argument_group( 

545 "Options specific for reading and manipulating pesummary metafiles\n" 

546 "-----------------------------------------------------------------" 

547 ) 

548 return self.add_known_options_to_parser_from_key(metafile_group, "metafile") 

549 

550 def add_all_groups_to_parser(self): 

551 self.add_core_group() 

552 self.add_samples_group() 

553 self.add_plot_group() 

554 self.add_webpage_group() 

555 self.add_prior_group() 

556 self.add_performance_group() 

557 self.add_metafile_group() 

558 

559 def add_all_known_options_to_parser(self): 

560 """Add all known pesummary options to the parser 

561 """ 

562 return self.add_known_options_to_parser(self.pesummary_options.keys()) 

563 

564 def add_additional_options_to_parser(self, options, parser=None): 

565 """Add additional options to the parser 

566 

567 Parameters 

568 ---------- 

569 options: dict 

570 dictionary containing options. Key should be the option name 

571 and value should be option kwargs. 

572 parser: ArgumentParser, optional 

573 parser to use when adding additional options. Default self 

574 """ 

575 import inspect 

576 if parser is None: 

577 parser = self 

578 for name, kwargs in options.items(): 

579 _kwargs = self.fallback_options.copy() 

580 _kwargs.update(kwargs) 

581 if "short" in _kwargs.keys(): 

582 args = [_kwargs["short"], name] 

583 else: 

584 args = [name] 

585 if "key" in _kwargs.keys(): 

586 _kwargs.pop("key") 

587 if "-" not in name: 

588 # must be a positional argument 

589 _kwargs.pop("dest") 

590 

591 action = _kwargs.get('action', None) 

592 action_class = self._registry_get('action', action, action) 

593 arglist = inspect.getfullargspec(action_class).args 

594 keys = _kwargs.copy().keys() 

595 for key in keys: 

596 if (key not in arglist) and (key != "action"): 

597 _kwargs.pop(key) 

598 parser.add_argument(*args, **_kwargs) 

599 return parser 

600 

601 def add_known_options_to_parser(self, options, parser=None): 

602 """Add a list of known options to the parser 

603 

604 Parameters 

605 ---------- 

606 options: list 

607 list of option names that you wish to add to the parser. Option 

608 names must be known to the pesummary ArgumentParser 

609 parser: ArgumentParser, optional 

610 parser to use when adding additional options. Default self 

611 """ 

612 _options = {} 

613 for option in options: 

614 if option not in self.pesummary_options.keys(): 

615 raise ValueError("Unknown option '{}'".format(option)) 

616 _options[option] = self.pesummary_options[option] 

617 return self.add_additional_options_to_parser(_options, parser=parser) 

618 

619 @property 

620 def dynamic_argparse(self): 

621 return [] 

622 

623 @property 

624 def command_line_arguments(self): 

625 return sys.argv[1:] 

626 

627 @property 

628 def command_line(self): 

629 return "{} {}".format(self.prog, " ".join(self.command_line_arguments)) 

630 

631 @staticmethod 

632 def intersection(a, b): 

633 """Return the common entries between two lists 

634 """ 

635 return list(set(a).intersection(set(b))) 

636 

637 def parse_known_args( 

638 self, *args, logger_level={"known": "info", "unknown": "warn"}, **kwargs 

639 ): 

640 """Parse known command line arguments and return unknown quantities 

641 

642 Parameters 

643 ---------- 

644 args: list, optional 

645 optional list of command line arguments you wish to pass 

646 logger_level: dict, optional 

647 dictionary containing the logger levels to use when printing the 

648 known and unknown quantities to stdout. Default 

649 {"known": "info", "unknown": "warn"} 

650 """ 

651 opts, unknown = super(ArgumentParser, self).parse_known_args( 

652 *args, **kwargs 

653 ) 

654 for dynamic in self.dynamic_argparse: 

655 _, _unknown = dynamic(opts, command_line=kwargs.get("args", None)) 

656 unknown = self.intersection(unknown, _unknown) 

657 getattr(logger, logger_level["known"])( 

658 "Command line arguments: %s" % (opts) 

659 ) 

660 if len(unknown): 

661 getattr(logger, logger_level["unknown"])( 

662 "Unknown command line arguments: {}".format( 

663 " ".join([cmd for cmd in unknown if "--" in cmd]) 

664 ) 

665 ) 

666 return opts, unknown 

667 

668 

669def convert_dict_to_namespace(dictionary, add_defaults=None): 

670 """Convert a dictionary to an argparse.Namespace object 

671 

672 Parameters 

673 ---------- 

674 dictionary: dict 

675 dictionary you wish to convert to an argparse.Namespace object 

676 """ 

677 from argparse import Namespace 

678 

679 _namespace = Namespace() 

680 for key, item in dictionary.items(): 

681 setattr(_namespace, key, item) 

682 if add_defaults is not None: 

683 _opts = add_defaults.parse_args(args="") 

684 for key in vars(_opts): 

685 if key not in dictionary.keys(): 

686 setattr(_namespace, key, add_defaults.get_default(key)) 

687 return _namespace