Source code for schrodinger.application.jaguar.hydrokinetic_input
"""
Functions and classes for defining the input to a Hydrokinetic workflow.
"""
# Contributors: Mark A. Watson, Leif D. Jacobson, Daniel S. Levine
import copy
import os
import time
import schrodinger.application.jaguar.hydrokinetic_input_constants as constants
from schrodinger.application.jaguar import hydrokinetic_validation as dv
from schrodinger.application.jaguar import utils as jag_utils
from schrodinger.application.jaguar import hydrokinetic_keywords
from schrodinger.application.jaguar.autots_input import AutoTSInput, read_mae_files
from schrodinger.application.jaguar.exceptions import JaguarUserFacingException
from schrodinger.application.jaguar.workflow_input import WorkflowInput
from schrodinger.utils import fileutils
# List of keys for all possible input files associated with a given
# Hydrokinetic workflow
INPUT_FILE_KEYS = [
    constants.INPUT_MOLECULE,
]
HYDROKINETIC_TEMPLATE_FILE = 'hydrokinetic_template.in'
[docs]class HydrokineticInput(WorkflowInput):
    """
    A class to completely specify a Hydrokinetic calculation.
    Example usage:
    input = HydrokineticInput()
    input.setValue('integerKW', 3)
    input.setValue('floatKW', '7.0')
    input.setValue('stringKW', 'foo')
    for keyword in input:
        print "keyword '%s' has value %s" % (keyword, input[keyword])
    try:
        input['mykeyword'] = 'value'
    except WorkflowKeywordException as e:
        print e.allowed_keywords
    """
[docs]    def __init__(self,
                 inputfile=None,
                 keywords=None,
                 jaguar_keywords=None,
                 jobname=None,
                 add_hydrokinetic_jaguar_defaults=False):
        """
        Create a HydrokineticInput 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 Hydrokinetic input file
        :type  keywords: dict
        :param keywords: Hydrokinetic 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_hydrokinetic_jaguar_defaults: boolean
        :param add_hydrokinetic_jaguar_defaults: if True add some custom Jaguar defaults
        """
        self._add_hydrokinetic_jaguar_defaults = add_hydrokinetic_jaguar_defaults
        super().__init__(inputfile=inputfile,
                         keywords=keywords,
                         jaguar_keywords=jaguar_keywords,
                         jobname=jobname)
        if inputfile or keywords or jaguar_keywords:
            self.validate()
[docs]    def generate_keywords(self):
        """
        Initialize dictionary of all possible Hydrokinetic keywords
        """
        if not hydrokinetic_keywords.HYDROKINETIC_KEYWORDS:
            hydrokinetic_keywords.generate_all_keywords()
        return copy.deepcopy(hydrokinetic_keywords.HYDROKINETIC_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_hydrokinetic_jaguar_defaults:
            self._addHydrokineticJaguarDefaults(input_jaguar_keywords)
    def _addHydrokineticJaguarDefaults(self, keywords_list):
        """
        Add a few defaults for Jaguar that are specific to Hydrokinetic.
        :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)
        if 'mulken' not in keywords_list:
            self._jaguarinput.setValue('mulken', 1)
        if 'props_each_step' not in keywords_list:
            self._jaguarinput.setValue('props_each_step', 1)
        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 getInputMolecule(self, fileonly=False, clean_st=False):
        """
        Return list of input molecules.
        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 return: list
        :return: reactant Structures or file paths.
        """
        fh = self.getValue(constants.INPUT_MOLECULE)
        reactants = read_mae_files([fh],
                                   fileonly,
                                   clean_st,
                                   reset_bonding=False)
        if reactants:
            return reactants[0]
        else:
            raise JaguarUserFacingException(
                f'Failed to specify a valid {constants.INPUT_MOLECULE}.')
[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
        :raise JaguarUnsupportedBasisSet: if basis is not supported
        """
        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 = "HydrokineticInput class is corrupted: %s != %s" % (
                    k, kwd.name)
                raise RuntimeError(msg)
            # Do internal HydrokineticKeyword validation
            kwd.validate()
            # Do some ad hoc self-consistency checks for conflicting keywords
            dv.check_conflicts(kwd, self._keywords)
        # validate structures
        dv.validate_structures(self)
        strs = [self.getInputMolecule()]
        self.validate_jaguar_keywords(strs)
[docs]    def validate_jaguar_keywords(self, strs):
        """
        Perform a check to ensure that Jaguar keywords are not
        set that Hydrokinetic cannot handle.
        """
        jaguar_keys = self.getJaguarNonDefault()
        super().validate_jaguar_keywords(strs)
[docs]    def setJobname(self, jobname):
        """
        Set the attribute jobname.
        :type jobname: string
        :param jobname: input name of job.
                        If jobname is None we try to use self._inputfile.
                        If that is also None we assign a unique name.
        """
        if jobname is not None:
            self.jobname = jobname
        elif self._inputfile is not None:
            name, ext = os.path.splitext(self._inputfile)
            self.jobname = os.path.basename(name)
        else:
            # assign randomly based on the script Hydrokinetic and a time stamp
            stamp = str(time.time())
            self.jobname = jag_utils.get_jobname("Hydrokinetic", stamp)
[docs]    def read(self, inputfile):
        """
        Read an existing Hydrokinetic input file.
        Any keywords specified in the input file will override
        existing values in this HydrokineticInput 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 Hydrokinetic input file
        """
        super().read(inputfile)
        if self._add_hydrokinetic_jaguar_defaults:
            jaguar_keys = self.getJaguarNonDefault().keys()
            self._addHydrokineticJaguarDefaults(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 remove_input_file_paths(self):
        """
        Remove full paths from file specifications.
        A new input file is no longer written, if that is desired
        the user must call the save method.
        """
        # Update internal state of this class
        for key in INPUT_FILE_KEYS:
            filepath = self.getValue(key)
            if isinstance(filepath, list):
                basename = [os.path.basename(x) for x in filepath]
            else:
                basename = os.path.basename(filepath)
            self.setValue(key, basename)
[docs]    def update_input_file_paths(self):
        """
        Update full paths for file specifications to reflect the CWD.
        This is useful if running this job in a subdirectory or on a
        remote host.
        """
        def _prepend_cwd(cwd, filename):
            if os.path.exists(filename):
                return os.path.join(cwd, filename)
            else:
                raise IOError("File does not exist: %s" % filename)
        # remove old paths
        self.remove_input_file_paths()
        cwd = os.getcwd()
        # Update internal state of this class
        for key in INPUT_FILE_KEYS:
            filepath = self.getValue(key)
            if filepath:  #only act on non-empty lists/strings
                if isinstance(filepath, list):
                    basename = [_prepend_cwd(cwd, x) for x in filepath]
                else:
                    basename = _prepend_cwd(cwd, filepath)
                self.setValue(key, basename)
[docs]    def get_input_files(self):
        """
        Return set of expected input files.
        """
        infiles = super().get_input_files()
        # Auxiliary files
        for key in INPUT_FILE_KEYS:
            filepath = self.getValue(key)
            if isinstance(filepath, list):
                infiles.update(filepath)
            elif filepath:
                infiles.add(filepath)
        return infiles
[docs]def users_default_template_filename():
    """
    Returns the name of the users template file that
    *should* be found in .schrodinger/hydrokinetic_templates.
    The file may or may not exist.
    """
    return os.path.join(fileutils.get_directory_path(fileutils.USERDATA),
                        "hydrokinetic_templates", "hydrokinetic_templates.mae")
[docs]def create_autots_template_file(hinp,
                                autots_kwds,
                                fname=HYDROKINETIC_TEMPLATE_FILE):
    """
    Create AutoTS template file for use in Hydrokinetic from HydrokineticInput.
    :type hinp: HydrokineticInput
    :param hinp: HydrokineticInput instance containing keywords for AutoTS
    :type autots_kwds: dict
    :param autots_kwds: AutoTS workflow keywords to use
    :type fname: str
    :param fname: filename to use for AutoTS template file
    """
    jaguar_keywords = hinp.getJaguarNonDefault()
    autots_template = AutoTSInput()
    autots_template.setValues(autots_kwds)
    autots_template.setJaguarValues(jaguar_keywords)
    autots_template.save(fname)
    return fname