"""
ANDES command-line interface and argument parsers.
"""
import argparse
import importlib
import logging
import platform
import sys
from time import strftime
from andes.shared import NCPUS_PHYSICAL
from andes.main import config_logger, find_log_path
from andes.routines import routine_cli
from andes.utils.paths import get_log_dir
logger = logging.getLogger(__name__)
command_aliases = {
'prepare': ['prep'],
'selftest': ['st'],
}
[docs]def create_parser():
"""
Create a parser for the command-line interface.
Returns
-------
argparse.ArgumentParser
Parser with all ANDES options
"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-v', '--verbose',
help='Verbosity level in 10-DEBUG, 20-INFO, 30-WARNING, '
'or 40-ERROR.',
type=int, default=20, choices=(1, 10, 20, 30, 40))
sub_parsers = parser.add_subparsers(dest='command', help='[run] run simulation routine; '
'[plot] plot results; '
'[doc] quick documentation; '
'[misc] misc. functions; '
'[prepare] prepare the numerical code; '
'[selftest] run self test; '
)
run = sub_parsers.add_parser('run')
run.add_argument('filename', help='Case file name. Power flow is calculated by default.', nargs='*')
run.add_argument('-r', '--routine', nargs='*', default=('pflow', ),
action='store', help='Simulation routine(s). Single routine or multiple separated with '
'space. Run PFlow by default.',
choices=list(routine_cli.keys()))
run.add_argument('-p', '--input-path', help='Path to case files', type=str, default='')
run.add_argument('-a', '--addfile', help='Additional files used by some formats.')
run.add_argument('-P', '--pert', help='Perturbation file path', default='')
run.add_argument('-o', '--output-path', help='Output path prefix', type=str, default='')
run.add_argument('-n', '--no-output', help='Force no output of any kind', action='store_true')
run.add_argument('--ncpu', help='Number of parallel processes', type=int, default=NCPUS_PHYSICAL)
run.add_argument('--dime-address', help='DiME streaming server protocol, address and port,'
'e.g., tcp://127.0.0.1:5000 or ipc:///tmp/dime2', type=str)
run.add_argument('--tf', help='End time of time-domain simulation', type=float)
run.add_argument('--qrt', help='Enable quasi-real-time stepping', action='store_true')
run.add_argument('--kqrt', help='Scaling factor for quasi-real-time; e.g., kqrt=2 means the wall-clock time '
'to complete a simulation is 2x the time of that simulation', type=float)
run.add_argument('-c', '--convert', help='Convert to format.', type=str, default='', nargs='?')
run.add_argument('-b', '--add-book', help='Add a template workbook for the specified model.', type=str)
run.add_argument('--convert-all', help='Convert to format with all templates.', type=str, default='',
nargs='?')
run.add_argument('--state-matrix', help='Export state matrix to a .mat file. Need to run with `-r eig`',
action='store_true')
run.add_argument('--profile', action='store_true', help='Enable Python cProfiler')
run.add_argument('-s', '--shell', action='store_true', help='Start in IPython shell')
run.add_argument('--no-preamble', action='store_true', help='Hide preamble')
run.add_argument('--no-pbar', action='store_true', help='Hide progress bar for time-domain')
run.add_argument('--flat', action='store_true', help='Run no-disturbance (flat) simulation')
run.add_argument('--pool', action='store_true', help='Start multiprocess with Pool '
'and return a list of Systems')
run.add_argument('--from-csv', help='Use data from a CSV file instead of from simulation')
run.add_argument('-O', '--config-option',
help='Set configuration option specificied by '
'NAME.FIELD=VALUE with no space. For example, "TDS.tf=2"',
type=str, default='', nargs='*')
run.add_argument('--init', help='Initialize variables only for time-domain simulation without running',
action='store_true')
plot = sub_parsers.add_parser('plot')
plot.add_argument('filename', nargs=1, default=[], help='simulation output file name, which should end '
'with `out`. File extension can be omitted.')
plot.add_argument('x', nargs='?', type=int, help='the X-axis variable index, typically 0 for Time',
default='0')
plot.add_argument('y', nargs='*', help='Y-axis variable indices. Space-separated indices or a '
'colon-separated range is accepted')
plot.add_argument('--xmin', type=float, help='minimum value for X axis', dest='left')
plot.add_argument('--xmax', type=float, help='maximum value for X axis', dest='right')
plot.add_argument('--ymax', type=float, help='maximum value for Y axis')
plot.add_argument('--ymin', type=float, help='minimum value for Y axis')
find_or_xargs = plot.add_mutually_exclusive_group()
find_or_xargs.add_argument('--find', type=str, help='find variable indices that matches the given pattern')
find_or_xargs.add_argument('-a', '--xargs', type=str,
help='find variable indices and return as a list of arguments '
'usable with "|xargs andes plot"')
plot.add_argument('--exclude', type=str, help='pattern to exclude in find or xargs results')
plot.add_argument('-x', '--xlabel', type=str, help='x-axis label text')
plot.add_argument('-y', '--ylabel', type=str, help='y-axis label text')
plot.add_argument('-s', '--savefig', action='store_true', help='save figure. The default format is `png`')
plot.add_argument('-f', '--format', dest='save_format',
help='format for savefig. Common formats such as png, pdf, jpg are supported')
plot.add_argument('--dpi', type=int, help='image resolution in dot per inch (DPI)')
plot.add_argument('--font-size', type=float, help='Text font size', default=12)
plot.add_argument('--line-width', type=float, help='Plot line width', default=1.5)
plot.add_argument('-g', '--grid', action='store_true', help='grid on')
plot.add_argument('-t', '--title', help='title text')
plot.add_argument('--greyscale', action='store_true', help='greyscale on')
plot.add_argument('-d', '--no-latex', action='store_false', dest='latex', help='disable LaTex formatting')
plot.add_argument('-n', '--no-show', action='store_false', dest='show', help='do not show the plot window')
plot.add_argument('--ytimes', type=str, help='scale the y-axis values by YTIMES')
plot.add_argument('-c', '--to-csv', help='convert npy output to csv', action='store_true')
plot.add_argument('--hline1', help='dashed horizontal line 1', type=float)
plot.add_argument('--hline2', help='dashed horizontal line 2', type=float)
plot.add_argument('--vline1', help='dashed vertical line 1', type=float)
plot.add_argument('--vline2', help='dashed vertical line 2', type=float)
doc = sub_parsers.add_parser('doc')
doc.add_argument('attribute', help='System attribute name to get documentation', nargs='?')
doc.add_argument('--config', '-c', help='Config help')
doc.add_argument('--list', '-l', help='List supported models and groups', action='store_true',
dest='list_supported')
doc.add_argument('--init-seq', help='Show model initialization sequence', action='store_true',
)
misc = sub_parsers.add_parser('misc')
config_exclusive = misc.add_mutually_exclusive_group()
config_exclusive.add_argument('--edit-config', help='Quick edit of the config file',
default='', nargs='?', type=str)
config_exclusive.add_argument('--save-config', help='save configuration to file name',
nargs='?', type=str, default='')
misc.add_argument('--license', action='store_true', help='Display software license', dest='show_license')
misc.add_argument('-C', '--clean', help='Clean output files', action='store_true')
misc.add_argument('-r', '--recursive', help='Recursively clean outputs (combined useage with --clean)',
action='store_true')
misc.add_argument('-O', '--config-option',
help='Set configuration option specificied by '
'NAME.FIELD=VALUE with no space. For example, "TDS.tf=2"',
type=str, default='', nargs='*')
misc.add_argument('--version', action='store_true', help='Display version information')
prep = sub_parsers.add_parser('prepare', aliases=command_aliases['prepare'])
prep_mode = prep.add_mutually_exclusive_group()
prep_mode.add_argument('-q', '--quick', action='store_true',
help='quick codegen by skipping pretty prints')
prep_mode.add_argument('-f', '--full', action='store_true', help='full codegen')
prep_mode.add_argument('-i', '--incremental', action='store_true',
help='rapid incrementally generate for updated models')
prep.add_argument('-c', '--compile', help='compile the code with numba after codegen',
action='store_true', dest='precompile')
prep.add_argument('--pycode-path', help='Save path for generated pycode')
prep.add_argument('-m', '--models', nargs='*', help='model names to be individually prepared',
)
prep.add_argument('--ncpu', help='Number of parallel processes', type=int, default=NCPUS_PHYSICAL)
prep.add_argument('--nomp', help='Disable multiprocessing', action='store_true',)
prep.add_argument('--incubate', help='Save generated pycode under the ANDES code directory to avoid codegen',
action='store_true')
selftest = sub_parsers.add_parser('selftest', aliases=command_aliases['selftest'])
selftest.add_argument('-q', '--quick', action='store_true',
help='quick selftest by skipping codegen')
demo = sub_parsers.add_parser('demo') # NOQA
return parser
[docs]def preamble():
"""
Log the ANDES command-line preamble at the `logging.INFO` level
"""
from andes import __version__ as version
py_version = platform.python_version()
system_name = platform.system()
date_time = strftime('%m/%d/%Y %I:%M:%S %p')
logger.info("\n"
rf" _ _ | Version {version}" + '\n'
rf" /_\ _ _ __| |___ ___ | Python {py_version} on {system_name}, {date_time}" + '\n'
r" / _ \| ' \/ _` / -_|_-< | " + "\n"
r' /_/ \_\_||_\__,_\___/__/ | This program comes with ABSOLUTELY NO WARRANTY.' + '\n')
log_path = find_log_path(logging.getLogger("andes"))
if len(log_path):
logger.debug('Logging to file "%s"', log_path[0])
[docs]def main():
"""
Entry point of the ANDES command-line interface.
"""
parser = create_parser()
args = parser.parse_args()
# Set up logging
config_logger(stream=True,
stream_level=args.verbose,
file=True,
log_path=get_log_dir(),
)
logger.debug(args)
module = importlib.import_module('andes.main')
if args.command in ('plot', 'doc', 'misc'):
pass
elif args.command == 'run' and args.no_preamble is True:
pass
else:
preamble()
# Run the command
if args.command is None:
parser.parse_args(sys.argv.append('--help'))
else:
cmd = args.command
for fullcmd, aliases in command_aliases.items():
if cmd in aliases:
cmd = fullcmd
func = getattr(module, cmd)
return func(cli=True, **vars(args))