Ejemplo n.º 1
0
def OERMSD_Array(refcrds, fitcrds, size):
    oechem.OERMSD(refcrds, fitcrds, size)

    overlay = True
    oechem.OERMSD(refcrds, fitcrds, size, overlay)

    rotmat = oechem.OEDoubleArray(9)
    oechem.OERMSD(refcrds, fitcrds, size, overlay, rotmat)

    transvec = oechem.OEDoubleArray(3)
    oechem.OERMSD(refcrds, fitcrds, size, overlay, rotmat, transvec)
Ejemplo n.º 2
0
def OERMSD_Part_MolBase(ref, fit):
    match = oechem.OEMatch()
    for aRef, aFit in zip(ref.GetAtoms(), fit.GetAtoms()):
        match.AddPair(aRef, aFit)

    oechem.OERMSD(ref, fit, match)

    overlay = True
    oechem.OERMSD(ref, fit, match, overlay)

    rotmat = oechem.OEDoubleArray(9)
    oechem.OERMSD(ref, fit, match, overlay, rotmat)

    transvec = oechem.OEDoubleArray(3)
    oechem.OERMSD(ref, fit, match, overlay, rotmat, transvec)
Ejemplo n.º 3
0
    def _compute_rmsd_matrix_oe(self, molecule: Molecule) -> numpy.ndarray:
        """Computes the RMSD between all conformers stored on a molecule using an OpenEye
        backend."""

        from openeye import oechem

        oe_molecule: oechem.OEMol = molecule.to_openeye()
        oe_conformers = {
            i: oe_conformer
            for i, oe_conformer in enumerate(oe_molecule.GetConfs())
        }

        n_conformers = len(molecule.conformers)

        rmsd_matrix = numpy.zeros((n_conformers, n_conformers))

        for i, j in itertools.combinations([*oe_conformers], 2):

            rmsd_matrix[i, j] = oechem.OERMSD(
                oe_conformers[i],
                oe_conformers[j],
                self.check_automorphs,
                self.heavy_atoms_only,
                True,
            )

        rmsd_matrix += rmsd_matrix.T
        return rmsd_matrix
Ejemplo n.º 4
0
def OERMSD_Part_MCMolBase(ref, fit):
    match = oechem.OEMatch()
    for aRef, aFit in zip(ref.GetAtoms(), fit.GetAtoms()):
        match.AddPair(aRef, aFit)

    nConfs = fit.GetMaxConfIdx()
    vecRmsd = oechem.OEDoubleArray(nConfs)
    oechem.OERMSD(ref, fit, vecRmsd, match)

    overlay = True
    oechem.OERMSD(ref, fit, vecRmsd, match, overlay)

    rotmat = oechem.OEDoubleArray(9*nConfs)
    oechem.OERMSD(ref, fit, vecRmsd, match, overlay, rotmat)

    transvec = oechem.OEDoubleArray(3*nConfs)
    oechem.OERMSD(ref, fit, vecRmsd, match, overlay, rotmat, transvec)
Ejemplo n.º 5
0
def main(argv=[__name__]):
    itf = oechem.OEInterface(InterfaceData, argv)

    if not itf.GetBool("-verbose"):
        oechem.OEThrow.SetLevel(oechem.OEErrorLevel_Warning)

    rfname = itf.GetString("-ref")
    ifname = itf.GetString("-in")

    automorph = itf.GetBool("-automorph")
    heavy = itf.GetBool("-heavyonly")
    overlay = itf.GetBool("-overlay")

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

    rmol = oechem.OEGraphMol()
    if not oechem.OEReadMolecule(ifs, rmol):
        oechem.OEThrow.Fatal("Unable to read reference molecule")

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

    ofs = oechem.oemolostream()
    if itf.HasString("-out"):
        ofname = itf.GetString("-out")
        if not ofs.open(ofname):
            oechem.OEThrow.Fatal("Unable to open %s for writing" % ofname)
        if not overlay:
            oechem.OEThrow.Warning(
                "Output is the same as input when overlay is false")

    for mol in ifs.GetOEMols():
        oechem.OEThrow.Info(mol.GetTitle())

        rmsds = oechem.OEDoubleArray(mol.GetMaxConfIdx())
        rmtx = oechem.OEDoubleArray(9 * mol.GetMaxConfIdx())
        tmtx = oechem.OEDoubleArray(3 * mol.GetMaxConfIdx())

        # perform RMSD for all confomers
        oechem.OERMSD(rmol, mol, rmsds, automorph, heavy, overlay, rmtx, tmtx)

        for conf in mol.GetConfs():
            cidx = conf.GetIdx()
            oechem.OEThrow.Info("Conformer %i : rmsd = %f" %
                                (cidx, rmsds[cidx]))

            if itf.GetBool("-overlay"):
                oechem.OERotate(conf, rmtx[cidx * 9:cidx * 9 + 9])
                oechem.OETranslate(conf, tmtx[cidx * 3:cidx * 3 + 3])

        if itf.HasString("-out"):
            oechem.OEWriteMolecule(ofs, mol)

    return 0
Ejemplo n.º 6
0
def compare_two_mols(rmol, qmol, rmsd_cutoff):
    """
    For two identical molecules, with varying conformers,
    make an M by N comparison to match the M minima of
    rmol to the N minima of qmol. Match is declared
    for lowest RMSD between the two conformers and
    if the RMSD is below rmsd_cutoff.

    Parameters
    ----------
    rmol : OEMol
        reference OEChem molecule with all its filtered conformers
    qmol : OEMol
        query OEChem molecule with all its filtered conformers
    rmsd_cutoff : float
        cutoff above which two structures are considered diff conformers

    Returns
    -------
    molIndices : list
        1D list of qmol conformer indices that correspond to rmol confs

    """

    automorph = True  # take into acct symmetry related transformations
    heavyOnly = False  # do consider hydrogen atoms for automorphisms
    overlay = True  # find the lowest possible RMSD

    molIndices = []  # 1D list, stores indices of matched qmol confs wrt rmol

    for ref_conf in rmol.GetConfs():
        print(f">>>> Matching {qmol.GetTitle()} conformers to minima: "
              f"{ref_conf.GetIdx()+1} <<<<")

        # for this ref_conf, calculate/store RMSDs with all qmol's conformers
        thisR_allQ = []
        for que_conf in qmol.GetConfs():
            rms = oechem.OERMSD(ref_conf, que_conf, automorph, heavyOnly,
                                overlay)
            thisR_allQ.append(rms)

        # for this ref_conf, get qmol conformer index of min RMSD if <=cutoff
        lowest_rmsd_index = [
            i for i, j in enumerate(thisR_allQ) if j == min(thisR_allQ)
        ][0]
        if thisR_allQ[lowest_rmsd_index] <= rmsd_cutoff:
            molIndices.append(lowest_rmsd_index)
        else:
            print('no match bc rmsd is ', thisR_allQ[lowest_rmsd_index])
            molIndices.append(None)

    return molIndices
Ejemplo n.º 7
0
def CliqueAlign(refmol, fitmol, ofs):
    cs = oechem.OECliqueSearch(refmol, oechem.OEExprOpts_DefaultAtoms,
                               oechem.OEExprOpts_DefaultBonds)
    cs.SetSaveRange(5)
    cs.SetMinAtoms(6)
    for mi in cs.Match(fitmol):
        rmat = oechem.OEDoubleArray(9)
        trans = oechem.OEDoubleArray(3)
        overlay = True
        oechem.OERMSD(cs.GetPattern(), fitmol, mi, overlay, rmat, trans)
        oechem.OERotate(fitmol, rmat)
        oechem.OETranslate(fitmol, trans)
        oechem.OEWriteMolecule(ofs, fitmol)
Ejemplo n.º 8
0
def SmartsAlign(refmol, fitmol, ss, ofs):
    unique = True
    for match1 in ss.Match(refmol, unique):
        for match2 in ss.Match(fitmol, unique):
            match = oechem.OEMatch()
            for mp1, mp2 in zip(match1.GetAtoms(), match2.GetAtoms()):
                match.AddPair(mp1.target, mp2.target)

            overlay = True
            rmat = oechem.OEDoubleArray(9)
            trans = oechem.OEDoubleArray(3)
            oechem.OERMSD(refmol, fitmol, match, overlay, rmat, trans)
            oechem.OERotate(fitmol, rmat)
            oechem.OETranslate(fitmol, trans)
            oechem.OEWriteConstMolecule(ofs, fitmol)
Ejemplo n.º 9
0
def RMSD(ref_mol2, query_mol2):
    """
    From one input reference molecule and one input query molecule,
    the RMSD is computed and returned.

    Parameters
    ---------
    ref_mol2: str - mol2 file of the reference force field
    query_mol2: str - mol2 file of the query force field

    Returns
    -------
    rms: float - RMSD in Angstroms

    """

    # open reference molecule
    ifsRef = oechem.oemolistream(ref_mol2)
    print("Opening reference molecule:", ref_mol2)

    # check if the file exist
    if not ifsRef.open(ref_mol2):
        print("Unable to locate %s. Skipping." % ref_mol2.split('/')[-1])

    # open query molecule
    #queryFile = ("/work/cluster/nthi/ForceField-Comparison/%s/%s/%s" % (homeDir, ffList, fName) )
    print("Opening query molecule: ", query_mol2)
    if os.path.exists(query_mol2):
        ifsQuery = oechem.oemolistream(query_mol2)
        # set flavor for the input file
        flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield
        ifsRef.SetFlavor(oechem.OEFormat_MOL2, flavor)
        ifsQuery.SetFlavor(oechem.OEFormat_MOL2, flavor)

        # create "blank" object
        rmol = oechem.OEGraphMol()
        qmol = oechem.OEGraphMol()

        # load molecule from files
        oechem.OEReadMolecule(ifsRef, rmol)
        oechem.OEReadMolecule(ifsQuery, qmol)

        # calculate rmsd setting automorph, heavyOnly, and overlay to True
        rms = oechem.OERMSD(rmol, qmol, True, True, True)
    else:
        rms = -2
    return rms
Ejemplo n.º 10
0
def compare2Mols(rmol, qmol):
    """
    For two identical molecules, with varying conformers,
        make an M by N comparison to match the M minima of
        rmol to the N minima of qmol. Match is declared
        for lowest RMSD between the two conformers and
        if the RMSD is below 0.5 Angstrom.

    Parameters
    ----------
    rmol:       reference OEChem molecule with all its filtered conformers
    qmol:       query OEChem molecule with all its filtered conformers

    Returns
    -------
    molIndices: 1D list of qmol conformer indices that correspond to rmol confs

    """

    automorph = True  # take into acct symmetry related transformations
    heavyOnly = False  # do consider hydrogen atoms for automorphisms
    overlay = True  # find the lowest possible RMSD

    molIndices = [
    ]  # 1D list for storing indices of matched qmol confs wrt rmol

    for Rconf in rmol.GetConfs():
        print(">>>> Matching %s conformers to minima: %d <<<<"\
            % (qmol.GetTitle(),Rconf.GetIdx()+1))

        # for this Rconf, calculate/store RMSDs with all of qmol's conformers
        rsublist = []
        for Qconf in qmol.GetConfs():
            rms = oechem.OERMSD(Rconf, Qconf, automorph, heavyOnly, overlay)
            rsublist.append(rms)

        # for this Rconf, get qmol conformer index for minimum RMSD
        thisMin = [i for i, j in enumerate(rsublist) if j == min(rsublist)][0]
        if rsublist[thisMin] <= 0.5:
            molIndices.append(thisMin)
        else:
            print('no match bc rmsd is ', rsublist[thisMin])
            molIndices.append(None)

    return molIndices
Ejemplo n.º 11
0
def MCSAlign(refmol, fitmol, ofs):
    atomexpr = oechem.OEExprOpts_AtomicNumber | oechem.OEExprOpts_Aromaticity
    bondexpr = 0
    mcss = oechem.OEMCSSearch(oechem.OEMCSType_Exhaustive)
    mcss.Init(refmol, atomexpr, bondexpr)
    mcss.SetMCSFunc(oechem.OEMCSMaxBondsCompleteCycles())

    rmat = oechem.OEDoubleArray(9)
    trans = oechem.OEDoubleArray(3)
    unique = True
    overlay = True
    for match in mcss.Match(fitmol, unique):
        rms = oechem.OERMSD(mcss.GetPattern(), fitmol, match, overlay, rmat, trans)
        if rms < 0.0:
            oechem.OEThrow.Warning("RMS overlay failure")
            continue
        oechem.OERotate(fitmol, rmat)
        oechem.OETranslate(fitmol, trans)
        oechem.OEWriteMolecule(ofs, fitmol)
Ejemplo n.º 12
0
def OERMSD_Full_MolBase(ref, fit):
    oechem.OERMSD(ref, fit)

    automorf = True
    oechem.OERMSD(ref, fit, automorf)

    heavyOnly = True
    oechem.OERMSD(ref, fit, automorf, heavyOnly)

    overlay = False
    oechem.OERMSD(ref, fit, automorf, heavyOnly, overlay)

    rotmat = oechem.OEDoubleArray(9)
    oechem.OERMSD(ref, fit, automorf, heavyOnly, overlay, rotmat)

    transvec = oechem.OEDoubleArray(3)
    oechem.OERMSD(ref, fit, automorf, heavyOnly, overlay, rotmat, transvec)
Ejemplo n.º 13
0
def SeqAlign(ref, fit, ofs):
    sa = oechem.OEGetAlignment(ref, fit)
    print()
    print("Alignment of %s to %s" % (fit.GetTitle(), ref.GetTitle()))
    print()
    print("  Method: %s" % oechem.OEGetAlignmentMethodName(sa.GetMethod()))
    print("  Gap   : %d" % sa.GetGap())
    print("  Extend: %d" % sa.GetExtend())
    print("  Score : %d" % sa.GetScore())
    print()

    oss = oechem.oeosstream()
    oechem.OEWriteAlignment(oss, sa)
    print(oss.str().decode("UTF-8"))

    onlyCAlpha = True
    overlay = True
    rot = oechem.OEDoubleArray(9)
    trans = oechem.OEDoubleArray(3)
    rmsd = oechem.OERMSD(ref, fit, sa, onlyCAlpha, overlay, rot, trans)
    print("  RMSD = %.1f" % rmsd)
    oechem.OERotate(fit, rot)
    oechem.OETranslate(fit, trans)
    oechem.OEWriteMolecule(ofs, fit)
Ejemplo n.º 14
0
def OERMSD_Full_MCMolBase(ref, fit):
    nConfs = fit.GetMaxConfIdx()
    vecRmsd = oechem.OEDoubleArray(nConfs)
    oechem.OERMSD(ref, fit, vecRmsd)

    automorf = True
    oechem.OERMSD(ref, fit, vecRmsd, automorf)

    heavyOnly = True
    oechem.OERMSD(ref, fit, vecRmsd, automorf, heavyOnly)

    overlay = True
    oechem.OERMSD(ref, fit, vecRmsd, automorf, heavyOnly, overlay)

    rotmat = oechem.OEDoubleArray(9*nConfs)
    oechem.OERMSD(ref, fit, vecRmsd, automorf, heavyOnly, overlay, rotmat)

    transvec = oechem.OEDoubleArray(3*nConfs)
    oechem.OERMSD(ref, fit, vecRmsd, automorf, heavyOnly, overlay, rotmat, transvec)
Ejemplo n.º 15
0
def IdentifyMinima(mol, tag, ThresholdE, ThresholdRMSD):
    """
    For a molecule's set of conformers computed with some level of theory,
        whittle down unique conformers based on energy and RMSD.

    Parameters
    ----------
    mol           OEChem molecule with all of its conformers
    tag           string name of the SD tag in this molecule
    ThresholdE    float value for abs(E1-E2), below which 2 confs are "same"
        Units are hartrees (default output units of Psi4)
    ThresholdR    float value for RMSD, below which 2 confs are "same"
        Units are in Angstrom (Psi4 default)

    Returns
    -------
    boolean True if successful filter + delete. False if there's only
        one conf and it didn't optimize, or something else funky.

    """
    # Parameters for OpenEye RMSD calculation
    automorph = True
    heavyOnly = False
    overlay = True

    # declare variables for conformers to delete
    confsToDel = set()
    delCount = 0

    # check if SD tag exists for the case of single conformer
    if mol.NumConfs() == 1:
        testmol = mol.GetConfs().next()
        for x in oechem.OEGetSDDataPairs(mol):
            if tag.lower() in x.GetTag().lower():
                return True
            else:
                return False

    # Loop over conformers twice (NxN diagonal comparison of RMSDs)
    for confRef in mol.GetConfs():
        print(" ~ Reference: %s conformer %d" %
              (mol.GetTitle(), confRef.GetIdx() + 1))

        # get real tag (correct for capitalization)
        for x in oechem.OEGetSDDataPairs(confRef):
            if tag.lower() in x.GetTag().lower():
                taglabel = x.GetTag()

        # delete cases that don't have energy (opt not converged; or other)
        if not oechem.OEHasSDData(confRef, taglabel):
            confsToDel.add(confRef.GetIdx())
            delCount += 1
            continue
        refE = float(oechem.OEGetSDData(confRef, taglabel))

        for confTest in mol.GetConfs():
            # upper right triangle comparison
            if confTest.GetIdx() <= confRef.GetIdx():
                continue
            # skip cases already set for removal
            if confTest.GetIdx() in confsToDel:
                continue
            # delete cases that don't have energy
            if not oechem.OEHasSDData(confTest, taglabel):
                confsToDel.add(confTest.GetIdx())
                continue

            testE = float(oechem.OEGetSDData(confTest, taglabel))
            # if MM (not Psi4) energies, convert absERel to Hartrees
            if 'mm' in taglabel.lower():
                absERel = abs(refE - testE) / 627.5095
            else:
                absERel = abs(refE - testE)
            # if energies are diff enough --> confs are diff --> keep & skip ahead
            if absERel > ThresholdE:
                continue
            # if energies are similar, see if they are diff by RMSD
            rmsd = oechem.OERMSD(confRef, confTest, automorph, heavyOnly,
                                 overlay)
            # if measured_RMSD < threshold_RMSD --> confs are same --> delete
            if rmsd < ThresholdRMSD:
                confsToDel.add(confTest.GetIdx())

    # for the same molecule, delete tagged conformers
    print("%s original number of conformers: %d" %
          (mol.GetTitle(), mol.NumConfs()))
    if delCount == mol.NumConfs():
        # all conformers in this mol has been tagged for deletion
        return False
    for conf in mol.GetConfs():
        if conf.GetIdx() in confsToDel:
            print('Removing %s conformer index %d' %
                  (mol.GetTitle(), conf.GetIdx()))
            if not mol.DeleteConf(conf):
                oechem.OEThrow.Fatal("Unable to delete %s GetIdx() %d" \
                                  % (mol.GetTitle(), conf.GetIdx()))
    return True
Ejemplo n.º 16
0
def create_merged_topology(system,
                           topology,
                           positions,
                           molecules,
                           softcore_alpha=0.5,
                           softcore_beta=12 * unit.angstrom**2):
    """
    Create an OpenMM system that utilizes a merged topology that can interpolate between many small molecules sharing a common core.

    Notes
    -----
    * Currently, only one set of molecules is supported.
    * Residue mutations are not yet supported.

    Parameters
    ----------
    system : simtk.openmm.System
       The system representing the environment, not yet containing any molecules.
    topology : simtk.openmm.app.Topology
       The topology object corresponding to system.
    positions : simtk.unit.Quantity of numpy array natoms x 3 compatible with units angstroms
       The positions array corresponding to system and topology.
    molecules : list of openeye.oechem.OEMol
       Molecules to be added to the system.  These molecules must share a common core with identical GAFF atom types.
    softcore_alpha : float, optional, default=0.5
       Softcore parameter for Lennard-Jones softening.
    softcore_beta : simtk.unit.Quantity with units compatible with angstrom**2
       Softcore parameter for Coulomb interaction softening.

    Returns
    -------
    system : simtk.openmm.System
       Modified version of system in which old system is recovered for global context paramaeter `lambda` = 0 and new molecule is substituted for `lambda` = 1.
    topology : system.openmm.Topology
       Topology corresponding to system.

    """

    #
    # First, process first molecule so we can add common substructure atoms to System along with valence terms among core atoms.
    #

    # Use the first molecule as a reference molecule.
    # TODO: Replace this with a different reference molecule.
    molecule = molecules[0].CreateCopy()
    gaff_molecule = gaff_molecules[0].CreateCopy()

    # Determine common atoms in second molecule.
    matches = [match for match in mcss.Match(gaff_molecule, True)]
    match = matches[0]  # we only need the first match

    # Make a list of the atoms in core.
    reverse_mapping = dict()
    print "molecule => core mapping"
    for matchpair in match.GetAtoms():
        core_index = matchpair.pattern.GetIdx()
        molecule_index = matchpair.target.GetIdx()
        print "%8d => %8d" % (molecule_index, core_index)
        reverse_mapping[core_index] = molecule_index
    # Make sure the list of atoms in molecule corresponds to the ordering of atoms within the core.
    core_atoms = [
        reverse_mapping[core_index] for core_index in range(core.NumAtoms())
    ]

    # Create OpenMM Topology and System objects for given molecules using GAFF/AM1-BCC.
    print "Generating OpenMM system for molecule..."
    [molecule_system, molecule_topology,
     molecule_positions] = generate_openmm_system(molecule)

    # TODO: Replace charges in molecule with RMS charges.

    # Add core fragment to system.
    core_mapping = add_molecule_to_system(system,
                                          molecule_system,
                                          core_atoms,
                                          variant=0)

    # Add molecule to topology as a new residue.
    chain = topology.addChain()
    residue = topology.addResidue('COR', chain)
    atoms = [atom for atom in molecule.GetAtoms()
             ]  # build a list of all atoms in reference molecule
    atoms = [atoms[index] for index in core_atoms
             ]  # select out only core atoms in proper order
    for atom in atoms:
        name = atom.GetName()
        atomic_number = atom.GetAtomicNum()
        element = app.Element.getByAtomicNumber(atomic_number)
        topology.addAtom(name, element, residue)

    # Append positions of new particles.
    positions = unit.Quantity(
        np.append(positions / positions.unit,
                  molecule_positions[core_atoms, :] / positions.unit,
                  axis=0), positions.unit)

    # Create a running list of atoms all variants should have interactions excluded with.
    atoms_to_exclude = core_mapping.values()

    #
    # Create restraint force to keep corresponding core atoms from molecules near their corresponding core atoms.
    # TODO: Can we replace these with length-zero constraints or virtual particles if OpenMM supports this in the future?
    #

    K = 10.0 * unit.kilocalories_per_mole / unit.angstrom**2  # spring constant
    energy_expression = '(K/2) * r^2;'
    energy_expression += 'K = %f;' % K.value_in_unit_system(
        unit.md_unit_system)
    restraint_force = mm.CustomBondForce(energy_expression)

    #
    # Now, process all molecules.
    #

    variants = list()

    variant = 1
    for (molecule, gaff_molecule) in zip(molecules, gaff_molecules):
        print ""
        print "*******************************************************"
        print "Incorporating molecule %s" % molecule.GetTitle()
        print "*******************************************************"
        print ""

        # Determine common atoms in second molecule.
        matches = [match for match in mcss.Match(gaff_molecule, True)]
        print matches
        match = matches[0]  # we only need the first match

        # Make a list of the atoms in core.
        core_atoms = list()
        core_atoms_bond_mapping = dict()
        print "molecule => core mapping"
        for matchpair in match.GetAtoms():
            core_index = matchpair.pattern.GetIdx()
            molecule_index = matchpair.target.GetIdx()
            core_atoms.append(
                molecule_index
            )  # list of atoms in the molecule that correspond to core atoms
            print "%8d => %8d" % (molecule_index, core_index)
            core_atoms_bond_mapping[molecule_index] = core_mapping[
                core_index]  # index of corresponding core atom in system, for restraining

        # Align molecule to overlay common core.
        overlay = True
        rmat = oe.OEDoubleArray(9)
        trans = oe.OEDoubleArray(3)
        rms = oe.OERMSD(mcss.GetPattern(), gaff_molecule, match, overlay, rmat,
                        trans)
        if rms < 0.0:
            raise Exception("RMS overlay failure")
        print "RMSD after overlay is %.3f A" % rms
        oe.OERotate(gaff_molecule, rmat)
        oe.OETranslate(gaff_molecule, trans)

        # Transfer positions to regular molecule with Tripos atom types.
        gaff_atoms = [atom for atom in gaff_molecule.GetAtoms()]
        atoms = [atom for atom in molecule.GetAtoms()]
        for (source_atom, dest_atom) in zip(gaff_atoms, atoms):
            molecule.SetCoords(atom, gaff_molecule.GetCoords(atom))

        # Create OpenMM Topology and System objects for given molecules using GAFF/AM1-BCC.
        print "Generating OpenMM system for molecule..."
        [molecule_system, molecule_topology,
         molecule_positions] = generate_openmm_system(molecule)

        # Append positions of new particles.
        positions = unit.Quantity(
            np.append(positions / positions.unit,
                      molecule_positions / positions.unit,
                      axis=0), positions.unit)

        # Add valence terms only.
        mapping = add_molecule_to_system(system,
                                         molecule_system,
                                         core_atoms,
                                         variant=variant,
                                         atoms_to_exclude=atoms_to_exclude)

        # DEBUG
        print "Atom mappings into System object:"
        print mapping

        # Add restraints to keep core atoms from this molecule near their corresponding core atoms.
        for index in core_atoms:
            restraint_force.addBond(mapping[index],
                                    core_atoms_bond_mapping[index], [])

        # Add molecule to topology as a new residue.
        # TODO: Can we simplify this by copying from molecule_topology instead?
        residue = topology.addResidue('LIG', chain)
        for atom in atoms:
            name = atom.GetName()
            atomic_number = atom.GetAtomicNum()
            element = app.Element.getByAtomicNumber(atomic_number)
            topology.addAtom(name, element, residue)

        # Increment variant index.
        variant += 1

        # Append to list of atoms to be excluded.
        atoms_to_exclude += mapping.values()

    print "Done!"

    # Add restraint force to core atoms.
    # NOTE: This cannot be added to system earlier because of dict lookup for 'CustomBondForce' in add_valence_terms().
    system.addForce(restraint_force)

    return [system, topology, positions]
Ejemplo n.º 17
0
def compare_ffs(in_dict, conf_id_tag, out_prefix, mol_slice=None):
    """
    For 2+ SDF files that are analogous in terms of molecules and their
    conformers, assess them by RMSD, TFD, and relative energy differences.

    Parameters
    ----------
    in_dict : OrderedDict
        dictionary from input file, where key is method and value is dictionary
        first entry should be reference method
        in sub-dictionary, keys are 'sdfile' and 'sdtag'
    conf_id_tag : string
        label of the SD tag that should be the same for matching conformers
        in different files
    out_prefix : string
        prefix appended to sdf file name to write out new SDF file
        with RMSD and TFD info added as SD tags
    mol_slice : numpy slice object
        The resulting integers are numerically sorted and duplicates removed.
        e.g., slices = np.s_[0, 3:5, 6::3] would be parsed to return
        [0, 3, 4, 6, 9, 12, 15, 18, ...]
        Can also parse from end: [-3:] gets the last 3 molecules, and
        [-2:-1] is the same as [-2] to get just next to last molecule.

    Returns
    -------
    enes_full : 3D list
        enes_full[i][j][k] = ddE of ith method, jth mol, kth conformer.
        ddE = (dE of query method) - (dE of ref method),
        where the dE is computed as conformer M - conformer N,
        and conformer N is chosen from the lowest energy of the ref confs.
        the reference method is not present; i.e., self-comparison is skipped,
        so the max i value represents total number of files minus one.
    rmsds_full : 3D list
        same format as that of enes_full but with conformer RMSDs
    tfds_full : 3D list
        same format as that of enes_full but with conformer TFDs
    smiles_full : 3D list
        same format as that of enes_full but with conformer SMILES strings

    """
    # set RMSD calculation parameters
    automorph = True  # take into acct symmetry related transformations
    heavyOnly = False  # do consider hydrogen atoms for automorphisms
    overlay = True  # find the lowest possible RMSD

    # initiate final data lists
    enes_full = []
    rmsds_full = []
    tfds_full = []
    smiles_full = []

    # get first filename representing the reference geometries
    sdf_ref = list(in_dict.values())[0]['sdfile']
    tag_ref = list(in_dict.values())[0]['sdtag']

    # assess each file against reference
    for ff_label, ff_dict in in_dict.items():

        # get details of queried file
        sdf_que = ff_dict['sdfile']
        tag_que = ff_dict['sdtag']

        if sdf_que == sdf_ref:
            continue

        # initiate new sublists
        enes_method = []
        rmsds_method = []
        tfds_method = []
        smiles_method = []

        # open an output file to store query molecules with new SD tags
        out_file = f'{out_prefix}_{os.path.basename(sdf_que)}'
        ofs = oechem.oemolostream()
        if not ofs.open(out_file):
            oechem.OEThrow.Fatal(f"Unable to open {out_file} for writing")

        # load molecules from open reference and query files
        print(f"\n\nOpening reference file {sdf_ref}")
        mols_ref = reader.read_mols(sdf_ref, mol_slice)

        print(f"Opening query file {sdf_que} for [ {ff_label} ] energies")
        mols_que = reader.read_mols(sdf_que, mol_slice)

        # loop over each molecule in reference and query files
        for rmol, qmol in zip(mols_ref, mols_que):

            # initial check that they have same title and number of confs
            rmol_name = rmol.GetTitle()
            rmol_nconfs = rmol.NumConfs()
            if (rmol_name != qmol.GetTitle()) or (rmol_nconfs !=
                                                  qmol.NumConfs()):
                raise ValueError(
                    "ERROR: Molecules not aligned in iteration. "
                    "Offending molecules and number of conformers:\n"
                    f"\'{rmol_name}\': {rmol_nconfs} nconfs\n"
                    f"\'{qmol.GetTitle()}\': {qmol.NumConfs()} nconfs")

            # initialize lists to store conformer energies
            enes_ref = []
            enes_que = []
            rmsds_mol = []
            tfds_mol = []
            smiles_mol = []

            # loop over each conformer of this mol
            for ref_conf, que_conf in zip(rmol.GetConfs(), qmol.GetConfs()):

                # check confomer match from the specified tag
                ref_id = oechem.OEGetSDData(ref_conf, conf_id_tag)
                que_id = oechem.OEGetSDData(que_conf, conf_id_tag)
                if ref_id != que_id:
                    raise ValueError(
                        "ERROR: Conformers not aligned in iteration"
                        f" for mol: '{rmol_name}'. The conformer "
                        f"IDs ({conf_id_tag}) for ref and query are:"
                        f"\n{ref_id}\n{que_id}.")

                # note the smiles id
                smiles_mol.append(ref_id)

                # get energies
                enes_ref.append(float(oechem.OEGetSDData(ref_conf, tag_ref)))
                enes_que.append(float(oechem.OEGetSDData(que_conf, tag_que)))

                # compute RMSD between reference and query conformers
                rmsd = oechem.OERMSD(ref_conf, que_conf, automorph, heavyOnly,
                                     overlay)
                rmsds_mol.append(rmsd)

                # compute TFD between reference and query conformers
                tfd = calc_tfd(ref_conf, que_conf, conf_id_tag)
                tfds_mol.append(tfd)

                # store data in SD tags for query conf, and write conf to file
                oechem.OEAddSDData(que_conf, f'RMSD to {sdf_ref}', str(rmsd))
                oechem.OEAddSDData(que_conf, f'TFD to {sdf_ref}', str(tfd))
                oechem.OEWriteConstMolecule(ofs, que_conf)

            # compute relative energies against lowest E reference conformer
            lowest_ref_idx = enes_ref.index(min(enes_ref))
            rel_enes_ref = np.array(enes_ref) - enes_ref[lowest_ref_idx]
            rel_enes_que = np.array(enes_que) - enes_que[lowest_ref_idx]

            # subtract them to get ddE = dE (query method) - dE (ref method)
            enes_mol = np.array(rel_enes_que) - np.array(rel_enes_ref)

            # store then move on
            enes_method.append(enes_mol)
            rmsds_method.append(np.array(rmsds_mol))
            tfds_method.append(np.array(tfds_mol))
            smiles_method.append(smiles_mol)
            #print(rmsds_method, len(rmsds_method))
            #print(enes_method, len(enes_method))

        enes_full.append(enes_method)
        rmsds_full.append(rmsds_method)
        tfds_full.append(tfds_method)
        smiles_full.append(smiles_method)

    ofs.close()

    return enes_full, rmsds_full, tfds_full, smiles_full
Ejemplo n.º 18
0
def create_relative_alchemical_transformation(system, topology, positions, molecule1_indices_in_system, molecule1, molecule2,
                                              softcore_alpha=0.5, softcore_beta=12*unit.angstrom**2):
    """
    Create an OpenMM System object to handle the alchemical transformation from molecule1 to molecule2.

    system : simtk.openmm.System
       The system to be modified, already containing molecule1 whose atoms correspond to molecule1_indices_in_system.
    topology : simtk.openmm.app.Topology
       The topology object corresponding to system.
    positions : simtk.unit.Quantity of numpy array natoms x 3 compatible with units angstroms
       The positions array corresponding to system and topology.
    molecule1_indices_in_system : list of int
       Indices of molecule1 in system, with atoms in same order.
    molecule1 : openeye.oechem.OEMol
       Molecule already present in system, where the atom mapping is given by molecule1_indices_in_system.
    molecule2 : openeye.oechem.OEMol
       New molecule that molecule1 will be transformed into as lambda parameter goes from 0 -> 1.
    softcore_alpha : float, optional, default=0.5
       Softcore parameter for Lennard-Jones softening.
    softcore_beta : simtk.unit.Quantity with units compatible with angstrom**2
       Softcore parameter for Coulomb interaction softening.

    Returns
    -------
    system : simtk.openmm.System
       Modified version of system in which old system is recovered for global context paramaeter `lambda` = 0 and new molecule is substituted for `lambda` = 1.
    topology : system.openmm.Topology
       Topology corresponding to system.

    """

    # Copy molecules.
    molecule1 = oe.OEMol(molecule1)
    molecule2 = oe.OEMol(molecule2)

    # Normalize molecules.
    # TODO: May need to do more normalization here.
    oe.OEPerceiveChiral(molecule1)
    oe.OEPerceiveChiral(molecule2)

    # Make copies to not destroy original objects.
    import copy
    system = copy.deepcopy(system)
    topology = copy.deepcopy(topology)
    positions = copy.deepcopy(positions)

    # Create lists of corresponding atoms for common substructure and groups specific to molecules 1 and 2.
    atomexpr = oe.OEExprOpts_DefaultAtoms
    bondexpr = oe.OEExprOpts_BondOrder | oe.OEExprOpts_EqSingleDouble | oe.OEExprOpts_EqAromatic
    mcss = oe.OEMCSSearch(molecule1, atomexpr, bondexpr, oe.OEMCSType_Exhaustive)
    # This modifies scoring function to prefer keeping cycles complete.
    mcss.SetMCSFunc( oe.OEMCSMaxAtomsCompleteCycles() )
    # TODO: Set initial substructure size?
    # mcss.SetMinAtoms( some_number )
    # We only need one match.
    mcss.SetMaxMatches(1)
    # Determine common atoms in second molecule.
    matches = [ match for match in mcss.Match(molecule2, True) ]
    match = matches[0] # we only need the first match

    # Align common substructure of molecule2 with molecule1.
    overlay = True
    rmat  = oe.OEDoubleArray(9)
    trans = oe.OEDoubleArray(3)
    rms = oe.OERMSD(mcss.GetPattern(), molecule2, match, overlay, rmat, trans)
    if rms < 0.0:
        raise Exception("RMS overlay failure")
    oe.OERotate(molecule2, rmat)
    oe.OETranslate(molecule2, trans)

    # Make a list of the atoms in common, molecule1 only, and molecule2 only
    common1 = list() # list of atom indices in molecule1 that also appear in molecule2
    common2 = list() # list of atom indices in molecule2 that also appear in molecule1
    unique1 = list() # list of atom indices in molecule1 that DO NOT appear in molecule2
    unique2 = list() # list of atom indices in molecule2 that DO NOT appear in molecule1
    mapping1 = dict() # mapping of atoms in molecule1 to molecule2
    mapping2 = dict() # mapping of atoms in molecule2 to molecule1
    for matchpair in match.GetAtoms():
        index1 = matchpair.pattern.GetIdx()
        index2 = matchpair.target.GetIdx()
        mapping1[ index1 ] = index2
        mapping2[ index2 ] = index1
    all1 = frozenset(range(molecule1.NumAtoms()))
    all2 = frozenset(range(molecule2.NumAtoms()))
    common1 = frozenset(mapping1.keys())
    common2 = frozenset(mapping2.keys())
    unique1 = all1 - common1
    unique2 = all2 - common2

    # DEBUG
    print "list of atoms common to both molecules:"
    print "molecule1: %s" % str(common1)
    print "molecule2: %s" % str(common2)
    print "list of atoms unqiue to individual molecules:"
    print "molecule1: %s" % str(unique1)
    print "molecule2: %s" % str(unique2)
    print "MAPPING FROM MOLECULE1 TO MOLECULE2"
    for atom1 in mapping1.keys():
        atom2 = mapping1[atom1]
        print "%5d => %5d" % (atom1, atom2)

    # Create OpenMM Topology and System objects for given molecules using GAFF/AM1-BCC.
    # NOTE: This must generate the same forcefield parameters as occur in `system`.
    [system1, topology1, positions1] = generate_openmm_system(molecule1)
    [system2, topology2, positions2] = generate_openmm_system(molecule2)

    #
    # Start building combined OpenMM System object.
    #

    molecule1_atoms = [ atom for atom in molecule1.GetAtoms() ]
    molecule2_atoms = [ atom for atom in molecule2.GetAtoms() ]

    molecule2_indices_in_system = dict()

    # Build mapping of common substructure for molecule 2.
    for atom2 in common2:
        molecule2_indices_in_system[atom2] = molecule1_indices_in_system[mapping2[atom2]]

    # Find residue for molecule1.
    residue = None
    for atom in topology.atoms():
        if atom.index in molecule1_indices_in_system:
            residue = atom.residue
            break

    # Handle additional particles.
    print "Adding particles from system2..."
    for atom2 in unique2:
        atom = molecule2_atoms[atom2]
        name = atom.GetName()
        atomic_number = atom.GetAtomicNum()
        element = app.Element.getByAtomicNumber(atomic_number)
        mass = system2.getParticleMass(atom2)
        print [name, element, mass]
        index = system.addParticle(mass)
        molecule2_indices_in_system[atom2] = index

        # TODO: Add new atoms to topology object as well.
        topology.addAtom(name, element, residue)

    # Turn molecule2_indices_in_system into list
    molecule2_indices_in_system = [ molecule2_indices_in_system[atom2] for atom2 in range(molecule2.NumAtoms()) ]

    print "Atom mappings into System object"
    print "molecule1: %s" % str(molecule1_indices_in_system)
    print "molecule2: %s" % str(molecule2_indices_in_system)

    # Handle constraints.
    # TODO: What happens if constraints change? Raise Exception then.
    print "Adding constraints from system2..."
    for index in range(system2.getNumConstraints()):
        # Extract constraint distance from system2.
        [atom2_i, atom2_j, distance] = system.getConstraintParameters(index)
        # Map atoms from system2 into system.
        atom_i = molecule2_indices_in_system[atom2_i]
        atom_j = molecule2_indices_in_system[atom2_j]
        # Add constraint to system.
        system.addConstraint(atom_i, atom_j, distance)

    # Create new positions array.
    natoms = positions.shape[0] + len(unique2) # new number of atoms
    positions = unit.Quantity(np.resize(positions/positions.unit, [natoms,3]), positions.unit)
    for atom2 in unique2:
        (x, y, z) = molecule2.GetCoords(molecule2_atoms[atom2])
        index = molecule2_indices_in_system[atom2]
        positions[index,0] = x * unit.angstrom
        positions[index,1] = y * unit.angstrom
        positions[index,2] = z * unit.angstrom

    # Build a list of Force objects in system.
    forces = [ system.getForce(index) for index in range(system.getNumForces()) ]
    forces1 = { system1.getForce(index).__class__.__name__ : system1.getForce(index) for index in range(system1.getNumForces()) }
    forces2 = { system2.getForce(index).__class__.__name__ : system2.getForce(index) for index in range(system2.getNumForces()) }

    # Process forces.
    for force in forces:
        # Get force name.
        force_name = force.__class__.__name__
        force1 = forces1[force_name]
        force2 = forces2[force_name]
        print force_name
        if force_name == 'HarmonicBondForce':
            #
            # Process HarmonicBondForce
            #

            # Create index of bonds in system, system1, and system2.
            def unique(*args):
                if args[0] > args[-1]:
                    return tuple(reversed(args))
                else:
                    return tuple(args)

            def index_bonds(force):
                bonds = dict()
                for index in range(force.getNumBonds()):
                    [atom_i, atom_j, length, K] = force.getBondParameters(index)
                    key = unique(atom_i, atom_j) # unique tuple, possibly in reverse order
                    bonds[key] = index
                return bonds

            bonds  = index_bonds(force)   # index of bonds for system
            bonds1 = index_bonds(force1)  # index of bonds for system1
            bonds2 = index_bonds(force2)  # index of bonds for system2

            # Find bonds that are unique to each molecule.
            print "Finding bonds unique to each molecule..."
            unique_bonds1 = [ bonds1[atoms] for atoms in bonds1 if not set(atoms).issubset(common1) ]
            unique_bonds2 = [ bonds2[atoms] for atoms in bonds2 if not set(atoms).issubset(common2) ]

            # Build list of bonds shared among all molecules.
            print "Building a list of shared bonds..."
            shared_bonds = list()
            for atoms2 in bonds2:
                if set(atoms2).issubset(common2):
                    atoms  = tuple(molecule2_indices_in_system[atom2] for atom2 in atoms2)
                    atoms1 = tuple(mapping2[atom2] for atom2 in atoms2)
                    # Find bond index terms.
                    index  = bonds[unique(*atoms)]
                    index1 = bonds1[unique(*atoms1)]
                    index2 = bonds2[unique(*atoms2)]
                    # Store.
                    shared_bonds.append( (index, index1, index2) )

            # Add bonds that are unique to molecule2.
            print "Adding bonds unique to molecule2..."
            for index2 in unique_bonds2:
                [atom2_i, atom2_j, length2, K2] = force2.getBondParameters(index2)
                atom_i = molecule2_indices_in_system[atom2_i]
                atom_j = molecule2_indices_in_system[atom2_j]
                force.addBond(atom_i, atom_j, length2, K2)

            # Create a CustomBondForce to handle interpolated bond parameters.
            print "Creating CustomBondForce..."
            energy_expression  = '(K/2)*(r-length)^2;'
            energy_expression += 'K = (1-lambda)*K1 + lambda*K2;' # linearly interpolate spring constant
            energy_expression += 'length = (1-lambda)*length1 + lambda*length2;' # linearly interpolate bond length
            custom_force = mm.CustomBondForce(energy_expression)
            custom_force.addGlobalParameter('lambda', 0.0)
            custom_force.addPerBondParameter('length1') # molecule1 bond length
            custom_force.addPerBondParameter('K1') # molecule1 spring constant
            custom_force.addPerBondParameter('length2') # molecule2 bond length
            custom_force.addPerBondParameter('K2') # molecule2 spring constant
            system.addForce(custom_force)

            # Process bonds that are shared by molecule1 and molecule2.
            print "Translating shared bonds to CustomBondForce..."
            for (index, index1, index2) in shared_bonds:
                # Zero out standard bond force.
                [atom_i, atom_j, length, K] = force.getBondParameters(index)
                force.setBondParameters(index, atom_i, atom_j, length, K*0.0)
                # Create interpolated bond parameters.
                [atom1_i, atom1_j, length1, K1] = force1.getBondParameters(index1)
                [atom2_i, atom2_j, length2, K2] = force2.getBondParameters(index2)
                custom_force.addBond(atom_i, atom_j, [length1, K1, length2, K2])

        if force_name == 'HarmonicAngleForce':
            #
            # Process HarmonicAngleForce
            #

            # Create index of angles in system, system1, and system2.
            def unique(*args):
                if args[0] > args[-1]:
                    return tuple(reversed(args))
                else:
                    return tuple(args)

            def index_angles(force):
                angles = dict()
                for index in range(force.getNumAngles()):
                    [atom_i, atom_j, atom_k, angle, K] = force.getAngleParameters(index)
                    key = unique(atom_i, atom_j, atom_k) # unique tuple, possibly in reverse order
                    angles[key] = index
                return angles

            angles  = index_angles(force)   # index of angles for system
            angles1 = index_angles(force1)  # index of angles for system1
            angles2 = index_angles(force2)  # index of angles for system2

            # Find angles that are unique to each molecule.
            print "Finding angles unique to each molecule..."
            unique_angles1 = [ angles1[atoms] for atoms in angles1 if not set(atoms).issubset(common1) ]
            unique_angles2 = [ angles2[atoms] for atoms in angles2 if not set(atoms).issubset(common2) ]

            # Build list of angles shared among all molecules.
            print "Building a list of shared angles..."
            shared_angles = list()
            for atoms2 in angles2:
                if set(atoms2).issubset(common2):
                    atoms  = tuple(molecule2_indices_in_system[atom2] for atom2 in atoms2)
                    atoms1 = tuple(mapping2[atom2] for atom2 in atoms2)
                    # Find angle index terms.
                    index  = angles[unique(*atoms)]
                    index1 = angles1[unique(*atoms1)]
                    index2 = angles2[unique(*atoms2)]
                    # Store.
                    shared_angles.append( (index, index1, index2) )

            # Add angles that are unique to molecule2.
            print "Adding angles unique to molecule2..."
            for index2 in unique_angles2:
                [atom2_i, atom2_j, atom2_k, theta2, K2] = force2.getAngleParameters(index2)
                atom_i = molecule2_indices_in_system[atom2_i]
                atom_j = molecule2_indices_in_system[atom2_j]
                atom_k = molecule2_indices_in_system[atom2_k]
                force.addAngle(atom_i, atom_j, atom_k, theta2, K2)

            # Create a CustomAngleForce to handle interpolated angle parameters.
            print "Creating CustomAngleForce..."
            energy_expression  = '(K/2)*(theta-theta0)^2;'
            energy_expression += 'K = (1-lambda)*K_1 + lambda*K_2;' # linearly interpolate spring constant
            energy_expression += 'theta0 = (1-lambda)*theta0_1 + lambda*theta0_2;' # linearly interpolate equilibrium angle
            custom_force = mm.CustomAngleForce(energy_expression)
            custom_force.addGlobalParameter('lambda', 0.0)
            custom_force.addPerAngleParameter('theta0_1') # molecule1 equilibrium angle
            custom_force.addPerAngleParameter('K_1') # molecule1 spring constant
            custom_force.addPerAngleParameter('theta0_2') # molecule2 equilibrium angle
            custom_force.addPerAngleParameter('K_2') # molecule2 spring constant
            system.addForce(custom_force)

            # Process angles that are shared by molecule1 and molecule2.
            print "Translating shared angles to CustomAngleForce..."
            for (index, index1, index2) in shared_angles:
                # Zero out standard angle force.
                [atom_i, atom_j, atom_k, theta0, K] = force.getAngleParameters(index)
                force.setAngleParameters(index, atom_i, atom_j, atom_k, theta0, K*0.0)
                # Create interpolated angle parameters.
                [atom1_i, atom1_j, atom1_k, theta1, K1] = force1.getAngleParameters(index1)
                [atom2_i, atom2_j, atom2_k, theta2, K2] = force2.getAngleParameters(index2)
                custom_force.addAngle(atom_i, atom_j, atom_k, [theta1, K1, theta2, K2])

        if force_name == 'PeriodicTorsionForce':
            #
            # Process PeriodicTorsionForce
            # TODO: Match up periodicities and deal with multiple terms per torsion
            #

            # Create index of torsions in system, system1, and system2.
            def unique(*args):
                if args[0] > args[-1]:
                    return tuple(reversed(args))
                else:
                    return tuple(args)

            def index_torsions(force):
                torsions = dict()
                for index in range(force.getNumTorsions()):
                    [atom_i, atom_j, atom_k, atom_l, periodicity, phase, K] = force.getTorsionParameters(index)
                    key = unique(atom_i, atom_j, atom_k, atom_l) # unique tuple, possibly in reverse order
                    torsions[key] = index
                return torsions

            torsions  = index_torsions(force)   # index of torsions for system
            torsions1 = index_torsions(force1)  # index of torsions for system1
            torsions2 = index_torsions(force2)  # index of torsions for system2

            # Find torsions that are unique to each molecule.
            print "Finding torsions unique to each molecule..."
            unique_torsions1 = [ torsions1[atoms] for atoms in torsions1 if not set(atoms).issubset(common1) ]
            unique_torsions2 = [ torsions2[atoms] for atoms in torsions2 if not set(atoms).issubset(common2) ]

            # Build list of torsions shared among all molecules.
            print "Building a list of shared torsions..."
            shared_torsions = list()
            for atoms2 in torsions2:
                if set(atoms2).issubset(common2):
                    atoms  = tuple(molecule2_indices_in_system[atom2] for atom2 in atoms2)
                    atoms1 = tuple(mapping2[atom2] for atom2 in atoms2)
                    # Find torsion index terms.
                    try:
                        index  = torsions[unique(*atoms)]
                        index1 = torsions1[unique(*atoms1)]
                        index2 = torsions2[unique(*atoms2)]
                    except Exception as e:
                        print e
                        print "torsions :  %s" % str(unique(*atoms))
                        print "torsions1:  %s" % str(unique(*atoms1))
                        print "torsions2:  %s" % str(unique(*atoms2))
                        raise Exception("Error occurred in building a list of torsions common to all molecules.")

                    # Store.
                    shared_torsions.append( (index, index1, index2) )

            # Add torsions that are unique to molecule2.
            print "Adding torsions unique to molecule2..."
            for index2 in unique_torsions2:
                [atom2_i, atom2_j, atom2_k, atom2_l, periodicity2, phase2, K2] = force2.getTorsionParameters(index2)
                atom_i = molecule2_indices_in_system[atom2_i]
                atom_j = molecule2_indices_in_system[atom2_j]
                atom_k = molecule2_indices_in_system[atom2_k]
                atom_l = molecule2_indices_in_system[atom2_l]
                force.addTorsion(atom_i, atom_j, atom_k, atom_l, periodicity2, phase2, K2)

            # Create a CustomTorsionForce to handle interpolated torsion parameters.
            print "Creating CustomTorsionForce..."
            energy_expression  = '(1-lambda)*U1 + lambda*U2;'
            energy_expression += 'U1 = K1*(1+cos(periodicity1*theta-phase1));'
            energy_expression += 'U2 = K2*(1+cos(periodicity2*theta-phase2));'
            custom_force = mm.CustomTorsionForce(energy_expression)
            custom_force.addGlobalParameter('lambda', 0.0)
            custom_force.addPerTorsionParameter('periodicity1') # molecule1 periodicity
            custom_force.addPerTorsionParameter('phase1') # molecule1 phase
            custom_force.addPerTorsionParameter('K1') # molecule1 spring constant
            custom_force.addPerTorsionParameter('periodicity2') # molecule2 periodicity
            custom_force.addPerTorsionParameter('phase2') # molecule2 phase
            custom_force.addPerTorsionParameter('K2') # molecule2 spring constant
            system.addForce(custom_force)

            # Process torsions that are shared by molecule1 and molecule2.
            print "Translating shared torsions to CustomTorsionForce..."
            for (index, index1, index2) in shared_torsions:
                # Zero out standard torsion force.
                [atom_i, atom_j, atom_k, atom_l, periodicity, phase, K] = force.getTorsionParameters(index)
                force.setTorsionParameters(index, atom_i, atom_j, atom_k, atom_l, periodicity, phase, K*0.0)
                # Create interpolated torsion parameters.
                [atom1_i, atom1_j, atom1_k, atom1_l, periodicity1, phase1, K1] = force1.getTorsionParameters(index1)
                [atom2_i, atom2_j, atom2_k, atom2_l, periodicity2, phase2, K2] = force2.getTorsionParameters(index2)
                custom_force.addTorsion(atom_i, atom_j, atom_k, atom_l, [periodicity1, phase1, K1, periodicity2, phase2, K2])

        if force_name == 'NonbondedForce':
            #
            # Process NonbondedForce
            #

            # Add nonbonded entries for molecule2 to ensure total number of particle entries is correct.
            for atom in unique2:
                [charge, sigma, epsilon] = force2.getParticleParameters(atom)
                force.addParticle(charge, sigma, epsilon)

            # Zero out nonbonded entries for molecule1.
            for atom in molecule1_indices_in_system:
                [charge, sigma, epsilon] = force.getParticleParameters(atom)
                force.setParticleParameters(atom, 0*charge, sigma, 0*epsilon)
            # Zero out nonbonded entries for molecule2.
            for atom in molecule2_indices_in_system:
                [charge, sigma, epsilon] = force.getParticleParameters(atom)
                force.setParticleParameters(atom, 0*charge, sigma, 0*epsilon)

            # Create index of exceptions in system, system1, and system2.
            def unique(*args):
                if args[0] > args[-1]:
                    return tuple(reversed(args))
                else:
                    return tuple(args)

            def index_exceptions(force):
                exceptions = dict()
                for index in range(force.getNumExceptions()):
                    [atom_i, atom_j, chargeProd, sigma, epsilon] = force.getExceptionParameters(index)
                    key = unique(atom_i, atom_j) # unique tuple, possibly in reverse order
                    exceptions[key] = index
                return exceptions

            exceptions  = index_exceptions(force)   # index of exceptions for system
            exceptions1 = index_exceptions(force1)  # index of exceptions for system1
            exceptions2 = index_exceptions(force2)  # index of exceptions for system2

            # Find exceptions that are unique to each molecule.
            print "Finding exceptions unique to each molecule..."
            unique_exceptions1 = [ exceptions1[atoms] for atoms in exceptions1 if not set(atoms).issubset(common1) ]
            unique_exceptions2 = [ exceptions2[atoms] for atoms in exceptions2 if not set(atoms).issubset(common2) ]

            # Build list of exceptions shared among all molecules.
            print "Building a list of shared exceptions..."
            shared_exceptions = list()
            for atoms2 in exceptions2:
                if set(atoms2).issubset(common2):
                    atoms  = tuple(molecule2_indices_in_system[atom2] for atom2 in atoms2)
                    atoms1 = tuple(mapping2[atom2] for atom2 in atoms2)
                    # Find exception index terms.
                    index  = exceptions[unique(*atoms)]
                    index1 = exceptions1[unique(*atoms1)]
                    index2 = exceptions2[unique(*atoms2)]
                    # Store.
                    shared_exceptions.append( (index, index1, index2) )

            # Add exceptions that are unique to molecule2.
            print "Adding exceptions unique to molecule2..."
            for index2 in unique_exceptions2:
                [atom2_i, atom2_j, chargeProd, sigma, epsilon] = force2.getExceptionParameters(index2)
                atom_i = molecule2_indices_in_system[atom2_i]
                atom_j = molecule2_indices_in_system[atom2_j]
                force.addException(atom_i, atom_j, chargeProd, sigma, epsilon)

            # Create list of alchemically modified atoms in system.
            alchemical_atom_indices = list(set(molecule1_indices_in_system).union(set(molecule2_indices_in_system)))

            # Create atom groups.
            natoms = system.getNumParticles()
            atomset1 = set(alchemical_atom_indices) # only alchemically-modified atoms
            atomset2 = set(range(system.getNumParticles())) # all atoms, including alchemical region

            # CustomNonbondedForce energy expression.
            sterics_energy_expression = ""
            electrostatics_energy_expression = ""

            # Create a CustomNonbondedForce to handle alchemically interpolated nonbonded parameters.
            # Select functional form based on nonbonded method.
            method = force.getNonbondedMethod()
            if method in [mm.NonbondedForce.NoCutoff]:
                # soft-core Lennard-Jones
                sterics_energy_expression += "U_sterics = 4*epsilon*x*(x-1.0); x1 = (sigma/reff_sterics)^6;"
                # soft-core Coulomb
                electrostatics_energy_expression += "U_electrostatics = ONE_4PI_EPS0*chargeprod/reff_electrostatics;"
            elif method in [mm.NonbondedForce.CutoffPeriodic, mm.NonbondedForce.CutoffNonPeriodic]:
                # soft-core Lennard-Jones
                sterics_energy_expression += "U_sterics = 4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;"
                # reaction-field electrostatics
                epsilon_solvent = force.getReactionFieldDielectric()
                r_cutoff = force.getCutoffDistance()
                electrostatics_energy_expression += "U_electrostatics = ONE_4PI_EPS0*chargeprod*(reff_electrostatics^(-1) + k_rf*reff_electrostatics^2 - c_rf);"
                k_rf = r_cutoff**(-3) * ((epsilon_solvent - 1) / (2*epsilon_solvent + 1))
                c_rf = r_cutoff**(-1) * ((3*epsilon_solvent) / (2*epsilon_solvent + 1))
                electrostatics_energy_expression += "k_rf = %f;" % (k_rf / k_rf.in_unit_system(unit.md_unit_system).unit)
                electrostatics_energy_expression += "c_rf = %f;" % (c_rf / c_rf.in_unit_system(unit.md_unit_system).unit)
            elif method in [mm.NonbondedForce.PME, mm.NonbondedForce.Ewald]:
                # soft-core Lennard-Jones
                sterics_energy_expression += "U_sterics = 4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;"
                # Ewald direct-space electrostatics
                [alpha_ewald, nx, ny, nz] = force.getPMEParameters()
                if alpha_ewald == 0.0:
                    # If alpha is 0.0, alpha_ewald is computed by OpenMM from from the error tolerance.
                    delta = force.getEwaldErrorTolerance()
                    r_cutoff = force.getCutoffDistance()
                    alpha_ewald = np.sqrt(-np.log(2*delta)) / r_cutoff
                electrostatics_energy_expression += "U_electrostatics = ONE_4PI_EPS0*chargeprod*erfc(alpha_ewald*reff_electrostatics)/reff_electrostatics;"
                electrostatics_energy_expression += "alpha_ewald = %f;" % (alpha_ewald / alpha_ewald.in_unit_system(unit.md_unit_system).unit)
                # TODO: Handle reciprocal-space electrostatics
            else:
                raise Exception("Nonbonded method %s not supported yet." % str(method))

            # Add additional definitions common to all methods.
            sterics_energy_expression += "epsilon = (1-lambda)*epsilonA + lambda*epsilonB;" #interpolation
            sterics_energy_expression += "reff_sterics = sigma*((softcore_alpha*lambda_alpha + (r/sigma)^6))^(1/6);" # effective softcore distance for sterics
            sterics_energy_expression += "softcore_alpha = %f;" % softcore_alpha
            # TODO: We may have to ensure that softcore_degree is 1 if we are close to an alchemically-eliminated endpoint.
            sterics_energy_expression += "lambda_alpha = lambda*(1-lambda);"
            electrostatics_energy_expression += "chargeProd = (1-lambda)*chargeProdA + lambda*chargeProdB;" #interpolation
            electrostatics_energy_expression += "reff_electrostatics = sqrt(softcore_beta*lambda_beta + r^2);" # effective softcore distance for electrostatics
            electrostatics_energy_expression += "softcore_beta = %f;" % (softcore_beta / softcore_beta.in_unit_system(unit.md_unit_system).unit)
            electrostatics_energy_expression += "ONE_4PI_EPS0 = %f;" % ONE_4PI_EPS0 # already in OpenMM units
            # TODO: We may have to ensure that softcore_degree is 1 if we are close to an alchemically-eliminated endpoint.
            sterics_energy_expression += "lambda_beta = lambda*(1-lambda);"

            # Define mixing rules.
            sterics_mixing_rules = ""
            sterics_mixing_rules += "epsilonA = sqrt(epsilonA1*epsilonA2);" # mixing rule for epsilon
            sterics_mixing_rules += "epsilonB = sqrt(epsilonB1*epsilonB2);" # mixing rule for epsilon
            sterics_mixing_rules += "sigmaA = 0.5*(sigmaA1 + sigmaA2);" # mixing rule for sigma
            sterics_mixing_rules += "sigmaB = 0.5*(sigmaB1 + sigmaB2);" # mixing rule for sigma
            electrostatics_mixing_rules = ""
            electrostatics_mixing_rules += "chargeprodA = chargeA1*chargeA2;" # mixing rule for charges
            electrostatics_mixing_rules += "chargeprodB = chargeB1*chargeB2;" # mixing rule for charges

            # Create CustomNonbondedForce to handle interactions between alchemically-modified atoms and rest of system.
            electrostatics_custom_nonbonded_force = mm.CustomNonbondedForce("U_electrostatics;" + electrostatics_energy_expression + electrostatics_mixing_rules)
            electrostatics_custom_nonbonded_force.addGlobalParameter("lambda", 0.0);
            electrostatics_custom_nonbonded_force.addPerParticleParameter("chargeA") # partial charge initial
            electrostatics_custom_nonbonded_force.addPerParticleParameter("chargeB") # partial charge final
            sterics_custom_nonbonded_force = mm.CustomNonbondedForce("U_sterics;" + sterics_energy_expression + sterics_mixing_rules)
            sterics_custom_nonbonded_force.addGlobalParameter("lambda", 0.0);
            sterics_custom_nonbonded_force.addPerParticleParameter("sigmaA") # Lennard-Jones sigma initial
            sterics_custom_nonbonded_force.addPerParticleParameter("epsilonA") # Lennard-Jones epsilon initial
            sterics_custom_nonbonded_force.addPerParticleParameter("sigmaB") # Lennard-Jones sigma final
            sterics_custom_nonbonded_force.addPerParticleParameter("epsilonB") # Lennard-Jones epsilon final

            # Restrict interaction evaluation to be between alchemical atoms and rest of environment.
            # TODO: Exclude intra-alchemical region if we are separately handling that through a separate CustomNonbondedForce for decoupling.
            sterics_custom_nonbonded_force.addInteractionGroup(atomset1, atomset2)
            electrostatics_custom_nonbonded_force.addInteractionGroup(atomset1, atomset2)

            # Add exclusions between unique parts of molecule1 and molecule2 so they do not interact.
            print "Add exclusions between unique parts of molecule1 and molecule2 that should not interact..."
            for atom1_i in unique1:
                for atom2_j in unique2:
                    atom_i = molecule1_indices_in_system[atom1_i]
                    atom_j = molecule2_indices_in_system[atom2_j]
                    electrostatics_custom_nonbonded_force.addExclusion(atom_i, atom_j)
                    sterics_custom_nonbonded_force.addExclusion(atom_i, atom_j)

            # Add custom forces to system.
            system.addForce(sterics_custom_nonbonded_force)
            system.addForce(electrostatics_custom_nonbonded_force)

            # Create CustomBondForce to handle exceptions for both kinds of interactions.
            #custom_bond_force = mm.CustomBondForce("U_sterics + U_electrostatics;" + sterics_energy_expression + electrostatics_energy_expression)
            #custom_bond_force.addGlobalParameter("lambda", 0.0);
            #custom_bond_force.addPerBondParameter("chargeprodA") # charge product
            #custom_bond_force.addPerBondParameter("sigmaA") # Lennard-Jones effective sigma
            #custom_bond_force.addPerBondParameter("epsilonA") # Lennard-Jones effective epsilon
            #custom_bond_force.addPerBondParameter("chargeprodB") # charge product
            #custom_bond_force.addPerBondParameter("sigmaB") # Lennard-Jones effective sigma
            #custom_bond_force.addPerBondParameter("epsilonB") # Lennard-Jones effective epsilon
            #system.addForce(custom_bond_force)

            # Copy over all Nonbonded parameters for normal atoms to Custom*Force objects.
            for particle_index in range(force.getNumParticles()):
                # Retrieve parameters.
                [charge, sigma, epsilon] = force.getParticleParameters(particle_index)
                # Add parameters to custom force handling interactions between alchemically-modified atoms and rest of system.
                sterics_custom_nonbonded_force.addParticle([sigma, epsilon, sigma, epsilon])
                electrostatics_custom_nonbonded_force.addParticle([charge, charge])

            # Copy over parameters for common substructure.
            for atom1 in common1:
                atom2 = mapping1[atom1] # index into system2
                index = molecule1_indices_in_system[atom1] # index into system
                [charge1, sigma1, epsilon1] = force1.getParticleParameters(atom1)
                [charge2, sigma2, epsilon2] = force2.getParticleParameters(atom2)
                sterics_custom_nonbonded_force.setParticleParameters(index, [sigma1, epsilon1, sigma2, epsilon2])
                electrostatics_custom_nonbonded_force.setParticleParameters(index, [charge1, charge2])

            # Copy over parameters for molecule1 unique atoms.
            for atom1 in unique1:
                index = molecule1_indices_in_system[atom1] # index into system
                [charge1, sigma1, epsilon1] = force1.getParticleParameters(atom1)
                sterics_custom_nonbonded_force.setParticleParameters(index, [sigma1, epsilon1, sigma1, 0*epsilon1])
                electrostatics_custom_nonbonded_force.setParticleParameters(index, [charge1, 0*charge1])

            # Copy over parameters for molecule2 unique atoms.
            for atom2 in unique2:
                index = molecule2_indices_in_system[atom2] # index into system
                [charge2, sigma2, epsilon2] = force2.getParticleParameters(atom2)
                sterics_custom_nonbonded_force.setParticleParameters(index, [sigma2, 0*epsilon2, sigma2, epsilon2])
                electrostatics_custom_nonbonded_force.setParticleParameters(index, [0*charge2, charge2])

        else:
            #raise Exception("Force type %s unknown." % force_name)
            pass

    return [system, topology, positions]
Ejemplo n.º 19
0
 def calc_rms(self, fitmol):
     fit_match = self.get_match(fitmol)
     match = oechem.OEMatch()
     for rm, fm in zip(self.ref_match, fit_match):
         match.AddPair(rm, fm)
     return oechem.OERMSD(self.refmol, fitmol, match, False)