Coverage for pesummary/core/cli/parser.py: 95.7%
116 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
1# Licensed under an MIT style license -- see LICENSE.md
3import sys
4import argparse
5from ... import conf
6from ...utils.utils import logger
7from .actions import (
8 ConfigAction, CheckFilesExistAction, DictionaryAction,
9 DeprecatedStoreFalseAction
10)
12__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"]
15class ArgumentParser(argparse.ArgumentParser):
16 """Extension of the argparse.ArgumentParser object to handle pesummary
17 command line arguments.
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 }
44 @property
45 def pesummary_options(self):
46 return self._pesummary_options()
48 def update_default_pesummary_option(self, option, key, value):
49 """Update the default dictionary of pesummary option
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()
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 }
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)
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")
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")
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")
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")
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")
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 )
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")
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()
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())
564 def add_additional_options_to_parser(self, options, parser=None):
565 """Add additional options to the parser
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")
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
601 def add_known_options_to_parser(self, options, parser=None):
602 """Add a list of known options to the parser
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)
619 @property
620 def dynamic_argparse(self):
621 return []
623 @property
624 def command_line_arguments(self):
625 return sys.argv[1:]
627 @property
628 def command_line(self):
629 return "{} {}".format(self.prog, " ".join(self.command_line_arguments))
631 @staticmethod
632 def intersection(a, b):
633 """Return the common entries between two lists
634 """
635 return list(set(a).intersection(set(b)))
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
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
669def convert_dict_to_namespace(dictionary, add_defaults=None):
670 """Convert a dictionary to an argparse.Namespace object
672 Parameters
673 ----------
674 dictionary: dict
675 dictionary you wish to convert to an argparse.Namespace object
676 """
677 from argparse import Namespace
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