Source code for schrodinger.protein.rotamers

"""
A rotamer library which can be applied to protein sidechains.

A Rotamers object is initialized with an atom from a Structure object.
Exceptions will be generated if there is no rotamer information for the
residue correspoding to that atom or if the residue is incomplete.
Once the Rotamers object is created it's possible to iterate through the
rotamers, to apply any of them or to restore the original geometry.

Copyright Schrodinger LLC. All rights reserved.

"""

# $Revision: 1.7 $
# $Date: 2008/12/11 16:03:58 $

#Contributors: Quentin McDonald

from schrodinger.infra import mm
from schrodinger.infra import mminit


class _Rotamer(object):
    """
    A single instance of a rotamer - represents a set of CHI angle
    values
    """

    def __init__(self, rl, idx):
        """
        Initialize the rotamer object with the Rotamer library and
        an the index of which rotamer this is:
        """
        self._rotamer_lib = rl
        self._index = idx

    def __index__(self):
        """
        Return the index - starting from 1 as the user sees them
        """
        return (self._index + 1)

    def apply(self):
        """
        Apply this rotamer to the structure - the sidechain angles will be
        set to the CHI values from the rotamer library
        """

        self._rotamer_lib._apply(self._index)

    def percentage(self):
        """
        Return the percentage for this rotamer
        """
        return mm.mmrotamer_get_population(self._rotamer_lib.resname,
                                           self._index)

    def chiAngles(self):
        """
        Return a list of the Chi angles for this rotamer.
        """
        ret_list = []

        for ichi in range(
                mm.mmrotamer_get_num_chi_angles(self._rotamer_lib.resname)):

            # SHARED-6000: see comment in Rotamers._apply
            try:
                angle = mm.mmrotamer_get_rotamer_chi(self._rotamer_lib.resname,
                                                     self._index, ichi)
            except mm.MmException as e:
                if e.rc != mm.MMROTAMER_INVALID_CHI_ANGLE:
                    raise
            else:
                ret_list.append(angle)

        return ret_list


class _RotamerIterator(object):
    """
    Container for the rotamers which are associated with a particular
    residue type
    """

    def __init__(self, rl):
        """
        Initialize the iterator with the rotamer library
        """
        self._rotamer_lib = rl

    def __len__(self):
        """
        Return the number of rotamers which are available:
        """
        return self._rotamer_lib.num_rotamers

    def __getitem__(self, rotamer_num):
        """
        Return the rotamer which is associated with rotamer_num. Index
        begins at 1
        """
        if (rotamer_num < 1 or rotamer_num > self._rotamer_lib.num_rotamers):
            raise KeyError("Rotamer key out of range (starts at 1): %d" %
                           rotamer_num)
        return _Rotamer(self._rotamer_lib, rotamer_num - 1)

    def __iter__(self):
        """
        Iterate through the available rotamers for the structure
        """
        for rot_num in range(self._rotamer_lib.num_rotamers):
            yield _Rotamer(self._rotamer_lib, rot_num)

        # Initialize the mmrotamer library via an Initializer object. This is to avoid
        # the use of a __del__ method in the Rotamers class, since there exists a
        # reference cycle in this code. The cycle is
        #   Rotamers -> _RotamerIterator -> _Rotamer -> Rotamers


_mmrotamer_initializer = mminit.Initializer([mm.mmrotamer_initialize],
                                            [mm.mmrotamer_terminate])


[docs]class Rotamers(object): """ Manages a rotamer library for a given residue type. """
[docs] def __init__(self, st, atom): """ Initialize the Rotamer Library with respect to a given atom in the input structure 'st'. """ # Start by setting up some basic information about this residue self.resname = st.atom[int(atom)].pdbres try: self.num_chi_angles = mm.mmrotamer_get_num_chi_angles(self.resname) except mm.MmException as e: if e.rc == mm.MMROTAMER_RES_NOT_FOUND: raise RuntimeError( "There is no rotamer information for residue %s" % self.resname) try: self.num_rotamers = mm.mmrotamer_get_num_rotamers(self.resname) except mm.MmException as e: if e.rc == mm.MMROTAMER_RES_NOT_FOUND: raise RuntimeError( "There is no rotamer information for residue %s" % self.resname) self._st = st res = st.atom[int(atom)].getResidue() res_atoms = res.atom # Locate the atoms in the residue self.chi_atoms = [] for i in range(self.num_chi_angles): dihedral_atoms = self.findChiAtoms(res, i) self.chi_atoms.append(dihedral_atoms) # Make a list of original coordinates for each atom in this residue: self.chi_coords = {} for resatom in res.atom: self.chi_coords[resatom.index] = resatom.xyz # Initialize the rotamer iterator self._rotamers = _RotamerIterator(self)
[docs] def findChiAtoms(self, res, chi_index): """ Return a list of atom numbers for the given Chi dihedral of the residue. """ at_names = mm.mmrotamer_get_chi_def(self.resname, chi_index) dihedral_anums = [] for i, pdbname in enumerate(at_names): anum = None matched_atom = res.getAtomByPdbName(pdbname) if matched_atom: anum = matched_atom.index elif i == 3 and 'H' in pdbname: # The rotamer library uses hard-coded names for all atoms # including hydrogens (See chidef.res of the mmrotamer # library). For Prime/IFD we don't require hydrogens to # have these exact names, so this code will detect the # hydrogen by the name of the heavy atom that the hydrogen # is bonded to. # Hydrogen is always the last atom in the list returned by # mmrotamer library (for bonds that contain hydrogens). heavy_atom = dihedral_anums[2] for neighbor in self._st.atom[heavy_atom].bonded_atoms: if neighbor.element == 'H': anum = neighbor.index break if anum is None: # Didn't find one of the sidechain atom so raise exception raise RuntimeError('Could not find atom "%s" of %s for chi%d' % (pdbname, res, chi_index + 1)) dihedral_anums.append(anum) return dihedral_anums
def _apply(self, idx): """ A private function which is used to apply the specified rotamer index (idx) to the structure """ for chi in range(self.num_chi_angles): state_angles = self.chi_atoms[chi] try: angle = mm.mmrotamer_get_rotamer_chi(self.resname, idx, chi) except mm.MmException as e: # Related to EV 74929: Ignore any CHI angles for which # we don't actually have rotamer data, O-H or SER for example: if e.rc != mm.MMROTAMER_INVALID_CHI_ANGLE: raise self._st.adjust(angle, state_angles[0], state_angles[1], state_angles[2], state_angles[3])
[docs] def restore(self): """ Restore the sidechain back to the original coordinates """ for anum, xyz in self.chi_coords.items(): self._st.atom[anum].xyz = xyz
def _getRotamerIterator(self): return self._rotamers _doc = """ A list of all the rotamers available for the current residue. """ rotamers = property(_getRotamerIterator, doc=_doc)