Source code for pdb2pqr.residue

"""Biomolecular residue class.

.. codeauthor:: Todd Dolinsky
.. codeauthor:: Nathan Baker
"""
import logging
from . import pdb
from . import structures
from . import utilities as util
from . import quatfit as quat


_LOGGER = logging.getLogger(__name__)


[docs] class Residue(object): """Residue class .. todo:: Should this class have a member variable for dihedrals? Pylint complains! The residue class contains a list of Atom objects associated with that residue and other helper functions. """
[docs] def __init__(self, atoms): """Initialize the class :param atoms: list of atom-like (:class:`HETATM` or :class:`ATOM`) objects to be stored :type atoms: list """ sample_atom = atoms[-1] self.atoms = [] self.name = sample_atom.res_name self.chain_id = sample_atom.chain_id self.res_seq = sample_atom.res_seq self.ins_code = sample_atom.ins_code self.map = {} self.naname = None self.reference = None self.is_n_term = None self.is_c_term = None self.dihedrals = [] atomclass = "" for atom in atoms: if isinstance(atom, pdb.ATOM): atomclass = "ATOM" elif isinstance(atom, pdb.HETATM): atomclass = "HETATM" atom = structures.Atom(atom, atomclass, self) atomname = atom.name if atomname not in self.map: self.add_atom(atom) else: # Don't add duplicate atom oldatom = self.get_atom(atomname) oldatom.alt_loc = "" if self.name == "HOH": self.name = "WAT" for atom in self.atoms: atom.res_name = "WAT"
def __str__(self): return f"{self.name} {self.chain_id} {self.res_seq}{self.ins_code}"
[docs] def get_moveable_names(self, pivot): """Return all atom names that are further away than the pivot atom. :param residue: the residue to use :type residue: Residue :param pivot: the pivot atomname :type pivot: str :return: names of atoms further away than pivot atom :rtype: [str] """ refdist = self.get_atom(pivot).refdistance return [atom.name for atom in self.atoms if atom.refdistance > refdist]
[docs] def update_terminus_status(self): """Update the :makevar:`is_n_terms` and :makevar:`is_c_term` flags.""" # If Nterm then update counter of hydrogens if self.is_n_term: count = 0 atoms = ["H", "H2", "H3"] for atom in atoms: for atom2 in self.atoms: atomname = atom2.name if atom == atomname: count += 1 self.is_n_term = count # If Cterm then update counter if self.is_c_term: self.is_c_term = None for atom in self.atoms: atomname = atom.name if atomname == "HO": self.is_c_term = 2 break if not self.is_c_term: self.is_c_term = 1
[docs] def set_res_seq(self, value): """Change the residue sequence number. Set the atom field :makevar:`res_seq` and change the residue's information. The :makevar:`icode` field is no longer useful. :param value: the new value of :makevar:`res_seq` :type value: int """ self.ins_code = "" self.res_seq = value for atom in self.atoms: atom.res_seq = value
[docs] def set_chain_id(self, value): """Set the chain ID. :param value: new :makevar:`chain_id` value :type value: str """ self.chain_id = value for atom in self.atoms: atom.chain_id = value
[docs] def add_atom(self, atom): """Add the atom object to the residue. :param atom: atom-like object, e.g., :class:`HETATM` or :class:`ATOM` """ self.atoms.append(atom) self.map[atom.name] = atom
[docs] def remove_atom(self, atomname): """Remove an atom from the residue object. :param atomname: the name of the atom to be removed :type atomname: str """ # Delete the atom from the map atom = self.map[atomname] bonds = atom.bonds del self.map[atomname] # Delete the atom from the list self.atoms.remove(atom) # Delete all instances of the atom as a bond for bondatom in bonds: if atom in bondatom.bonds: bondatom.bonds.remove(atom) del atom
[docs] def rename_atom(self, oldname, newname): """Rename an atom to a new name. :param oldname: old atom name :type oldname: str :param newname: new atom name :type newname: str """ atom = self.map[oldname] atom.name = newname self.map[newname] = atom del self.map[oldname]
[docs] def get_atom(self, name): """Retrieve a residue atom based on its name. :param resname: name of the residue to retrieve :type resname: str :return: residue :rtype: structures.Atom """ return self.map.get(name)
[docs] def has_atom(self, name): """Return True if atom in residue. :param name: atom name in question :type name: str :return: True if atom in residue :rtype: bool """ return name in self.map
@property def charge(self): """Get the total charge of the residue. In order to get rid of floating point rounding error, do a string transformation. Returns: charge: The charge of the residue (float) """ charge = (atom.ffcharge for atom in self.atoms if atom.ffcharge) charge = sum(charge) charge = float(f"{charge:.4f}") return charge
[docs] def rename_residue(self, name): """Rename the residue. :param name: the new name of the residue :type name: str """ self.name = name for atom in self.atoms: atom.res_name = name
[docs] @classmethod def rotate_tetrahedral(cls, atom1, atom2, angle): """Rotate about the atom1-atom2 bond by a given angle. All atoms connected to atom2 will rotate. :param atom1: first atom of the bond to rotate about :type atom1: structures.Atom :param atom2: second atom of the bond to rotate about :type atom2: structures.Atom :param angle: degrees to rotate :type angle: float """ moveatoms = [] movecoords = [] initcoords = util.subtract(atom2.coords, atom1.coords) # Determine which atoms to rotate for atom in atom2.bonds: if atom == atom1: continue moveatoms.append(atom) movecoords.append(util.subtract(atom.coords, atom1.coords)) newcoords = quat.qchichange(initcoords, movecoords, angle) for iatom, atom in enumerate(moveatoms): atom.x = newcoords[iatom][0] + atom1.x atom.y = newcoords[iatom][1] + atom1.y atom.z = newcoords[iatom][2] + atom1.z
[docs] def pick_dihedral_angle(self, conflict_names, oldnum=None): """Choose an angle number to use in debumping. Instead of simply picking a random chiangle, this function uses a more intelligent method to improve efficiency. The algorithm uses the names of the conflicting atoms within the residue to determine which angle number has the best chance of fixing the problem(s). The method also insures that the same chiangle will not be run twice in a row. :param residue: residue that is being debumped :type residue: Residue :param conflict_names: list of atom names that are currently conflicts :type conflict_names: [str] :param oldnum: old dihedral angle number :type oldnum: int :return: new dihedral angle number :rtype: int """ bestnum = -1 best = 0 ilist = list(range(len(self.dihedrals))) if oldnum is not None and oldnum >= 0 and ilist: del ilist[oldnum] test_dihedral_indices = ilist[oldnum:] + ilist[:oldnum] else: test_dihedral_indices = ilist for i in test_dihedral_indices: if i == oldnum: continue if self.dihedrals[i] is None: continue score = 0 atomnames = self.reference.dihedrals[i].split() pivot = atomnames[2] moveablenames = self.get_moveable_names(pivot) if conflict_names == moveablenames: return i for name in conflict_names: if name in moveablenames: score += 1 if score > best: best = score bestnum = i return bestnum
[docs] def set_donors_acceptors(self): """Set the donors and acceptors within the residue.""" if self.reference is None: return for atom in self.atoms: atomname = atom.name atom.hdonor = False atom.hacceptor = False if atomname.startswith("N"): bonded = 0 for bondedatom in atom.bonds: if bondedatom.is_hydrogen: atom.hdonor = True bonded = 1 break if not bonded and self.reference.name == "HIS": atom.hacceptor = True elif atomname.startswith("O") or ( atomname.startswith("S") and self.reference.name == "CYS" ): atom.hacceptor = True for bondedatom in atom.bonds: if bondedatom.is_hydrogen: atom.hdonor = True break
[docs] def reorder(self): """Reorder the atoms to start with N, CA, C, O if they exist.""" templist = [] if self.has_atom("N"): templist.append(self.get_atom("N")) if self.has_atom("CA"): templist.append(self.get_atom("CA")) if self.has_atom("C"): templist.append(self.get_atom("C")) if self.has_atom("O"): templist.append(self.get_atom("O")) # Add remaining atoms for atom in self.atoms: if atom.name not in ["N", "CA", "C", "O"]: templist.append(atom) # Change the list pointer self.atoms = templist[:]
[docs] @staticmethod def letter_code(): """Default letter code for residue. :return: letter code for residue :rtype: str """ return "X"