Source code for luna.interaction.contact
from itertools import product
from luna.util.default_values import COV_SEARCH_RADIUS, BOUNDARY_CONFIG
from luna.util.exceptions import EntityLevelError, IllegalArgumentError
from luna.MyBio.util import ENTITY_LEVEL_NAME, is_covalently_bound
from luna.MyBio.PDB.NeighborSearch import NeighborSearch
from luna.MyBio.PDB import Selection
from luna.MyBio.PDB.Residue import Residue
import logging
logger = logging.getLogger()
[docs]def get_all_contacts(entity, radius=BOUNDARY_CONFIG["boundary_cutoff"], level='A'):
"""Recover all residue-residue or atom-atom contacts in ``entity``.
Parameters
----------
entity : :class:`~luna.MyBio.PDB.Entity.Entity`
The PDB object from where atoms and residues will be recovered.
radius : float
The cutoff distance (in Å) for defining contacts. The default value is 6.2.
level : {'R', 'A'}
Return residues ('R') or atoms ('A') in contact with ``source``.
Returns
-------
: list of tuple of (:class:`~luna.MyBio.PDB.Residue.Residue` or :class:`~luna.MyBio.PDB.Atom.Atom`, \
:class:`~luna.MyBio.PDB.Residue.Residue` or :class:`~luna.MyBio.PDB.Atom.Atom`)
Each tuple contains either a pair of residues or atoms in contact.
Raises
------
EntityLevelError
If ``level`` is neither 'R' nor 'A'.
Examples
--------
In this example, we will identify all residue-residue contacts within 2.5 Å in the PDB 3QQK.
So, let's first parse the PDB file.
>>> from luna.util.default_values import LUNA_PATH
>>> from luna.MyBio.PDB.PDBParser import PDBParser
>>> pdb_parser = PDBParser(PERMISSIVE=True, QUIET=True)
>>> structure = pdb_parser.get_structure("Protein", f"{LUNA_PATH}/tutorial/inputs/3QQK.pdb")
Then, to recover all residue-residue contacts within 2.5 Å use :meth:`get_all_contacts`
with ``level`` set to 'R' and radius set to 2.5.
>>> from luna.interaction.contact import get_all_contacts
>>> contacts = get_all_contacts(structure, radius=2.5, level="R")
>>> print(len(contacts))
314
"""
try:
if level == 'S' or level == 'M' or level == 'C':
raise EntityLevelError("Maximum entity level to be chosen is: R (residues) or A (atoms)")
if level not in ENTITY_LEVEL_NAME:
raise EntityLevelError("The defined level '%s' does not exist" % level)
logger.debug("Trying to select all contacts in the PDB file %s." % entity.get_parent_by_level('S').id)
all_atoms = list(entity.get_atoms())
ns = NeighborSearch(all_atoms)
pairs = ns.search_all(radius, level)
logger.debug("Number of nearby %s(s) found: %d." % (ENTITY_LEVEL_NAME[level].lower(), len(pairs)))
return pairs
except Exception as e:
logger.exception(e)
raise
[docs]def get_contacts_with(entity, source, target=None, radius=BOUNDARY_CONFIG["boundary_cutoff"], level='A'):
"""Recover atoms or residues in contact with ``source``.
Parameters
----------
entity : :class:`~luna.MyBio.PDB.Entity.Entity`
The PDB object from where atoms and residues will be recovered.
source : :class:`~luna.MyBio.PDB.Entity.Entity`
The reference, which can be any :class:`~luna.MyBio.PDB.Entity.Entity` instance (structure, model, chain, residue, or atom).
target : :class:`~luna.MyBio.PDB.Entity.Entity`, optional
If provided, only contacts with the ``target`` will be considered.
radius : float
The cutoff distance (in Å) for defining contacts. The default value is 6.2.
level : {'R', 'A'}
Return residues ('R') or atoms ('A') in contact with ``source``.
Returns
-------
: set of tuple of (:class:`~luna.MyBio.PDB.Residue.Residue` or :class:`~luna.MyBio.PDB.Atom.Atom`, \
:class:`~luna.MyBio.PDB.Residue.Residue` or :class:`~luna.MyBio.PDB.Atom.Atom`)
Each tuple contains two items:\
the first corresponds to a residue/atom from the ``source``,\
and the second corresponds to a residue/atom in contact with ``source``.
Raises
------
EntityLevelError
If ``level`` is neither 'R' nor 'A'.
Examples
--------
**Example 1)** In this example, we will identify residue-residue contacts between a ligand and nearby residues.
First, let's parse a PDB file to work with.
>>> from luna.util.default_values import LUNA_PATH
>>> from luna.MyBio.PDB.PDBParser import PDBParser
>>> pdb_parser = PDBParser(PERMISSIVE=True, QUIET=True)
>>> structure = pdb_parser.get_structure("Protein", f"{LUNA_PATH}/tutorial/inputs/3QQK.pdb")
Now, we define the target ligand.
>>> ligand = structure[0]["A"][('H_X02', 497, ' ')]
Then, to recover contacts between this ligand and nearby residues we use :meth:`get_contacts_with`
with ``level`` set to 'R'.
>>> from luna.interaction.contact import get_contacts_with
>>> contacts = sorted(get_contacts_with(structure, ligand, radius=3, level="R"))
>>> for pair in contacts:
... print(pair)
(<Residue X02 het=H_X02 resseq=497 icode= >, <Residue GLU het= resseq=81 icode= >)
(<Residue X02 het=H_X02 resseq=497 icode= >, <Residue LEU het= resseq=83 icode= >)
(<Residue X02 het=H_X02 resseq=497 icode= >, <Residue X02 het=H_X02 resseq=497 icode= >)
(<Residue X02 het=H_X02 resseq=497 icode= >, <Residue HOH het=W resseq=321 icode= >)
**Example 2)** In this example, we will identify atom-atom contacts between a ligand and a given residue.
First, let's parse a PDB file to work with.
>>> from luna.util.default_values import LUNA_PATH
>>> from luna.MyBio.PDB.PDBParser import PDBParser
>>> pdb_parser = PDBParser(PERMISSIVE=True, QUIET=True)
>>> structure = pdb_parser.get_structure("Protein", f"{LUNA_PATH}/tutorial/inputs/3QQK.pdb")
Now, we define the target ligand and residue.
>>> ligand = structure[0]["A"][('H_X02', 497, ' ')]
>>> residue = structure[0]["A"][(' ', 81, ' ')]
Then, to recover contacts between these compounds we use :meth:`get_contacts_with`.
As we need atom-wise contacts, set ``level`` to 'A'.
>>> from luna.interaction.contact import get_contacts_with
>>> contacts = sorted(get_contacts_with(structure, ligand, target=residue, radius=4, level="A"))
>>> for pair in contacts:
... print(pair)
(<Atom C7>, <Atom O>)
(<Atom N10>, <Atom C>)
(<Atom N10>, <Atom O>)
"""
try:
if level == 'S' or level == 'M' or level == 'C':
raise EntityLevelError("Maximum entity level to be chosen is: R (residues) or A (atoms)")
if level not in ENTITY_LEVEL_NAME:
raise EntityLevelError("The defined level '%s' does not exist" % level)
source_atoms = Selection.unfold_entities([source], 'A')
target_atoms = []
if target is None:
target_atoms = list(entity.get_atoms())
else:
target_atoms = Selection.unfold_entities(target, 'A')
ns = NeighborSearch(target_atoms)
entities = set()
for atom in source_atoms:
entity = atom.get_parent_by_level(level)
nb_entities = ns.search(atom.coord, radius, level)
pairs = set(product([entity], nb_entities))
entities.update(pairs)
logger.debug("Number of nearby %s(s) found: %d." % (ENTITY_LEVEL_NAME[level].lower(), len(entities)))
return entities
except Exception as e:
logger.exception(e)
raise
[docs]def get_cov_contacts_with(entity, source, target=None):
"""Recover potential covalent bonds with ``source``.
Covalent bonds between two nearby atoms A and B are determined as in Open Babel:
.. math:: 0.4 <= \overrightarrow{\|AB\|} <= A_{cov} + B_{cov} + 0.45
Where :math:`\overrightarrow{\|AB\|}` is the distance between atoms A and B,
and :math:`A_{cov}` and :math:`B_{cov}` are the covalent radius of atoms A and B, respectively.
Parameters
----------
entity : :class:`~luna.MyBio.PDB.Entity.Entity`
The PDB object from where atoms will be recovered.
source : :class:`~luna.MyBio.PDB.Entity.Entity`
The reference, which can be any :class:`~luna.MyBio.PDB.Entity.Entity` instance (structure, model, chain, residue, or atom).
target : :class:`~luna.MyBio.PDB.Entity.Entity`, optional
If provided, only covalent bonds with the ``target`` will be considered.
Returns
-------
: set of tuple of (:class:`~luna.MyBio.PDB.Atom.Atom`, :class:`~luna.MyBio.PDB.Atom.Atom`)
Pairs of atoms with potential covalent bonds.
Examples
--------
In this example, we will identify all covalent bonds involving a given residue in the PDB 3QQK.
So, let's first parse the PDB file.
>>> from luna.util.default_values import LUNA_PATH
>>> from luna.MyBio.PDB.PDBParser import PDBParser
>>> pdb_parser = PDBParser(PERMISSIVE=True, QUIET=True)
>>> structure = pdb_parser.get_structure("Protein", f"{LUNA_PATH}/tutorial/inputs/3QQK.pdb")
Now, we select the residue of our interest.
>>> residue = structure[0]["A"][(' ', 81, ' ')]
Finally, let`s recover potential covalent bonds involving this residue with :meth:`get_cov_contacts_with`.
In the below snippet, pairs of atoms are sorted by atoms` serial number,
and the residue information is printed together with the atom name.
>>> from luna.interaction.contact import get_cov_contacts_with
>>> cov_bonds = sorted(get_cov_contacts_with(structure, residue), key=lambda x: (x[0].serial_number, x[1].serial_number))
>>> for atm1, atm2 in cov_bonds:
>>> pair = ("%s%d/%s" % (atm1.parent.resname, atm1.parent.id[1], atm1.name),
... "%s%d/%s" % (atm2.parent.resname, atm2.parent.id[1], atm2.name))
>>> print(pair)
('PHE80/C', 'GLU81/N')
('GLU81/N', 'GLU81/CA')
('GLU81/CA', 'GLU81/C')
('GLU81/CA', 'GLU81/CB')
('GLU81/C', 'GLU81/O')
('GLU81/C', 'PHE82/N')
('GLU81/CB', 'GLU81/CG')
('GLU81/CG', 'GLU81/CD')
('GLU81/CD', 'GLU81/OE1')
('GLU81/CD', 'GLU81/OE2')
"""
entities = get_contacts_with(entity, source, target, radius=COV_SEARCH_RADIUS, level='A')
cov_bonds = set()
for atm1, atm2 in entities:
if is_covalently_bound(atm1, atm2):
cov_bonds.add(tuple(sorted([atm1, atm2], key=lambda x: x.serial_number)))
return cov_bonds
[docs]def get_proximal_compounds(source, radius=COV_SEARCH_RADIUS):
"""Recover proximal compounds to ``source``.
Parameters
----------
source : :class:`~luna.MyBio.PDB.Residue.Residue`
The reference compound.
radius : float
The cutoff distance (in Å) for defining proximity.\
The default value is 2.2, which may recover potential residues bound to ``source`` through covalent bonds.
Returns
-------
: list of :class:`~luna.MyBio.PDB.Residue.Residue`
The list of proximal compounds always include ``source``.
Raises
------
IllegalArgumentError
If ``source`` is not a :class:`~luna.MyBio.PDB.Residue.Residue`
Examples
--------
In this example, we will identify all proximal compounds to a given residue in the PDB 3QQK.
So, let's first parse the PDB file.
>>> from luna.util.default_values import LUNA_PATH
>>> from luna.MyBio.PDB.PDBParser import PDBParser
>>> pdb_parser = PDBParser(PERMISSIVE=True, QUIET=True)
>>> structure = pdb_parser.get_structure("Protein", f"{LUNA_PATH}/tutorial/inputs/3QQK.pdb")
Now, we select the residue of our interest.
>>> residue = structure[0]["A"][(' ', 81, ' ')]
Finally, we call :meth:`get_proximal_compounds`.
>>> from luna.interaction.contact import get_proximal_compounds
>>> compounds = get_proximal_compounds(residue)
>>> for c in compounds:
... print(c)
<Residue PHE het= resseq=80 icode= >
<Residue GLU het= resseq=81 icode= >
<Residue PHE het= resseq=82 icode= >
"""
if not isinstance(source, Residue):
raise IllegalArgumentError("Invalid object type. A Residue object is expected instead.")
model = source.get_parent_by_level('M')
proximal = get_contacts_with(entity=model, source=source, radius=radius, level='R')
# Sorted by the compound order as in the PDB.
return sorted(list(set([p[1] for p in proximal])), key=lambda r: (r.parent.parent.id, r.parent.id, r.idx))