Source code for schrodinger.application.jaguar.autots_input
"""
Functions and classes for defining the input to a AutoTS workflow.
"""
# Contributors: Mark A. Watson, Leif D. Jacobson, Daniel S. Levine
import copy
import os
import schrodinger.application.jaguar.autots_constants as autots_constants
import schrodinger.application.jaguar.autots_input_constants as constants
from schrodinger.application.jaguar import autots_keywords
from schrodinger.application.jaguar import autots_validation as rv
from schrodinger.application.jaguar import utils as jag_utils # noqa: F401, mocked in autots_input_test
from schrodinger.application.jaguar import workflow_validation as wv # noqa: F401
from schrodinger.application.jaguar.autots_bonding import AutoTSStructureReader
from schrodinger.application.jaguar.input import JaguarInput # noqa: F401, mocked in autots_input_test
from schrodinger.application.jaguar.workflow_input import WorkflowInput
from schrodinger.structure import StructureReader
from schrodinger.structutils.analyze import generate_smiles
from schrodinger.utils import fileutils
[docs]def read_mae_files(fhlist, fileonly, clean_st, reset_bonding=True):
"""
Process a list of mae files and return extant file paths
or associated Structure instances if requested.
:type fhlist: list of strings
:param fhlist: list of filehandles
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True, cleanup structure file by removing dummy atoms, etc.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:return: Structures or file paths.
"""
# filter off non-strings
fname_strings = [x for x in fhlist if isinstance(x, str)]
# if file doesn't exist use a basename
# this is a fallback for when running
# remotely under jobcontrol and the path is bad
fname_basename = [x if os.path.exists(x) \
else os.path.basename(x) for x in fname_strings]
# filter out files that can't be found
fnames = filter(os.path.exists, fname_basename)
structs = []
for fh in fnames:
if fileonly:
# Append path to .mae file
structs.append(fh)
else:
# Append all structures in this filehandle
# if clean_st run through AutoTSStructureReader
if clean_st:
reader = AutoTSStructureReader(fh, reset_bonding=reset_bonding)
else:
reader = StructureReader(fh)
for st in reader:
structs.append(st)
return structs
#------------------------------------------------------------------------------
[docs]class AutoTSInput(WorkflowInput):
"""
A class to completely specify a AutoTS calculation.
Example usage::
input = AutoTSInput()
# Set user-defined values
input.setValue('integerKW', 3)
input.setValue('floatKW', '7.0')
input.setValue('stringKW', 'foo')
# Print all keyword/value pairs.
for keyword in input:
print "keyword '%s' has value %s" % (keyword, input[keyword])
# Handling the case of trying to set an unsupported keyword
try:
input['mykeyword'] = 'value'
except WorkflowKeywordException as e:
print e.allowed_keywords
"""
# List of keys for all possible input files associated with a given
# AutoTS workflow
input_file_keys = [
constants.REACTANT, constants.PRODUCT, constants.REACTANT_COMPLEX,
constants.PRODUCT_COMPLEX, constants.REFERENCE_REACTANT_COMPLEX,
constants.REFERENCE_PRODUCT_COMPLEX, constants.TEMPLATE_DATABASE_FILE,
constants.FULL_PATH_FILE, constants.SPECTATORS
]
workflow_name = 'AutoTS'
[docs] def __init__(self,
inputfile=None,
keywords=None,
jaguar_keywords=None,
jobname=None,
add_autots_jaguar_defaults=False):
"""
Create a AutoTSInput instance.
If a keyword is specified in both 'inputfile' and 'keywords',
then the values in 'keywords' will be set preferrentially.
This also applies to 'jaguar_keywords'.
:type inputfile: str
:param inputfile: Path to a AutoTS input file
:type keywords: dict
:param keywords: AutoTS keyword/value pairs
:type jaguar_keywords: dict
:param jaguar_keywords: Jaguar &gen section keyword/value pairs
:type jobname: string
:param jobname: Name of job, if it is not None it will be set to
the basename of the input file name.
:type add_autots_jaguar_defaults: boolean
:param add_autots_jaguar_defaults: if True add some custom Jaguar defaults
"""
self._add_autots_jaguar_defaults = add_autots_jaguar_defaults
super().__init__(inputfile=inputfile,
keywords=keywords,
jaguar_keywords=jaguar_keywords,
jobname=jobname)
self.setConstants()
if inputfile or keywords or jaguar_keywords:
self.validate()
@property
def reaction_smarts(self):
"""
Reaction smarts identifying input reaction
"""
reactants = [generate_smiles(r) for r in self.getReactants()]
products = [generate_smiles(p) for p in self.getProducts()]
return ".".join(reactants) + ">>" + ".".join(products)
[docs] def generate_keywords(self):
"""
Initialize dictionary of all possible AutoTS keywords
"""
if not autots_keywords.AUTOTS_KEYWORDS:
autots_keywords.generate_all_keywords()
return copy.deepcopy(autots_keywords.AUTOTS_KEYWORDS)
[docs] def setJaguarValues(self, keywords):
"""
Set multiple Jaguar &gen section keywords.
:type keywords: dict of string/anytype pairs
:param keywords: Jaguar &gen section keyword/value pairs
"""
super().setJaguarValues(keywords)
input_jaguar_keywords = list(keywords)
if self._add_autots_jaguar_defaults:
self._addAutoTSJaguarDefaults(input_jaguar_keywords)
def _addAutoTSJaguarDefaults(self, keywords_list):
"""
Add a few defaults for Jaguar that are specific to AutoTS.
:type keywords_list: list
:param keywords_list: List of jaguar keywords that have already been
set. No specialized non-defaults will be set for any of these
keywords.
"""
# set some extra defaults unless the user has specified otherwise
if 'isymm' not in keywords_list:
self._jaguarinput.setValue('isymm', 0)
if 'maxit' not in keywords_list:
self._jaguarinput.setValue('maxit', 200)
if 'nogas' not in keywords_list and 'isolv' in keywords_list:
self._jaguarinput.setValue('nogas', 2)
is_single_diffuse = self._jaguarinput.getValue('basis').count('+') == 1
if is_single_diffuse and 'iusediffuse' not in keywords_list:
self._jaguarinput.setValue('iusediffuse', 1)
[docs] def updateJaguarUserKeywords(self, keywords):
"""
Update the Jaguar &gen section keywords of the user-defined settings only
if previous settings exist.
:type keywords: dict of string/anytype pairs
:param keywords: Jaguar &gen section keyword/value pairs
"""
# This dict is empty unless a .in file has been read or AutoTSInput
# was instantiated with a non-empty jaguar_keywords argument.
if self._jaguar_user_keys:
self._jaguar_user_keys.update(keywords)
[docs] def getReactants(self, fileonly=False, clean_st=False, reset_bonding=True):
"""
Return list of reactants.
If no file(s) found, return empty list.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:type return: list
:return: reactant Structures or file paths.
"""
fhlist = self.getValue(constants.REACTANT)
reactants = read_mae_files(fhlist, fileonly, clean_st, reset_bonding)
return reactants
[docs] def getProducts(self, fileonly=False, clean_st=False, reset_bonding=True):
"""
Return list of products.
If no file(s) found, return empty list.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:type return: list
:return: product Structures or file paths.
"""
fhlist = self.getValue(constants.PRODUCT)
products = read_mae_files(fhlist, fileonly, clean_st, reset_bonding)
return products
[docs] def getReactantComplex(self,
fileonly=False,
clean_st=False,
reset_bonding=True):
"""
Return Reactant Complex.
If no file found, return NoneType.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:return: Reactant Complex Structures or file path.
"""
fh = self.getValue(constants.REACTANT_COMPLEX)
rcomplex = read_mae_files([fh], fileonly, clean_st, reset_bonding)
if rcomplex:
return rcomplex[0]
else:
return None
[docs] def getProductComplex(self,
fileonly=False,
clean_st=False,
reset_bonding=True):
"""
Return Product Complex.
If no file found, return NoneType.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:return: Product Complex Structures or file path.
"""
fh = self.getValue(constants.PRODUCT_COMPLEX)
pcomplex = read_mae_files([fh], fileonly, clean_st, reset_bonding)
if pcomplex:
return pcomplex[0]
else:
return None
[docs] def getReferenceReactantComplex(self,
fileonly=False,
clean_st=False,
reset_bonding=True):
"""
Return Reference Reactant Complex.
If no file found, return NoneType.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:return: Reactant Complex Structures or file path.
"""
fh = self.getValue(constants.REFERENCE_REACTANT_COMPLEX)
rcomplex = read_mae_files([fh], fileonly, clean_st, reset_bonding)
if rcomplex:
return rcomplex[0]
else:
return None
[docs] def getReferenceProductComplex(self,
fileonly=False,
clean_st=False,
reset_bonding=True):
"""
Return Reference Product Complex.
If no file found, return NoneType.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:type reset_bonding: bool
:param reset_bonding: if True and clean_st is True, redefine bonding using mmjag.
:return: Product Complex Structures or file path.
"""
fh = self.getValue(constants.REFERENCE_PRODUCT_COMPLEX)
pcomplex = read_mae_files([fh], fileonly, clean_st, reset_bonding)
if pcomplex:
return pcomplex[0]
else:
return None
[docs] def getSpectators(self, fileonly=False, clean_st=False):
"""
Return spectator.
If no file found, return NoneType.
:type fileonly: bool
:param fileonly: if True, return only file paths, else Structures.
:type clean_st: bool
:param clean_st: if True redefine bonding using mmlewis.
:return: Reactant Complex Structures or file path.
"""
fh = self.getValue(constants.SPECTATORS)
specs = read_mae_files(fh, fileonly, clean_st)
if specs:
return specs
else:
return None
[docs] def validate(self):
"""
Perform a self-consistency check of all currently set keywords.
:raise WorkflowKeywordConflictError if conflicting values found
:raise WorkflowConservationError if matter not conserved
"""
for k, kwd in self._keywords.items():
# The keys to self._keywords are keyword names so should be kept
# in sync else confusion could arise.
if k != kwd.name:
msg = "AutoTSInput class is corrupted: %s != %s" % (k, kwd.name)
raise RuntimeError(msg)
# Do internal AutoTSKeyword validation
kwd.validate()
# Do some ad hoc self-consistency checks for conflicting keywords
rv.check_conflicts(kwd, self._keywords)
# validate structures
rv.validate_structures(self)
strs = self.getReactants() + self.getProducts()
self.validate_jaguar_keywords(strs)
rv.validate_constraints(self)
[docs] def validate_jaguar_keywords(self, strs):
"""
Perform a check to ensure that Jaguar keywords are not
set that AutoTS cannot handle.
"""
jaguar_keys = self.getJaguarNonDefault()
rv.validate_jag_keys(jaguar_keys)
super().validate_jaguar_keywords(strs)
[docs] def setConstants(self):
"""
Set any constants defined by keywords
"""
autots_constants.FRACTION_DIFFERENT = self.getValue(
"bond_stretch_fraction")
[docs] def read(self, inputfile):
"""
Read an existing AutoTS input file.
Any keywords specified in the input file will override
existing values in this AutoTSInput instance.
Jaguar &gen section keywords are defined like::
&JaguarKeywords
key=val
key=val
...
&
Constraints can be defined with::
&Constraints
st_title atom_index1 atom_index2... value
&
:type inputfile: str
:param inputfile: Path to a AutoTS input file
"""
super().read(inputfile)
if self._add_autots_jaguar_defaults:
jaguar_keys = self.getJaguarNonDefault().keys()
self._addAutoTSJaguarDefaults(jaguar_keys)
[docs] def set_default_template_file(self):
"""
If the default template file is not set
and the keyword use_default_templates is True
and that file exists, set the template file to the default one.
"""
if not self.getValue(constants.TEMPLATE_DATABASE_FILE
) and self.getValue("use_default_templates"):
template_file = users_default_template_filename()
if os.path.exists(template_file):
self.setValue(constants.TEMPLATE_DATABASE_FILE, template_file)
[docs]def users_default_template_filename():
"""
Returns the name of the users template file that
*should* be found in .schrodinger/autots_templates.
The file may or may not exist.
"""
return os.path.join(fileutils.get_directory_path(fileutils.USERDATA),
"autots_templates", "autots_templates.mae")