Source code for ell1fit.cli

"""Command-line interface for the ``ell1fit`` script.

Argument parsing and nothing else: every option here maps onto a parameter of
:func:`ell1fit.pipeline.ell1fit`, which is where the work happens and where the
behaviour is documented.
"""

import argparse

from .likelihoods import pletsch_clarke_likelihood
from .likelihoods import rayleigh_as_likelihood
from .logging import configure_logging
from .pipeline import ell1fit
from .plotting import add_figure_format_argument, set_figure_format
from .priors import parse_prior_specs


__all__ = [
    "main",
]


[docs] def main(args=None): """Main function called by the `ell1fit` script""" configure_logging() description = "Fit an ELL1 model and frequency derivatives to an X-ray pulsar observation." parser = argparse.ArgumentParser(description=description) parser.add_argument("files", help="List of files", nargs="+") parser.add_argument( "-p", "--parfile", type=str, nargs="+", default=None, help=( "Input parameter files, one per event file. Must contain a simple ELL1 binary model, " "with no orbital derivatives, and a number of spin derivatives (F0, F1, ...). " "All other models will be ignored." ), ) parser.add_argument("-o", "--outroot", type=str, default=None, help="Root of output file names") parser.add_argument( "-N", "--nharm", type=int, help="Number of harmonics to describe the pulse profile", default=1, ) parser.add_argument( "--deorb-tolerance", type=float, help="Tolerance of deorbit operation, in seconds", default=1e-8, ) parser.add_argument( "-E", "--erange", nargs=2, type=float, help="Energy range", default=None, ) parser.add_argument( "--nsteps", type=int, help="Maximum number of MCMC steps", default=100_000, ) parser.add_argument( "-P", "--parameters", type=str, help="Comma-separated list of parameters to fit", default="F0,F1", ) parser.add_argument( "--likelihood", type=str, help="Can be PC (Pletsch & Clarke, default) or Rayleigh", default="PC", ) parser.add_argument( "--minimize-first", action="store_true", default=False, help="Minimize first, then MCMC (don't trust the solution in the par file)", ) parser.add_argument( "--use-weight", action="store_true", default=False, help="Use pulse energy dependence of profile as weight", ) parser.add_argument( "--use-pi", action="store_true", default=False, help=( "Base pulsed-fraction weighting (--use-weight) on PI channels instead of " "calibrated energy. No effect without --use-weight." ), ) parser.add_argument( "--template-iterations", type=int, default=1, help=( "Maximum passes of iterative template refinement. Each pass refolds " "with the current best solution and rebuilds the pulse template, so " "the template is not the one smeared by errors in the input parfile. " "1 (default) disables refinement." ), ) parser.add_argument("--ignore-uncertainties", action="store_true", default=False) parser.add_argument( "--prior", action="append", default=None, metavar="NAME:SHAPE:ARGS", help=( "Override the prior on one parameter; repeat for more. SHAPE is " "uniform or normal. ARGS is either two numbers -- bounds for " "uniform, mean and sigma for normal -- or +-WIDTH to centre the " "prior on the parameter's input value. Values are in the same " "units as the parfile. A bare spin-parameter name covers every " "file (F1 means F1_0, F1_1, ...). Examples: " "--prior F1:uniform:-1e-10,1e-10 --prior TASC:normal:+-1e-6" ), ) parser.add_argument( "--sampler", type=str, choices=["emcee", "nuts", "nested"], default="emcee", help=( "Posterior-exploration backend. 'emcee' (default) is always available. " "'nuts' and 'nested' need extra dependencies: pip install ell1fit[nuts] " "or ell1fit[nested]. Only 'nested' produces log_evidence, which is what " "a model-comparison Bayes factor needs." ), ) parser.add_argument( "--nlive", type=int, default=1000, help=( "Live points for --sampler nested. Ignored otherwise. Nested sampling " "can miss a narrow mode and still report a confident, wrong evidence, " "so raise this well past the default on anything but a small fit." ), ) parser.add_argument( "--dlogz", type=float, default=0.1, help="Evidence-remaining stopping criterion for --sampler nested. Ignored otherwise.", ) parser.add_argument( "--workers", type=int, default=0, help=( "Worker processes for --sampler nested. Ignored otherwise. 0 (the " "default) runs single-process; the prior transform always does, " "regardless of this setting -- only likelihood evaluations spread " "across workers." ), ) add_figure_format_argument(parser) args = parser.parse_args(args) # Parsed here rather than deep in the pipeline so a typo is reported as a # usage error in the first second, not after the events are loaded. try: prior_specs = parse_prior_specs(args.prior) except ValueError as error: parser.error(str(error)) set_figure_format(args.figure_format) files = args.files parfiles = args.parfile like = pletsch_clarke_likelihood if args.likelihood.lower() == "rayleigh": like = rayleigh_as_likelihood ell1fit( files, parfiles, nsteps=args.nsteps, nharm=args.nharm, tolerance=args.deorb_tolerance, energy_range=args.erange, fit_parameters=args.parameters.split(","), minimize_first=args.minimize_first, general_outroot=args.outroot, likelihood_func=like, use_weight=args.use_weight, use_pi=args.use_pi, ignore_uncertainties=args.ignore_uncertainties, template_iterations=args.template_iterations, priors=prior_specs, sampler=args.sampler, nlive=args.nlive, dlogz=args.dlogz, workers=args.workers, )
if __name__ == "__main__": # pragma: no cover # Without this, "python -m ell1fit.cli ..." silently does nothing at all. main()