Esempio n. 1
0
def generate_conformations(molecule: oechem.OEGraphMol,
                           max_conformations: int = 1000,
                           dense: bool = False) -> oechem.OEMol:
    """
    Generate conformations of a given molecule.
    Parameters
    ----------
    molecule: oechem.OEGraphMol
        An OpenEye molecule.
    max_conformations: int
        Maximal number of conformations to generate.
    dense: bool
        If densely sampled conformers should be generated. Will overwrite max_conformations settings.
    Returns
    -------
    conformations: oechem.OEMol
        An OpenEye multi-conformer molecule holding the generated conformations.
    """
    from openeye import oechem, oeomega

    if dense:
        omega_options = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
    else:
        omega_options = oeomega.OEOmegaOptions()
        omega_options.SetMaxSearchTime(60.0)  # time out
        omega_options.SetMaxConfs(max_conformations)

    omega = oeomega.OEOmega(omega_options)
    omega.SetStrictStereo(False)

    conformations = oechem.OEMol(molecule)
    omega.Build(conformations)

    return conformations
Esempio n. 2
0
def FromMol(mol, use_flipper=True, num_sterocenters=12, force_flipper=False):
    """
    Generates a set of conformers as an OEMol object
    Inputs:
        mol is an OEMol
        isomers is a boolean controling whether or not the various diasteriomers of a molecule are created
        num_enantiomers is the allowable number of enantiomers. For all, set to -1
    """
    omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts.SetMaxConfRange("200,800")
    omegaOpts.SetRangeIncrement(8)
    omegaOpts.SetMaxSearchTime(30)
    omega = oeomega.OEOmega(omegaOpts)

    out_conf = []

    for enantiomer in oeomega.OEFlipper(mol.GetActive(), num_sterocenters,
                                        force_flipper):
        enantiomer = oechem.OEMol(enantiomer)
        ret_code = omega.Build(enantiomer)
        if ret_code == oeomega.OEOmegaReturnCode_Success:
            out_conf.append(enantiomer)

        else:
            oechem.OEThrow.Warning(
                "%s: %s" % (mol.GetTitle(), oeomega.OEGetOmegaError(ret_code)))

    return out_conf
Esempio n. 3
0
def dock_molecules(receptor_filename, molecules, filename):
    """
    Dock the specified molecules, writing out to specified file

    Parameters
    ----------
    receptor_filename : str
        Receptor .oeb.gz filename
    molecules : list of openeye.oechem.OEMol
        The read molecules to dock
    filename : str
        The filename to stream docked molecules to

    """
    # Read the receptor
    print('Loading receptor...')
    from openeye import oechem, oedocking
    receptor = oechem.OEGraphMol()
    if not oedocking.OEReadReceptorFile(receptor, 'receptor.oeb.gz'):
        oechem.OEThrow.Fatal("Unable to read receptor")

    if oedocking.OEReceptorHasBoundLigand(receptor):
        print("Receptor has a bound ligand")
    else:
        print("Receptor does not have bound ligand")

    print('Initializing receptor...')
    dockMethod = oedocking.OEDockMethod_Hybrid2
    dockResolution = oedocking.OESearchResolution_High
    dock = oedocking.OEDock(dockMethod, dockResolution)
    success = dock.Initialize(receptor)

    # Set up Omega
    from openeye import oeomega
    omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
    #omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    omega.SetStrictStereo(False)

    # Dock molecules
    with oechem.oemolostream(filename) as ofs:
        from tqdm import tqdm
        for mol in tqdm(molecules):
            dockedMol = oechem.OEGraphMol()

            # Expand conformers
            omega.Build(mol)

            # Dock molecule
            retCode = dock.DockMultiConformerMolecule(dockedMol, mol)
            if (retCode != oedocking.OEDockingReturnCode_Success):
                oechem.OEThrow.Fatal("Docking Failed with error code " + oedocking.OEDockingReturnCodeGetName(retCode))

            # Store docking data
            sdtag = oedocking.OEDockMethodGetName(dockMethod)
            oedocking.OESetSDScore(dockedMol, dock, sdtag)
            dock.AnnotatePose(dockedMol)

            # Write molecule
            oechem.OEWriteMolecule(ofs, dockedMol)
def main(argv=[__name__]):
    if len(argv) != 3:
        oechem.OEThrow.Usage("%s <infile> <outfile>" % argv[0])

    ifs = oechem.oemolistream()
    if not ifs.open(argv[1]):
        oechem.OEThrow.Fatal("Unable to open %s for reading" % argv[1])

    ofs = oechem.oemolostream()
    if not ofs.open(argv[2]):
        oechem.OEThrow.Fatal("Unable to open %s for writing" % argv[2])

    if not oechem.OEIs3DFormat(ofs.GetFormat()):
        oechem.OEThrow.Fatal("Invalid output file format for 3D coordinates!")

    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)

    for mol in ifs.GetOEMols():
        oechem.OEThrow.Info("Title: %s" % mol.GetTitle())

        for enantiomer in oeomega.OEFlipper(mol.GetActive(), 12, True):
            enantiomer = oechem.OEMol(enantiomer)
            ret_code = omega.Build(enantiomer)
            if ret_code == oeomega.OEOmegaReturnCode_Success:
                oechem.OEWriteMolecule(ofs, enantiomer)
            else:
                oechem.OEThrow.Warning(
                    "%s: %s" %
                    (enantiomer.GetTitle(), oeomega.OEGetOmegaError(ret_code)))

    return 0
def dock_molecules_to_receptor(receptor_filename):
    """
    Dock the specified molecules, writing out to specified file

    Parameters
    ----------
    receptor_filename : str
        Receptor .oeb.gz filename
    fragment : str
        The fragment name to dock to

    """
    import os

    # Read the receptor
    from openeye import oechem, oedocking
    receptor = oechem.OEGraphMol()
    if not oedocking.OEReadReceptorFile(receptor, receptor_filename):
        oechem.OEThrow.Fatal("Unable to read receptor")

    if not oedocking.OEReceptorHasBoundLigand(receptor):
        raise Exception("Receptor does not have bound ligand")

    #print('Initializing receptor...')
    dockMethod = oedocking.OEDockMethod_Hybrid2
    dockResolution = oedocking.OESearchResolution_Default
    dock = oedocking.OEDock(dockMethod, dockResolution)
    success = dock.Initialize(receptor)

    # Set up Omega
    #print('Expanding conformers...')
    from openeye import oeomega
    #omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    omega.SetStrictStereo(False)

    # Dock molecules
    docked_molecules = list()
    for mol in molecules:
        dockedMol = oechem.OEGraphMol()

        # Expand conformers
        omega.Build(mol)

        # Dock molecule
        #print(f'Docking {mol.NumConfs()} conformers...')
        retCode = dock.DockMultiConformerMolecule(dockedMol, mol)
        if (retCode != oedocking.OEDockingReturnCode_Success):
            print("Docking Failed with error code " + oedocking.OEDockingReturnCodeGetName(retCode))
            continue

        # Store docking data
        sdtag = oedocking.OEDockMethodGetName(dockMethod)
        oedocking.OESetSDScore(dockedMol, dock, sdtag)
        dock.AnnotatePose(dockedMol)

        docked_molecules.append( oechem.OEMol(dockedMol) )

    return docked_molecules
Esempio n. 6
0
def configure_omega(library,
                    rotor_predicate,
                    rms_cutoff,
                    energy_window,
                    num_conformers=MAX_CONFS):
    opts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
    opts.SetEnumRing(False)
    opts.SetEnumNitrogen(oeomega.OENitrogenEnumeration_Off)
    opts.SetSampleHydrogens(True)
    opts.SetRotorPredicate(rotor_predicate)
    opts.SetIncludeInput(False)

    opts.SetEnergyWindow(energy_window)
    opts.SetMaxConfs(num_conformers)
    opts.SetRMSThreshold(rms_cutoff)

    conf_sampler = oeomega.OEOmega(opts)
    conf_sampler.SetCanonOrder(False)
    torlib = conf_sampler.GetTorLib()

    # torlib.ClearTorsionLibrary()
    for rule in library:
        if not torlib.AddTorsionRule(rule):
            oechem.OEThrow.Fatal('Failed to add torsion rule: {}'.format(rule))
    conf_sampler.SetTorLib(torlib)

    return conf_sampler
Esempio n. 7
0
def FromMol(mol, isomer=True, num_enantiomers=-1):
    """
    Generates a set of conformers as an OEMol object
    Inputs:
        mol is an OEMol
        isomers is a boolean controling whether or not the various diasteriomers of a molecule are created
        num_enantiomers is the allowable number of enantiomers. For all, set to -1
    """
    omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts.SetMaxConfs(199)
    omega = oeomega.OEOmega(omegaOpts)
    out_conf = []
    ofs = oechem.oemolostream("test.sdf")
    if not isomer:
        ret_code = omega.Build(mol)
        if ret_code == oeomega.OEOmegaReturnCode_Success:
            out_conf.append(mol)
        else:
            oechem.OEThrow.Warning("%s: %s" % (mol.GetTitle(), oeomega.OEGetOmegaError(ret_code)))

    elif isomer:
        for enantiomer in oeomega.OEFlipper(mol.GetActive(), 12, True):
            enantiomer = oechem.OEMol(enantiomer)
            ret_code = omega.Build(enantiomer)
            if ret_code == oeomega.OEOmegaReturnCode_Success:
                out_conf.append(enantiomer)
                num_enantiomers -= 1
                oechem.OEWriteMolecule(ofs, mol)
                if num_enantiomers == 0:
                    break
            else:
                oechem.OEThrow.Warning("%s: %s" % (mol.GetTitle(), oeomega.OEGetOmegaError(ret_code)))

    return out_conf
Esempio n. 8
0
def main(argv=[__name__]):

    ifs = oechem.oemolistream()
    if not ifs.open("lig.smi"):  #input: ligand SMILES
        oechem.OEThrow.Fatal("Unable to open %s for reading" % argv[1])

    ofs = oechem.oemolostream()
    if not ofs.open("lig.oeb.gz"):  #output: OEBinary Format
        oechem.OEThrow.Fatal("Unable to open %s for writing" % argv[2])

    omegaOpts = oeomega.OEOmegaOptions()  #Parameters
    omegaOpts.SetMaxConfs(800)
    omegaOpts.SetCanonOrder(False)
    omegaOpts.SetSampleHydrogens(True)
    omegaOpts.SetEnergyWindow(15.0)
    omegaOpts.SetRMSThreshold(1.0)
    omegaOpts.SetStrictStereo(True)
    omegaOpts.SetRangeIncrement(8)
    omega = oeomega.OEOmega(omegaOpts)

    for mol in ifs.GetOEMols():
        oechem.OEThrow.Info("Title: %s" % mol.GetTitle())
        if omega(mol):
            oechem.OEWriteMolecule(ofs, mol)

    return 0
Esempio n. 9
0
    def __init__(self, prefix: str, ref_file: str, **kwargs):
        """
        Shape alignment on generated molecules to a reference molecule
        :param prefix: Name (to help keep track metrics, if using a scoring function class more than once)
        :param ref_file: Path to reference file to overlay query to (.pdb)
        :param return_best_overlay: Whether to also return best overlay (for use with docking)
        :param kwargs: Ignored
        """

        self.prefix = prefix.replace(" ", "_")
        self.rocs_metrics = [
            'GetColorScore', 'GetColorTanimoto', 'GetColorTversky',
            'GetComboScore', 'GetFitColorTversky', 'GetFitSelfColor',
            'GetFitSelfOverlap', 'GetFitTversky', 'GetFitTverskyCombo',
            'GetOverlap', 'GetRefColorTversky', 'GetRefSelfColor',
            'GetRefSelfOverlap', 'GetRefTversky', 'GetRefTverskyCombo',
            'GetShapeTanimoto', 'GetTanimoto', 'GetTanimotoCombo',
            'GetTversky', 'GetTverskyCombo'
        ]
        self.ref_file = ref_file
        self.refmol = oechem.OEMol()
        ifs = oechem.oemolistream(self.ref_file)  # Set up input file stream
        oechem.OEReadMolecule(ifs, self.refmol)  # Read ifs to empty mol object
        self.fitmol = None
        self.rocs_results = None
        self.best_overlay = None

        omegaOpts = oeomega.OEOmegaOptions()
        omegaOpts.SetStrictStereo(False)
        omegaOpts.SetMaxSearchTime(1.0)
        self.omega = oeomega.OEOmega(omegaOpts)
Esempio n. 10
0
def enumerate_from_smiles(smiles, oe_options=None):
    oe_options = oe_options or OEOptions()

    omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Pose)
    omegaOpts.SetMaxSearchTime(60)
    omega = oeomega.OEOmega(omegaOpts)

    if oe_options.use_tautomer:
        tautomer_options = oequacpac.OETautomerOptions()
        pKa_norm = True
        taut_iter = lambda x: oequacpac.OEGetReasonableTautomers(x, tautomer_options, pKa_norm)
    else:
        taut_iter = lambda x: [x]

    if oe_options.use_flipper:
        flipper = lambda x: oeomega.OEFlipper(x.GetActive(), oe_options.num_sterocenters, oe_options.force_flipper)
    else:
        flipper = lambda x: [x]

    # get molecule
    try:
        molecule = mol_from_smiles(smiles)
    except ValueError:
        return [None]

    results = []
    for enantiomer in flipper(molecule):
        for tautomer in taut_iter(enantiomer):
            tautomer = oechem.OEMol(tautomer)
            omega.Build(tautomer)
            tautomer2 = oechem.OEMol(tautomer)
            results.append(tautomer2)
    return results
def expand_stereochemistry(mols):
    """Expand stereochemistry when uncertain

    Parameters
    ----------
    mols : openeye.oechem.OEGraphMol
        Molecules to be expanded

    Returns
    -------
    expanded_mols : openeye.oechem.OEMol
        Expanded molecules
    """
    expanded_mols = list()

    from openeye import oechem, oeomega
    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    maxcenters = 12
    forceFlip = False
    enumNitrogen = True
    warts = True # add suffix for stereoisomers
    for mol in mols:
        for enantiomer in oeomega.OEFlipper(mol, maxcenters, forceFlip, enumNitrogen, warts):
            enantiomer = oechem.OEMol(enantiomer)
            expanded_mols.append(enantiomer)

    return expanded_mols
Esempio n. 12
0
def create_conformers(infile=None, outfile=None, resname=None, folder= None, name = None):

    """
    This function takes a mol1 file and runs Openeye's omega to create conformers for the molecules
    The conformers are stored in separated files, adding the number of the conformer at the end of the filename

    :param infile: Path to input file
    :param outfile: Path to output file return
    :param folder: Name of the folder for the target. If not specified. {name}-liquid is used.
    :param resname: Abbreviation of the Residue. Specified in the mol2
    :return: Number of conformers for this molecule
    """
    if folder is None and name is None:
        log.error('Please specify keyword argument folder or name')
        sys.exit(1)
    elif folder is None:
        folder = name +'-liquid'
    infilepath = os.path.join(folder, infile)
    outfilepath = os.path.join(folder, outfile)
    ifs = oechem.oemolistream()
    if not ifs.open(infilepath):
        oechem.OEThrow.Fatal("Unable to open %s for reading" % infilepath)

    ofs = oechem.oemolostream()
    if not ofs.open(outfilepath):
        oechem.OEThrow.Fatal("Unable to open %s for writing" % outfilepath)

    if not oechem.OEIs2DFormat(ofs.GetFormat()):
        oechem.OEThrow.Fatal("Invalid output file format for 2D coordinates!")

    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    omega.SetCommentEnergy(True)
    omega.SetEnumNitrogen(True)
    omega.SetSampleHydrogens(True)
    omega.SetEnergyWindow(9.0)
    omega.SetMaxConfs(5)
    omega.SetRangeIncrement(2)
    omega.SetRMSRange([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5])
    filename = '{}-conformers'.format(resname)
    for mol in ifs.GetOEMols():
        ret_code = omega.Build(mol)
        if ret_code == oeomega.OEOmegaReturnCode_Success:
            oechem.OEWriteMolecule(ofs, mol)
            for k, conf in enumerate(mol.GetConfs()):
                ofs1 = oechem.oemolostream()
                if not ofs1.open(os.path.join(folder, filename + '_' + str(k + 1) + '.mol2')):
                    oechem.OEThrow.Fatal("Unable to open %s for writing" % os.path.join(folder, filename + '_' + str(k + 1) + '.mol2'))
                oechem.OEWriteMolecule(ofs1, conf)
                nconf = k + 1
            log.info('Created conformations for {} and saved them to {}'.format(infilepath, outfilepath))

        else:
            oechem.OEThrow.Warning("%s: %s" % (mol.GetTitle(), oeomega.OEGetOmegaError(ret_code)))

    return nconf
Esempio n. 13
0
 def __init__(self, receptor: utils.FilePath):
     self.receptor_file = receptor
     omegaOpts = oeomega.OEOmegaOptions()
     omegaOpts.SetStrictStereo(False)
     self.omega = oeomega.OEOmega(omegaOpts)
     oechem.OEThrow.SetLevel(10000)
     oereceptor = oechem.OEGraphMol()
     oedocking.OEReadReceptorFile(oereceptor, self.receptor_file)
     self.dock = oedocking.OEDock()
     self.dock.Initialize(oereceptor)
Esempio n. 14
0
def initialize_system(dt=0.001,
                      temperature=100,
                      forcefield_file='forcefield/smirnoff99Frosst.offxml',
                      smiles="C1CCCCC1"):

    mol = OEMol()
    # OEParseSmiles(mol, 'CCOCCSCC')
    # OEParseSmiles(mol, 'c1ccccc1')
    OEParseSmiles(mol, smiles)
    # OEParseSmiles(mol, 'C([C@@H]1[C@H]([C@@H]([C@H](C(O1)O)O)O)O)O')
    OEAddExplicitHydrogens(mol)
    masses = get_masses(mol)
    num_atoms = mol.NumAtoms()

    topology = generateTopologyFromOEMol(mol)

    ff = ForceField(get_data_filename(forcefield_file))

    nrgs, total_params, offsets = system_builder.construct_energies(
        ff, mol, False)

    # dt = 0.0025
    # friction = 10.0
    # temperature = 300

    # gradient descent
    dt = dt
    friction = 10.0
    temperature = temperature

    a, b, c = get_abc_coefficents(masses, dt, friction, temperature)

    buf_size = estimate_buffer_size(1e-10, a)

    print("BUFFER SIZE", buf_size)
    omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts.SetMaxConfs(1)
    omega = oeomega.OEOmega(omegaOpts)
    omega.SetStrictStereo(False)

    if not omega(mol):
        assert 0

    x0 = mol_coords_to_numpy_array(mol) / 10

    intg = custom_ops.Integrator_double(dt, buf_size, num_atoms, total_params,
                                        a, b, c)

    context = custom_ops.Context_double(nrgs, intg)

    x0 = minimizer.minimize_newton_cg(nrgs, x0, total_params)

    return nrgs, offsets, intg, context, x0, total_params
Esempio n. 15
0
def _docking_internal(receptor: oechem.OEGraphMol,
                      bound_ligand: oechem.OEGraphMol, ligand_string):
    """
    Internal method for docking to a receptor given a bound ligand and ligand to dock to

    Parameters
    ----------
    receptor: oechem.OEGraphMol, required
        An oechem receptor to dock to

    bound_ligand: oechem.OEGraphMol, required
        The ligand bound to the current receptor pocket

    docking_ligand: oechem.OEGraphMol, required
        The ligand to dock.

    Returns
    -------
    An oechem molecule with coordinates of its docking

    """
    # Create posit config
    oedocking.OEReceptorSetBoundLigand(receptor, bound_ligand)
    poser = oedocking.OEHybrid(oedocking.OEDockMethod_Hybrid2,
                               oedocking.OESearchResolution_High)
    poser.Initialize(receptor)

    # Create multiple conformations
    omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts.SetMaxConfs(1000)
    omega_driver = oeomega.OEOmega(omegaOpts)
    conformer_docking = oechem.OEMol()  # type: OEMol
    oechem.OESmilesToMol(conformer_docking, ligand_string)
    oechem.OEAddExplicitHydrogens(conformer_docking)
    print(conformer_docking.NumAtoms())

    if not omega_driver(conformer_docking):
        single_sdf = tempfile.NamedTemporaryFile(suffix=".sdf")
        double_sdf = tempfile.NamedTemporaryFile(suffix=".sdf")
        smiles = ligand_string
        subprocess.run(
            "obabel -:'%s' -O %s --gen3d; obabel %s -O %s --confab --conf = 100"
            % (smiles, single_sdf.name, single_sdf.name, double_sdf.name),
            shell=True)
        ins = oechem.oemolistream()
        ins.SetConfTest(oechem.OEOmegaConfTest())
        ins.open(double_sdf.name)
        oechem.OEReadMolecule(ins, conformer_docking)

    # Dock and get top conformer
    posed_ligand = oechem.OEGraphMol()  # type: OEGraphMol
    poser.DockMultiConformerMolecule(posed_ligand, conformer_docking)
    return posed_ligand
Esempio n. 16
0
    def from_oemol(self, from_oemol):
        with self.logger("from_oemol") as logger:
            tautomer_options = oequacpac.OETautomerOptions()
            tautomer_options.SetMaxTautomersGenerated(4096)
            tautomer_options.SetMaxTautomersToReturn(16)
            tautomer_options.SetCarbonHybridization(True)
            tautomer_options.SetMaxZoneSize(50)
            tautomer_options.SetApplyWarts(True)

            pKa_norm = True

            omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Pose)
            omegaOpts.SetStrictAtomTypes(False)
            omegaOpts.SetSampleHydrogens(True)
            omegaOpts.SetMaxSearchTime(30)
            omegaOpts.SetFixDeleteH(True)
            omega = oeomega.OEOmega(omegaOpts)

            options = oeshape.OEROCSOptions()
            overlayoptions = oeshape.OEOverlayOptions()
            overlayoptions.SetOverlapFunc(
                oeshape.OEOverlapFunc(oeshape.OEAnalyticShapeFunc()))
            options.SetOverlayOptions(overlayoptions)
            # options.SetNumBestHits(10)
            options.SetConfsPerHit(200)
            # options.SetMaxHits(10000)
            rocs = oeshape.OEROCS(options)
            for tautomer in oequacpac.OEGetReasonableTautomers(
                    from_oemol, tautomer_options, pKa_norm):
                logger.log("got enantiomer")
                for enantiomer in oeomega.OEFlipper(tautomer, 4, False):
                    logger.log("got tautomer ")
                    enantiomer_ = oechem.OEMol(enantiomer)
                    ret_code = omega.Build(enantiomer_)
                    if ret_code != oeomega.OEOmegaReturnCode_Success:
                        logger.error("got oemeg_failed",
                                     oeomega.OEGetOmegaError(ret_code))
                    else:
                        rocs.AddMolecule(oechem.OEMol(enantiomer_))

            for res in rocs.Overlay(self.refmol):
                outmol = oechem.OEMol(res.GetOverlayConfs())
                good_mol = oechem.OEMol(outmol)
                oechem.OEAddExplicitHydrogens(good_mol)
                oechem.OEClearSDData(good_mol)
                oeshape.OEDeleteCompressedColorAtoms(good_mol)
                oeshape.OEClearCachedSelfColor(good_mol)
                oeshape.OEClearCachedSelfShape(good_mol)
                oeshape.OERemoveColorAtoms(good_mol)
                return good_mol
            logger.error("Returning None.")

        return None
Esempio n. 17
0
def expand_stereochemistry(mols):
    """Expand stereochemistry when uncertain

    Parameters
    ----------
    mols : openeye.oechem.OEGraphMol
        Molecules to be expanded

    Returns
    -------
    expanded_mols : openeye.oechem.OEMol
        Expanded molecules
    """
    expanded_mols = list()

    from openeye import oechem, oeomega
    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    maxcenters = 12
    forceFlip = False
    enumNitrogen = False
    warts = True  # add suffix for stereoisomers
    for mol in mols:
        compound_title = mol.GetTitle()
        compound_smiles = oechem.OEMolToSmiles(mol)

        enantiomers = list()
        for enantiomer in oeomega.OEFlipper(mol, maxcenters, forceFlip,
                                            enumNitrogen, warts):
            enantiomer = oechem.OEMol(enantiomer)
            enantiomer_smiles = oechem.OEMolToSmiles(enantiomer)
            oechem.OESetSDData(enantiomer, 'compound', compound_title)
            oechem.OESetSDData(enantiomer, 'compound_smiles', compound_smiles)
            oechem.OESetSDData(enantiomer, 'enantiomer_smiles',
                               enantiomer_smiles)
            enantiomers.append(enantiomer)

        expanded_mols += enantiomers

        # DEBUG
        if 'EDJ-MED-e4b030d8-2' in mol.GetTitle():
            msg = 'Enumerated microstates for compound: '
            msg += mol.GetTitle() + '\n'
            msg += f'{"":5s}   ' + oechem.OEMolToSmiles(mol) + '\n'
            for index, m in enumerate(enantiomers):
                msg += f'{index:5d} : ' + oechem.OEMolToSmiles(m) + '\n'
            print(msg)

    return expanded_mols
Esempio n. 18
0
def gen_conf(mol):
    oemols = []
    omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_FastROCS)
    omega = oeomega.OEOmega(omegaOpts)
    for enantiomer in oeomega.OEFlipper(mol.GetActive(), 6, True):
        enantiomer = oechem.OEMol(enantiomer)
        ret_code = omega.Build(enantiomer)
        if ret_code == oeomega.OEOmegaReturnCode_Success:
            halfMol = oechem.OEMol(mol, oechem.OEMCMolType_HalfFloatCartesian)
            oemols.append(halfMol)
        else:
            oechem.OEThrow.Warning(
                "%s: %s" %
                (enantiomer.GetTitle(), oeomega.OEGetOmegaError(ret_code)))
    return oemols
Esempio n. 19
0
 def __init__(self, prefix: str, receptor_file: os.PathLike):
     """
     :param prefix: Prefix to identify scoring function instance (e.g., Risperidone)
     :param receptor_file: Path to receptor file (.oeb).
     """
     logger.warning("This code has not been tested (at all!)")
     self.prefix = prefix
     self.receptor_file = receptor_file
     omegaOpts = oeomega.OEOmegaOptions()
     omegaOpts.SetStrictStereo(False)
     self.omega = oeomega.OEOmega(omegaOpts)
     oechem.OEThrow.SetLevel(10000)
     oereceptor = oechem.OEGraphMol()
     oedocking.OEReadReceptorFile(oereceptor, self.receptor_file)
     self.oedock = oedocking.OEDock()
     self.oedock.Initialize(oereceptor)
Esempio n. 20
0
    def begin(self):

        omegaOpts = oeomega.OEOmegaOptions()
        omega = oeomega.OEOmega(omegaOpts)

        omega = oeomega.OEOmega()
        omega.SetIncludeInput(False)
        omega.SetCanonOrder(False)
        omega.SetSampleHydrogens(True)
        omega.SetStrictStereo(False)  # JDC
        omega.SetMaxSearchTime(
            self.args.max_search_time)  # maximum omega search time
        omega.SetEnergyWindow(self.args.energy_window)
        omega.SetMaxConfs(self.args.max_confs)
        omega.SetRMSThreshold(1.0)
        self.omega = omega
Esempio n. 21
0
 def __init__(self, prefix: str, receptor_file: str):
     """
     Scoring function class to perform OpenEye docking (FRED)
     :param prefix: Name (to help keep track metrics, if using a scoring function class more than once)
     :param receptor_file: Path to oe receptor file.
     """
     logger.warning("This code has not been tested (at all!)")
     self.prefix = prefix
     self.receptor_file = receptor_file
     omegaOpts = oeomega.OEOmegaOptions()
     omegaOpts.SetStrictStereo(False)
     self.omega = oeomega.OEOmega(omegaOpts)
     oechem.OEThrow.SetLevel(10000)
     oereceptor = oechem.OEGraphMol()
     oedocking.OEReadReceptorFile(oereceptor, self.receptor_file)
     self.oedock = oedocking.OEDock()
     self.oedock.Initialize(oereceptor)
Esempio n. 22
0
def initialize(input_smiles, gp):
    train_reference_args = []
    train_args = []
    train_offset_idxs = []
    train_charge_idxs = []

    for smi_idx, smiles in enumerate(input_smiles):
        print("processing", smiles, smi_idx, "/", len(input_smiles))
        mol = OEMol()
        OEParseSmiles(mol, smiles)
        OEAddExplicitHydrogens(mol)
        masses = get_masses(mol)
        num_atoms = mol.NumAtoms()

        omegaOpts = oeomega.OEOmegaOptions()
        omegaOpts.SetMaxConfs(1)
        omega = oeomega.OEOmega(omegaOpts)
        omega.SetStrictStereo(False)

        if not omega(mol):
            assert 0

        topology = generateTopologyFromOEMol(mol)
        reference_forcefield_file = 'forcefield/smirnoff99Frosst_perturbed.offxml'
        ff = ForceField(get_data_filename(reference_forcefield_file))
        params = system_builder.construct_energies(ff, mol, True)
        train_reference_args.append((params[0], params[1], masses, mol))

        params = system_builder.construct_energies(ff, mol, False)
        # global_params = args[0]
        # nrg_params = args[1]
        # total_params = args[2]
        # masses = args[3]
        # mol = args[4]
        # charge_idxs = args[5]
        train_args.append((gp, params[0], params[1], masses, mol, params[3]))
        train_offset_idxs.append(params[2])
        train_charge_idxs.append(params[3])

    label_confs = [generate_conformations(a) for a in train_reference_args]

    return train_args, train_offset_idxs, train_charge_idxs, label_confs
Esempio n. 23
0
    def __init__(self, prefix: str, ref_file: os.PathLike, **kwargs):
        """
        :param prefix: Prefix to identify scoring function instance (e.g., Risperidone)
        :param ref_file: Path to reference file to overlay query to (.pdb)
        :param kwargs:
        """

        self.prefix = prefix.replace(" ", "_")
        self.rocs_metrics = ROCS.return_metrics
        self.ref_file = ref_file
        self.refmol = oechem.OEMol()
        ifs = oechem.oemolistream(self.ref_file)  # Set up input file stream
        oechem.OEReadMolecule(ifs, self.refmol)  # Read ifs to empty mol object
        self.fitmol = None
        self.rocs_results = None
        self.best_overlay = None

        omegaOpts = oeomega.OEOmegaOptions()
        omegaOpts.SetStrictStereo(False)
        omegaOpts.SetMaxSearchTime(1.0)
        self.omega = oeomega.OEOmega(omegaOpts)
def expand_stereochemistry(mols):
    """Expand stereochemistry when uncertain

    Parameters
    ----------
    mols : openeye.oechem.OEGraphMol
        Molecules to be expanded

    Returns
    -------
    expanded_mols : openeye.oechem.OEMol
        Expanded molecules
    """
    expanded_mols = list()

    from openeye import oechem, oeomega
    omegaOpts = oeomega.OEOmegaOptions()
    omega = oeomega.OEOmega(omegaOpts)
    maxcenters = 12
    forceFlip = False
    enumNitrogen = True
    warts = True  # add suffix for stereoisomers
    for mol in mols:
        compound_title = mol.GetTitle()
        compound_smiles = oechem.OEMolToSmiles(mol)

        enantiomers = list()
        for enantiomer in oeomega.OEFlipper(mol, maxcenters, forceFlip,
                                            enumNitrogen, warts):
            enantiomer = oechem.OEMol(enantiomer)
            enantiomer_smiles = oechem.OEMolToSmiles(enantiomer)
            oechem.OESetSDData(enantiomer, 'compound', compound_title)
            oechem.OESetSDData(enantiomer, 'compound_smiles', compound_smiles)
            oechem.OESetSDData(enantiomer, 'enantiomer_smiles',
                               enantiomer_smiles)
            enantiomers.append(enantiomer)

        expanded_mols += enantiomers

    return expanded_mols
def generate_restricted_conformers(receptor, refmol, mol, core_smarts=None):
    """
    Generate and select a conformer of the specified molecule using the reference molecule

    Parameters
    ----------
    receptor : openeye.oechem.OEGraphMol
        Receptor (already prepped for docking) for identifying optimal pose
    refmol : openeye.oechem.OEGraphMol
        Reference molecule which shares some part in common with the proposed molecule
    mol : openeye.oechem.OEGraphMol
        Molecule whose conformers are to be enumerated
    core_smarts : str, optional, default=None
        If core_smarts is specified, substructure will be extracted using SMARTS.
    """
    from openeye import oechem, oeomega

    # DEBUG: For benzotriazoles, truncate refmol
    core_smarts = 'c1ccc(NC(=O)[C,N]n2nnc3ccccc32)cc1' # prospective
    core_smarts = 'NC(=O)[C,N]n2nnc3ccccc32' # retrospective

    # Get core fragment
    if core_smarts:
        # Truncate refmol to SMARTS if specified
        #print(f'Trunctating using SMARTS {refmol_smarts}')
        ss = oechem.OESubSearch(core_smarts)
        oechem.OEPrepareSearch(refmol, ss)
        for match in ss.Match(refmol):
            core_fragment = oechem.OEGraphMol()
            oechem.OESubsetMol(core_fragment, match)
            break
        #print(f'refmol has {refmol.NumAtoms()} atoms')
    else:
        core_fragment = GetCoreFragment(refmol, [mol])
        oechem.OESuppressHydrogens(core_fragment)
        #print(f'  Core fragment has {core_fragment.NumAtoms()} heavy atoms')
        MIN_CORE_ATOMS = 6
        if core_fragment.NumAtoms() < MIN_CORE_ATOMS:
            return None

    # Create an Omega instance
    #omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)

    # Set the fixed reference molecule
    omegaFixOpts = oeomega.OEConfFixOptions()
    omegaFixOpts.SetFixMaxMatch(10) # allow multiple MCSS matches
    omegaFixOpts.SetFixDeleteH(True) # only use heavy atoms
    omegaFixOpts.SetFixMol(core_fragment)
    #omegaFixOpts.SetFixSmarts(smarts)
    omegaFixOpts.SetFixRMS(0.5)

    atomexpr = oechem.OEExprOpts_Aromaticity | oechem.OEExprOpts_Hybridization
    bondexpr = oechem.OEExprOpts_BondOrder | oechem.OEExprOpts_Aromaticity
    omegaFixOpts.SetAtomExpr(atomexpr)
    omegaFixOpts.SetBondExpr(bondexpr)
    omegaOpts.SetConfFixOptions(omegaFixOpts)

    molBuilderOpts = oeomega.OEMolBuilderOptions()
    molBuilderOpts.SetStrictAtomTypes(False) # don't give up if MMFF types are not found
    omegaOpts.SetMolBuilderOptions(molBuilderOpts)

    omegaOpts.SetWarts(False) # expand molecule title
    omegaOpts.SetStrictStereo(False) # set strict stereochemistry
    omegaOpts.SetIncludeInput(False) # don't include input
    omegaOpts.SetMaxConfs(1000) # generate lots of conformers
    #omegaOpts.SetEnergyWindow(10.0) # allow high energies
    omega = oeomega.OEOmega(omegaOpts)

    from openeye import oequacpac
    if not oequacpac.OEGetReasonableProtomer(mol):
        print('No reasonable protomer found')
        return None

    mol = oechem.OEMol(mol) # multi-conformer molecule

    ret_code = omega.Build(mol)
    if (mol.GetDimension() != 3) or (ret_code != oeomega.OEOmegaReturnCode_Success):
        print(f'Omega failure: {mol.GetDimension()} and {oeomega.OEGetOmegaError(ret_code)}')
        return None

    # Extract poses
    class Pose(object):
        def __init__(self, conformer):
            self.conformer = conformer
            self.clash_score = None
            self.docking_score = None
            self.overlap_score = None

    poses = [ Pose(conf) for conf in mol.GetConfs() ]

    # Score clashes
    bump_check = BumpCheck(receptor)
    for pose in poses:
        pose.clash_score = bump_check.count(pose.conformer)

    # Score docking poses
    from openeye import oedocking
    score = oedocking.OEScore(oedocking.OEScoreType_Chemgauss4)
    score.Initialize(receptor)
    for pose in poses:
        pose.docking_score = score.ScoreLigand(pose.conformer)

    # Compute overlap scores
    from openeye import oeshape
    overlap_prep = oeshape.OEOverlapPrep()
    overlap_prep.Prep(refmol)
    shapeFunc = oeshape.OEExactShapeFunc()
    shapeFunc.SetupRef(refmol)
    oeshape_result = oeshape.OEOverlapResults()
    for pose in poses:
        tmpmol = oechem.OEGraphMol(pose.conformer)
        overlap_prep.Prep(tmpmol)
        shapeFunc.Overlap(tmpmol, oeshape_result)
        pose.overlap_score = oeshape_result.GetRefTversky()

    # Filter poses based on top 10% of overlap
    poses = sorted(poses, key= lambda pose : pose.overlap_score)
    poses = poses[int(0.9*len(poses)):]

    # Select the best docking score
    import numpy as np
    poses = sorted(poses, key=lambda pose : pose.docking_score)
    pose = poses[0]
    mol.SetActive(pose.conformer)
    oechem.OESetSDData(mol, 'clash_score', str(pose.clash_score))
    oechem.OESetSDData(mol, 'docking_score', str(pose.docking_score))
    oechem.OESetSDData(mol, 'overlap_score', str(pose.overlap_score))

    # Convert to single-conformer molecule
    mol = oechem.OEGraphMol(mol)

    return mol
Esempio n. 26
0
def oesolvate(solute,
              density=1.0,
              padding_distance=10.0,
              distance_between_atoms=2.5,
              solvents='tip3p',
              molar_fractions='1.0',
              geometry='box',
              close_solvent=True,
              salt='[Na+], [Cl-]',
              salt_concentration=0.0,
              neutralize_solute=True,
              verbose=False,
              return_components=False,
              **kargs):
    """
    This function solvates the passed solute in a cubic box or a sphere by using Packmol. Packmol
    creates an initial point for molecular dynamics simulations by packing molecule in defined regions
    of space. For additional info:
    http://www.ime.unicamp.br/~martinez/packmol/home.shtml

    The geometry volume is estimated by the using the padding parameter and the solute size.
    The number of solvent molecules is calculated by using the specified density and volume.
    Solvent molecules are specified as comma separated smiles strings. The molar fractions
    of each solvent molecule are specified in a similar fashion. By default if the solute is
    charged counter ions are added to neutralize it

    Parameters:
    -----------
    solute: OEMol molecule
        The solute to solvate
    density: float
        The solution density in g/ml
    padding_distance: float
        The largest dimension of the solute (along the x, y, or z axis) is determined (in A), 
        and a cubic box of size (largest dimension)+2*padding is used
    distance_between_atoms: float
        The minimum distance between atoms in A
    solvents: python string
        A comma separated smiles string or keywords for the solvent molecules.
        Special water models can be selected by using the keywords:
        tip3p for TIP3P water model geometry
    molar_fractions: python string
        A comma separated molar fraction string of the solvent molecules
    close_solvent: boolean
        If True solvent molecules will be placed very close to the solute
    salt: python string
        A comma separated string of the dissociated salt in solution
    salt_concentration: float
        Salt concentration in millimolar
    neutralize_solute: boolean
        If True counter-ions will be added to the solution to neutralize the solute
    verbose: Bool
        If True verbose mode is enabled
    return_components: Bool
        If True the added solvent molecules are also returned as OEMol

    Return:
    -------
    oe_mol: OEMol
        The solvated system. If the selected geometry is a box a SD tag with
        name 'box_vector' is attached the output molecule containing
        the system box vectors.
    oe_mol_components: OEMol
        If the return_components flag is True the added solvent molecules are
        returned as an additional OEMol
    """
    def BoundingBox(molecule):
        """
        This function calculates the Bounding Box of the passed
        molecule

        molecule: OEMol

        return: bb (numpy array)
            the calculated bounding box is returned as numpy array:
            [(xmin,ymin,zmin), (xmax,ymax,zmax)]
        """
        coords = [v for k, v in molecule.GetCoords().items()]
        np_coords = np.array(coords)
        min_coord = np_coords.min(axis=0)
        max_coord = np_coords.max(axis=0)
        bb = np.array([min_coord, max_coord])
        return bb

    if shutil.which("packmol") is None:
        raise (IOError("Packmol executable not found"))

    # Extract solvent smiles strings and mole fractions
    solvents = [sm.strip() for sm in solvents.split(',')]
    fractions = [float(mf) for mf in molar_fractions.split(',')]

    # If the smiles string and mole fractions lists have different lengths raise an error
    if len(solvents) != len(fractions):
        raise ValueError(
            "Selected solvent number and selected molar fraction number mismatch: {} vs {}"
            .format(len(solvents), len(fractions)))

    # Remove smiles string with 0.0 mole fraction
    solvent_smiles = [
        solvents[i] for i, v in enumerate(fractions) if fractions[i]
    ]
    mol_fractions = [mf for mf in fractions if mf]

    # Mole fractions are non-negative numbers
    if any([v < 0.0 for v in mol_fractions]):
        raise ValueError("Error: Mole fractions are non-negative real numbers")

    # Mole fractions must sum up to 1.0
    if abs(sum(mol_fractions) - 1.0) > 0.001:
        oechem.OEThrow.Error("Error: Mole fractions do not sum up to 1.0")

    if geometry not in ['box', 'sphere']:
        raise ValueError(
            "Error geometry: the supported geometries are box and sphere not {}"
            .format(geometry))

    # Set Units
    density = density * unit.grams / unit.milliliter
    padding_distance = padding_distance * unit.angstrom
    salt_concentration = salt_concentration * unit.millimolar

    # Calculate the Solute Bounding Box
    BB_solute = BoundingBox(solute)

    # Estimate of the box cube length
    box_edge = 2.0 * padding_distance + np.max(BB_solute[1] -
                                               BB_solute[0]) * unit.angstrom

    if geometry == 'box':
        # Box Volume
        Volume = box_edge**3
    if geometry == 'sphere':
        Volume = (4.0 / 3.0) * 3.14159265 * (0.5 * box_edge)**3

    # Omega engine is used to generate conformations
    omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts.SetMaxConfs(1)
    omegaOpts.SetStrictStereo(False)
    omega = oeomega.OEOmega(omegaOpts)

    # Create a string code to identify the solute residues. The code ID used is based
    # on the residue number id, the residue name and the chain id:
    # id+resname+chainID
    hv_solute = oechem.OEHierView(
        solute,
        oechem.OEAssumption_BondedResidue + oechem.OEAssumption_ResPerceived)
    solute_resid_list = []
    for chain in hv_solute.GetChains():
        for frag in chain.GetFragments():
            for hres in frag.GetResidues():
                oe_res = hres.GetOEResidue()
                solute_resid_list.append(
                    str(oe_res.GetResidueNumber()) + oe_res.GetName() +
                    chain.GetChainID())

    # Solvent component list_names
    solvent_resid_dic_names = dict()

    # Neutralize solute
    ion_sum_wgt_n_ions = 0.0 * unit.grams / unit.mole
    if neutralize_solute:
        # Container for the counter-ions
        oe_ions = []
        # Container for the ion smiles strings
        ions_smiles = []
        solute_formal_charge = 0
        for at in solute.GetAtoms():
            solute_formal_charge += at.GetFormalCharge()
        if solute_formal_charge > 0:
            ions_smiles.append("[Cl-]")
        elif solute_formal_charge < 0:
            ions_smiles.append("[Na+]")
        else:
            pass

        # Total number of counter-ions to neutralize the solute
        n_ions = abs(solute_formal_charge)

        # print("Counter ions to add = {} of {}".format(n_ions, ions_smiles[0]))

        # Ions
        if n_ions >= 1:
            for sm in ions_smiles:
                mol = oechem.OEMol()
                if not oechem.OESmilesToMol(mol, sm):
                    raise ValueError(
                        "Error counter ions: SMILES string parsing fails for the string: {}"
                        .format(sm))

                # Generate conformer
                if not omega(mol):
                    raise ValueError(
                        "Error counter ions: Conformer generation fails for the molecule with "
                        "smiles string: {}".format(sm))

                oe_ions.append(mol)

                if sm == '[Na+]':
                    solvent_resid_dic_names[' NA'] = mol
                else:
                    solvent_resid_dic_names[' CL'] = mol

            ion_sum_wgt = 0.0 * unit.grams / unit.mole
            for ion in oe_ions:
                # Molecular weight
                ion_sum_wgt += oechem.OECalculateMolecularWeight(
                    ion) * unit.grams / unit.mole

            ion_sum_wgt_n_ions = ion_sum_wgt * n_ions

            # Create ions .pdb files
            ions_smiles_pdbs = []
            for i in range(0, len(ions_smiles)):
                pdb_name = os.path.basename(tempfile.mktemp(suffix='.pdb'))
                pdb_name = ions_smiles[i] + '_' + pdb_name
                ions_smiles_pdbs.append(pdb_name)

            for i in range(0, len(ions_smiles)):
                ofs = oechem.oemolostream(ions_smiles_pdbs[i])
                oechem.OEWriteConstMolecule(ofs, oe_ions[i])

    # Add salts to the solution

    # Solvent smiles string parsing
    char_set = string.ascii_uppercase
    salt_sum_wgt_n_salt = 0.0 * unit.grams / unit.mole
    if salt_concentration > 0.0 * unit.millimolar:

        salt_smiles = [sm.strip() for sm in salt.split(',')]

        # Container list of oemol salt molecules generated by using smiles strings
        oe_salt = []

        for sm in salt_smiles:
            mol_salt = oechem.OEMol()
            if not oechem.OESmilesToMol(mol_salt, sm):
                raise ValueError(
                    "Error salt: SMILES string parsing fails for the string: {}"
                    .format(sm))

            # Generate conformer
            if not omega(mol_salt):
                raise ValueError(
                    "Error salt: Conformer generation fails for the "
                    "molecule with smiles string: {}".format(sm))

            # Unique 3 code letter are set as solvent residue names
            solv_id = ''.join(random.sample(char_set * 3, 3))

            # Try to recognize the residue name
            oechem.OEPerceiveResidues(mol_salt)

            for atmol in mol_salt.GetAtoms():
                res = oechem.OEAtomGetResidue(atmol)
                if res.GetName() == 'UNL':
                    res.SetName(solv_id)
                    oechem.OEAtomSetResidue(atmol, res)
                    if solv_id not in solvent_resid_dic_names:
                        solvent_resid_dic_names[solv_id] = mol_salt
                else:
                    if res.GetName() not in solvent_resid_dic_names:
                        solvent_resid_dic_names[res.GetName()] = mol_salt
                    break

            oe_salt.append(mol_salt)

        n_salt = int(
            round(unit.AVOGADRO_CONSTANT_NA * salt_concentration *
                  Volume.in_units_of(unit.liter)))

        # for i in range(0, len(salt_smiles)):
        #     print("Number of molecules for the salt component {} = {}".format(salt_smiles[i], n_salt))

        salt_sum_wgt = 0.0 * unit.grams / unit.mole
        for salt in oe_salt:
            # Molecular weight
            salt_sum_wgt += oechem.OECalculateMolecularWeight(
                salt) * unit.grams / unit.mole

        salt_sum_wgt_n_salt = salt_sum_wgt * n_salt

        # Create salt .pdb files
        if n_salt >= 1:
            salt_pdbs = []
            for i in range(0, len(salt_smiles)):
                pdb_name = os.path.basename(tempfile.mktemp(suffix='.pdb'))
                # pdb_name = salt_smiles[i] + '_' + pdb_name
                salt_pdbs.append(pdb_name)

            for i in range(0, len(salt_smiles)):
                ofs = oechem.oemolostream(salt_pdbs[i])
                oechem.OEWriteConstMolecule(ofs, oe_salt[i])

    # Container list of oemol solvent molecules generated by using smiles strings
    oe_solvents = []

    for sm in solvent_smiles:

        if sm == 'tip3p':
            tip3p_fn = os.path.join(PACKAGE_DIR, 'oeommtools', 'data',
                                    'tip3p.pdb')
            ifs = oechem.oemolistream(tip3p_fn)
            mol_sol = oechem.OEMol()

            if not oechem.OEReadMolecule(ifs, mol_sol):
                raise IOError(
                    "It was not possible to read the tip3p molecule file")
        else:

            mol_sol = oechem.OEMol()

            if not oechem.OESmilesToMol(mol_sol, sm):
                raise ValueError(
                    "Error solvent: SMILES string parsing fails for the string: {}"
                    .format(sm))

            # Generate conformer
            if not omega(mol_sol):
                raise ValueError(
                    "Error solvent: Conformer generation fails for "
                    "the molecule with smiles string: {}".format(sm))

        # Unique 3 code letter are set as solvent residue names
        solv_id = ''.join(random.sample(char_set * 3, 3))

        # Try to recognize the residue name
        oechem.OEPerceiveResidues(mol_sol)

        for atmol in mol_sol.GetAtoms():
            res = oechem.OEAtomGetResidue(atmol)
            if res.GetName() == 'UNL':
                res.SetName(solv_id)
                oechem.OEAtomSetResidue(atmol, res)
                if solv_id not in solvent_resid_dic_names:
                    solvent_resid_dic_names[solv_id] = mol_sol
            else:
                if res.GetName() not in solvent_resid_dic_names:
                    solvent_resid_dic_names[res.GetName()] = mol_sol
                break

        oe_solvents.append(mol_sol)

    # Sum of the solvent molecular weights
    solvent_sum_wgt_frac = 0.0 * unit.grams / unit.mole

    for idx in range(0, len(oe_solvents)):
        # Molecular weight
        wgt = oechem.OECalculateMolecularWeight(
            oe_solvents[idx]) * unit.grams / unit.mole
        solvent_sum_wgt_frac += wgt * mol_fractions[idx]

    # Solute molecular weight
    solute_wgt = oechem.OECalculateMolecularWeight(
        solute) * unit.gram / unit.mole

    # Estimate of the number of each molecular species present in the solution accordingly
    # to their molar fraction fi:
    #
    # ni = fi*(density*volume*NA - wgt_solute - sum_k(wgt_salt_k*nk) - wgt_ion*n_ion)/sum_j(wgt_nj * fj)
    #
    # where ni is the number of molecule of specie i, density the mixture density, volume the
    # mixture volume, wgt_solute the molecular weight of the solute, wgt_salt_k the molecular
    # weight of the salt component k, nk the number of molecule of salt component k, wgt_ion
    # the counter ion molecular weight, n_ions the number of counter ions and wgt_nj the molecular
    # weight of the molecule specie j with molar fraction fj

    div = (unit.AVOGADRO_CONSTANT_NA * density * Volume -
           (solute_wgt + salt_sum_wgt_n_salt +
            ion_sum_wgt_n_ions)) / solvent_sum_wgt_frac

    # Solvent number of monomers
    n_monomers = [int(round(mf * div)) for mf in mol_fractions]

    if not all([nm > 0 for nm in n_monomers]):
        raise ValueError(
            "Error negative number of solvent components: the density could be too low"
        )

    # for i in range(0, len(solvent_smiles)):
    #     print("Number of molecules for the component {} = {}".format(solvent_smiles[i], n_monomers[i]))

    # Packmol Configuration file setting
    if close_solvent:
        header_template = """\n# Mixture\ntolerance {}\nfiletype pdb\noutput {}\nadd_amber_ter\navoid_overlap no"""
    else:
        header_template = """\n# Mixture\ntolerance {}\nfiletype pdb\noutput {}\nadd_amber_ter\navoid_overlap yes"""

    # Templates strings
    solute_template = """\n\n# Solute\nstructure {}\nnumber 1\nfixed 0. 0. 0. 0. 0. 0.\nresnumbers 1\nend structure"""

    if geometry == 'box':
        solvent_template = """\nstructure {}\nnumber {}\ninside box {:0.3f} {:0.3f} {:0.3f} {:0.3f} {:0.3f} {:0.3f}\
        \nchain !\nresnumbers 3\nend structure"""
    if geometry == 'sphere':
        solvent_template = """\nstructure {}\nnumber {}\ninside sphere {:0.3f} {:0.3f} {:0.3f} {:0.3f}\
        \nchain !\nresnumbers 3\nend structure"""

    # Create solvents .pdb files
    solvent_pdbs = []
    for i in range(0, len(solvent_smiles)):
        pdb_name = os.path.basename(tempfile.mktemp(suffix='.pdb'))
        solvent_pdbs.append(pdb_name)

    for i in range(0, len(solvent_smiles)):
        ofs = oechem.oemolostream(solvent_pdbs[i])
        oechem.OEWriteConstMolecule(ofs, oe_solvents[i])

    solute_pdb = 'solute' + '_' + os.path.basename(
        tempfile.mktemp(suffix='.pdb'))
    ofs = oechem.oemolostream(solute_pdb)

    if solute.GetMaxConfIdx() > 1:
        raise ValueError("Solutes with multiple conformers are not supported")
    else:
        oechem.OEWriteConstMolecule(ofs, solute)

    # Write Packmol header section
    mixture_pdb = 'mixture' + '_' + os.path.basename(
        tempfile.mktemp(suffix='.pdb'))
    body = header_template.format(distance_between_atoms, mixture_pdb)
    # Write Packmol configuration file solute section
    body += solute_template.format(solute_pdb)

    # The solute is centered inside the box
    xc = (BB_solute[0][0] + BB_solute[1][0]) / 2.
    yc = (BB_solute[0][1] + BB_solute[1][1]) / 2.
    zc = (BB_solute[0][2] + BB_solute[1][2]) / 2.

    # Correct for periodic box conditions to avoid
    # steric clashes at the box edges
    pbc_correction = 1.0 * unit.angstrom

    xmin = xc - ((box_edge - pbc_correction) / 2.) / unit.angstrom
    xmax = xc + ((box_edge - pbc_correction) / 2.) / unit.angstrom
    ymin = yc - ((box_edge - pbc_correction) / 2.) / unit.angstrom
    ymax = yc + ((box_edge - pbc_correction) / 2.) / unit.angstrom
    zmin = zc - ((box_edge - pbc_correction) / 2.) / unit.angstrom
    zmax = zc + ((box_edge - pbc_correction) / 2.) / unit.angstrom

    # Packmol setting for the solvent section
    body += '\n\n# Solvent'
    for i in range(0, len(solvent_smiles)):
        if geometry == 'box':
            body += solvent_template.format(solvent_pdbs[i], n_monomers[i],
                                            xmin, ymin, zmin, xmax, ymax, zmax)
        if geometry == 'sphere':
            body += solvent_template.format(solvent_pdbs[i], n_monomers[i], xc,
                                            yc, zc,
                                            0.5 * box_edge / unit.angstrom)

    # Packmol setting for the salt section
    if salt_concentration > 0.0 * unit.millimolar and n_salt >= 1:
        body += '\n\n# Salt'
        for i in range(0, len(salt_smiles)):
            if geometry == 'box':
                body += solvent_template.format(salt_pdbs[i],
                                                int(round(n_salt)), xmin, ymin,
                                                zmin, xmax, ymax, zmax)
            if geometry == 'sphere':
                body += solvent_template.format(salt_pdbs[i],
                                                int(round(n_salt)), xc, yc, zc,
                                                0.5 * box_edge / unit.angstrom)

    # Packmol setting for the ions section
    if neutralize_solute and n_ions >= 1:
        body += '\n\n# Counter Ions'
        for i in range(0, len(ions_smiles)):
            if geometry == 'box':
                body += solvent_template.format(ions_smiles_pdbs[i], n_ions,
                                                xmin, ymin, zmin, xmax, ymax,
                                                zmax)
            if geometry == 'sphere':
                body += solvent_template.format(ions_smiles_pdbs[i], n_ions,
                                                xc, yc, zc,
                                                0.5 * box_edge / unit.angstrom)

    # Packmol configuration file
    packmol_filename = os.path.basename(tempfile.mktemp(suffix='.inp'))

    with open(packmol_filename, 'w') as file_handle:
        file_handle.write(body)

    # Call Packmol
    if not verbose:
        mute_output = open(os.devnull, 'w')
        with open(packmol_filename, 'r') as file_handle:
            subprocess.check_call(['packmol'],
                                  stdin=file_handle,
                                  stdout=mute_output,
                                  stderr=mute_output)
    else:
        with open(packmol_filename, 'r') as file_handle:
            subprocess.check_call(['packmol'], stdin=file_handle)

    # Read in the Packmol solvated system
    solvated = oechem.OEMol()

    if os.path.exists(mixture_pdb + '_FORCED'):
        os.rename(mixture_pdb + '_FORCED', mixture_pdb)
        print("Warning: Packing solution is not optimal")

    ifs = oechem.oemolistream(mixture_pdb)
    oechem.OEReadMolecule(ifs, solvated)

    # To avoid to change the user oemol starting solute by reading in
    # the generated mixture pdb file and loosing molecule info, the
    # solvent molecules are extracted from the mixture system and
    # added back to the starting solute

    # Extract from the solution system the solvent molecules
    # by checking the previous solute generated ID: id+resname+chainID
    hv_solvated = oechem.OEHierView(
        solvated,
        oechem.OEAssumption_BondedResidue + oechem.OEAssumption_ResPerceived)

    # This molecule will hold the solvent molecules generated directly from
    # the omega conformers. This is useful to avoid problems related to read in
    # the solvent molecules from pdb files and triggering unwanted perceiving actions
    new_components = oechem.OEMol()

    bv = oechem.OEBitVector(solvated.GetMaxAtomIdx())
    for chain in hv_solvated.GetChains():
        for frag in chain.GetFragments():
            for hres in frag.GetResidues():
                oe_res = hres.GetOEResidue()
                if str(oe_res.GetResidueNumber()) + oe_res.GetName(
                ) + chain.GetChainID() not in solute_resid_list:
                    oechem.OEAddMols(new_components,
                                     solvent_resid_dic_names[oe_res.GetName()])
                    atms = hres.GetAtoms()
                    for at in atms:
                        bv.SetBitOn(at.GetIdx())

    pred = oechem.OEAtomIdxSelected(bv)
    components = oechem.OEMol()
    oechem.OESubsetMol(components, solvated, pred)

    new_components.SetCoords(components.GetCoords())

    # This is necessary otherwise just one big residue is created
    oechem.OEPerceiveResidues(new_components)

    # Add the solvent molecules to the solute copy
    solvated_system = solute.CreateCopy()
    oechem.OEAddMols(solvated_system, new_components)

    # Set Title
    solvated_system.SetTitle(solute.GetTitle())

    # Set ions resname to Na+ and Cl-
    for at in solvated_system.GetAtoms():
        res = oechem.OEAtomGetResidue(at)
        if res.GetName() == ' NA':
            res.SetName("Na+")
            oechem.OEAtomSetResidue(atmol, res)
        elif res.GetName() == ' CL':
            res.SetName("Cl-")
            oechem.OEAtomSetResidue(atmol, res)
        else:
            pass

    # Cleaning
    to_delete = solvent_pdbs + [packmol_filename, solute_pdb, mixture_pdb]

    if salt_concentration > 0.0 * unit.millimolar and n_salt >= 1:
        to_delete += salt_pdbs
    if neutralize_solute and n_ions >= 1:
        to_delete += ions_smiles_pdbs

    for fn in to_delete:
        try:
            os.remove(fn)
        except:
            pass

    # Calculate the solution total density
    total_wgt = oechem.OECalculateMolecularWeight(
        solvated_system) * unit.gram / unit.mole
    density_mix = (1 / unit.AVOGADRO_CONSTANT_NA) * total_wgt / Volume
    print("Computed Solution Density = {}".format(
        density_mix.in_units_of(unit.gram / unit.milliliter)))
    # Threshold checking
    ths = 0.1 * unit.gram / unit.milliliter
    if not abs(density -
               density_mix.in_units_of(unit.gram / unit.milliliter)) < ths:
        raise ValueError(
            "Error: the computed density for the solute {} does not match the selected density {} vs {}"
            .format(solute.GetTitle(), density_mix, density))

    if geometry == 'box':
        # Define the box vector and attached it as SD tag to the solvated system
        # with ID tag: 'box_vectors'
        box_vectors = (Vec3(box_edge / unit.angstrom, 0.0,
                            0.0), Vec3(0.0, box_edge / unit.angstrom, 0.0),
                       Vec3(0.0, 0.0,
                            box_edge / unit.angstrom)) * unit.angstrom

        box_vectors = data_utils.encodePyObj(box_vectors)
        solvated_system.SetData(oechem.OEGetTag('box_vectors'), box_vectors)

    if return_components:
        new_components.SetTitle(solute.GetTitle() + '_solvent_comp')
        return solvated_system, new_components
    else:
        return solvated_system
def generate_restricted_conformers(receptor, refmol, mol, core_smarts=None):
    """
    Generate and select a conformer of the specified molecule using the reference molecule

    Parameters
    ----------
    receptor : openeye.oechem.OEGraphMol
        Receptor (already prepped for docking) for identifying optimal pose
    refmol : openeye.oechem.OEGraphMol
        Reference molecule which shares some part in common with the proposed molecule
    mol : openeye.oechem.OEGraphMol
        Molecule whose conformers are to be enumerated
    core_smarts : str, optional, default=None
        If core_smarts is specified, substructure will be extracted using SMARTS.
    """
    from openeye import oechem, oeomega

    logging.debug(
        f'mol: {oechem.OEMolToSmiles(mol)} | core_smarts: {core_smarts}')

    # Be quiet
    from openeye import oechem
    oechem.OEThrow.SetLevel(oechem.OEErrorLevel_Quiet)
    #oechem.OEThrow.SetLevel(oechem.OEErrorLevel_Error)

    # Get core fragment
    if core_smarts:
        # Truncate refmol to SMARTS if specified
        #print(f'Trunctating using SMARTS {refmol_smarts}')
        ss = oechem.OESubSearch(core_smarts)
        oechem.OEPrepareSearch(refmol, ss)
        for match in ss.Match(refmol):
            core_fragment = oechem.OEGraphMol()
            oechem.OESubsetMol(core_fragment, match)
            logging.debug(
                f'Truncated refmol to generate core_fragment: {oechem.OEMolToSmiles(core_fragment)}'
            )
            break
        #print(f'refmol has {refmol.NumAtoms()} atoms')
    else:
        core_fragment = GetCoreFragment(refmol, [mol])
        oechem.OESuppressHydrogens(core_fragment)
        #print(f'  Core fragment has {core_fragment.NumAtoms()} heavy atoms')
        MIN_CORE_ATOMS = 6
        if core_fragment.NumAtoms() < MIN_CORE_ATOMS:
            return None

    # Create an Omega instance
    #omegaOpts = oeomega.OEOmegaOptions()
    omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)

    # Set the fixed reference molecule
    omegaFixOpts = oeomega.OEConfFixOptions()
    omegaFixOpts.SetFixMaxMatch(10)  # allow multiple MCSS matches
    omegaFixOpts.SetFixDeleteH(True)  # only use heavy atoms
    omegaFixOpts.SetFixMol(core_fragment)
    #omegaFixOpts.SetFixSmarts(core_smarts) # DEBUG
    omegaFixOpts.SetFixRMS(0.5)

    # This causes a warning:
    #Warning: OESubSearch::Match() is unable to match unset hybridization in the target (EN300-221518_3_1) for patterns with set hybridization, call OEPrepareSearch on the target first
    #atomexpr = oechem.OEExprOpts_Aromaticity | oechem.OEExprOpts_Hybridization

    atomexpr = oechem.OEExprOpts_Aromaticity | oechem.OEExprOpts_AtomicNumber
    bondexpr = oechem.OEExprOpts_BondOrder | oechem.OEExprOpts_Aromaticity
    omegaFixOpts.SetAtomExpr(atomexpr)
    omegaFixOpts.SetBondExpr(bondexpr)
    omegaOpts.SetConfFixOptions(omegaFixOpts)

    molBuilderOpts = oeomega.OEMolBuilderOptions()
    molBuilderOpts.SetStrictAtomTypes(
        False)  # don't give up if MMFF types are not found
    omegaOpts.SetMolBuilderOptions(molBuilderOpts)

    omegaOpts.SetWarts(False)  # expand molecule title
    omegaOpts.SetStrictStereo(True)  # set strict stereochemistry
    omegaOpts.SetIncludeInput(False)  # don't include input
    omegaOpts.SetMaxConfs(1000)  # generate lots of conformers
    omegaOpts.SetEnergyWindow(20.0)  # allow high energies
    omega = oeomega.OEOmega(omegaOpts)

    # TODO: Expand protonation states and tautomers
    from openeye import oequacpac
    if not oequacpac.OEGetReasonableProtomer(mol):
        logging.warning('No reasonable protomer found')
        return None

    mol = oechem.OEMol(mol)  # multi-conformer molecule

    ret_code = omega.Build(mol)
    if (mol.GetDimension() != 3) or (ret_code !=
                                     oeomega.OEOmegaReturnCode_Success):
        msg = f'\nOmega failure for {mol.GetTitle()} : SMILES {oechem.OEMolToSmiles(mol)} : core_smarts {core_smarts} : {oeomega.OEGetOmegaError(ret_code)}\n'
        logging.warning(msg)
        return None
        # Return the molecule with an error code
        #oechem.OESetSDData(mol, 'error', '{oeomega.OEGetOmegaError(ret_code)}')
        #return mol

    # Extract poses
    class Pose(object):
        def __init__(self, conformer):
            self.conformer = conformer
            self.clash_score = None
            self.docking_score = None
            self.overlap_score = None

    poses = [Pose(conf) for conf in mol.GetConfs()]

    # Score clashes
    bump_check = BumpCheck(receptor)
    for pose in poses:
        pose.clash_score = bump_check.count(pose.conformer)

    # Score docking poses
    from openeye import oedocking
    score = oedocking.OEScore(oedocking.OEScoreType_Chemgauss4)
    score.Initialize(receptor)
    for pose in poses:
        pose.docking_score = score.ScoreLigand(pose.conformer)

    # Compute overlap scores
    from openeye import oeshape
    overlap_prep = oeshape.OEOverlapPrep()
    overlap_prep.Prep(refmol)
    shapeFunc = oeshape.OEExactShapeFunc()
    shapeFunc.SetupRef(refmol)
    oeshape_result = oeshape.OEOverlapResults()
    for pose in poses:
        tmpmol = oechem.OEGraphMol(pose.conformer)
        overlap_prep.Prep(tmpmol)
        shapeFunc.Overlap(tmpmol, oeshape_result)
        pose.overlap_score = oeshape_result.GetRefTversky()

    # Filter poses based on top 10% of overlap
    poses = sorted(poses, key=lambda pose: pose.overlap_score)
    poses = poses[int(0.9 * len(poses)):]

    # Select the best docking score
    import numpy as np
    poses = sorted(poses, key=lambda pose: pose.docking_score)
    pose = poses[0]
    mol.SetActive(pose.conformer)
    oechem.OESetSDData(mol, 'clash_score', str(pose.clash_score))
    oechem.OESetSDData(mol, 'docking_score', str(pose.docking_score))
    oechem.OESetSDData(mol, 'overlap_score', str(pose.overlap_score))

    # Convert to single-conformer molecule
    mol = oechem.OEGraphMol(mol)

    # Compute MMFF energy
    energy = mmff_energy(mol)
    oechem.OESetSDData(mol, 'MMFF_internal_energy', str(energy))

    # Store SMILES
    docked_smiles = oechem.OEMolToSmiles(mol)
    oechem.OESetSDData(mol, 'docked_smiles', docked_smiles)

    return mol
Esempio n. 28
0
import sys
from openeye import oechem
from openeye import oeomega

if len(sys.argv) != 2:
    oechem.OEThrow.Usage("%s <outfile>" % sys.argv[0])

mol = oechem.OEMol()
oechem.OESmilesToMol(mol, "O=COC")

ofs = oechem.oemolostream()
if not ofs.open(sys.argv[1]):
    oechem.OEThrow.Fatal("Unable to open %s for writing" % sys.argv[1])

omegaOpts = oeomega.OEOmegaOptions()
omega = oeomega.OEOmega(omegaOpts)
torlib = oeomega.OETorLib()

# @ <SNIPPET-AddTorsionRule-string>
# Adding the torsion rule "[O:1]=[C:2]-[O:3][CH3:4] 90" as a string
# This takes precedent over previous rule
rule = "[O:1]=[C:2]-[O:3][CH3:4] 90"
if not torlib.AddTorsionRule(rule):
    oechem.OEThrow.Fatal("Failed to add torsion rule: %s" % rule)

omegaOpts.SetTorLib(torlib)
omega.SetOptions(omegaOpts)
if omega(mol):
    oechem.OEWriteMolecule(ofs, mol)
# @ </SNIPPET-AddTorsionRule-string>
Esempio n. 29
0
def compute_conformers(smiles=None, smiles_file=None, start_index=0, batch_size=0, out_file=None, bad_file=None, save_csv=False,  overwrite=False, save_gzip=False, license=None, timeout=0, max_failures=2):
    import csv
    import os
    from openeye import oechem
    from openeye import oeomega
    from openeye import oemolprop
    import signal

    os.environ['OE_LICENSE'] = license    
    
    if save_gzip or save_csv: 
        raise Exception("GZip and CSV not supported")
 
    if not overwrite and  os.path.exists(out_file):
        raise Exception("File exists: %s" % out_file)

    if smiles_file: 
        with open(smiles_file) as current:
            current.seek(start_index)
            smiles = [current.readline() for i in range(batch_size)]
    
    if len(smiles) == 0: 
        return ""

    # function to compute enantiomers
    # separated out so we can use an alarm to timeout
    def get_enan(omega, enan):
        enan = oechem.OEMol(enan)
        ret = omega.Build(enan)
        return enan, ret


    def alarm_handler(signum, frame):
        #print("ALARM signal received")
        raise Exception()

    #ofs = oechem.oemolostream()
    #ofs.SetFormat(oechem.OEFormat_OEB)
    #if not ofs.open(out_file):
    #    oechem.OEThrow.Fatal("Unable to open %s for writing" % out_file)

    sep = ","
    bad = []
    mols = []
    for s in smiles:
        mol = s.split(sep)
        if len(mol) > 1: 
            mols.append(mol[2].rstrip())

    # put all mols in a string as we're told it is faster to process this way
    in_smiles = "\n".join(mols)

    ims = oechem.oemolistream()
    ims.SetFormat(oechem.OEFormat_SMI)
    ims.openstring(in_smiles)

    # Turn off logging except errors
    oechem.OEThrow.SetLevel(5)

    filt = oemolprop.OEFilter(oemolprop.OEFilterType_BlockBuster)
    #ofs.open(out_file)
    signal.signal(signal.SIGALRM, alarm_handler)
    oe_results = []
    for mol in ims.GetOEMols():
        if filt(mol):
            oemols = []
            ret_code = None
            omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_FastROCS)
            omega = oeomega.OEOmega(omegaOpts)
            
            failures = 0
            for enantiomer in oeomega.OEFlipper(mol.GetActive(), 6, True):
                if max_failures > 0 and failures >= max_failures: 
                    break
                if len(oemols) >= 10:
                    break

                ret_code = None
                error = False
                signal.alarm(timeout)
                try: 
                    enantiomer, ret_code = get_enan(omega, enantiomer)
                except:
                    print("Timeout %s" % out_file) 
                    failures += 1
                    error = True
                signal.alarm(0)

                if not error and ret_code == oeomega.OEOmegaReturnCode_Success:
                    halfMol = oechem.OEMol(mol, oechem.OEMCMolType_HalfFloatCartesian)
                    oemols.append(halfMol)
                #else:
                    #oechem.OEThrow.Warning("%s: %s" %
                    #    (enantiomer.GetTitle(), oeomega.OEGetOmegaError(ret_code)))
                oe_results.append(oemols)

    ofs = oechem.oemolostream()
    ofs.SetFormat(oechem.OEFormat_OEB)
    ofs.open(out_file)
    for r in oe_results: 
        for res in r:
            oechem.OEWriteMolecule(ofs, res)

    ofs.close()
 
    return out_file
def dock_molecule_to_receptor(molecule, receptor_filename, covalent=False):
    """
    Dock the specified molecules, writing out to specified file

    Parameters
    ----------
    molecule : oechem.OEMol
        The molecule to dock
    receptor_filename : str
        Receptor to dock to
    covalent : bool, optional, default=False
        If True, try to place covalent warheads in proximity to CYS145

    Returns
    -------
    docked_molecule : openeye.oechem.OEMol
        Returns the best tautomer/protomer in docked geometry, annotated with docking score
        None is returned if no viable docked pose found
    """
    import os

    # Extract the fragment name for the receptor
    fragment = extract_fragment_from_filename(receptor_filename)

    # Read the receptor
    from openeye import oechem, oedocking
    receptor = oechem.OEGraphMol()
    if not oedocking.OEReadReceptorFile(receptor, receptor_filename):
        oechem.OEThrow.Fatal("Unable to read receptor")
    #print(f'Receptor has {receptor.NumAtoms()} atoms')

    if not oedocking.OEReceptorHasBoundLigand(receptor):
        raise Exception("Receptor does not have bound ligand")

    #print('Initializing receptor...')
    dockMethod = oedocking.OEDockMethod_Hybrid2
    dockResolution = oedocking.OESearchResolution_High
    dock = oedocking.OEDock(dockMethod, dockResolution)
    success = dock.Initialize(receptor)

    # Add covalent restraint if specified
    warheads_found = find_warheads(molecule)
    if covalent and len(warheads_found) > 0:
        warheads_found = set(warheads_found.keys())

        # Initialize covalent constraints
        customConstraints = oedocking.OEReceptorGetCustomConstraints(receptor)

        # Find CYS145 SG atom
        hv = oechem.OEHierView(receptor)
        hres = hv.GetResidue("A", "CYS", 145)
        proteinHeavyAtom = None
        for atom in hres.GetAtoms():
            if atom.GetName().strip() == 'SG':
                proteinHeavyAtom = atom
                break
        if proteinHeavyAtom is None:
            raise Exception('Could not find CYS145 SG')

        # Add the constraint
        feature = customConstraints.AddFeature()
        feature.SetFeatureName("CYS145 proximity")
        for warhead_type in warheads_found:
            smarts = covalent_warhead_smarts[warhead_type]
            print(f'Adding constraint for SMARTS pattern {smarts}')
            feature.AddSmarts(smarts)
        sphereRadius = 4.0 # Angstroms
        sphereCenter = oechem.OEFloatArray(3)
        receptor.GetCoords(proteinHeavyAtom, sphereCenter)
        sphere = feature.AddSphere()
        sphere.SetRad(sphereRadius)
        sphere.SetCenter(sphereCenter[0], sphereCenter[1], sphereCenter[2])
        oedocking.OEReceptorSetCustomConstraints(receptor, customConstraints)

    # Enumerate tautomers
    from openeye import oequacpac
    tautomer_options = oequacpac.OETautomerOptions()
    tautomer_options.SetMaxTautomersGenerated(4096)
    tautomer_options.SetMaxTautomersToReturn(16)
    tautomer_options.SetCarbonHybridization(True)
    tautomer_options.SetMaxZoneSize(50)
    tautomer_options.SetApplyWarts(True)
    pKa_norm = True
    tautomers = [ oechem.OEMol(tautomer) for tautomer in oequacpac.OEGetReasonableTautomers(molecule, tautomer_options, pKa_norm) ]

    # Set up Omega
    #print('Expanding conformers...')
    from openeye import oeomega
    #omegaOpts = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
    omegaOpts = oeomega.OEOmegaOptions()
    #omegaOpts.SetMaxConfs(5000)
    omegaOpts.SetMaxSearchTime(60.0) # time out
    omega = oeomega.OEOmega(omegaOpts)
    omega.SetStrictStereo(False) # enumerate sterochemistry if uncertain

    # Dock tautomers
    docked_molecules = list()
    from tqdm import tqdm
    for mol in tautomers:
        dockedMol = oechem.OEGraphMol()

        # Expand conformers
        omega.Build(mol)

        # Dock molecule
        retCode = dock.DockMultiConformerMolecule(dockedMol, mol)
        if (retCode != oedocking.OEDockingReturnCode_Success):
            #print("Docking Failed with error code " + oedocking.OEDockingReturnCodeGetName(retCode))
            continue

        # Store docking data
        sdtag = oedocking.OEDockMethodGetName(dockMethod)
        oedocking.OESetSDScore(dockedMol, dock, sdtag)
        oechem.OESetSDData(dockedMol, "docked_fragment", fragment)
        dock.AnnotatePose(dockedMol)

        docked_molecules.append( dockedMol.CreateCopy() )

    if len(docked_molecules) == 0:
        return None

    # Select the best-ranked molecule and pose
    # Note that this ignores protonation state and tautomer penalties
    docked_molecules.sort(key=score)
    best_molecule = docked_molecules[0]

    return best_molecule