Source code for schrodinger.application.phase.packages.conformer_reader

"""
Conformer structure reader, which extracts blocks of conformers from the given
input based on perception options.

Copyright Schrodinger LLC, All Rights Reserved.
"""

from schrodinger import structure
from schrodinger.application.phase.packages import phase_screen_utils
from schrodinger.application.phase.packages import phase_utils
from schrodinger.application.phase.packages import shape_ligprep
from schrodinger.infra import mm
from schrodinger.infra import phase

CONF_SAMPLE_METHODS = {
    phase.CONF_SAMPLE_COARSE_NAME: phase.CONF_SAMPLE_COARSE,
    phase.CONF_SAMPLE_FINE_NAME: phase.CONF_SAMPLE_FINE
}

CONF_FORCE_FIELDS = {
    mm.OPLS_NAME_F14: phase.CONF_FF_OPLS_2005,
    mm.OPLS_NAME_F16: phase.CONF_FF_OPLS3
}


[docs]class ConformerReaderOptions():
[docs] def __init__(self, args, preferred_subset): """ Initializes command line options used to read conformer blocks. :param args: argparser.Namespace with command line options :type args: argparser.Namespace """ # File and database screeing options self.distinct = args.distinct self.connect = args.connect self.stereo = args.stereo self.title = args.title if args.title else "" self.subset_file = phase_screen_utils.get_subset_file(args) self.preferred_subset = preferred_subset # Confgen options self.flex = args.flex self.confgen_options = get_confgen_options(args) # Ligprep options self.ligprep = getattr(args, 'ligprep', False) self.ligprep_options = getattr(args, 'ligprep_options', '')
[docs]class ConformerReader():
[docs] def __init__(self, source, options): """ Initializes... :param source: conformer structure input source :type source: str :param options: reader options derived from command line options :type options: ConformerReaderOptions """ self._db_ids = None # => not reading from Phase DB if phase_utils.is_phase_database_path(source): # Determine which database entries to read if options.subset_file: self._db_ids = phase.read_phase_subset(options.subset_file) else: self._db_ids = phase.get_database_ids(source, options.preferred_subset) self._reader = self._getReaderFromPhaseDb(source, options) elif options.ligprep: self._reader = self._getReaderFromLigprep(source, options) else: self._reader = self._getReaderFromFile(source, options) self._conf_adaptor = None self._append = options.confgen_options.getAppendConfs() if options.flex: self._conf_adaptor = phase.PhpMMconfAdaptor() self._conf_adaptor.setOptions(options.confgen_options)
[docs] def close(self): if isinstance(self._reader, shape_ligprep.LigPrep): self._reader.close()
[docs] def getConformers(self): """ Yields blocks of conformers read (or derived) from the source. :yield: Conformer structures. :ytype: list(structure.Structure) """ if isinstance(self._reader, shape_ligprep.LigPrep): # ligprep for prepared_structures in self._reader.yieldStructures(): for st in prepared_structures: structure_bus = phase.PhpStructureBus(st) yield self._getConformers(structure_bus) else: # PhaseDB or structure file (no ligprep) structure_bus = self._reader.next() while not structure_bus.empty(): yield self._getConformers(structure_bus) structure_bus = self._reader.next()
def _getReaderFromPhaseDb(self, source, options): """ Creates a Phase structure reader attached to the given Phase database, perceiving conformer blocks from the given options. :param source: conformer structure database input source :type source: str :param options: reader options derived from command line options :type options: ConformerReaderOptions :return: Phase structure reader attached to a Phase database :rtype: phase.PhpStructureReader """ # Determine how to perceive entries in the structure file read_mode = phase.DB_MULTI_CONF if options.flex and not options.confgen_options.getAppendConfs(): read_mode = phase.DB_SINGLE_CONF return phase.PhpStructureReader(source, read_mode, self._db_ids) def _getReaderFromFile(self, source, options): """ Creates a Phase structure reader attached to the given Maestro or SD file, perceiving conformer blocks from the given options. :param source: conformer structure file input source :type source: str :param options: reader options derived from command line options :type options: ConformerReaderOptions :return: Phase structure reader attached to a Maestro or SD file :rtype: phase.PhpStructureReader """ # Determine how to perceive entries in the file read_mode = phase.FILE_SINGLE_CONF if not options.flex and not options.distinct: if options.connect: read_mode = phase.FILE_MULTI_CONF_CONNECT elif options.stereo: read_mode = phase.FILE_MULTI_CONF_STEREO else: read_mode = phase.FILE_MULTI_CONF robust_read = True # Check for file format defects return phase.PhpStructureReader(source, read_mode, robust_read, options.title) def _getReaderFromLigprep(self, source, options): """ Instantiates a ligprepping Phase structure reader attached to the source. :param source: name of the input file name :type source: str :param options: reader options derived from command line options :type options: ConformerReaderOptions :return: the reader :rtype: ligprep """ return shape_ligprep.LigPrep(input_file=source, options=options.ligprep_options) def _getConformers(self, structure_bus): """ Extracts list of conformers from the given structure bus, generating conformers if necessary, and appending them if specified. :param structure_bus: current structure bus extracted from the reader :type structure_bus: phase.PhpStructureBus :return: conformer block extracted from the given structure bus :rtype: list(structure.Structure) """ # Extract cts from the structure bus if structure_bus.getStructureFormat() == phase.FORMAT_MMCT: cts = [structure_bus.getCtCopy()] else: cts = structure_bus.getCtVectorCopy() # Generate conformers if necessary if self._conf_adaptor: flex_cts = self._conf_adaptor.generateConformers(cts[0]) if self._append: cts += flex_cts else: for ct in cts: mm.mmct_ct_delete(ct) cts = flex_cts return [structure.Structure(ct) for ct in cts]
[docs]def add_file_options(parser, create_group=True): """ Adds file screening options to the provided parser and returns the argument group object that holds those options. :param parser: Argument parser object :type parser: argparser.ArgumentParser :param create_group: Create a new argument group and put file screening options into that group. :type create_group: bool :return: Argument group object :rtype: argparse._ArgumentGroup """ if create_group: file_options = parser.add_argument_group(title="File Screening Options") else: file_options = parser conf_treatment = file_options.add_mutually_exclusive_group(required=False) conf_treatment.add_argument( "-distinct", action="store_true", help="Treat each structure as a distinct molecule (i.e., one conformer " "only). By default, consecutive structures with identical titles " "and connectivity are treated as conformers of a single molecule.") conf_treatment.add_argument( "-connect", action="store_true", help= "Consider connectivities only (not titles) when perceiving conformers.") conf_treatment.add_argument( "-stereo", action="store_true", help="Consider stereochemistry when perceiving conformers. Consecutive " "structures with the same connectivity will be treated as conformers " "of a single molecule if and only if they have the same " "stereochemistry. Titles are ignored.") file_options.add_argument( "-title", metavar="<propname>", help="Use an alternate property (of string or integer type) " "as the source of titles for conformer perception.") return file_options
[docs]def add_confgen_options(parser, refine, default_conf_sample_name=phase.CONF_SAMPLE_COARSE_NAME): """ Adds conformer generation options to the provided parser and returns the argument group object that holds those options. :param parser: Argument parser object :type parser: argparser.ArgumentParser :param refine: Flag to add -refine as mutually exclusive option to -flex :type refine: bool :param default_conf_sample_name: Name of the conformer sampling method to be used by default (for help string only). :type default_conf_sample_name: str :return: Argument group object :rtype: argparse._ArgumentGroup """ confgen_options = parser.add_argument_group( title="Conformer Generation Options") if refine: flex_refine = confgen_options.add_mutually_exclusive_group( required=False) flex_refine.add_argument( "-flex", action="store_true", help="Generate conformers on-the-fly for each input structure. Not " "valid in combination with -distinct, -connect, -stereo or -refine." ) flex_refine.add_argument( "-refine", action="store_true", help= "Generate conformers on-the-fly for the highest scoring match and " "search for additional matches. May be used in combination with " "-distinct, -connect or -stereo, but not with -flex. Use of " "-append has no effect.") else: confgen_options.add_argument( "-flex", action="store_true", help="Generate conformers on-the-fly for each input structure. Not " "valid in combination with -distinct, -connect or -stereo or " "when input is a Phase Database.") add_standard_confgen_options(confgen_options, refine, default_conf_sample_name)
[docs]def add_standard_confgen_options( argument_group, refine, default_conf_sample_name=phase.CONF_SAMPLE_COARSE_NAME): """ Adds standard conformer generation options to the provided argument group. :param argument_group: Argument group object :type argument_group: argparser._ArgumentGroup :param refine: Set to True if argument_group supports -refine flag :type refine: bool :param default_conf_sample_name: Name of the conformer sampling method to be used by default (for help string only). :type default_conf_sample_name: str """ argument_group.add_argument( "-sample", choices=[phase.CONF_SAMPLE_COARSE_NAME, phase.CONF_SAMPLE_FINE_NAME], help="Conformational sampling method (default: %s)." % default_conf_sample_name) argument_group.add_argument( "-max", type=int, metavar="<numconfs>", choices=[phase_utils.RestrictedRange(0, None, False)], help="Maximum number of conformers to generate (default: %d)." % phase.PHASE_DEFAULT_MAX_CONFS) argument_group.add_argument( "-force_field", choices=[mm.OPLS_NAME_F14, mm.OPLS_NAME_F16], help="Use a force field to minimize conformers. Increases conformer " "generation time by approximately a factor of 50. The default is no " "force field minimization.") argument_group.add_argument( "-nddo", action="store_true", help="Minimize conformers using NDDO CM1A-BCC charge model. Adds " "significant time to minimization. Valid only with -force_field " "%s. The default is off." % mm.OPLS_NAME_F16) argument_group.add_argument( "-ewin", type=float, metavar="<deltaE>", choices=[phase_utils.RestrictedRange(0.0, None, False)], help="Conformer energy window in kJ/mol (default: %.1f)." % phase.PHASE_DEFAULT_ENERGY_WINDOW) argument_group.add_argument( "-append", action="store_true", help="Append new conformers to existing conformer(s). The default is " "to discard existing conformers.")
[docs]def get_confgen_options(args): """ Creates conformer generation options from the supplied parser. :param args: Command line arguments. :type args: argparse.Namespace :returned: Conformer generation options :rtype: PhpConfOptions """ confgen_options = phase.PhpConfOptions() if args.sample is not None: confgen_options.setSamplingMethod(CONF_SAMPLE_METHODS[args.sample]) if args.max is not None: confgen_options.setMaxConfs(args.max) if args.force_field is not None: confgen_options.setForceField(CONF_FORCE_FIELDS[args.force_field]) if args.nddo: confgen_options.setForceField(phase.CONF_FF_OPLS3_NDDO) if args.ewin is not None: confgen_options.setEnergyWindow(args.ewin) confgen_options.setAppendConfs(args.append) return confgen_options
[docs]def validate_confgen_conflicts(args): """ Checks options for conflicts between file options and confgen options not detected by ArgumentParser.parse_args and returns an informative error message if a conflict is found. :param args: argparser.Namespace with command line options :type args: argparser.Namespace :return: tuple of validity and error message if a conflict is found :rtype: bool, str """ # Catch exception if -refine was not added to the confgen options try: flex_or_refine = args.flex or args.refine flex_or_refine_str = "-flex or -refine" except AttributeError: flex_or_refine = args.flex flex_or_refine_str = "-flex" if args.flex: conf_treatment_args = { "-distinct": args.distinct, "-connect": args.connect, "-stereo": args.stereo } for key in conf_treatment_args: if conf_treatment_args[key]: mesg = "-flex is not allowed with %s" % key return False, mesg if not flex_or_refine: confgen_args = { "-sample": args.sample, "-max": args.max, "-force_field": args.force_field, "-ewin": args.ewin } for key in confgen_args: if confgen_args[key] is not None: mesg = "%s is allowed only with %s" % (key, flex_or_refine_str) return False, mesg if args.append: # Technically used only with -flex, so issue a -flex error message. return False, "-append is allowed only with -flex" nddo_ok, mesg = validate_confgen_nddo(args) if not nddo_ok: return False, mesg return True, ""
[docs]def validate_confgen_nddo(args): """ Checks for illegal use of -nddo flag. :param args: argparser.Namespace with command line arguments :type args: argparser.Namespace :return: tuple of validity and non-empty error message if not valid :rtype: bool, str """ if args.nddo: expected_ff = mm.OPLS_NAME_F16 if args.force_field != expected_ff: mesg = "-nddo is allowed only with -force_field %s" % expected_ff return False, mesg return True, ""
[docs]def validate_title_option(args): """ Checks for illegal values of -title flag. :param args: argparser.Namespace with command line arguments :type args: argparser.Namespace :return: tuple of validity and non-empty error message if not valid :rtype: bool, str """ if args.title: if not args.title.startswith(('i_', 's_')): return (False, "-title must refer to a string or integer property " "(property name starts with s_ or i_)") return True, ""