Source code for schrodinger.application.matsci.parserutils

"""
Utilities for working with argument parsers

Copyright Schrodinger, LLC. All rights reserved.
"""

import argparse
import enum
import functools
import json
import operator
import os
import random
import sys
from collections import namedtuple

import yaml

from schrodinger import structure
from schrodinger.application.desmond import cmj
from schrodinger.application.desmond import cms
from schrodinger.application.desmond import constants as dconst
from schrodinger.application.matsci import atomicsymbols
from schrodinger.application.matsci import jobutils
from schrodinger.application.matsci import msconst
from schrodinger.application.matsci import msprops
from schrodinger.application.matsci import msutils
from schrodinger.application.matsci import rdpattern
from schrodinger.application.matsci import textlogger
from schrodinger.infra import mm
from schrodinger.job import launchparams
from schrodinger.structutils.analyze import validate_asl
from schrodinger.test import ioredirect
from schrodinger.utils import cmdline
from schrodinger.utils import fileutils
from schrodinger.utils import sea

OLD_TRJ_END = '-out.idx'
NEW_TRJ_END = '_trj'
RANDOM_SEED_MIN = 0
# MATSCI-4747 largest integer on Windows
RANDOM_SEED_MAX = min(2**31 - 1, sys.maxsize)
RANDOM_SEED_DEFAULT = 1234
RANDOM_SEED_RANDOM = 'random'
YAML_EXT = '.yaml'

# Physical flags
FLAG_TEMP = '-temperature'
FLAG_PRESSURE = '-pressure'

UNIT_KELVIN = 'K'
UNIT_BAR = 'bar'
UNIT_ATM = 'atm'
UNIT_PA = 'Pa'

PHYSICAL_FLAGS_DEFAULTS = {
    (FLAG_TEMP, UNIT_KELVIN): 298.15,
    (FLAG_PRESSURE, UNIT_BAR): 1.01325,
    (FLAG_PRESSURE, UNIT_ATM): 1.,
    (FLAG_PRESSURE, UNIT_PA): 101325.,
}

# Desmond Trajectory
UNFLAGGED_CMS_PATH = 'cms_file'
FLAG_CMS_PATH = '-cms_file'
FLAG_TRJ_PATH = '-trj'
FLAG_TRJ_MIN = '-trj_min'
FLAG_TRJ_MAX = '-trj_max'
FLAG_TRJ_STEP = '-trj_step'

# Coarsre-grain flags
FLAG_CGFFLD = '-cgffld'
FLAG_CGFFLD_LOCATION_TYPE = '-cgffld_loc_type'

# Coarse-grained forcefield file location types
INSTALLED_CG_FF_LOCATION_TYPE = 'installed'
LOCAL_CG_FF_LOCATION_TYPE = 'local'
CG_FF_LOCATION_TYPES = [
    INSTALLED_CG_FF_LOCATION_TYPE, LOCAL_CG_FF_LOCATION_TYPE
]

# TODO:  Remove these pointers after the cgforcefield pointers to them are
# removed. See MATSCI-11895
INSTALLED_FF_LOCATION_TYPE = INSTALLED_CG_FF_LOCATION_TYPE
LOCAL_FF_LOCATION_TYPE = LOCAL_CG_FF_LOCATION_TYPE
FF_LOCATION_TYPES = CG_FF_LOCATION_TYPES

# ASL Flags
FLAG_ASL = '-asl'
FLAG_CENTER_ASL = '-center_asl'
FLAG_SUBSTRATE_ASL = '-substrate_asl'

# Force constant flag
FLAG_SUBSTRATE_FORCE_C = '-force_c'

# Distance Flags
FLAG_RESOLUTION = '-resolution'

FLAG_KEYWORDS = '-keywords'

SAVE_FLAG = "-save_trj_data"
SAVE_NONE = "none"
SAVE_CMS = "cms"
SAVE_TRJ = "trj"
SAVE_FLAG_OPTS = [SAVE_NONE, SAVE_CMS, SAVE_TRJ]
FLAG_COMBINE_TRJ = '-combine_trj'
FLAG_MD_UMBRELLA = '-md_umbrella'

FLAG_MONOMER_CHARGE = '-monomer_ff_q'
FLAG_MD_TIME = '-md_time'  # In ns or ps
FLAG_MD_TIMESTEP = '-md_timestep'  # In fs
FLAG_MD_TRJ_INT = '-md_trj_int'  # In ps
FLAG_MD_ENEGRP_INT = '-md_enegrp_int'  # In ps
FLAG_MD_PTENSOR_INT = '-md_ptensor_int'  # In ps
FLAG_MD_ENESEQ_INT = '-md_eneseq_int'  # In ps
FLAG_MD_TEMP = '-md_temp'  # In K
FLAG_MD_PRESS = '-md_press'  # In bar
FLAG_MD_ENSEMBLE = '-md_ensemble'
FLAG_MD_ISOTROPY = '-md_isotropy'
FLAG_GPU = '-gpu'
SPLIT_COMPONENTS_FLAG = '-split_components'
FLAG_FORCEFIELD = '-forcefield'
FLAG_PTENSOR_AVG = '-ptensor_avg'
FLAG_QARG = '-QARG'

FLAG_RANDOM_SEED = '-seed'

# eta vs centroid representation of a metallocene
FLAG_OUT_REP = '-out_rep'
CENTROID = 'centroid'
ETA = 'eta'

# rxnwf
FLAG_DEDUP_GEOM_EPS = '-dedup_geom_eps'
DEFAULT_DEDUP_GEOM_EPS = 0.25  # Angstrom
FLAG_ROBUST = '-robust'
FLAG_LOWEST_ENERGY = '-lowest'
FLAG_ENERGY_PROP = '-energy_prop'

# Subjob compression
FLAG_COMPRESS_SUBJOBS = '-compress_subjobs'

FLAG_FORCEFIELD_NUMBER = '-forcefield_number'  # non-customer facing
WATER_FF_TYPE_FLAG = '-water_fftype'

MD_FLAGS_DEFAULTS = {
    FLAG_MD_TEMP: 300.0,
    FLAG_MD_PRESS: 1.01325,
    FLAG_MD_TIME: 100.0,
    FLAG_MD_TIMESTEP: 2.0,
    FLAG_MD_TRJ_INT: 10.0,
    FLAG_MD_ENEGRP_INT: 10.0,
    FLAG_MD_PTENSOR_INT: 10.0,
    FLAG_MD_ENESEQ_INT: 10.0,
    FLAG_MD_ENSEMBLE: msconst.ENSEMBLE_NVT,
    FLAG_MD_ISOTROPY: dconst.IsotropyPolicy.ISOTROPIC,
    WATER_FF_TYPE_FLAG: msconst.SPC,
    SPLIT_COMPONENTS_FLAG: False,
    FLAG_RANDOM_SEED: RANDOM_SEED_DEFAULT,
    SAVE_FLAG: SAVE_NONE,
    FLAG_FORCEFIELD: mm.OPLS_NAME_F14
}

FORCEFIELD_FLAGS_DEFAULTS = {
    FLAG_CGFFLD: None,
    FLAG_CGFFLD_LOCATION_TYPE: LOCAL_CG_FF_LOCATION_TYPE
}

FLAG_USEZIPDATA = '-use_zip_data'
FLAG_RESTART_PROJ = '-restart_proj'
FLAG_RESTART_DISP = '-restart_disp'
FLAG_RESTART_VIEWNAME = '-restart_viewname'
FLAG_RESTART_HOST = '-restart_host'
FLAG_RESTART_JOBNAME = '-restart_jobname'
FLAG_CONFIG_YAML = '-config_yaml'

DEFAULT_SEED_HELP = "Seed for random number generator."
TRJ_STEP_HELP = ('Use every n-th frame in the trajectory range. The'
                 ' first and last frames in the range will always be included.')

# TPP
FLAG_TPP = '-TPP'
DEFAULT_TPP = 1

# Flag used for hydrogen mass repartition
FLAG_HYDROGEN_MASS_REPARTITION = '-hydrogen_mass_repartition'

FLAG_SMARTS_METHOD = '-smarts_method'
SMARTS_METHOD = enum.Enum('SMARTS_METHOD', 'internal canvas rdkit')
SMARTS_CHOICES = tuple(m.name for m in SMARTS_METHOD)


[docs]def type_ranged_num(arg, top=None, bottom=0.0, top_allowed=False, bottom_allowed=False, typer=float): """ Validate that the argument is a number over a given range. By default, it is (0, +inf) :note: This function can be used to directly create argparse typing for numbers within a specified range using a lambda function such as:: eqopts.add_argument( FLAG_TEMP, action='store', type=lambda x:parserutils.type_ranged_num(x, top=423, bottom=273), metavar='KELVIN', help='Simulation temperature for MD equilibration step') :type arg: str :param arg: The argument to validate :type top: float or int :param top: The upper limit of the allowed range :type bottom: float or int :param bottom: The lower limit of the allowed range :type top_allowed: bool :param top_allowed: Whether arg may take the top value or not (whether the upper limit is inclusive or exclusive) :type bottom_allowed: bool :param bottom_allowed: Whether arg may take the bottom value or not (whether the bottom limit is inclusive or exclusive) :type typer: callable :param typer: Should be one of the built-in float or int functions and defines the type of the value returned from this function. :rtype: float or int :return: The argument converted to a float or int, depending on the value of the typer keyword argument :raise `argparse.ArgumentTypeError`: If the argument cannot be converted to a ranged floating point number in the given range """ msg = '{val} is not {ntype} in the range of {bsym}{bbound}, {tbound}{tsym}' if typer == int: ntype = 'an integer' else: ntype = 'a real number' if bottom_allowed: bcomp = operator.lt bsym = '[' else: bcomp = operator.le bsym = '(' if bottom is None: bots = '-infinity' else: bots = bottom if top_allowed: tcomp = operator.gt tsym = ']' else: tcomp = operator.ge tsym = ')' if top is None: tops = 'infinity' else: tops = top error = msg.format(val=arg, ntype=ntype, bsym=bsym, bbound=bots, tbound=tops, tsym=tsym) try: val = typer(arg) except (TypeError, ValueError): raise argparse.ArgumentTypeError(error) if bottom is not None and bcomp(val, bottom): raise argparse.ArgumentTypeError(error) if top is not None and tcomp(val, top): raise argparse.ArgumentTypeError(error) return val
[docs]def type_ranged_int(*args, **kwargs): """ Validate that the argument is an int over a given range see type_ranged_num for documentation of arguments, return values and exceptions raised """ kwargs['typer'] = int return type_ranged_num(*args, **kwargs)
[docs]def get_ranged_int_typer(bottom_allowed=True, top_allowed=True, **kwargs): """ Get a validator that will validate an integer over a custom range Usage:: parser.add_argument( A_FLAG, type=get_ranged_int_typer(bottom=4, top=12, bottom_allowed=False)) See type_ranged_num for documentation of arguments, return values and exceptions raised """ return functools.partial(type_ranged_int, bottom_allowed=bottom_allowed, top_allowed=top_allowed, **kwargs)
[docs]def get_ranged_float_typer(bottom_allowed=True, top_allowed=True, **kwargs): """ Get a validator that will validate a float over a custom range Usage:: parser.add_argument( A_FLAG, type=get_ranged_float_typer(bottom=-5.0, top=5.0, top_allowed=False)) See type_ranged_num for documentation of arguments, return values and exceptions raised """ return functools.partial(type_ranged_num, bottom_allowed=bottom_allowed, top_allowed=top_allowed, **kwargs)
[docs]def type_smarts(args): """ Validate that the argument is a valid smarts :note: This function can be used to directly create argparse typing for list of smarts or single smarts string. eqopts.add_argument( FLAG_SMARTS, action='append', type=parserutils.type_smarts, help='Define smarts') :type args: str, list :param args: The argument to validate :rtype: str, list :return: The validated smarts list or string :raise `argparse.ArgumentTypeError`: If the argument is not a valid smarts raise exception """ err = rdpattern.validate_smarts(args) if err: raise argparse.ArgumentTypeError(err) return args
[docs]def type_positive_float(arg): """ Validate that the argument is a positive float see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=None, bottom=0.0, bottom_allowed=False)
[docs]def type_negative_float(arg): """ Validate that the argument is a negative float see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=0.0, bottom=None, top_allowed=False)
[docs]def type_positive_int(arg): """ Validate that the argument is a positive int see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_int(arg, top=None, bottom=0, bottom_allowed=False)
[docs]def type_nonnegative_int(arg): """ Validate that the argument is a nonnegative int see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_int(arg, top=None, bottom=0, bottom_allowed=True)
[docs]def type_exclusive_percent(arg): """ Validate that the argument is a percent, but neither zero nor 100 see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=100.0, bottom=0.0, bottom_allowed=False, top_allowed=False)
[docs]def type_nonpositive_float(arg): """ Validate that the argument is a nonpositive float see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=0.0, bottom=None, top_allowed=True)
[docs]def type_nonzero_percent(arg): """ Validate that the argument is a percent but not zero see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=100.0, bottom=0.0, bottom_allowed=False, top_allowed=True)
[docs]def type_random_seed(arg, seed_min=RANDOM_SEED_MIN, seed_max=RANDOM_SEED_MAX): """ Validate that the argument is a valid random seed value. If a random value is requested, that value is generated and returned. :param str arg: The argument to validate :param int seed_min: The minimum allowed random number seed :param int seed_max: The maximum allowed random number seed :rtype: int :return: The random seed :raise `argparse.ArgumentTypeError`: If the argument is not RANDOM_SEED_RANDOM or an integer """ if arg == RANDOM_SEED_RANDOM: return random.randint(seed_min, seed_max) return type_ranged_int(arg, top=seed_max, bottom=seed_min, bottom_allowed=True, top_allowed=True)
[docs]def type_file(arg): """ Validate that the argument is an existing filename :type arg: str or unicode :param arg: The argument to validate :rtype: str :return: The str-ed argument :raise `argparse.ArgumentTypeError`: If the given filename does not exist """ # if called directly from argparse.ArgumentParser.add_argument then # arg is a unicode which is typically typed as a str via the type=str # kwarg, do the same in this custom typer arg = str(arg) file_found = fileutils.get_existing_filepath(arg) if not file_found: raise argparse.ArgumentTypeError('File does not exist: %s' % arg) return file_found
[docs]def type_msj(arg, return_msj=False): """ Validate that the argument is a valid MSJ file. :param str arg: the argument to validate :param bool return_msj: Whether to return arg or msj object :raise argparse.ArgumentTypeError: if the given argument is not a valid MSJ :rtype: str or sea.Map :return: the str-ed argument or MSJ object """ type_file(arg) try: cfg_sea = cmj.msj2sea(arg, None) except RuntimeError as err: raise argparse.ArgumentTypeError('Could not parse %s file. %s' % (arg, str(err))) return cfg_sea if return_msj else arg
[docs]def type_nonnegative_float(arg): """ Validate that the argument is a nonnegative float see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(arg, top=None, bottom=0.0, bottom_allowed=True)
[docs]def get_trj_dir_name(st): """ Get the trajectory directory name from the given structure. :type st: schrodinger.structure.Structure :param st: the structure :rtype: str or None :return: the trajectory directory name or None if there isn't one """ trj_prop = st.property.get(msprops.TRAJECTORY_FILE_PROP) if not trj_prop: return elif trj_prop.endswith(OLD_TRJ_END): trj_prop = trj_prop.replace(OLD_TRJ_END, NEW_TRJ_END) if trj_prop.endswith(NEW_TRJ_END): return trj_prop
[docs]def type_csv_file(arg): """ Validate that the argument is a CSV file. :param str arg: the argument to validate :raise argparse.ArgumentTypeError: if the given argument is not a CSV file :rtype: str :return: the validated argument """ arg = type_file(arg) ext = fileutils.get_file_extension(arg) if ext != msconst.CSV: msg = f'"{arg}" is not a {msconst.CSV} file.' raise argparse.ArgumentTypeError(msg) # Here we would normally try to open the file, but CSV formatting is too # flexible and ambiguous to test easily. So we forgo opening the file and # rely on downstream code to handle the appropriate formatting criteria. return arg
[docs]def type_cms_file(arg, ensure_trj=False): """ Validate that the argument is a cms file. :type arg: str or unicode :param arg: the argument to validate :type ensure_trj: bool :param ensure_trj: ensure that the cms file has trajectory information, for example is a Desmond output file :raise argparse.ArgumentTypeError: if the given argument is not a cms file :rtype: str :return: the str-ed argument """ arg = type_file(arg) ext = fileutils.splitext(arg)[1] if ext not in fileutils.EXTENSIONS['maestro']: msg = ('File {arg} is not a cms file.').format(arg=arg) raise argparse.ArgumentTypeError(msg) try: obj = cms.Cms(arg) except Exception: msg = ('File {arg} is not a valid cms file.').format(arg=arg) raise argparse.ArgumentTypeError(msg) if ensure_trj: traj_dir = get_trj_dir_name(obj.fsys_ct) orig_cms = obj.fsys_ct.property.get(msprops.ORIGINAL_CMS_PROP) if not (traj_dir and orig_cms): msg = ('File {arg} is missing trajectory information.').format( arg=arg) raise argparse.ArgumentTypeError(msg) adir = os.path.join(os.path.split(arg)[0], traj_dir) if not os.path.isdir(adir): msg = ('File {arg} points to a trajectory directory that does not ' 'exist.').format(arg=arg) raise argparse.ArgumentTypeError(msg) return arg
[docs]def type_xyz_file(arg): """ Validate that the argument is an existing filename with xyz extension and can be converted into sdf format. :param arg: str :type arg: The argument to validate :return: str :rtype: The validated argument :raise `argparse.ArgumentTypeError`: If the given filename does not exist :raise `argparse.ArgumentTypeError`: If the given filename does not have xyz as file extension. :raise `argparse.ArgumentTypeError`: If the given filename cannot be converted """ file_path = type_file(arg) try: fileutils.xyz_to_sdf(file_path, save_file=False) except ValueError as err: raise argparse.ArgumentTypeError(f"{err}") except RuntimeError as err: raise argparse.ArgumentTypeError( f"Open Babel cannot convert the input {file_path} with stderr being {err}" ) return file_path
[docs]def type_cms_with_trj(arg): """ Validate that the argument is a cms file with trajectory information. :type arg: str or unicode :param arg: the argument to validate :rtype: str :return: the str-ed argument """ return type_cms_file(arg, ensure_trj=True)
[docs]def type_json_file(arg): """ Validate that the argument is a json file. :type arg: str or unicode :param arg: the argument to validate :raise argparse.ArgumentTypeError: if the given argument is not a json file :rtype: str :return: the str-ed argument """ arg = type_file(arg) ext = fileutils.splitext(arg)[1] if ext != '.json': msg = ('File {arg} is not a json file.').format(arg=arg) raise argparse.ArgumentTypeError(msg) try: with open(arg, 'r') as afile: json.load(afile) except Exception: msg = ('File {arg} is not a valid json file.').format(arg=arg) raise argparse.ArgumentTypeError(msg) return arg
[docs]def type_yaml_file(arg, loader=None): """ Validate that the argument is a yaml file. :type arg: str or unicode :param arg: the argument to validate :type loader:`yaml.loader` :param loader: yaml loader :raise argparse.ArgumentTypeError: if the given argument is not a yaml file :rtype: str :return: the str-ed argument """ arg = type_file(arg) ext = fileutils.splitext(arg)[1] if ext != YAML_EXT: msg = (f"File {arg} doesn't have {YAML_EXT} as extension.").format( arg=arg) raise argparse.ArgumentTypeError(msg) loader = loader or yaml.SafeLoader try: with open(arg, 'r') as afile: yaml.load(afile, Loader=loader) except Exception as err: msg = (f'File {arg} is not a valid yaml file. {str(err)}').format( arg=arg) raise argparse.ArgumentTypeError(msg) return arg
[docs]def type_yaml_str(arg, loader=None): """ Load the arg as a yaml string. :type arg: str or unicode :param arg: the argument to validate :type loader: yaml loader :param loader: yaml loader :raise argparse.ArgumentTypeError: if the given argument is not in yaml format :rtype: dict :return: loaded yaml string """ loader = loader or yaml.SafeLoader try: parsed_str = yaml.load(arg, Loader=loader) except Exception as err: msg = (f'String {arg} is not in yaml format. {str(err)}').format( arg=arg) raise argparse.ArgumentTypeError(msg) return parsed_str
[docs]def type_structure_file(arg): """ Validate that the argument is a structure file that can be read by StructureReader. :type arg: str or unicode :param arg: the argument to validate :raise argparse.ArgumentTypeError: if the given argument is not a readable structure file :rtype: str :return: the str-ed argument """ arg = type_file(arg) try: structure.Structure.read(arg) except ValueError: msg = '%s is not a valid structure file' % arg raise argparse.ArgumentTypeError(msg) return arg
[docs]def type_element(value): """ Validate that the argument is a valid atomic atymbol :type value: str :param value: The argument to validate :rtype: str :return: The validated argument :raise `argparse.ArgumentTypeError`: If the argument is not a valid atomic symbol """ if value not in atomicsymbols.ATOMIC_SYMBOLS: raise argparse.ArgumentTypeError( '%s is not found in the list of atomic symbols' % value) return value
[docs]def type_forcefield(value): """ Validate that the argument is a valid force field name This accepts both OPLS3 and OPLS3e for force field #16 :type value: str :param value: The argument to validate :rtype: str :return: The canonical force field name :raise `argparse.ArgumentTypeError`: If the argument is not a valid atomic symbol """ try: with ioredirect.IOSilence(): ff_int = mm.opls_name_to_version(value) except IndexError: msg = ('%s is an unrecognized force field name. %s' % (value, valid_forcefield_info())) raise argparse.ArgumentTypeError(msg) return mm.opls_version_to_name(ff_int)
[docs]def type_forcefield_and_get_number(value): """ Validate that the argument is a valid force field name and returns both the canonical force field name and number. This accepts both OPLS3 and OPLS3e for force field #16 :type value: str :param value: The argument to validate :rtype: str, int :return: The canonical force field name and the corresponding force field int :raise `argparse.ArgumentTypeError`: If the argument is not a valid atomic symbol """ name = type_forcefield(value) number = mm.opls_name_to_version(name) return name, number
[docs]def type_desmond_ensemble(value): """ Validate that the argument is a valid desmond ensemble :type value: str :param value: The argument to validate :rtype: str :return: The validated argument :raise `argparse.ArgumentTypeError`: If the argument is not a valid desmond ensemble """ if value not in msconst.ENSEMBLES: raise argparse.ArgumentTypeError( '%s is not found in the list of known desmond ensembles' % value) return value
[docs]def valid_forcefield_info(): """ Get an informative sentence that can be included in help messages that indicate the valid force field names. Note that these are the default names and type_forcefield will accept many variants of them, including OPLS3 for OPLS3E. :rtype: str :return: A full sentence describing the valid force field names """ ffnames = " and ".join(mm.opls_names()) return 'Valid force fields are %s.' % ffnames
[docs]class NoArgCustomAction(argparse.Action): """ An action that allows giving an argparse flag a custom action so that the flag does not require an argument One possible use is the implementation of a flag like '-doc' which immediately performs an action when it is found on the command line - such as printing out detailed documentation and then exiting. This avoids the user having to specify any other flags even if they are marked as required. """
[docs] def __init__(self, *args, **kwargs): kwargs['nargs'] = 0 super().__init__(*args, **kwargs)
def __call__(self, parser, namespace, values, option_string=""): # Overwrite this function to perform the custom action super().__call__(parser, namespace, values, option_string=option_string)
[docs]class KeyPairDict(dict): """ This is a subclass of dict so that the {a:b} can be displayed as "a=b" (e.g, as the help message" """ def __str__(self, *args, **kwargs): return ' '.join(['='.join([x, str(y)]) for x, y in self.items()])
[docs]class StoreDictKeyPair(argparse._StoreAction): """ This is the action used with nargs="+" and values='KEYWORD=VALUE KEYWORD=VALUE' By default nargs="+" and action='store' save the multiple values "k1=v1 k2=v2" as list. With this StoreDictKeyPair as the action, "k1=v1 k2=v2" will be stored as {k1:v1, k2:v2} in the options. """ def __call__(self, parser, namespace, values, option_string=None): """ See parent classes for documentation. """ if isinstance(values, list): try: keywords = ' '.join(values) values = type_keywords_to_dict(keywords) except argparse.ArgumentTypeError as err: parser.error(str(err) + f" in {keywords}") super().__call__(parser, namespace, values, option_string=None)
[docs]def type_keywords_to_dict(value): """ Convert a string of 'keyword=value keyword=value ...' to a dictionary keyed by keyword. Values are the keyword values. :note: When calling parser.add_argument for the flag that uses this function, do *not* use "nargs=+". That will result in the return value from this function being placed as an item in a list. Instead, do not use nargs at all and simply use "action='store'". All of the keyword=value pairs will then be passed into this function as a single string. Example:: parser.add_argument( FLAG_KEYWORDS, action='store', metavar='KEYWORD=VALUE KEYWORD=VALUE ...', type=parserutils.type_keywords_to_dict, help='Jaguar keywords given as a space-separated keyword=value ' ' pairs' ) will result in options.keyword = the dictionary returned by this function :type value: str :param value: The argument to validate :rtype: dict :return: A dictionary with keywords as key keyword values as values :raise `argparse.ArgumentTypeError`: If the argument is not a valid keyword string """ try: keydict = msutils.keyword_string_to_dict(value) except ValueError as msg: raise argparse.ArgumentTypeError(str(msg)) return keydict
[docs]def type_keywords_to_string(value): """ Validate the format of a string of 'keyword=value keyword=value ...' :note: When calling parser.add_argument for the flag that uses this function, do *not* use "nargs=+". That will result in the return value from this function being placed as an item in a list. Instead, do not use nargs at all and simply use "action='store'". All of the keyword=value pairs will then be passed into this function as a single string. Example:: parser.add_argument( FLAG_KEYWORDS, action='store', metavar='KEYWORD=VALUE KEYWORD=VALUE ...', type=parserutils.type_keywords_to_string, help='Jaguar keywords given as a space-separated keyword=value ' ' pairs' ) will result in options.keyword = the string returned by this function :type value: str :param value: The argument to validate :rtype: string :return: The original command line string :raise `argparse.ArgumentTypeError`: If the argument is not a valid keyword string """ try: msutils.keyword_string_to_dict(value) except ValueError as msg: raise argparse.ArgumentTypeError(str(msg)) return value
[docs]def type_num_list(value, delimiter=',', typer=float, bottom=None, bottom_allowed=False): """ Validate that the argument is a list of numbers separated by the delimiter :type value: str :param value: string containing numbers separated by delimiter :type delimiter: str :param delimiter: the char to split the input value :type typer: callable :param typer: Should be one of the built-in float or int functions and defines the type of the value returned from this function. :type bottom: float or int :param bottom: The lower limit of the allowed range :type bottom_allowed: bool :param bottom_allowed: Whether arg may take the bottom value or not (whether the bottom limit is inclusive or exclusive) :rtype: list :return: The validated argument converted to a list of numbers :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of numbers """ value_list = [x.strip() for x in value.split(delimiter)] return [ type_ranged_num(x, bottom=bottom, bottom_allowed=bottom_allowed, typer=typer) for x in value_list ]
[docs]def type_num_comma_list(value, typer=float, bottom=None, bottom_allowed=False): """ Validate that the argument is a list of numbers separated by comma :type value: str :param value: string containing numbers separated by commas :type typer: callable :param typer: Should be one of the built-in float or int functions and defines the type of the value returned from this function. :type bottom: float or int :param bottom: The lower limit of the allowed range :type bottom_allowed: bool :param bottom_allowed: Whether arg may take the bottom value or not (whether the bottom limit is inclusive or exclusive) :rtype: list :return: The validated argument positive integers coverted to list :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of floats """ return type_num_list(value, delimiter=',', bottom=bottom, bottom_allowed=bottom_allowed, typer=typer)
[docs]def type_num_colon_list(value, typer=float, bottom=None, bottom_allowed=False): """ Validate that the argument is a list of numbers separated by colon :type value: str :param value: string containing numbers separated by colon :type typer: callable :param typer: Should be one of the built-in float or int functions and defines the type of the value returned from this function. :type bottom: float or int :param bottom: The lower limit of the allowed range :type bottom_allowed: bool :param bottom_allowed: Whether arg may take the bottom value or not (whether the bottom limit is inclusive or exclusive) :rtype: list :return: The validated argument positive integers coverted to list :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of floats """ return type_num_list(value, delimiter=':', bottom=bottom, bottom_allowed=bottom_allowed, typer=typer)
[docs]def type_float_comma_list(value): """ Validate that the argument is a float list separated by comma :type value: str :param value: string containing numbers separated by commas :rtype: list :return: The validated argument positive integers converted to list :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of floats """ return type_num_comma_list(value)
[docs]def type_nonnegative_float_comma_list(value): """ Validate that the argument is a nonnegative float list separated by comma :type value: str :param value: string containing numbers separated by commas :rtype: list :return: The validated argument positive integers converted to list :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of floats """ return type_num_comma_list(value, bottom=0.0, bottom_allowed=True)
[docs]def type_positive_integer_comma_list(value): """ Validate that the argument is a integer list separated by comma :type value: str :param value: string containing numbers separated by commas :rtype: list :return: The validated argument positive integers coverted to list :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of positive integers """ return type_num_comma_list(value, typer=int, bottom=0)
[docs]def type_element_comma_list(value): """ Validate that the argument is a element separated by comma :type str value: element separated by commas :rtype: list :return: The validated argument list of element symbols :raise `argparse.ArgumentTypeError`: If the argument is not a valid list of strings """ value = [x.strip() for x in value.split(',')] return [type_element(x) for x in value]
[docs]def type_percentage(value): """ Validate that the argument is a percentage between 0 and 100 see type_ranged_num for documentation of arguments, return values and exceptions raised """ return type_ranged_num(value, top=100, bottom=0.0, top_allowed=True, bottom_allowed=True)
[docs]def type_asl(asl): """ Validate ASL. :rtype: str :return: ASL :raise `argparse.ArgumentTypeError`: If ASL is not valid """ with ioredirect.IOSilence(): if not validate_asl(asl): raise argparse.ArgumentTypeError('Invalid ASL: %s' % asl) return asl
[docs]def type_desmond_cfg(arg): """ Validate that the argument is a desmond CFG file. :type arg: str or unicode :param arg: the argument to validate :raise argparse.ArgumentTypeError: if the given argument is not a readable CFG file :rtype: str :return: the str-ed argument """ arg = type_file(arg) # Took from analyze_simulation.py :: _parse_ark try: with open(arg) as cfg_fh: sea.Map(cfg_fh.read()) except Exception as err: raise argparse.ArgumentTypeError('Invalid desmond cfg file (%s): %s' % (arg, str(err))) return arg
[docs]class SubscriptableNameSpace(argparse.Namespace): """ Extend the default namespace to allow getting options using "options[flag]" """
[docs] def getNameFromFlag(self, flag): """ Get the option variable name from the flag :param str flag: The commandline flag :return str: The variable name in the parsed options """ if flag.startswith('-'): flag = flag[1:] return flag
def __getitem__(self, flag): """ :param str flag: The flag for the option :rtype: None or Any :return: The option value if there is one, or None """ var_name = self.getNameFromFlag(flag) try: return getattr(self, var_name) except AttributeError: raise KeyError(var_name) def __setitem__(self, flag, value): """ Set the option for flag to value :param str flag: The flag to set the option for :param Any value: The value for the flag """ var_name = self.getNameFromFlag(flag) setattr(self, var_name, value)
[docs] def get(self, flag, default=None): """ Get the option value for the flag, or return the default value if the option doesn't exist :param str flag: The flag to get the option for :param Any default: The default value :return Any: The option value or default value """ try: return self[flag] except KeyError: return default
[docs] def copy(self): """ Return a copy of this namespace :return SubscriptableNameSpace: A copy of this namespace """ return self.__class__(**vars(self))
[docs]class DriverParser(argparse.ArgumentParser): """ Subclass that shows driver usage relative to python/scripts or python/common directories and adds a help argument by default """
[docs] def __init__(self, *args, add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, **kwargs): """ Accepts all arguments normally given for an ArgumentParser """ # Gets the full path to the driver, and calculates the path shown as # "usage" in the driver help message by removing the extra parts. # If the driver is located in python/scripts or python/common, usage just # includes the driver file name. If the driver is in a subdirectory of # python/scripts or python/common, usage also includes the subdirectory's name. # This is so the driver can be found when using: $SCHRODINGER/run <usage> if 'prog' not in kwargs: file_path = os.path.abspath(sys.argv[0]) base_name = os.path.basename(file_path) dirname = os.path.basename(os.path.dirname(file_path)) if dirname in ('common', 'scripts'): relative_path = base_name else: relative_path = os.path.join(dirname, base_name) kwargs['prog'] = "$SCHRODINGER/run " + relative_path super().__init__(*args, add_help=add_help, formatter_class=formatter_class, **kwargs) self.add_argument('-h', '-help', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.')
[docs] def parse_args(self, args=None, namespace=None): """ Overwrite parent method to use SubscriptableNameSpace as default namespace :param args: List of commandline arguments :type args: list or None :param namespace: The namespace to use. Uses SubscriptableNameSpace by default :type namespace: None or argparse.Namespace :return argparse.Namespace: The parsed commandline arguments """ if namespace is None: namespace = SubscriptableNameSpace() return super().parse_args(args, namespace)
[docs]def parse_keyword_list(options, exit_on_error=True, logger=None, validate=False): """ Adds keystring and keydict properties to the argparse options object that are the list of keyword arguments turned into, respectively, a string or a dictionary. Valid keyword syntax is checked for. Typical usage: parserutils.add_keyword_parser_argument(parser) options = parser.parse_args(args) parserutils.parse_keyword_list(options) print options.keystring, options.keydict :type options: `argparse.Namespace` :param options: The parser Namespace object with a keywords attribute :type exit_on_error: bool :param exit_on_error: True if the ValueError for keyword syntax should result in a message being printed and sys.exit() being called, False if the error should just be raised. :type logger: `logging.Logger` :param logger: The logger to use for recording error messages, otherwise they'll be printed. :type validate: bool :param validate: Whether keywords/values not recognized by Jaguar should be considered an error. :raises ValueError: If an invalid keyword pair syntax is detected and handle_error is False :raises `schrodinger.application.jaguar.validation.JaguarKeywordException` if validate=True, exit_on_error=False and an invalid Jaguar keyword/value has been used. """ if options.keywords: options.keystring = ' '.join(options.keywords) else: options.keystring = "" options.keystring = options.keystring.lower() try: options.keydict = msutils.keyword_string_to_dict(options.keystring) except ValueError as msg: if exit_on_error: err_msg = 'Keyword parsing failed - stopping:\n%s' % str(msg) textlogger.log(logger, err_msg) sys.exit(1) else: raise if validate: # MATSCI-5669 import warnings msg = ('Jaguar keyword validation is no longer performed') warnings.warn(msg, DeprecationWarning, stacklevel=2)
[docs]def get_stripped_defaults(adict): """ Remove dashes in the beginning of the keys. Used with ArgumentParser. :type adict: dict{string:any} :param adict: Dictionary of defaults :rtype: dict{string:any} :return: Dictionary of defaults with dashed stripped from the keys """ ret = {} for key, val in adict.items(): if key.startswith('-'): key = key[1:] ret[key] = val return ret
[docs]def argparser_set_defaults(parser, defaults): """ Set default values to the arguments already present in the parser. Note that, parser.set_defaults contaminates namespace with the arguments that might be absent from the parser. :type parser: `argparse.ArgumentParser` :param parser: The parser to set defaults :type defaults: dict{string:any} :param defaults: Dictionary of the defaults """ defaults = get_stripped_defaults(defaults) # See cpython/Lib/argparse.py :: set_defaults for action in parser._actions: if action.dest in defaults: parser._defaults[action.dest] = defaults[action.dest] action.default = defaults[action.dest]
[docs]class PhysicalFlag(namedtuple('PhysicalFlag', ['name', 'unit', 'default'])): """ `namedtuple` containing the information needed to create an argument flag for a physical property. :ivar str name: The name of the physical property being flagged :ivar str unit: The displayed units of the physical property (e.g., 'K' for temperature) :ivar default: The default value to use for the given physical flag. If `None`, a default value will be chosen for you. """
[docs]def add_physical_arguments(parser, flags): """ Add physical arguments to the parser :param argparse.ArgumentParser parser: The parser to add arguments to :param list(PhysicalFlag) flags: List of physical flags to add to the parser. Each element should be an instance of the `PhysicalFlag` namedtuple. :raise argparse.ArgumentTypeError: If an argument is not recognized as a physical argument. """ for name, unit, default in flags: if default is None: default = PHYSICAL_FLAGS_DEFAULTS[(name, unit)] if name == FLAG_TEMP: parser.add_argument(FLAG_TEMP, default=default, type=type_positive_float, help=f'Temperature (in {unit}).') elif name == FLAG_PRESSURE: parser.add_argument(FLAG_PRESSURE, default=default, type=type_positive_float, help=f'Pressure (in {unit}).') else: msg = f'Unrecognized parser argument: {name}' raise argparse.ArgumentTypeError(msg)
[docs]def add_restart_parser_arguments(parser): """ Add restart arguments to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add arguments to """ rsopts = parser.add_argument_group('Restart options') rsopts.add_argument( FLAG_USEZIPDATA, metavar='PATH', help='Extract the archive before running. Any completed files ' 'in the archive will be used instead of submitting new jobs.') rsopts.add_argument(FLAG_RESTART_PROJ, metavar='PROJECT_NAME', help='Project name the restart job is associated with.') pde = launchparams.ProjectDisposition disp_choices = [pde.APPEND.value, pde.IGNORE.value] disp_str = ', '.join(disp_choices) rsopts.add_argument( FLAG_RESTART_DISP, metavar='INCORPORATION', choices=disp_choices, help='Incorporation state for the restart job. Should be one of: ' '%s.' % disp_str) rsopts.add_argument( FLAG_RESTART_VIEWNAME, metavar='VIEWNAME', help='Viewname for the panel submitting the restart job. Leave ' 'blank if running from the command line.') rsopts.add_argument(FLAG_RESTART_HOST, metavar='HOSTNAME:PROCESSORS', help='Host for restart in the form of "hostname:cpus".') rsopts.add_argument(FLAG_RESTART_JOBNAME, metavar='JOBNAME', help='Jobname for restart.')
[docs]def add_keyword_parser_argument(parser, keyword_flags=None, default='', arghelp=None, action='store'): """ Add a keyword argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to :type keyword_flags: None or list of str :param keyword_flags: the customized flags for this argument :type default: str or None :param default: The customized default values for this argument :type arghelp: str or None :param arghelp: The customized help message for this argument :type action: `argparse.Action` or str :param action: The customized action for this argument or the standard ones: 'store', 'store_true', 'store_false', 'append' and so on. """ if keyword_flags is None: keyword_flags = ['-keywords', '-k'] if not arghelp: arghelp = ('Jaguar keywords to include in calculations. Can be given ' 'multiple keywords, ex. -k dftname=b3lyp basis=midix... If ' 'this is the last flag before the input/output file names, ' 'use -- to terminate the list of keywords.') parser.add_argument(*keyword_flags, default=default, action=action, metavar='KEYWORD=VALUE KEYWORD=VALUE', nargs='+', help=arghelp)
[docs]def add_save_desmond_files_parser_argument(parser): """ Add a `SAVE_FLAG` argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to """ parser.add_argument( SAVE_FLAG, choices=SAVE_FLAG_OPTS, default=SAVE_NONE, help="Specify whether intermediate CMS or trajectory " "files be included with the job output. Choices " "are {0} (save no files), {1} (save .cms files) and {2} " "(save .cms and trajectory files). Default is " "{3}.".format(SAVE_NONE, SAVE_CMS, SAVE_TRJ, SAVE_NONE))
[docs]def add_compress_subjobs_parser_argument(parser, help=None): """ Add a `FLAG_COMPRESS_SUBJOBS` argument to the parser :param `argparse.ArgumentParser` parser: The parser to add this argument to :type str help: The help message """ if help is None: help = 'Compress files from successful subjobs' parser.add_argument(FLAG_COMPRESS_SUBJOBS, action='store_true', help=help)
[docs]def add_desmond_parser_arguments(parser, args, defaults=None): """ Add desmond related arguments to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to :type args: list :param args: List of arguments to add to the parser :type defaults: dict or None :param defaults: Default values for the arguments """ md_defaults = MD_FLAGS_DEFAULTS.copy() if defaults: md_defaults.update(defaults) defaults = md_defaults for arg in args: if arg == SPLIT_COMPONENTS_FLAG: parser.add_argument( SPLIT_COMPONENTS_FLAG, action='store_true', help='Split system in components when Desmond system is built. ' 'This can speed up the the force field assignment for systems ' 'that contain multiple identical molecules.') elif arg == FLAG_MD_TEMP: parser.add_argument(FLAG_MD_TEMP, default=defaults[FLAG_MD_TEMP], type=type_positive_float, help='Temperature (in K) of the simulations.') elif arg == FLAG_MD_PRESS: parser.add_argument(FLAG_MD_PRESS, default=defaults[FLAG_MD_PRESS], type=type_positive_float, help='Pressure (in bar) of the simulations.') elif arg == FLAG_MD_TIME: parser.add_argument(FLAG_MD_TIME, default=defaults[FLAG_MD_TIME], type=type_positive_float, help='MD time (in ps) of the simulations.') elif arg == FLAG_MD_TIMESTEP: parser.add_argument(FLAG_MD_TIMESTEP, default=defaults[FLAG_MD_TIMESTEP], type=type_positive_float, help='MD time step (in fs) of the simulations.') elif arg == FLAG_MD_TRJ_INT: parser.add_argument( FLAG_MD_TRJ_INT, default=defaults[FLAG_MD_TRJ_INT], type=type_positive_float, help='MD trajectory recording interval (in ps).') elif arg == FLAG_MD_ENEGRP_INT: parser.add_argument( FLAG_MD_ENEGRP_INT, default=defaults[FLAG_MD_ENEGRP_INT], type=type_positive_float, help='MD energy group recording interval (in ps).') elif arg == FLAG_MD_PTENSOR_INT: parser.add_argument( FLAG_MD_PTENSOR_INT, default=defaults[FLAG_MD_PTENSOR_INT], type=type_positive_float, help='MD pressure tensor recording interval (in ps).') elif arg == FLAG_MD_ENESEQ_INT: parser.add_argument(FLAG_MD_ENESEQ_INT, default=defaults[FLAG_MD_ENESEQ_INT], type=type_positive_float, help='MD energy recording interval (in ps).') elif arg == FLAG_MD_ENSEMBLE: parser.add_argument(FLAG_MD_ENSEMBLE, default=defaults[FLAG_MD_ENSEMBLE], choices=msconst.ENSEMBLES, metavar='ENSEMBLE', help='Desmond ensemble. Known values are: %s' % ', '.join(msconst.ENSEMBLES)) elif arg == FLAG_MD_ISOTROPY: parser.add_argument( FLAG_MD_ISOTROPY, default=defaults[FLAG_MD_ISOTROPY], choices=dconst.IsotropyPolicy, metavar='ISOTROPY_POLICY', help='Desmond barostat isotropy policy. Known values are: ' '%s' % ', '.join(dconst.IsotropyPolicy)) elif arg == FLAG_GPU: # MATSCI - 9064: remove cpu desmond support parser.add_argument(FLAG_GPU, action='store_true', default=True, help=argparse.SUPPRESS) elif arg == WATER_FF_TYPE_FLAG: parser.add_argument( WATER_FF_TYPE_FLAG, action='store', metavar='WATER_TYPE', default=defaults[WATER_FF_TYPE_FLAG], choices=msconst.WATER_FFTYPES.keys(), help='Force field type for water molecules. Must be used ' f'with {SPLIT_COMPONENTS_FLAG}. If using "{msconst.NONE}"' ', the Residue Name atom property is used instead.') elif arg == FLAG_RANDOM_SEED: add_random_seed_parser_argument(parser) elif arg == SAVE_FLAG: add_save_desmond_files_parser_argument(parser) elif arg == FLAG_FORCEFIELD: parser.add_argument(FLAG_FORCEFIELD, default=defaults[FLAG_FORCEFIELD], type=type_forcefield, metavar='FORCE_FIELD', help='Force field to use. %s' % valid_forcefield_info()) elif arg == FLAG_MONOMER_CHARGE: parser.add_argument( FLAG_MONOMER_CHARGE, action='store_true', help= 'Use forcefield charges computed for the monomer for the polymer. ' f'This option is particularly useful for {mm.OPLS_NAME_F16}, which ' f'uses {mm.OPLS_NAME_F14} charges by default for molecules larger ' 'than 100 heavy atoms.') elif arg == FLAG_COMBINE_TRJ: parser.add_argument( FLAG_COMBINE_TRJ, action='store_true', help='Merge all trajectories into a single one.') elif arg == FLAG_MD_UMBRELLA: parser.add_argument(FLAG_MD_UMBRELLA, action='store_true', help='Run desmond subjobs on the driver host.') elif arg == FLAG_SUBSTRATE_ASL: parser.add_argument( FLAG_SUBSTRATE_ASL, type=type_asl, help='ASL for the substrate to apply positional restraints.') elif arg == FLAG_SUBSTRATE_FORCE_C: parser.add_argument( FLAG_SUBSTRATE_FORCE_C, type=type_positive_float, help='Force constant (kcal mol-1 Å-2) to use for the substrate ' 'positional restraints.') else: raise TypeError('Unrecognized parser argument: ' + str(arg))
[docs]def add_cg_forcefield_parser_arguments(parser, local_path, defaults=None): """ Add desmond related arguments to the parser :param argparse.ArgumentParser parser: The parser to add the arguments to :param str local_path: We can search for CG forcefield files in either an "installed" directory or a "local" directory. This argument is the location of the "local" directory. :param dict defaults: Default values for the arguments """ # If you're not sure what you want to set the `local_path` to, then a good # idea would be to use `cgforcefield.FF_PARAMETERS_LOCAL_PATH`. Please # don't import `cgforcefield` into this module though. See MATSCI-11895. if defaults is None: defaults = FORCEFIELD_FLAGS_DEFAULTS else: user_defaults = defaults defaults = FORCEFIELD_FLAGS_DEFAULTS.copy() defaults.update(user_defaults) parser.add_argument(FLAG_CGFFLD, default=defaults[FLAG_CGFFLD], type=str, metavar='FORCE_FIELD_NAME', help=msutils.dedent(f""" Name of coarse-grained force field to use. See related location type flag {FLAG_CGFFLD_LOCATION_TYPE}. """)) parser.add_argument(FLAG_CGFFLD_LOCATION_TYPE, default=defaults[FLAG_CGFFLD_LOCATION_TYPE], choices=CG_FF_LOCATION_TYPES, type=str, metavar='FORCE_FIELD_LOCATION_TYPE', help=msutils.dedent(f""" Location type for the force field specified with "{FLAG_CGFFLD}". Option "{INSTALLED_CG_FF_LOCATION_TYPE}" means from a standard location in the Schrodinger installation, while option "{LOCAL_CG_FF_LOCATION_TYPE}" means either from {local_path} or the job launch directory, i.e. the CWD. """))
[docs]def add_traj_analysis_parser_arguments(parser, trj_optional=False): """ Add trajectory analysis parser arguments/ :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to :type trj_optional: bool :param trj_optional: Whether trajectory is optional """ parser.add_argument(FLAG_CMS_PATH, help='Input cms file', required=True, type=type_cms_file) parser.add_argument(FLAG_TRJ_PATH, required=not trj_optional, help='Directory of the trajectory') parser.add_argument(FLAG_TRJ_MIN, help='Minimum of trajectory frame to be analysed.', type=type_nonnegative_int) parser.add_argument(FLAG_TRJ_MAX, help='Maximum of trajectory frame to be analysed.', type=type_nonnegative_int) parser.add_argument(FLAG_TRJ_STEP, help=TRJ_STEP_HELP, type=type_positive_int)
[docs]def add_random_seed_parser_argument(parser, additional_args=(), help=DEFAULT_SEED_HELP): """ Add the command line flag for the random number seed :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to :param tuple additional_args: Arguments that should also be accepted in addition to FLAG_RANDOM_SEED :param str help: The help string to show for random seed. The default string will be used if nothing is supplied. """ args = [FLAG_RANDOM_SEED] args.extend(additional_args) parser.add_argument(*args, type=type_random_seed, default=RANDOM_SEED_DEFAULT, help=help)
[docs]def add_tpp_parser_argument(parser): """ Add a `FLAG_TPP` argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to """ parser.add_argument( FLAG_TPP, type=type_positive_int, default=DEFAULT_TPP, help='Specify the number ' 'of threads to use for parallelizing each subjob that supports ' 'threading.')
# eta vs centroid representation of a metallocene
[docs]def add_out_rep_parser_argument(parser): """ Add a `FLAG_OUT_REP` argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to """ parser.add_argument( FLAG_OUT_REP, choices=[CENTROID, ETA], help= 'Force a specific output representation. If not provided it will be the ' 'opposite of the input representation.')
[docs]def add_dedup_geom_eps_parser_argument(parser): """ Add a `FLAG_DEDUP_GEOM_EPS` argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to """ parser.add_argument( FLAG_DEDUP_GEOM_EPS, type=type_nonnegative_float, default=DEFAULT_DEDUP_GEOM_EPS, help='Reduce the number of calculations by deduplicating ' 'the input structures based on geometry, using this threshold ' 'in Ang., and only calculating the representatives. A ' 'value of zero means no deduplicating.')
[docs]def add_robust_jaguar_argument(parser): """ Add a `FLAG_ROBUST` argument to the parser :type parser: `argparse.ArgumentParser` :param parser: The parser to add this argument to """ parser.add_argument( FLAG_ROBUST, action='store_true', help='Use a robust procedure that will attempt to rerun failed Jaguar ' 'jobs with different settings in order to try to obtain success.')
[docs]def add_boltzmann_energy_argument(parser, lowest=True, prop_required=True): """ Add parser arguments for Boltzmann averaging :param `argparse.ArgumentParser` parser: The parser to add this argument to :param bool lowest: Whether to add the -lowest flag :param bool required: Whether the energy prop flag is required """ ahelp = ( 'Specify the energy property to use for Boltzmann averaging. If ' 'temperature dependent then the temperature must be reported in K and ' 'this temperature will be used for averaging. Otherwise the input ' 'temperatures in K will be used. This energy property key must be of ' 'known units. Valid keys are for example r_j_Gas_Phase_Energy, ' 'r_j_Total_Free_Energy_(au)_298.15K_1.00E+00atm, ' 'r_matsci_my_energy_(kcal/mol), etc.') parser.add_argument(FLAG_ENERGY_PROP, metavar='PROPERTY_NAME', required=prop_required, help=ahelp) if lowest: parser.add_argument( FLAG_LOWEST_ENERGY, action='store_true', help='Specify whether to return the lowest energy conformer ' 'instead of averaging properties over conformers.')
[docs]def get_jobname(options, default_jobname): """ Gets the job name from the parsed options. If there is no job name, use the default specified in this driver. :param argparse.Namespace options: The parsed command line options :param str default_jobname: The job name to use if no explicit name is found in `options` :return: The name of the job. Prioritizes the name given in the options, but falls back to a default job name. :rtype: str """ return (options.get(cmdline.JOBNAME.name) or jobutils.get_jobname(default_jobname))