Source code for luna.mol.atom

import numpy as np
from openbabel import openbabel as ob

import logging
logger = logging.getLogger()


[docs]class AtomData: """Store atomic data (atomic number, coordinates, bond type, and serial number). Parameters ---------- atomic_num : int Atomic number. coord : array_like of float (size 3) Atomic coordinates (x, y, z). bond_type : int Bond type. serial_number : int, optional Atom serial number. Attributes ---------- atomic_num : int The atomic number. bond_type : int The bond type. serial_number : int or None The atom serial number. """ def __init__(self, atomic_num, coord, bond_type, serial_number=None): self.atomic_num = atomic_num # Standardize all coordinate data to the same Numpy data type for consistence. self._coord = np.array(coord, "f") self.bond_type = bond_type self.serial_number = serial_number @property def x(self): """float: The orthogonal coordinates for ``x`` in Angstroms.""" return self._coord[0] @property def y(self): """float: The orthogonal coordinates for ``y`` in Angstroms.""" return self._coord[1] @property def z(self): """float: The orthogonal coordinates for ``z`` in Angstroms.""" return self._coord[2] @property def coord(self): """array_like of float (size 3): The atomic coordinates (x, y, z).""" return self._coord @coord.setter def coord(self, xyz): # Standardize all coordinate data to the same Numpy data type for consistence. self._coord = np.array(xyz, "f") def __repr__(self): return ("<ExtendedAtomData: atomic number=%d, coord=(%.3f, %.3f, %.3f), serial number=%s>" % (self.atomic_num, self.x, self.y, self.z, str(self.serial_number))) def __eq__(self, other): """Overrides the default implementation""" if isinstance(self, other.__class__): return (self.atomic_num == other.atomic_num and np.all(self._coord == other._coord) and self.serial_number == other.serial_number) return False def __ne__(self, other): """Overrides the default implementation""" return not self.__eq__(other) def __hash__(self): """Overrides the default implementation""" return hash((self.atomic_num, tuple(self._coord), self.serial_number))
[docs]class ExtendedAtom: """Extend :class:`~luna.MyBio.PDB.Atom.Atom` with additional properties and methods. Parameters ---------- atom : :class:`~luna.MyBio.PDB.Atom.Atom` An atom. nb_info : iterable of `AtomData`, optional A sequence of `AtomData` containing information about atoms covalently bound to ``atom``. atm_grps : iterable of :class:`~luna.groups.AtomGroup`, optional A sequence of atom groups that contain ``atom``. invariants : list or tuple, optional Atomic invariants. """ def __init__(self, atom, nb_info=None, atm_grps=None, invariants=None): self._atom = atom self._nb_info = nb_info or [] self._atm_grps = atm_grps or [] self._invariants = invariants @property def atom(self): """:class:`~luna.MyBio.PDB.Atom.Atom`, read-only.""" return self._atom @property def neighbors_info(self): """list of `AtomData`, read-only: The list of `AtomData` containing information about atoms covalently bound to ``atom``. To add or remove neighbors information from ``neighbors_info`` use :py:meth:`add_nb_info` or :py:meth:`remove_nb_info`, respectively.""" return self._nb_info @property def atm_grps(self): """list of :class:`~luna.groups.AtomGroup`, read-only: The list of atom groups that contain ``atom``. To add or remove atom groups from ``atm_grps`` use :py:meth:`add_atm_grps` or :py:meth:`remove_atm_grps`, respectively.""" return self._atm_grps @property def invariants(self): """list: The list of atomic invariants.""" return self._invariants @invariants.setter def invariants(self, invariants): self._invariants = invariants @property def electronegativity(self): """float, read-only: The Pauling electronegativity for this atom. This information is obtained from Open Babel.""" return ob.GetElectroNeg(ob.GetAtomicNum(self.element)) @property def full_id(self): """tuple, read-only: The full id of an atom is the tuple (structure id, model id, chain id, residue id, atom name, alternate location).""" return self._atom.get_full_id() @property def full_atom_name(self): """str, read-only: The full name of an atom is composed by the structure id, model id, chain id, residue name, residue id, atom name, and alternate location if available. Fields are slash-separated.""" full_atom_name = "%s/%s/%s" % self.get_full_id()[0:3] res_name = "%s/%d%s" % (self._atom.parent.resname, self._atom.parent.id[1], self._atom.parent.id[2].strip()) atom_name = "%s" % self._atom.name if self.altloc != " ": atom_name += "-%s" % self.altloc full_atom_name += "/%s/%s" % (res_name, atom_name) return full_atom_name
[docs] def add_nb_info(self, nb_info): """ Add `AtomData` objects to ``neighbors_info``.""" self._nb_info = list(set(self._nb_info + list(nb_info)))
[docs] def add_atm_grps(self, atm_grps): """ Add :class:`~luna.groups.AtomGroup` objects to ``atm_grps``.""" self._atm_grps = list(set(self._atm_grps + list(atm_grps)))
[docs] def remove_nb_info(self, nb_info): """ Remove `AtomData` objects from ``neighbors_info``.""" self._nb_info = list(set(self._nb_info) - set(nb_info))
[docs] def remove_atm_grps(self, atm_grps): """ Remove :class:`~luna.groups.AtomGroup` objects from ``atm_grps``.""" self._atm_grps = list(set(self._atm_grps) - set(atm_grps))
[docs] def get_neighbor_info(self, atom): """Get information from a covalently bound atom.""" for info in self._nb_info: if atom.serial_number == info.serial_number: return info
[docs] def is_neighbor(self, atom): """Check if a given atom is covalently bound to it.""" return atom.serial_number in [i.serial_number for i in self._nb_info]
[docs] def as_json(self): """Represent the atom as a dict containing the structure id, model id, chain id, residue name, residue id, and atom name. The dict is defined as follows: * ``pdb_id`` (str): structure id; * ``model`` (str): model id; * ``chain`` (str): chain id; * ``res_name`` (str): residue name; * ``res_id`` (tuple): residue id (hetflag, sequence identifier, insertion code); * ``name`` (tuple): atom name (atom name, alternate location). """ full_id = self.get_full_id() return {"pdb_id": full_id[0], "model": full_id[1], "chain": full_id[2], "res_name": self.parent.resname, "res_id": full_id[3], "name": full_id[4]}
def __getattr__(self, attr): if hasattr(self._atom, attr): return getattr(self._atom, attr) else: raise AttributeError("The attribute '%s' does not exist in the class %s." % (attr, self.__class__.__name__)) def __getstate__(self): return self.__dict__ def __setstate__(self, state): self.__dict__.update(state) def __repr__(self): return "<ExtendedAtom: %s>" % self.full_atom_name def __sub__(self, other): # It calls __sub__() from Biopython. return self._atom - other._atom def __eq__(self, other): """Overrides the default implementation""" if isinstance(self, other.__class__): return self.full_atom_name == other.full_atom_name return False def __ne__(self, other): """Overrides the default implementation""" return not self.__eq__(other) def __lt__(self, a2): # It substitutes the residue id for its index in order to keep the same order as in the PDB. full_id1 = self.full_id[0:2] + (self.parent.idx, ) + self.full_id[4:] full_id2 = a2.full_id[0:2] + (a2.parent.idx, ) + a2.full_id[4:] return full_id1 < full_id2 def __hash__(self): """Overrides the default implementation""" return hash(self.full_atom_name)