示例#1
0
文件: mcs.py 项目: biocad/leadoptmap
        def search(self, mol0, mol1):
            mol0 = mol0._struc
            mol1 = mol1._struc

            p0 = mol0.CreateCopy()
            p1 = mol1.CreateCopy()
            #set atom int type.
            for mol in (
                    p0,
                    p1,
            ):
                for atom in mol.GetAtoms():
                    if (atom.IsHydrogen()):
                        atom.SetIntType(1)
                    else:
                        atom.SetIntType(2)
            #suppress hydrogens before mcs search
            oechem.OESuppressHydrogens(p0)
            oechem.OESuppressHydrogens(p1)
            if (self._is_approximate):
                mcss = oechem.OEMCSSearch(p1, self._atom_expr, self._bond_expr,
                                          oechem.OEMCSType_Approximate)
            else:
                mcss = oechem.OEMCSSearch(p1, self._atom_expr, self._bond_expr)
            #set minimum atom of the mcs
            mcss.SetMinAtoms(1)
            #set the function to evalue the mcs search
            mcss.SetMCSFunc(oechem.OEMCSMaxAtomsCompleteCycles(1.5))

            # There could be multiple matches. We select the one with the maximum number of atoms.
            # If there are more than 1 matches with the same maximum number of atoms, we arbitrarily select the first one.
            mcs_mol = None
            max_num = 0
            #do the mcs search
            for match in mcss.Match(p0, True):
                num_atom = 0
                mcs_tmp = oechem.OEMol()
                oechem.OESubsetMol(mcs_tmp, match, True)
                oechem.OEFindRingAtomsAndBonds(mcs_tmp)
                for atom in mcs_tmp.GetAtoms():
                    if (not atom.IsHydrogen()):
                        num_atom += 1

                if (num_atom > max_num):
                    max_num = num_atom
                    mcs_mol = mcs_tmp
                    atom_match0 = []
                    atom_match1 = []
                    for matchpair in match.GetAtoms():
                        atom_match0.append(matchpair.target.GetIdx() + 1)
                        atom_match1.append(matchpair.pattern.GetIdx() + 1)
            #dump search result to kbase
            if (mcs_mol):
                mol0 = struc.OeStruc(mol0)
                mol1 = struc.OeStruc(mol1)
                mcs_mol = struc.OeStruc(mcs_mol)
                return self.deposit_to_kbase(mol0.id(), mol1.id(), atom_match0,
                                             atom_match1)
示例#2
0
    def determineCommonSubstructure(self):
        """
        Find a common substructure shared by all ligands.

        The atom type name strings and integer bond types are used to obtain an exact match.

        Will not run if self.common_substructure is not None

        Arguments
            (none)

        Creates class variables:
            self.common_substructure (OEMol) 
              openeye molecule representing the common substructure

        """
        if self.common_substructure is not None:
            return
        ligands = self.ligands
        min_atoms = self.min_atoms

        # First, initialize with first ligand.
        common_substructure = ligands[0].CreateCopy(
        )  #DLM modification 11/15/10 -- this is how copies should now be made

        atomexpr = oechem.OEExprOpts_DefaultAtoms
        bondexpr = oechem.OEExprOpts_DefaultBonds

        # Now delete bits that don't match every other ligand.
        for ligand in ligands[1:]:

            # Create an OEMCSSearch from this molecule.
            mcss = oechem.OEMCSSearch(ligand, atomexpr, bondexpr,
                                      oechem.OEMCSType_Exhaustive)

            # ignore substructures smaller than 6 atoms
            mcss.SetMinAtoms(min_atoms)

            # perform match
            for match in mcss.Match(common_substructure):
                nmatched = match.NumAtoms()

                # build list of matched atoms in common substructure
                matched_atoms = []
                for matchpair in match.GetAtoms():
                    atom = matchpair.target
                    matched_atoms.append(atom)

                # delete all unmatched atoms from common substructure
                for atom in common_substructure.GetAtoms():
                    if atom not in matched_atoms:
                        common_substructure.DeleteAtom(atom)

                # we only need to consider one match
                break

        # return the common substructure
        self.common_substructure = common_substructure
示例#3
0
    def generateMergedTopology(self, reference_molecule=None, verbose=False):
        """\
        Generate an alchemical merged topology for the added molecule variants.

        Parameters
        ----------
        reference_molecule : openeye.oechem.OEMol, optional, default=None
            If specified, a molecule whose positions the core is to be aligned.
        verbose : bool, optional, default=False
            If True, will print out lots of debug info.

        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.
        positions: simtk.unit.Quantity of (natoms,3) with units compatible with length
            Positions corresponding to constructed system.

        """
        # Copy molecules so as not to accidentally overwrite them.
        molecules = [molecule.CreateCopy() for molecule in self._molecules]

        # Determine common substructure using exact match of GAFF atom and bond types.
        if verbose: print "Determining common core substructure..."
        core = oldmmtools.determineCommonSubstructure(molecules, verbose=True)

        # Find RMS-fit charges for common intermediate.
        if verbose:
            print "Determining RMS-fit charges for common intermediate..."
        core = oldmmtools.determineMinimumRMSCharges(core, molecules)

        # DEBUG: Write out info for common core / scaffold.
        if verbose:
            print "Common core atoms, types, and partial charges:"
            print "\n%s" % core.GetTitle()
            for atom in core.GetAtoms():
                print "%6s : %3s %8.3f" % (atom.GetName(), atom.GetType(),
                                           atom.GetPartialCharge())

            # Write out common substructure in GAFF format.
            filename = 'core.gaff.mol2'
            print "Writing common intermediate with GAFF atomtypes to %s" % filename
            oldmmtools.writeMolecule(core, filename, preserve_atomtypes=True)

        # Set up MCSS to detect overlap with common substructure.
        atomexpr = oe.OEExprOpts_StringType  # match GAFF atom type (str) exactly
        bondexpr = oe.OEExprOpts_IntType  # match GAFF bond type (int) exactly
        min_atoms = 4
        mcss = oe.OEMCSSearch(core, atomexpr, bondexpr)
        mcss.SetMinAtoms(min_atoms)  # ensure a minimum number of atoms match
        mcss.SetMCSFunc(oe.OEMCSMaxAtomsCompleteCycles()
                        )  # prefer keeping cycles complete.
        mcss.SetMaxMatches(1)

        return [system, topology, positions]
示例#4
0
 def __setupMCSS(self, refmol, useExhaustive=True):
     """Internal initialization for the MCSS comparison."""
     #
     mode = oechem.OEMCSType_Exhaustive if useExhaustive else oechem.OEMCSType_Approximate
     self.__mcss = oechem.OEMCSSearch(mode)
     atomexpr, bondexpr = OeCommonUtils.getAtomBondExprOpts(self.__searchType)
     self.__mcss.Init(refmol, atomexpr, bondexpr)
     if self.__verbose:
         logger.info("Initialize MCSS (%r)", self.__searchType)
示例#5
0
def are_equal_microstates(mol1: oechem.OEMol, mol2: oechem.OEMol):
    """ Check if two supplied OE(Graph)Mol objects have the same molecular microstate present.
    
    If all atoms and bonds are matched in MCSS returns True. Ignores charges and bond order
    to deal with chirality and geometry differences as well as resonance structures
    (which we won't consider as different).
    
    Returns
    -------
    bool    
    """
    # Copy input
    pattern = oechem.OEMol(mol1)
    target = oechem.OEMol(mol2)

    # Atoms are equal if they have same atomic number (so explicit Hydrogens are needed as well for a match)
    atomexpr = oechem.OEExprOpts_AtomicNumber
    # single or double bonds are considered identical (resonance,chirality fix)
    bondexpr = oechem.OEExprOpts_EqSingleDouble
    # create maximum common substructure object
    mcss = oechem.OEMCSSearch(pattern, atomexpr, bondexpr,
                              oechem.OEMCSType_Exhaustive)

    # set scoring function
    mcss.SetMCSFunc(oechem.OEMCSMaxAtomsCompleteCycles())
    # ignore matches smaller than 6 atoms
    mcss.SetMinAtoms(6)
    unique = True

    # loop over matches
    count = 0
    match = oechem.OEMol()
    for i, match in enumerate(mcss.Match(target, unique)):
        count = i + 1
        logger.debug("Match %d:" % (count))

        logger.debug("Num atoms in match %d" % match.NumAtoms())
        logger.debug("Num atoms in mol1 %d" % pattern.NumAtoms())
        logger.debug("Num atoms in mol2 %d" % target.NumAtoms())

    # check if there is only single match
    if (count > 1):
        logger.warning("Warning! There are multiple matches.")
    elif count == 0:
        logger.debug("No match")

    m_num = match.NumAtoms()
    p_num = pattern.NumAtoms()
    t_num = target.NumAtoms()

    return m_num == p_num == t_num
示例#6
0
def main(argv=[__name__]):

    itf = oechem.OEInterface(InterfaceData)

    if not oechem.OEParseCommandLine(itf, argv):
        oechem.OEThrow.Fatal("Unable to interpret command line!")

    rname = itf.GetString("-ref")
    fname = itf.GetString("-fit")
    oname = itf.GetString("-out")

    rifs = oechem.oemolistream()
    if not rifs.open(rname):
        oechem.OEThrow.Fatal("Cannot open reference molecule file!")

    refmol = oechem.OEGraphMol()
    if not oechem.OEReadMolecule(rifs, refmol):
        oechem.OEThrow.Fatal("Cannot read reference molecule!")

    fifs = oechem.oemolistream()
    if not fifs.open(fname):
        oechem.OEThrow.Fatal("Cannot open align molecule file!")

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

    oedepict.OEPrepareDepiction(refmol)

    mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
    atomexpr = oechem.OEExprOpts_DefaultAtoms
    bondexpr = oechem.OEExprOpts_DefaultBonds
    mcss.Init(refmol, atomexpr, bondexpr)
    mcss.SetMCSFunc(oechem.OEMCSMaxBondsCompleteCycles())

    oechem.OEWriteConstMolecule(ofs, refmol)

    for fitmol in fifs.GetOEGraphMols():
        alignres = oedepict.OEPrepareAlignedDepiction(fitmol, mcss)
        if alignres.IsValid():
            oechem.OEThrow.Info("%s  mcs size: %d" % (fitmol.GetTitle(), alignres.NumAtoms()))
            oechem.OEWriteMolecule(ofs, fitmol)

    return 0
示例#7
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)
示例#8
0
def align_molecules(mol1, mol2):
    """
    MCSS two OEmols. Return the mapping of new : old atoms
    """
    mcs = oechem.OEMCSSearch(oechem.OEMCSType_Exhaustive)
    atomexpr = oechem.OEExprOpts_Aromaticity | oechem.OEExprOpts_AtomicNumber | oechem.OEExprOpts_HvyDegree
    bondexpr = oechem.OEExprOpts_Aromaticity
    #atomexpr = oechem.OEExprOpts_HvyDegree
    #bondexpr = 0
    mcs.Init(mol1, atomexpr, bondexpr)
    mcs.SetMCSFunc(oechem.OEMCSMaxAtomsCompleteCycles())
    unique = True
    match = [m for m in mcs.Match(mol2, unique)][0]
    new_to_old_atom_mapping = {}
    for matchpair in match.GetAtoms():
        old_index = matchpair.pattern.GetIdx()
        new_index = matchpair.target.GetIdx()
        new_to_old_atom_mapping[new_index] = old_index
    return new_to_old_atom_mapping
示例#9
0
pattern = oechem.OEGraphMol()
target = oechem.OEGraphMol()
oechem.OESmilesToMol(pattern, "c1(cc(nc2c1C(CCC2)Cl)CCl)O")
oechem.OESmilesToMol(target, "c1(c2c(nc(n1)CF)COC=C2)N")

# @ <SNIPPET-EXPR>
atomexpr = oechem.OEExprOpts_DefaultAtoms
bondexpr = oechem.OEExprOpts_DefaultBonds
# @ </SNIPPET-EXPR>

patternQ = oechem.OEQMol(pattern)
# generate query with atom and bond expression options
# @ <SNIPPET-BUILDEXPR>
patternQ.BuildExpressions(atomexpr, bondexpr)
# @ </SNIPPET-BUILDEXPR>
mcss = oechem.OEMCSSearch(patternQ)

unique = True
count = 1
# loop over matches
for match in mcss.Match(target, unique):
    print("Match %d:" % count)
    print("Number of matched atoms: %d" % match.NumAtoms())
    print("Number of matched bonds: %d" % match.NumBonds())
    # create match subgraph
    m = oechem.OEGraphMol()
    oechem.OESubsetMol(m, match, True)
    print("match smiles = %s" % oechem.OEMolToSmiles(m))
    count += 1
# @ </SNIPPET>
示例#10
0
# liable for any damages or liability in connection with the Sample Code
# or its use.

from openeye import oechem
from openeye import oedepict

# @ <SNIPPET-MCS-ALIGN>
refmol = oechem.OEGraphMol()
oechem.OESmilesToMol(refmol, "c1cc(c2cc(cnc2c1)CCCO)C(=O)CCO")
oedepict.OEPrepareDepiction(refmol)

fitmol = oechem.OEGraphMol()
oechem.OESmilesToMol(fitmol, "c1cc2ccc(cc2c(c1)C(=O)O)CCO")
oedepict.OEPrepareDepiction(fitmol)

mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
atomexpr = oechem.OEExprOpts_DefaultAtoms
bondexpr = oechem.OEExprOpts_DefaultBonds
mcss.Init(refmol, atomexpr, bondexpr)
mcss.SetMCSFunc(oechem.OEMCSMaxBondsCompleteCycles())

alignres = oedepict.OEPrepareAlignedDepiction(fitmol, mcss)

image = oedepict.OEImage(400, 200)

rows, cols = 1, 2
grid = oedepict.OEImageGrid(image, rows, cols)

opts = oedepict.OE2DMolDisplayOptions(grid.GetCellWidth(),
                                      grid.GetCellHeight(),
                                      oedepict.OEScale_AutoScale)
示例#11
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]
示例#12
0
    def createDualTopology(self):
        """
        Create a dual topology combining all ligands into one OEMol sharing as many atoms as possible.
    
        The atom type name strings and integer bond types are used to obtain an exact match.

        Will not run if self.dual_topology is not None.

        Arguments:
            (none)

        Creates class variables:
            self.dual_topology (OEMol) 
              openeye molecule combining all input molecules
            self.each_molecule_N (list of tuples) 
              list of N+1 tuples containing (first atom index, last atom index) for the atoms in the dual topology that come from
              [0] substructure and [1:] each molecule
            self.mapping_dictionaries (list of dictionaries) 
              a list of N+1 dictionaries ([0] is the common substructure, and the subsequent list entries each represent 
              an input molecule) which map {atom index in the molecule : atom index in the dual topology OEMol}

        """

        if self.dual_topology is not None:
            return
        ligands = self.ligands

        # First, initialize as common substructure.
        self.determineCommonSubstructure()
        common_substructure = self.common_substructure

        # Initialize a dual topology
        dual_topology = common_substructure.CreateCopy()
        min_atoms = common_substructure.NumAtoms()

        # self.each_molecule_N will be a list of tuples containing the first and last index
        # of atoms belonging to a given substructure
        # self.each_molecule_N[0] represents the common substructure
        # self.each_molecule_N[1:] represent the unique portion of each subsequent ligand
        self.each_molecule_N.append((0, min_atoms - 1))
        new_index = min_atoms - 1

        # a query atom is mapped to a target atom only if they have the same atomic number,
        # aromaticity value and formal charge
        atomexpr = oechem.OEExprOpts_DefaultAtoms
        # a query bond is mapped to a target bond only if they have the same bond order and
        # aromaticity value
        bondexpr = oechem.OEExprOpts_DefaultBonds

        # Create a new OEMCSSearch object, using the common substructure as the pattern molecule
        # all atoms that don't match will be unique to that ligand
        mcss = oechem.OEMCSSearch(common_substructure, atomexpr, bondexpr,
                                  oechem.OEMCSType_Exhaustive)
        mcss.SetMinAtoms(min_atoms)

        # Create intermediate dictionary to translate from atoms in the common substructure to the new
        # dual topology
        common_to_dual = {}
        for match in mcss.Match(dual_topology):
            # matchpair is an object with matchpair.pattern and matchpair.target as atom objects representing
            # the matched atoms from each ligand
            for matchpair in match.GetAtoms():
                # add an entry in the dictionary which maps the substructure atom to that atom in dual_topology
                common_to_dual[matchpair.pattern] = matchpair.target
            break  # only need to do it for one match

        # adding this only for index consistency
        self.mapping_dictionaries.append(common_to_dual)

        # Now add details of each ligand while ignoring non-unique bits.
        for ligand in ligands:
            old_index = new_index
            # start a new mapping dictionary between this ligand and the dual topology
            ligand_to_dual = {}

            # perform match
            for match in mcss.Match(ligand):
                nmatched = match.NumAtoms()

                # build list of matched atoms in common substructure
                for matchpair in match.GetAtoms():
                    atom = matchpair.target
                    # Use the intermediate dictionary to translate from ligand atoms to corresponding atoms
                    # in the dual topology
                    ligand_to_dual[atom] = common_to_dual[matchpair.pattern]

                # add unique substructure atoms and their bonds to dual topology
                for atom in ligand.GetAtoms():
                    # only use atoms not in common substructure
                    if atom not in ligand_to_dual.keys():
                        # add the atom to the dual topology, saving the new copy as dual_equiv
                        dual_equiv = dual_topology.NewAtom(atom)
                        new_index = dual_equiv.GetIdx()
                        # add the atom and its new copy to the mapping dictionary
                        ligand_to_dual[atom] = dual_equiv
                        # bonds also need to be added to the dual topology
                        for bonded_atom in atom.GetAtoms():
                            # only use bonds with atoms already added to dual topology, to avoid duplicates
                            if bonded_atom in ligand_to_dual.keys():
                                # save attributes of the bond from the ligand, so they can be copied
                                # to the dual topology
                                this_bond = ligand.GetBond(atom, bonded_atom)
                                order = this_bond.GetOrder()
                                dual_bonded = ligand_to_dual[bonded_atom]
                                # create a new bond in the dual topology between the corresponding atoms
                                # and with the same order
                                new_bond = dual_topology.NewBond(
                                    dual_bonded, dual_equiv)
                                new_bond.SetOrder(order)

                self.mapping_dictionaries.append(ligand_to_dual)
                self.each_molecule_N.append((old_index + 1, new_index))
                # we only need to consider one match
                break

        # return the common substructure
        self.dual_topology = dual_topology
示例#13
0
# EXPRESS OR IMPLIED.  OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall OpenEye be
# liable for any damages or liability in connection with the Sample Code
# or its use.
# @ <SNIPPET>
from openeye import oechem
# @ <SNIPPET-CUSTOM-MCSFUNC>
pattern = oechem.OEGraphMol()
target = oechem.OEGraphMol()
oechem.OESmilesToMol(pattern, "c1cc(O)c(O)cc1CCN")
oechem.OESmilesToMol(target, "c1c(O)c(O)c(Cl)cc1CCCBr")

atomexpr = oechem.OEExprOpts_DefaultAtoms
bondexpr = oechem.OEExprOpts_DefaultBonds
mcss = oechem.OEMCSSearch(pattern, atomexpr, bondexpr,
                          oechem.OEMCSType_Exhaustive)


class MyMaxAtomsBondsMCSFunc(oechem.OEMCSFunc):
    def __call__(self, pattern, target, amap, bmap):
        atommap = oechem.OEAtomArray(amap, pattern.GetMaxAtomIdx())
        bondmap = oechem.OEBondArray(bmap, pattern.GetMaxBondIdx())
        atomcount = 0
        bondcount = 0
        for atom in atommap:
            if atom is not None:
                atomcount += 1
        for bond in bondmap:
            if bond is not None:
                bondcount += 1
        return atomcount + bondcount