1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
# Copyright (C) 2002, Thomas Hamelryck (thamelry@binf.ku.dk)
#
# This file is part of the Biopython distribution and governed by your
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License".
# Please see the LICENSE file that should have been included as part of this
# package.
"""Residue class, used by Structure objects."""
from typing import TYPE_CHECKING
from typing import TypeVar
from Bio.PDB.Entity import DisorderedEntityWrapper
from Bio.PDB.Entity import Entity
from Bio.PDB.PDBExceptions import PDBConstructionException
if TYPE_CHECKING:
from Bio.PDB.Atom import Atom
from Bio.PDB.Chain import Chain
_ResidueT = TypeVar("_ResidueT", bound="Residue")
_atom_name_dict = {}
_atom_name_dict["N"] = 1
_atom_name_dict["CA"] = 2
_atom_name_dict["C"] = 3
_atom_name_dict["O"] = 4
class Residue(Entity["Chain", "Atom"]):
"""Represents a residue. A Residue object stores atoms."""
def __init__(self, id, resname, segid):
"""Initialize the class."""
self.level = "R"
self.disordered = 0
self.resname = resname
self.segid = segid
self.internal_coord = None
Entity.__init__(self, id)
def __repr__(self):
"""Return the residue full id."""
resname = self.get_resname()
hetflag, resseq, icode = self.get_id()
full_id = (resname, hetflag, resseq, icode)
return "<Residue %s het=%s resseq=%s icode=%s>" % full_id
def strictly_equals(
self: _ResidueT, other: _ResidueT, compare_coordinates: bool = False
) -> bool:
"""Compare this residue to the other residue using a strict definition of equality.
The residues are equal if they have the same name, identifier,
and their constituent atoms are strictly equal.
:param other: The residue to compare this residue to
:type other: Residue
:param compare_coordinates: Whether to compare the coordinates of the atoms
:type compare_coordinates: bool
:return: Whether the residues are strictly equal
:rtype: bool
"""
if not isinstance(other, type(self)):
return False
return (
self.resname == other.resname
and self.id == other.id
and Entity.strictly_equals(self, other, compare_coordinates)
)
def add(self, atom):
"""Add an Atom object.
Checks for adding duplicate atoms, and raises a
PDBConstructionException if so.
"""
atom_id = atom.get_id()
if self.has_id(atom_id):
raise PDBConstructionException(
f"Atom {atom_id} defined twice in residue {self}"
)
Entity.add(self, atom)
def flag_disordered(self):
"""Set the disordered flag."""
self.disordered = 1
def is_disordered(self):
"""Return 1 if the residue contains disordered atoms."""
return self.disordered
def get_resname(self):
"""Return the residue name."""
return self.resname
def get_unpacked_list(self):
"""Return the list of all atoms, unpack DisorderedAtoms."""
atom_list = self.get_list()
undisordered_atom_list = []
for atom in atom_list:
if atom.is_disordered():
undisordered_atom_list += atom.disordered_get_list()
else:
undisordered_atom_list.append(atom)
return undisordered_atom_list
def get_segid(self):
"""Return the segment identifier."""
return self.segid
def get_atoms(self):
"""Return atoms."""
yield from self
class DisorderedResidue(DisorderedEntityWrapper):
"""DisorderedResidue is a wrapper around two or more Residue objects.
It is used to represent point mutations (e.g. there is a Ser 60 and a Cys 60
residue, each with 50 % occupancy).
"""
def __init__(self, id):
"""Initialize the class."""
DisorderedEntityWrapper.__init__(self, id)
def __repr__(self):
"""Return disordered residue full identifier."""
if self.child_dict:
resname = self.get_resname()
hetflag, resseq, icode = self.get_id()
full_id = (resname, hetflag, resseq, icode)
return "<DisorderedResidue %s het=%s resseq=%i icode=%s>" % full_id
else:
return "<Empty DisorderedResidue>"
def add(self, atom):
"""Add atom to residue."""
residue = self.disordered_get()
if atom.is_disordered() != 2:
# Atoms in disordered residues should have non-blank
# altlocs, and are thus represented by DisorderedAtom objects.
resname = residue.get_resname()
het, resseq, icode = residue.get_id()
# add atom anyway, if PDBParser ignores exception the atom will be part of the residue
residue.add(atom)
raise PDBConstructionException(
"Blank altlocs in duplicate residue %s (%s, %i, %s)"
% (resname, het, resseq, icode)
)
residue.add(atom)
def sort(self):
"""Sort the atoms in the child Residue objects."""
for residue in self.disordered_get_list():
residue.sort()
def disordered_add(self, residue):
"""Add a residue object and use its resname as key.
Arguments:
- residue - Residue object
"""
resname = residue.get_resname()
# add chain parent to residue
chain = self.get_parent()
residue.set_parent(chain)
assert not self.disordered_has_id(resname)
self[resname] = residue
self.disordered_select(resname)
def disordered_remove(self, resname):
"""Remove a child residue from the DisorderedResidue.
Arguments:
- resname - name of the child residue to remove, as a string.
"""
# Get child residue
residue = self.child_dict[resname]
is_selected = self.selected_child is residue
# Detach
del self.child_dict[resname]
residue.detach_parent()
if is_selected and self.child_dict: # pick another selected_child
child = next(iter(self.child_dict))
self.disordered_select(child)
elif not self.child_dict: # no more children
self.selected_child = None
|