Source code for schrodinger.application.mopac.mopac_parser

"""
Command line parsing for the MOPAC backend.
"""
# Contributor: Mark A. Watson

import argparse
import os
import textwrap

import schrodinger.application.jaguar.utils as jag_utils
from schrodinger.application.mopac import mopac_launchers
from schrodinger.application.mopac import utils
from schrodinger.application.mopac.exceptions import MopacRuntimeError
from schrodinger.application.mopac.results71 import MOPAC71
from schrodinger.application.mopac.results_main import MOPAC_MAIN
from schrodinger.utils import fileutils

NOJOBID_DEFAULT = False
WAIT_DEFAULT = False
JOBNAME_FLAG = '-jobname'
JOBNAME_DEFAULT = None
NO_SUBJOB_FILES_DEFAULT = True
RETURN_ALL_STRUCTS_FLAG = '-return_all_structs'
RETURN_ALL_STRUCTS_DEFAULT = False
ENERGY_ONLY_FLAG = '-energy_only'
ENERGY_ONLY_DEFAULT = False

GEOPT_DEFAULT = True
GRIDEXT_DEFAULT = None
GRIDRES_DEFAULT = None
PLOTMO_DEFAULT = None
MOPAC_DEFAULT_VERSION = MOPAC_MAIN


[docs]class MethodOptionAction(argparse.Action): """ Modified argparse Action object that allows for upper or lower-case specification of string options, including synonyms """
[docs] def __init__(self, option_strings, method_synonyms, valid_methods, **kwargs): super(self.__class__, self).__init__(option_strings, **kwargs) self.method_synonyms = method_synonyms self.valid_methods = valid_methods
def __call__(self, parser, namespace, values, option_string=None): # Transform input to upper case and handle synonym if necessary. vu = values.upper() try: vu = self.method_synonyms[vu] except KeyError: pass # Match upper-case method name and save if recognized. for method in self.valid_methods: if vu == method.upper(): # Update the parser namespace setattr(namespace, self.dest, method.upper()) break else: raise MopacRuntimeError( f"Unrecognized method specification: {values}")
def _add_options(mopac_version, parser): """ Populate the parser with cmdline options. """ mo = mopac_launchers.get_mopac_launcher(mopac_version) valid_methods = mo.valid_methods default_method = mo.default_method method_synonyms = mo.method_synonyms # MOPAC version group = parser.add_mutually_exclusive_group() group.add_argument(f'-{MOPAC_MAIN}', action='store_const', const=MOPAC_MAIN, dest='mopac_version', default=MOPAC_DEFAULT_VERSION, help=f'Run {MOPAC_MAIN}.') group.add_argument(f'-{MOPAC71}', action='store_const', const=MOPAC71, dest='mopac_version', default=MOPAC_DEFAULT_VERSION, help='Run MOPAC7.1.') # MOPAC calculation keywords parser.add_argument( '-method', method_synonyms=method_synonyms, valid_methods=valid_methods, action=MethodOptionAction, default=default_method, help=( "Specify the method to be used by chosen MOPAC." f"Choices for -{MOPAC71} are " f"{', '.join(mopac_launchers.MopacLauncher71._valid_methods)}; " f"default is {mopac_launchers.MopacLauncher71._default_method}." f"Choices for -{MOPAC_MAIN} are " f"{', '.join(mopac_launchers.MopacLauncherMain._valid_methods)}; " f"default is {mopac_launchers.MopacLauncherMain._default_method}.")) parser.add_argument('-keywords', default='', help='String to specify additional MOPAC keywords.') group = parser.add_mutually_exclusive_group() group.add_argument('-geopt', action='store_true', dest='geopt', default=GEOPT_DEFAULT, help='Run a geometry optimization (default).') group.add_argument('-nogeopt', action='store_false', dest='geopt', default=GEOPT_DEFAULT, help='Do not run a geometry optimization.') # Data plotting arguments parser.add_argument('-plotMO', type=int, default=PLOTMO_DEFAULT, help='Plot <n> MOs around the HOMO/LUMO gap.') parser.add_argument( '-gridres', type=float, default=GRIDRES_DEFAULT, help='Grid resolution for plots (default 5.0 pts/angstrom).') parser.add_argument( '-gridext', type=float, default=GRIDEXT_DEFAULT, help='Grid size beyond the nuclei (default 3.5 angstrom).') # Job options parser.add_argument(JOBNAME_FLAG, default=JOBNAME_DEFAULT, help='Specify a jobname for .log and .out files.') parser.add_argument( '-no_subjob_files', default=NO_SUBJOB_FILES_DEFAULT, help='Do not return subjob output files to launch directory.', dest='keep_subjobs', action='store_false') parser.add_argument('-zip_out', action='store_true', default=False, help=argparse.SUPPRESS) parser.add_argument( RETURN_ALL_STRUCTS_FLAG, default=RETURN_ALL_STRUCTS_DEFAULT, help='Return input structures of failed subjobs in out .mae.', action='store_true') parser.add_argument( ENERGY_ONLY_FLAG, default=ENERGY_ONLY_DEFAULT, help='Only record energy from MOPAC job. Useful for fast screening.', action='store_true') # Jobcontrol options parser.add_argument('-NOJOBID', action='store_true', default=NOJOBID_DEFAULT, dest='nojobid', help='Run without Jobcontrol.') parser.add_argument('-WAIT', action='store_true', default=WAIT_DEFAULT, dest='wait', help='Wait for job to finish before returning prompt.') # Input files parser.add_argument('infiles', nargs='+', help='Specify one or more input files.') def _check_parsed_args(parsed_args): """ Check for user errors/conflicts in the parsed arguments. """ if all(utils.is_mopac_file(x) for x in parsed_args.infiles): allowed_opts = [ f'-{MOPAC71}', f'-{MOPAC_MAIN}', JOBNAME_FLAG, '-no_zip', '-NOJOBID', '-WAIT' ] for token in parsed_args.flags: if token not in allowed_opts: msg = "cmdline options are not allowed with .mop input files" msg += " except for the following:\n" msg += '\n'.join(allowed_opts) raise MopacRuntimeError(msg) elif any(utils.is_mopac_file(x) for x in parsed_args.infiles): msg = "Running with a mixture of .mop and .mae type\n" msg += "files is not supported. Please run them separately." raise MopacRuntimeError(msg) if 'ESP' in parsed_args.keywords and \ parsed_args.mopac_version == MOPAC_MAIN: msg = f"\nESP properties are not supported in {MOPAC_MAIN}.\n" msg += "Please try the Semiempirical NDDO Module instead." raise MopacRuntimeError(msg) def _set_jobname(args, parsed_args): """ Create a sensible default jobname from input file names. :type parsed_args: ParsedArgs object :param parsed_args: parsed cmdline arguments """ if parsed_args.jobname != JOBNAME_DEFAULT: pass elif len(parsed_args.infiles) == 1: # Simply take jobname from name of first input file filename = os.path.basename(parsed_args.infiles[0]) parsed_args.jobname, ext = fileutils.splitext(filename) else: parsed_args.jobname = jag_utils.get_jobname('mopac', ''.join(args)) def _get_mopac_version(args): """ Return MOPAC version to use based on cmdline invocation :type args: list :param args: cmdline arguments """ if f'-{MOPAC_MAIN}' in args: mopac_version = MOPAC_MAIN elif f'-{MOPAC71}' in args: mopac_version = MOPAC71 else: mopac_version = MOPAC_DEFAULT_VERSION return mopac_version
[docs]def index_of_flag_in_args(flag, args): """ Look for flag in list of cmdline arguments, allowing for abbreviations. e.g. '-jobname' will pick up '-j'. :type args: list :param args: cmdline arguments :type args: list :param args: cmdline arguments :return: index of flag in list of arguments, or None if not found. """ if flag in args: return args.index(flag) elif len(flag) > 1: return index_of_flag_in_args(flag[:-1], args) else: return None
[docs]def parse_args(args): """ Parse "$SCHRODINGER/run semi_emp.py" commandline invocation. :type args: list :param args: cmdline arguments :return parsed_args: parsed cmdline arguments """ # Create a cmdline parser prog = '$SCHRODINGER/run semi_emp.py ' desc = textwrap.dedent("""\ Run MOPAC on an input file or files. The files can be either MOPAC input files or general structure input files, but these two cannot be mixed in one invocation. When the files are MOPAC input files, no command-line calculation arguments are allowed - the file itself must specify the calculation parameters. MOPAC files are recognized by the ".mop" (or ".dat") suffix. """) parser = argparse.ArgumentParser(description=desc, prog=prog, add_help=True) mopac_version = _get_mopac_version(args) _add_options(mopac_version, parser) # Parse args parsed_args = parser.parse_args(args) # Save the cmdline flags (e.g. so they can be passed on to subjobs) # and filter off -jobname option if present. parsed_args.flags = [x for x in args if x not in parsed_args.infiles] if parsed_args.jobname != JOBNAME_DEFAULT: idx = index_of_flag_in_args(JOBNAME_FLAG, parsed_args.flags) del parsed_args.flags[idx] del parsed_args.flags[idx] _check_parsed_args(parsed_args) _set_jobname(args, parsed_args) return parsed_args