Source code for ssp_parse_arguments

# -*- coding: utf-8 -*-
# SPDX-License-Identifier: CECILL-2.1
"""
Argument parser for sourcespec.

:copyright:
    2021-2026 Claudio Satriano <satriano@ipgp.fr>
:license:
    CeCILL Free Software License Agreement v2.1
    (http://www.cecill.info/licences.en.html)
"""
import sys
from argparse import ArgumentParser, RawTextHelpFormatter
from itertools import zip_longest
from sourcespec._version import get_versions


def _parse_values(value_str):
    # Lazy-import for speed
    # pylint: disable=import-outside-toplevel
    import numpy as np
    if value_str[0] == 'i':
        value_str = value_str[1:]
        val_min, val_max, val_step =\
            tuple(map(float, value_str.rstrip(',').split(',')))
        # we add a small number to val_max to make sure
        # it is included by np.arange()
        output = tuple(np.arange(val_min, val_max + 1e-9, val_step))
    else:
        try:
            output = tuple(map(float, value_str.rstrip(',').split(',')))
        except ValueError:
            sys.stderr.write(f'ERROR: Invalid value: {value_str}\n')
            sys.exit(1)
    return output


def _get_description(progname):
    if progname == 'source_spec':
        nargs = '+'
        description = (
            'Estimation of seismic source parameters from '
            'inversion of P- or S-wave displacement spectra.'
        )
        epilog = (
            'Check the online documentation at '
            'https://sourcespec.rtfd.io for more details.'
        )
    elif progname == 'source_model':
        nargs = '*'
        description = 'Direct modeling of P- or S-wave displacement spectra.'
        epilog = (
            'Several values of moment magnitude, seismic moment, t-star\n'
            'and alpha can be specified using a comma-separated list,'
            'eg.:\n'
            '  --mag=3,3.5,4,4.5\n\n'
            'A value interval can be specified by prepending "i" and\n'
            'indicating min, max and step, eg.:\n'
            '  --fc=i1.0,5.0,0.5\n\n'
            'When specifying several values, by default a simple\n'
            'correspondence is established, e.g.:\n'
            '  --mag=3,3.5 --fc=1.0,2.0,3.0\n'
            'will generate the couples:\n'
            '  (3, 1.0), (3.5, 2.0), (3.5, 3.0)\n'
            '(note that the magnitude value "3.5" is repeated twice).\n'
            'Use "-C" to generate all the possible combinations.'
        )
    else:
        sys.stderr.write(f'Wrong program name: {progname}\n')
        sys.exit(1)
    return description, epilog, nargs


def _init_parser(description, epilog, nargs):
    parser = ArgumentParser(
        description=description, epilog=epilog,
        formatter_class=RawTextHelpFormatter
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        '-S', '--sampleconf', dest='sampleconf',
        action='store_true', default=False,
        help='write sample configuration to file and exit'
    )
    group.add_argument(
        '-U', '--updateconf', dest='updateconf',
        action='store', default=None,
        help='update an existing config file from a previous version',
        metavar='FILE'
    )
    group.add_argument(
        '-u', '--updatedb', dest='updatedb',
        action='store', default=None,
        help='update an existing SourceSpec database from a previous version',
        metavar='DATABASE_FILE'
    )
    group.add_argument(
        '-y', '--samplesspevent', dest='samplesspevent',
        action='store_true', default=False,
        help='write sample SourceSpec Event File and exit'
    )
    parser.add_argument(
        '-c', '--configfile', dest='config_file',
        action='store', default='source_spec.conf',
        help='load configuration from FILE (default: source_spec.conf)',
        metavar='FILE'
    )
    group.add_argument(
        '-t', '--trace_path', nargs=nargs,
        help='path to trace file(s) or trace dir. It can be any format\n'
             'supported by ObsPy (e.g. miniSEED, SAC, etc.), a directory\n'
             'containing such files, or a TAR(GZ) or ZIP archive containing\n'
             'such files'
    )
    parser.add_argument(
        '-q', '--qmlfile', dest='qml_file',
        action='store', default=None,
        help='get picks and hypocenter information from QuakeML FILE',
        metavar='FILE'
    )
    parser.add_argument(
        '-H', '--hypocenter', dest='hypo_file',
        action='store', default=None,
        help='get hypocenter information from FILE.\n'
             'Supported formats: SourceSpec Event File, HYPO71,\n'
             'HYPOINVERSE-2000.\n'
             'If this file contains picks, they will be parsed as well.',
        metavar='FILE'
    )
    parser.add_argument(
        '-d', '--depth', dest='depth',
        action='store', type=float, default=None,
        help='fix event depth to DEPTH (km), overriding any depth in\n'
             'hypocenter file',
        metavar='DEPTH'
    )
    parser.add_argument(
        '-p', '--pickfile', dest='pick_file',
        action='store', default=None,
        help='get picks from FILE. Supported formats: HYPO71',
        metavar='FILE'
    )
    parser.add_argument(
        '-w', '--station_metadata', dest='station_metadata',
        action='store', default=None,
        help='get station metadata from FILE (directory or single file\n'
             'name). Supported format: StationXML, dataless SEED, SEED\n'
             'RESP, PAZ (SAC polezero format).\n'
             'This option overrides the config parameter with the same name.',
        metavar='FILE'
    )
    parser.add_argument(
        '-n', '--evname', dest='evname',
        action='store', default=None,
        help='event name (used for plots and output files) ',
        metavar='NAME'
    )
    parser.add_argument(
        '-e', '--evid', dest='evid',
        action='store', default=None,
        help='select this EVID from QuakeML or SourceSpec Event File',
        metavar='EVID'
    )
    parser.add_argument(
        '-s', '--station', dest='station',
        action='store', default=None,
        help='only use this station', metavar='STATION'
    )
    return parser


def _update_parser(parser, progname):
    if progname == 'source_spec':
        _update_parser_for_source_spec(parser)
    elif progname == 'source_model':
        _update_parser_for_source_model(parser)
    parser.add_argument(
        '-v', '--version', action='version',
        version=get_versions()['version']
    )


def _update_parser_for_source_spec(parser):
    parser.add_argument(
        '-o', '--outdir', dest='outdir',
        action='store', default='sspec_out',
        help='save output to OUTDIR (default: sspec_out).\n'
             'The results are further organized in event subdirectories\n'
             'named after the event id',
        metavar='OUTDIR'
    )
    parser.add_argument(
        '-r', '--run_id', dest='run_id',
        action='store', default='',
        help='string identifying current run (default: none)\n'
             'If specified, it will be appended to the event directory name\n'
             'unless the option "-R" is used, in which case it will be used\n'
             'as a subdirectory of the event directory.',
        metavar='RUN_ID'
    )
    parser.add_argument(
        '-R', '--run_id_subdir', dest='run_id_subdir',
        action='store_true', default=False,
        help='use run_id as a subdirectory of the event directory\n'
             '(default: False)'
    )


def _update_parser_for_source_model(parser):
    parser.add_argument(
        '-f', '--fmin', dest='fmin', action='store',
        type=float, default='0.01',
        help='minimum frequency (Hz, default 0.01)',
        metavar='FMIN'
    )
    parser.add_argument(
        '-F', '--fmax', dest='fmax', action='store',
        type=float, default='50.0',
        help='maximum frequency (Hz, default 50.0)',
        metavar='FMAX'
    )
    parser.add_argument(
        '-k', '--fc', dest='fc', action='store',
        default='10.0',
        help='(list of) corner frequency (Hz, default 10.0)',
        metavar='Fc'
    )
    parser.add_argument(
        '-m', '--mag', dest='mag', action='store',
        default='2.0',
        help='(list of) moment magnitude (default 2.0)',
        metavar='Mw'
    )
    parser.add_argument(
        '-M', '--moment', dest='Mo', action='store',
        default='NaN',
        help='(list of) seismic moment (N·m, default undefined)',
        metavar='Mo'
    )
    parser.add_argument(
        '-*', '--tstar', dest='t_star', action='store',
        default='0.0',
        help='(list of) t-star (attenuation, default 0.0)',
        metavar='T-STAR'
    )
    parser.add_argument(
        '-a', '--alpha', dest='alpha', action='store',
        default='1.0',
        help='(list of) alpha (exponent for frequency dependence\n'
             'of attenuation, default 1.0)',
        metavar='1.0'
    )
    parser.add_argument(
        '-C', '--combine', dest='combine',
        action='store_true', default=False,
        help='generate all the combinations of fc, mag, Mo, tstar, alpha'
    )
    parser.add_argument(
        '-P', '--plot', dest='plot', action='store_true',
        default=False, help='plot results'
    )


def _parse_args_for_source_model(options):
    options.mag = _parse_values(options.mag)
    options.Mo = _parse_values(options.Mo)
    options.alpha = _parse_values(options.alpha)
    options.fc = _parse_values(options.fc)
    options.t_star = _parse_values(options.t_star)

    if options.combine:
        oplist = [(fc, mag, Mo, t_star, alpha)
                  for fc in options.fc
                  for mag in options.mag
                  for Mo in options.Mo
                  for t_star in options.t_star
                  for alpha in options.alpha]
        oplist = list(map(list, zip(*oplist)))
    else:
        # Add trailing "None" to shorter lists and zip:
        oplist = [options.fc, options.mag, options.Mo,
                  options.t_star, options.alpha]
        oplist = list(zip_longest(*oplist))
        # Unzip and convert tuple to lists:
        oplist = list(map(list, zip(*oplist)))
        for opt in oplist:
            for n, x in enumerate(opt):
                if x is None:
                    opt[n] = opt[n - 1]

    options.fc, options.mag, options.Mo, options.t_star, options.alpha = \
        oplist
    # Add unused options (required by source_spec):
    options.correction = False
    options.outdir = '.'


[docs] def parse_args(progname): """Parse command line arguments.""" description, epilog, nargs = _get_description(progname) parser = _init_parser(description, epilog, nargs) _update_parser(parser, progname) options = parser.parse_args() if progname == 'source_model': _parse_args_for_source_model(options) return options