示例#1
0
def smiles_to_oemol(smiles, title='MOL', max_confs=1):
    """
    Generate an oemol from a SMILES string

    Parameters
    ----------
    smiles : str
        SMILES string of molecule
    title : str, default 'MOL'
        title of OEMol molecule
    max_confs : int, default 1
        maximum number of conformers to generate
    Returns
    -------
    molecule : openeye.oechem.OEMol
        OEMol object of the molecule
    """
    from openeye import oeomega

    # Create molecule
    molecule = oechem.OEMol()
    oechem.OESmilesToMol(molecule, smiles)

    # create unique atom names
    if len([atom.GetName() for atom in molecule.GetAtoms()]) > len(
            set([atom.GetName() for atom in molecule.GetAtoms()])):
        # the atom names are not unique
        molecule = generate_unique_atom_names(molecule)
    else:
        pass

    # Set title.
    molecule.SetTitle(title)

    # Assign aromaticity and hydrogens.
    oechem.OEAssignAromaticFlags(molecule, oechem.OEAroModelOpenEye)
    oechem.OEAssignHybridization(molecule)
    oechem.OEAddExplicitHydrogens(molecule)
    oechem.OEPerceiveChiral(molecule)

    # Create atom names.
    oechem.OETriposAtomNames(molecule)
    oechem.OETriposBondTypeNames(molecule)

    # perceive chirality before attempting omega geometry proposal
    assert oechem.OEPerceiveChiral(molecule), f"chirality perception failed"

    # Assign geometry
    omega = oeomega.OEOmega()
    omega.SetMaxConfs(max_confs)
    omega.SetIncludeInput(False)
    omega.SetStrictStereo(True)

    omega(molecule)
    return molecule
def atom_bond_set_to_mol(frag, oemol, adjust_hcount=False, RGroup=True):
    from openeye import oechem
    import warnings
    fragatompred = oechem.OEIsAtomMember(frag.GetAtoms())
    fragbondpred = oechem.OEIsBondMember(frag.GetBonds())

    fragment_oemol = oechem.OEMol()
    adjustHCount = adjust_hcount
    oechem.OESubsetMol(fragment_oemol, oemol, fragatompred, fragbondpred,
                       adjustHCount, RGroup)

    oechem.OEAddExplicitHydrogens(fragment_oemol)
    # sanity check that all atoms are bonded
    for atom in fragment_oemol.GetAtoms():
        if not list(atom.GetBonds()):
            warnings.warn(
                "Yikes!!! An atom that is not bonded to any other atom in the fragment. "
                "You probably ran into a bug. Please report the input molecule to the issue tracker"
            )
    # Perceive stereo and check that defined stereo did not change
    oechem.OEPerceiveChiral(fragment_oemol)
    oechem.OE3DToAtomStereo(fragment_oemol)
    oechem.OE3DToBondStereo(fragment_oemol)

    return fragment_oemol
示例#3
0
文件: openeye.py 项目: LaYeqa/perses
def has_undefined_stereocenters(mol):
    """
    Check that _if_ a molecule has a stereocenter,
    the stereochemistry is defined
    if no stereocenter then will return False too

    Parameters
    ----------
    molecule : openeye.oechem.OEMol object
        oemol object to check

    Returns
    -------
    bool : True if undefined Stereochemistry
           False if no stereochemistry or all stereocenter's are labelled
    """

    assert oechem.OEPerceiveChiral(mol), f"chirality perception failed"

    for atom in mol.GetAtoms():
        if atom.IsChiral():
            if not atom.HasStereoSpecified():
                return True  # we have a stereocenter with no stereochemistry!
    for bond in mol.GetBonds():
        if bond.IsChiral():
            if not bond.HasStereoSpecified():
                return True  # we have a geometric isomer that isn't specified!
    return False  # nothing bad found
示例#4
0
def file_to_oemols(filename):
    """Create OEMol from file. If more than one mol in file, return list of OEMols.

    Parameters
    ----------
    filename: str
        absolute path to oeb file

    Returns
    -------
    mollist: list
        list of OEMol for multiple molecules. OEMol if file only has one molecule.
    """
    from openeye import oechem

    if not os.path.exists(filename):
        raise Exception("File {} not found".format(filename))

    ifs = oechem.oemolistream(filename)
    mollist = []

    molecule = oechem.OEMol()
    while oechem.OEReadMolecule(ifs, molecule):
        molecule_copy = oechem.OEMol(molecule)
        oechem.OEPerceiveChiral(molecule_copy)
        oechem.OE3DToAtomStereo(molecule_copy)
        oechem.OE3DToBondStereo(molecule_copy)
        mollist.append(molecule_copy)
    ifs.close()

    if len(mollist) is 1:
        mollist = mollist[0]
    return mollist
示例#5
0
def mol_from_json(symbols, connectivity, geometry, permute_xyz=False):
    """
    Generate OEMol from QCSchema molecule specs
    Parameters
    ----------
    inp_molecule: dict
        Must have symbols and connectivity and/or geometry
        Note: If geometry is given, the molecule will have a tag indicating that the goemetry came from QCSchema. This
        will ensure that the order of the atoms and configuration is not change for generation of mapped SMILES and
        isomeric SMILES.

    Returns
    -------
    molecule: OEMol

    """

    molecule = oechem.OEMol()
    for s in symbols:
        molecule.NewAtom(_symbols[s])

    # Add connectivity
    for bond in connectivity:
        a1 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[0]))
        a2 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[1]))
        bond_order = bond[-1]
        if not isinstance(bond_order, int) and not bond_order.is_integer():
            raise ValueError(
                "bond order must be a whole number. A bond order of 1.5 is not allowed"
            )
        molecule.NewBond(a1, a2, int(bond_order))

    # Add geometry
    if molecule.NumAtoms() != geometry.shape[0] / 3:
        raise ValueError(
            "Number of atoms in molecule does not match length of position array"
        )

    molecule.SetCoords(oechem.OEFloatArray(geometry))
    molecule.SetDimension(3)

    if not permute_xyz:
        # Add tag that the geometry is from JSON and shouldn't be changed.
        geom_tag = oechem.OEGetTag("json_geometry")
        molecule.SetData(geom_tag, True)
    oechem.OEDetermineConnectivity(molecule)
    oechem.OEFindRingAtomsAndBonds(molecule)
    # No need to perceive Bond Order because the information was added from the connectivity table. Apparently, this
    # function does many different perceptions under the hood and it can add implicit hydrogens to divalent N that should be negatively charged.
    #oechem.OEPerceiveBondOrders(molecule)
    # This seems to add hydrogens that are not in the json
    #oechem.OEAssignImplicitHydrogens(molecule)
    oechem.OEAssignFormalCharges(molecule)
    oechem.OEAssignAromaticFlags(molecule)
    oechem.OEPerceiveChiral(molecule)
    oechem.OE3DToAtomStereo(molecule)
    oechem.OE3DToBondStereo(molecule)

    return molecule
示例#6
0
文件: utils.py 项目: oess/oeommtools
def sanitizeOEMolecule(molecule):
    """
    This function checks if the molecule has coordinates,
    explicit hydrogens, aromaticity missing and not unique
    atom names. If the molecule does not have coordinates
    a fatal error is raised. If the molecule does not have
    hydrogens or aramatic flags are missing then a copy of
    the molecule is fixed, if missing or not unique atom
    names are found then a copy of the molecule is fixed

    Parameters:
    -----------
    molecule: OEMol
        The molecule to be checked

    Return:
    -------
    mol_copy: OEMol
        A copy of the checked molecule with fixed aromaticity,
        hydrogens and unique atom names if they are missing
    """
    mol_copy = oechem.OEMol(molecule)

    # Check if the molecule has 3D coordinates
    if not mol_copy.NumAtoms() == 1:  # Mono-atomic molecules are skipped
        if not oechem.OEGetDimensionFromCoords(mol_copy):
            raise ValueError("The molecule coordinates are set to zero")
    # Check if the molecule has hydrogens
    if not oechem.OEHasExplicitHydrogens(mol_copy):
        oechem.OEAddExplicitHydrogens(mol_copy)
    # Check if the molecule has assigned aromaticity
    if not mol_copy.HasPerceived(oechem.OEPerceived_Aromaticity):
        # oechem.OEAssignAromaticFlags(mol_copy, oechem.OEAroModelOpenEye)
        oechem.OEAssignAromaticFlags(mol_copy, oechem.OEAroModelMDL)
    if not mol_copy.HasPerceived(oechem.OEPerceived_Chiral):
        oechem.OEPerceiveChiral(mol_copy)

    # Check for any missing and not unique atom names.
    # If found reassign all of them as Tripos atom names

    atm_list_names = []

    for atom in mol_copy.GetAtoms():
        atm_list_names.append(atom.GetName())

    reassign_names = False

    if len(set(atm_list_names)) != len(atm_list_names):
        reassign_names = True

    if '' in atm_list_names:
        reassign_names = True

    if reassign_names:
        oechem.OETriposAtomNames(mol_copy)

    return mol_copy
示例#7
0
def createOEMolFromSDF(sdf_filename,
                       index=0,
                       add_hydrogens=True,
                       allow_undefined_stereo=False):
    """
    # TODO change this to return a list of all the mols if required
    Load an SDF file into an OEMol. Since SDF files can contain multiple
    molecules, an index can be provided as well.

    Parameters
    ----------
    sdf_filename : str
        The name of the SDF file
    index : int, default 0
        The index of the molecule in the SDF file
    allow_undefined_stereo : bool, default=False
        wether to skip stereo perception

    Returns
    -------
    mol : openeye.oechem.OEMol object
        The loaded oemol object
    """
    # TODO this needs a test
    ifs = oechem.oemolistream()
    ifs.open(sdf_filename)
    # get the list of molecules
    mol_list = [oechem.OEMol(mol) for mol in ifs.GetOEMols()]
    # we'll always take the first for now

    # pick out molecule of interest
    molecule = mol_list[index]

    # Generate unique atom names
    if len([atom.GetName() for atom in molecule.GetAtoms()]) > len(
            set([atom.GetName() for atom in molecule.GetAtoms()])):
        molecule = generate_unique_atom_names(molecule)

    # Assign aromaticity and hydrogens.
    oechem.OEAssignAromaticFlags(molecule, oechem.OEAroModelOpenEye)
    oechem.OEAssignHybridization(molecule)
    if add_hydrogens:
        oechem.OEAddExplicitHydrogens(molecule)
    oechem.OEPerceiveChiral(molecule)

    # perceive chirality
    if not allow_undefined_stereo:
        assert oechem.OE3DToInternalStereo(
            molecule
        ), f"the stereochemistry perception from 3D coordinates failed"
        assert not has_undefined_stereocenters(
            molecule), f"there is an atom with an undefined stereochemistry"

    return molecule
def canonical_order_molecule_inplace(mol_list):
    """rearrange atoms in molecules to canonical ordering
    based on example from Chaya """
    if not mol_list: return
    toolkit = cmiles.utils._set_toolkit(mol_list[0])
    for oe_mol in mol_list:
        toolkit.canonical_order_atoms(oe_mol)
        # make sure stereochemistry is defined
        if not cmiles.utils.has_stereo_defined(oe_mol):
            oechem.OEPerceiveChiral(oe_mol)
            oechem.OE3DToAtomStereo(oe_mol)
            oechem.OE3DToBondStereo(oe_mol)
示例#9
0
def mol_from_json(symbols, connectivity, geometry, permute_xyz=False):
    """
    Generate OEMol from QCSchema molecule specs
    Parameters
    ----------
    inp_molecule: dict
        Must have symbols and connectivity and/or geometry
        Note: If geometry is given, the molecule will have a tag indicating that the goemetry came from QCSchema. This
        will ensure that the order of the atoms and configuration is not change for generation of mapped SMILES and
        isomeric SMILES.

    Returns
    -------
    molecule: OEMol

    """

    molecule = oechem.OEMol()
    for s in symbols:
        molecule.NewAtom(_symbols[s])

    # Add connectivity
    for bond in connectivity:
        a1 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[0]))
        a2 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[1]))
        molecule.NewBond(a1, a2, bond[-1])

    # Add geometry
    if molecule.NumAtoms() != geometry.shape[0] / 3:
        raise ValueError(
            "Number of atoms in molecule does not match length of position array"
        )

    molecule.SetCoords(oechem.OEFloatArray(geometry))
    molecule.SetDimension(3)

    if not permute_xyz:
        # Add tag that the geometry is from JSON and shouldn't be changed.
        geom_tag = oechem.OEGetTag("json_geometry")
        molecule.SetData(geom_tag, True)
    oechem.OEDetermineConnectivity(molecule)
    oechem.OEFindRingAtomsAndBonds(molecule)
    oechem.OEPerceiveBondOrders(molecule)
    # This seems to add hydrogens that are not in the json
    #oechem.OEAssignImplicitHydrogens(molecule)
    oechem.OEAssignFormalCharges(molecule)
    oechem.OEAssignAromaticFlags(molecule)
    oechem.OEPerceiveChiral(molecule)
    oechem.OE3DToAtomStereo(molecule)
    oechem.OE3DToBondStereo(molecule)

    return molecule
示例#10
0
 def chiral_atoms( self ) :
     """
     Returns the indices of chiral atoms.
     @rtype : C{list} of C{int}
     @return: A list of atom indices
     """
     ret = []                                          
     ret_atoms = []                                    
     oechem.OEPerceiveChiral(self._struc)              
     for e in self._struc.GetAtoms():                  
         ret_atoms.append(e)                           
     for atom in ret_atoms:                            
         if atom.IsChiral():                           
             oe_idx = atom.GetIdx() + 1                
             ret.append( oe_idx )                      
     return ret
示例#11
0
def PrepareDepiction(mol, clearcoords=False, suppressH=True):

    oechem.OESetDimensionFromCoords(mol)
    oechem.OEPerceiveChiral(mol)

    if mol.GetDimension() != 2 or clearcoords:
        if mol.GetDimension() == 3:
            oechem.OE3DToBondStereo(mol)
            oechem.OE3DToAtomStereo(mol)
        if suppressH:
            oechem.OESuppressHydrogens(mol)
        oechem.OEAddDepictionHydrogens(mol)

        oechem.OEDepictCoordinates(mol)
        oechem.OEMDLPerceiveBondStereo(mol)

    mol.SetDimension(2)
    return True
def main(infile):

    # open multi-molecule, multi-conformer file
    ifs = oechem.oemolistream()
    ifs.SetConfTest(oechem.OEAbsCanonicalConfTest())
    if not ifs.open(infile):
        raise FileNotFoundError(f"Unable to open {infile} for reading")
    mols = ifs.GetOEMols()

    for i, mol in enumerate(mols):

        # perceive stereochemistry for mol
        oechem.OEPerceiveChiral(mol)
        oechem.OEAssignAromaticFlags(mol, oechem.OEAroModel_MDL)

        # assign charges to copy of mol
        # note that chg_mol does NOT have conformers
        try:
            chg_mol = charge_mol(mol)

        except RuntimeError:

            # perceive stereochem
            #find_unspecified_stereochem(mol)
            oechem.OE3DToInternalStereo(mol)

            # reset perceived and call OE3DToBondStereo, since it may be missed
            # by OE3DToInternalStereo if it thinks mol is flat
            mol.ResetPerceived()
            oechem.OE3DToBondStereo(mol)

            try:
                chg_mol = charge_mol(mol)
                print(f'fixed stereo: {mol.GetTitle()}')
            except RuntimeError:
                find_unspecified_stereochem(mol)

                title = mol.GetTitle()
                smilabel = oechem.OEGetSDData(mol, "SMILES QCArchive")
                print(' >>> Charge assignment failed due to unspecified '
                      f'stereochemistry {title} {smilabel}')

                continue
示例#13
0
文件: openeye.py 项目: LaYeqa/perses
def iupac_to_oemol(iupac, title='MOL', max_confs=1):
    """
    Generate an oemol from an IUPAC name
    Parameters
    ----------
    iupac : str
        iupac name of molecule
    title : str, default 'MOL'
        title of OEMol molecule
    max_confs : int, default 1
        maximum number of conformers to generate
    Returns
    -------
    molecule : openeye.oechem.OEMol
        OEMol object of the molecule
    """
    from openeye import oeiupac, oeomega

    # Create molecule
    molecule = oechem.OEMol()
    oeiupac.OEParseIUPACName(molecule, iupac)

    # Set title.
    molecule.SetTitle(title)

    # Assign aromaticity and hydrogens.
    oechem.OEAssignAromaticFlags(molecule, oechem.OEAroModelOpenEye)
    oechem.OEAssignHybridization(molecule)
    oechem.OEAddExplicitHydrogens(molecule)
    oechem.OEPerceiveChiral(molecule)

    # Create atom names.
    oechem.OETriposAtomNames(molecule)
    oechem.OETriposBondTypeNames(molecule)

    # Assign geometry
    omega = oeomega.OEOmega()
    omega.SetMaxConfs(max_confs)
    omega.SetIncludeInput(False)
    omega.SetStrictStereo(True)
    omega(molecule)
    return molecule
示例#14
0
def frag_to_smiles(frags, mol):
    """
    Convert fragments (AtomBondSet) to canonical isomeric SMILES string
    Parameters
    ----------
    frags: list
    mol: OEMol
    OESMILESFlag: str
        Either 'ISOMERIC' or 'DEFAULT'. This flag determines which OE function to use to generate SMILES string

    Returns
    -------
    smiles: dict of smiles to frag

    """

    smiles = {}
    for frag in frags:
        fragatompred = oechem.OEIsAtomMember(frag.GetAtoms())
        fragbondpred = oechem.OEIsBondMember(frag.GetBonds())

        #fragment = oechem.OEGraphMol()
        fragment = oechem.OEMol()
        adjustHCount = True
        oechem.OESubsetMol(fragment, mol, fragatompred, fragbondpred, adjustHCount)

        oechem.OEPerceiveChiral(fragment)
        # sanity check that all atoms are bonded
        for atom in fragment.GetAtoms():
            if not list(atom.GetBonds()):
                raise Warning("Yikes!!! An atom that is not bonded to any other atom in the fragment. "
                              "You probably ran into a bug. Please report the input molecule to the issue tracker")
        #s = oechem.OEMolToSmiles(fragment)
        #s2 = fragmenter.utils.create_mapped_smiles(fragment, tagged=False, explicit_hydrogen=False)
        s = mol_to_smiles(fragment, mapped=False, explicit_hydrogen=True, isomeric=True)

        if s not in smiles:
            smiles[s] = []
        smiles[s].append(frag)

    return smiles
示例#15
0
def main(infile, ffxml):

    # open multi-molecule, multi-conformer file
    ifs = oechem.oemolistream()
    ifs.SetConfTest(oechem.OEAbsCanonicalConfTest())
    if not ifs.open(infile):
        raise FileNotFoundError(f"Unable to open {infile} for reading")
    mols = ifs.GetOEMols()

    for i, mol in enumerate(mols):

        # perceive stereochemistry for mol
        oechem.OEPerceiveChiral(mol)
        oechem.OEAssignAromaticFlags(mol, oechem.OEAroModel_MDL)

        for j, conf in enumerate(mol.GetConfs()):

            # perceive sterochemistry for conf coordinates
            oechem.OE3DToInternalStereo(conf)

            min_ffxml(conf, ffxml)
示例#16
0
def _find_oe_stereocenters(
    molecule: Molecule,
) -> Tuple[List[int], List[Tuple[int, int]]]:
    """A method which returns the of the stereogenic atom and bonds of a molecule
    using the OpenEye toolkit.

    Parameters
    ----------
    molecule
        The molecule whose stereocenters should be returned.

    Notes
    -----
    * This method currently deals with OE directly and should be removed once
      the API points suggested in openff-toolkit issue #903 have been added.

    Returns
    -------
        The indices of the stereogenic atoms in the molecule and the indices of the
        atoms involved in stereogenic bonds in the molecule.
    """

    from openeye import oechem

    oe_molecule = molecule.to_openeye()
    oechem.OEPerceiveChiral(oe_molecule)

    stereogenic_atoms = {
        atom.GetIdx() for atom in oe_molecule.GetAtoms() if atom.IsChiral()
    }

    stereogenic_bonds = {
        (bond.GetBgnIdx(), bond.GetEndIdx())
        for bond in oe_molecule.GetBonds()
        if bond.IsChiral()
    }

    return sorted(stereogenic_atoms), sorted(stereogenic_bonds)
示例#17
0
def main(infile, outfile, ffxml, minimizer):

    # open multi-molecule, multi-conformer file
    ifs = oechem.oemolistream()
    ifs.SetConfTest(oechem.OEAbsCanonicalConfTest())
    if not ifs.open(infile):
        raise FileNotFoundError(f"Unable to open {infile} for reading")
    mols = ifs.GetOEMols()

    # open an outstream file
    ofs = oechem.oemolostream()
    if os.path.exists(outfile):
        raise FileExistsError("Output file {} already exists in {}".format(
            outfile, os.getcwd()))
    if not ofs.open(outfile):
        oechem.OEThrow.Fatal("Unable to open %s for writing" % outfile)

    # minimize with openforcefield ffxml file
    for i, mol in enumerate(mols):

        # perceive stereochemistry for mol
        oechem.OEPerceiveChiral(mol)
        oechem.OEAssignAromaticFlags(mol, oechem.OEAroModel_MDL)

        # assign charges to copy of mol
        # note that chg_mol does NOT have conformers
        try:
            chg_mol = charge_mol(mol)

        except RuntimeError:
            # perceive stereochem
            #find_unspecified_stereochem(mol)
            oechem.OE3DToInternalStereo(mol)

            # reset perceived and call OE3DToBondStereo, since it may be missed
            # by OE3DToInternalStereo if it thinks mol is flat
            mol.ResetPerceived()
            oechem.OE3DToBondStereo(mol)

            try:
                chg_mol = charge_mol(mol)
                print(f'fixed stereo: {mol.GetTitle()}')
            except RuntimeError:
                title = mol.GetTitle()
                smilabel = oechem.OEGetSDData(mol, "SMILES QCArchive")
                print(' >>> Charge assignment failed due to unspecified '
                      f'stereochemistry {title} {smilabel}')
                continue

        for j, conf in enumerate(mol.GetConfs()):

            # perceive sterochemistry for conf coordinates
            oechem.OE3DToInternalStereo(conf)

            # assign charges to the conf itself
            chg_conf = charge_conf(chg_mol, conf)

            if minimizer == 'ffxml':
                # minimize with parsley (charges set by ff not used from conf)
                min_ffxml(chg_conf, ofs, ffxml)

            if minimizer == 'mmff94':
                # minimize with mmff94
                min_mmff94x(chg_conf, ofs, mmff94s=False)

            if minimizer == 'mmff94s':
                # minimize with mmff94S
                min_mmff94x(chg_conf, ofs, mmff94s=True)

            if minimizer == 'gaff':
                # minimize with gaff
                min_gaffx(chg_conf, ofs, gaff2=False)

            if minimizer == 'gaff2':
                # minimize with gaff2
                min_gaffx(chg_conf, ofs, gaff2=True)

    ifs.close()
    ofs.close()
示例#18
0
def has_stereo_defined(molecule):
    """
    Check if any stereochemistry in undefined.
    Parameters
    ----------
    molecule: OEMol

    Returns
    -------
    bool: True if all stereo chemistry is defined.
        If any stereochemsitry is undefined, raise and exception.

    """

    # perceive stereochemistry
    oechem.OEPerceiveChiral(molecule)
    oechem.OE3DToAtomStereo(molecule)
    oechem.OE3DToBondStereo(molecule)

    unspec_chiral = False
    unspec_db = False
    problematic_atoms = list()
    problematic_bonds = list()
    for atom in molecule.GetAtoms():
        if atom.IsChiral() and not atom.HasStereoSpecified(
                oechem.OEAtomStereo_Tetrahedral):
            # Check if handness is specified
            v = []
            for nbr in atom.GetAtoms():
                v.append(nbr)
            stereo = atom.GetStereo(v, oechem.OEAtomStereo_Tetrahedral)
            if stereo == oechem.OEAtomStereo_Undefined:
                unspec_chiral = True
                problematic_atoms.append(
                    (atom.GetIdx(),
                     oechem.OEGetAtomicSymbol(atom.GetAtomicNum())))
    for bond in molecule.GetBonds():
        if bond.IsChiral() and not bond.HasStereoSpecified(
                oechem.OEBondStereo_CisTrans):
            v = []
            for neigh in bond.GetBgn().GetAtoms():
                if neigh != bond.GetEnd():
                    v.append(neigh)
                    break
            for neigh in bond.GetEnd().GetAtoms():
                if neigh != bond.GetBgn():
                    v.append(neigh)
                    break
            stereo = bond.GetStereo(v, oechem.OEBondStereo_CisTrans)

            if stereo == oechem.OEBondStereo_Undefined:
                unspec_db = True
                a1 = bond.GetBgn()
                a2 = bond.GetEnd()
                a1_idx = a1.GetIdx()
                a2_idx = a2.GetIdx()
                a1_s = oechem.OEGetAtomicSymbol(a1.GetAtomicNum())
                a2_s = oechem.OEGetAtomicSymbol(a2.GetAtomicNum())
                bond_order = bond.GetOrder()
                problematic_bonds.append(
                    (a1_idx, a1_s, a2_idx, a2_s, bond_order))
    if unspec_chiral or unspec_db:
        warnings.warn(
            "Stereochemistry is unspecified. Problematic atoms {}, problematic bonds {}, SMILES: {}"
            .format(problematic_atoms, problematic_bonds,
                    oechem.OEMolToSmiles(molecule)))
        return False
    else:
        return True
示例#19
0
def create_openeye_molecule(pdb, options, verbose=True):
    """
    Create OpenEye molecule from PDB representation.

    The molecule will have hydrogens added and be normalized, but the overall geometry will not be altered.

    Parameters
    ----------
    pdb : Pdb
       The PDB-extracted entries for the ligand.

    Returns
    -------
    molecule : openeye.oechem.OEMol
        Molecule representation.
    options : options struct
        Options structure.

    """

    # Create a molecule container.
    molecule = oechem.OEGraphMol()

    # Open a PDB file reader from the stored PDB string representation of HETATM and CONECT records.
    print pdb.pdb_extract
    ifs = oechem.oemolistream()
    ifs.openstring(pdb.pdb_extract)
    flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_PDB_Default | oechem.OEIFlavor_PDB_ALL
    ifs.SetFlavor(oechem.OEFormat_PDB, flavor)
    oechem.OEReadPDBFile(ifs, molecule)

    # Add explicit hydrogens.
    oechem.OEDetermineConnectivity(molecule)
    oechem.OEFindRingAtomsAndBonds(molecule)
    oechem.OEAssignAromaticFlags(molecule) # check aromaticity
    oechem.OEPerceiveBondOrders(molecule)

    # We must assign implicit hydrogens first so that the valence model will be correct.
    oechem.OEAssignImplicitHydrogens(molecule)
    oechem.OEAssignFormalCharges(molecule)

    # Now add explicit hydrogens.
    polarOnly = False
    set3D = True
    oechem.OEAddExplicitHydrogens(molecule, polarOnly, set3D)

    # TODO: Sequentially number hydrogen atoms.

    # Perceive stereochemostry.
    oechem.OEPerceiveChiral(molecule)

    # Set title.
    molecule.SetTitle(options.ligand)

    # Write out PDB form of this molecule.
    # TODO: Fix atom numbering.
    #if verbose: print "Writing input molecule as PDB..."
    #outmol = oechem.OEMol(molecule)
    #ofs = oechem.oemolostream()
    #flavor = oechem.OEOFlavor_Generic_Default | oechem.OEOFlavor_PDB_Default
    #ofs.SetFlavor(oechem.OEFormat_PDB, flavor)
    #ofs.open(options.ligand + '.pdb')
    #oechem.OEWriteMolecule(ofs, outmol)
    #ofs.close()

    # Write mol2 file for this molecule.
    if verbose: print "Writing input molecule as mol2..."
    outmol = oechem.OEMol(molecule)
    ofs = oechem.oemolostream()
    filename = options.ligand + '.mol2'
    ofs.open(filename)
    oechem.OEWriteMolecule(ofs, outmol)
    ofs.close()
    # Use low level writer to get atom names correct.
    ofs = oechem.oemolostream()
    ofs.open(filename)
    for (dest_atom, src_atom) in zip(outmol.GetAtoms(), molecule.GetAtoms()):
        dest_atom.SetName(src_atom.GetName())
    oechem.OEWriteMol2File(ofs, outmol, True)
    ofs.close()
    # Read and write in PDB format.
    if verbose: print "Converting mol2 to PDB..."
    ifs = oechem.oemolistream()
    ofs = oechem.oemolostream()
    if ifs.open(options.ligand + '.mol2'):
        if ofs.open(options.ligand + '.pdb'):
            for mol in ifs.GetOEGraphMols():
                oechem.OEWriteMolecule(ofs, mol)

    return molecule
示例#20
0
def _extract_oe_fragment(
    molecule: Molecule, atom_indices: Set[int], bond_indices: Set[Tuple[int, int]]
) -> Molecule:

    from openeye import oechem

    oe_molecule = molecule.to_openeye()

    # Restore the map indices as to_openeye does not automatically add them.
    for atom_index, map_index in molecule.properties["atom_map"].items():

        oe_atom = oe_molecule.GetAtom(oechem.OEHasAtomIdx(atom_index))
        oe_atom.SetMapIdx(map_index)

    # Include any Hs bonded to the included atom set so we can retain their map
    # indices.
    for map_index in {*atom_indices}:

        oe_atom = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index))

        for neighbour in oe_atom.GetAtoms():

            if (
                neighbour.GetAtomicNum() != 1
                or neighbour.GetMapIdx() < 1
                or neighbour.GetMapIdx() in atom_indices
            ):
                continue

            atom_indices.add(neighbour.GetMapIdx())
            bond_indices.add((map_index, neighbour.GetMapIdx()))

    atom_bond_set = oechem.OEAtomBondSet()

    for map_index in atom_indices:
        atom = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index))
        atom_bond_set.AddAtom(atom)

    for map_index_1, map_index_2 in bond_indices:

        atom_1 = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index_1))
        atom_2 = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index_2))

        bond = oe_molecule.GetBond(atom_1, atom_2)

        if not bond:
            raise ValueError(f"{(map_index_1, map_index_2)} is a disconnected bond")

        atom_bond_set.AddBond(bond)

    atom_predicate = oechem.OEIsAtomMember(atom_bond_set.GetAtoms())
    bond_predicate = oechem.OEIsBondMember(atom_bond_set.GetBonds())

    fragment = oechem.OEMol()
    oechem.OESubsetMol(fragment, oe_molecule, atom_predicate, bond_predicate, True)

    oechem.OEAddExplicitHydrogens(fragment)
    oechem.OEPerceiveChiral(fragment)

    # Always restore map?
    # if restore_maps:
    # In some cases (symmetric molecules) this changes the atom map so skip it
    # restore_atom_map(fragment)
    # atom map should be restored for combinatorial fragmentation
    # Perceive stereo and check that defined stereo did not change
    oechem.OEPerceiveChiral(fragment)
    oechem.OE3DToAtomStereo(fragment)
    oechem.OE3DToBondStereo(fragment)

    return Molecule.from_openeye(fragment, allow_undefined_stereo=True)
示例#21
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]