# -*- coding: utf-8 -*-
"""This module defines atom pointer base class."""
from numbers import Integral
from numpy import all, array, concatenate, ones, unique
from .atomic import Atomic
from .bond import Bond
from .angle import Angle
from .dihedral import Dihedral
from .improper import Improper
from .donor import Donor
from .acceptor import Acceptor
from .crossterm import Crossterm
from .nbexclusion import NBExclusion
from prody import LOGGER
__all__ = ['AtomPointer']
[docs]class AtomPointer(Atomic):
"""A base for classes pointing to atoms in :class:`.AtomGroup` instances.
Derived classes are:
* :class:`.Atom`
* :class:`.AtomSubset`
* :class:`.AtomMap`"""
__slots__ = ['_ag', '_acsi']
def __init__(self, ag, acsi):
if not isinstance(ag, AtomGroup):
raise TypeError('ag must be an AtomGroup instance, not {0}'
.format(type(ag)))
self._ag = ag
if acsi is None:
self._acsi = ag.getACSIndex()
else:
self._acsi = int(acsi)
def __contains__(self, item):
try:
ag = item.getAtomGroup()
except AttributeError:
return False
else:
return (self._ag == ag and len(item) <= len(self) and
set(item._getIndices()).issubset(set(self._getIndices())))
def __eq__(self, other):
try:
ag = other.getAtomGroup()
except AttributeError:
return False
else:
return (self._ag == ag and other.numAtoms() == self.numAtoms() and
all(self._getIndices() == other._getIndices()))
def __ne__(self, other):
return not self.__eq__(other)
def __invert__(self):
torf = ones(self._ag.numAtoms(), bool)
torf[self._indices] = False
return Selection(self._ag, torf.nonzero()[0],
'not ({0})'.format(self.getSelstr()),
self.getACSIndex(), unique=True)
def __or__(self, other):
if self is other:
return self
try:
ag = other.getAtomGroup()
except AttributeError:
raise TypeError('other must be an AtomPointer')
if self._ag != ag:
raise ValueError('both selections must be from the same AtomGroup')
acsi = self.getACSIndex()
if acsi != other.getACSIndex():
LOGGER.warn('Active coordinate set indices do not match, it will '
'be set to zero.')
acsi = 0
indices = unique(concatenate((self._getIndices(),
other._getIndices())))
if indices[-1] == atommap.DUMMY:
indices = indices[:-1]
return Selection(self._ag, indices, '({0}) or ({1})'
.format(self.getSelstr(), other.getSelstr()),
acsi, unique=True)
def __and__(self, other):
if self is other:
return self
try:
ag = other.getAtomGroup()
except AttributeError:
raise TypeError('other must be an AtomPointer')
if self._ag != ag:
raise ValueError('both selections must be from the same AtomGroup')
acsi = self.getACSIndex()
if acsi != other.getACSIndex():
LOGGER.warning('active coordinate set indices do not match, '
'so it will be set to zero in the union.')
acsi = 0
acsi = self.getACSIndex()
if acsi != other.getACSIndex():
LOGGER.warn('Active coordinate set indices do not match, it will '
'be set to zero.')
acsi = 0
indices = set(self._getIndices())
indices = indices.intersection(other.getIndices())
if indices:
indices = unique(indices)
if indices[-1] == atommap.DUMMY:
indices = indices[:-1]
return Selection(self._ag, indices, '({0}) and ({1})'
.format(self.getSelstr(), other.getSelstr()),
acsi)
def __add__(self, other):
"""Returnss an :class:`.AtomMap` instance. Order of pointed atoms are
preserved."""
try:
ag = other.getAtomGroup()
except AttributeError:
raise TypeError('unsupported operand type(s) for +: {0} and '
'{1}'.format(repr(type(self).__name__),
repr(type(other).__name__)))
if ag != self._ag:
raise ValueError('AtomPointer instances must point to the same '
'AtomGroup instance')
acsi = self.getACSIndex()
if acsi != other.getACSIndex():
LOGGER.warning('Active coordset indices of atoms are not the same.'
' Result will have ACSI {0}.'.format(acsi))
title = '({0}) + ({1})'.format(str(self), str(other))
indices = concatenate([self._getIndices(), other._getIndices()])
dummies = 0
try:
dummies += self.numDummies()
except AttributeError:
pass
try:
dummies += other.numDummies()
except AttributeError:
pass
return AtomMap(ag, indices, acsi, title=title, intarrays=True,
dummies=dummies)
def _getTimeStamp(self, index=None):
if index is None:
index = self.getACSIndex()
return self._ag._getTimeStamp(index)
def _getKDTree(self):
"""Returns KDTree for the active coordinate set from the atom group."""
return self._ag._getKDTree(self.getACSIndex())
[docs] def getAtomGroup(self):
"""Returns associated atom group."""
return self._ag
[docs] def numCoordsets(self):
"""Returns number of coordinate sets."""
return self._ag._n_csets
[docs] def getACSIndex(self):
"""Returns index of the coordinate set."""
acsi = self._acsi
if acsi >= self._ag._n_csets:
raise ValueError('{0} has fewer coordsets than assumed by {1}'
.format(str(self._ag), str(self)))
return acsi
[docs] def setACSIndex(self, index):
"""Set coordinates at *index* active."""
if self._ag._coords is None:
raise AttributeError('coordinates are not set')
if not isinstance(index, Integral):
raise TypeError('index must be an integer')
n_csets = self._ag._n_csets
if n_csets <= index or n_csets < abs(index):
raise IndexError('coordinate set index is out of range')
if index < 0:
index += n_csets
self._acsi = index
[docs] def getACSLabel(self):
"""Returns active coordinate set label."""
if self._ag._n_csets:
return self._ag._cslabels[self.getACSIndex()]
[docs] def getCSLabels(self):
"""Returns coordinate set labels."""
return self._ag.getCSLabels()
[docs] def isDataLabel(self, label):
"""Returns **True** if data associated with *label* is present."""
return self._ag.isDataLabel(label)
[docs] def getDataLabels(self, which=None):
"""Returns data labels. For ``which='user'``, return only labels of
user provided data."""
return self._ag.getDataLabels(which)
[docs] def getDataType(self, label):
"""Returns type of the data (i.e. ``data.dtype``) associated with
*label*, or **None** label is not used."""
return self._ag.getDataType(label)
[docs] def getFlagLabels(self, which=None):
"""Returns flag labels. For ``which='user'``, return labels of user
or parser (e.g. :term:`hetatm`) provided flags, for ``which='all'``
return all possible :ref:`flags` labels in addition to those present
in the instance."""
return self._ag.getFlagLabels(which)
[docs] def isFlagLabel(self, label):
"""Returns **True** if flags associated with *label* are present."""
return self._ag.isFlagLabel(label)
def _getFlags(self, label):
"""Returns atom flags."""
flags = self._ag._getFlags(label)
if flags is not None:
return flags[self._getIndices()]
def _getSubset(self, label):
subset = array(list(set(self._ag._getSubset(label))
.intersection(set(self._getIndices()))), int)
subset.sort()
return subset
def _iterBonds(self):
"""Yield pairs of indices for bonded atoms that are within the pointer.
Use :meth:`setBonds` for setting bonds."""
if self._ag._bonds is None:
raise ValueError('bonds are not set, use `setBonds` or `inferBonds`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 2 >= len(self):
for a, b in self._ag._iterBonds():
if a in iset and b in iset:
yield a, b
else:
for a, bmap in zip(indices, self._ag._bmap[indices]):
for b in bmap:
if b > -1 and b in iset:
yield a, b
iset.remove(a)
[docs] def iterBonds(self):
"""Yield bonds formed by the atom. Use :meth:`setBonds` or
:meth:`inferBonds` for setting bonds."""
ag = self._ag
acsi = self.getACSIndex()
for bond in self._iterBonds():
yield Bond(ag, bond, acsi)
def _iterAngles(self):
"""Yield triplets of indices for angled atoms that are within the pointer.
Use :meth:`setAngles` for setting angles."""
if self._ag._angles is None:
raise ValueError('angles are not set, use `AtomGroup.setAngles`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 3 >= len(self):
for a, b, c in self._ag._iterAngles():
if a in iset and b in iset and c in iset:
yield a, b, c
else:
for a, amap in zip(indices, self._ag._angmap[indices]):
for b, c in amap:
if b > -1 and b in iset and c > -1 and c in iset:
yield a, b, c
iset.remove(a)
[docs] def iterAngles(self):
"""Yield angles formed by the atom. Use :meth:`setAngles` for setting
angles."""
ag = self._ag
acsi = self.getACSIndex()
for angle in self._iterAngles():
yield Angle(ag, angle, acsi)
def _iterDihedrals(self):
"""Yield quadruples of indices for dihedraled atoms that are within the pointer.
Use :meth:`setDihedrals` for setting dihedrals."""
if self._ag._dihedrals is None:
raise ValueError('dihedrals are not set, use `AtomGroup.setDihedrals`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 4 >= len(self):
for a, b, c, d in self._ag._iterDihedrals():
if a in iset and b in iset and c in iset and d in iset:
yield a, b, c, d
else:
for a, dmap in zip(indices, self._ag._dmap[indices]):
for b, c, d in dmap:
if b > -1 and b in iset and c > -1 and c in iset \
and d > -1 and d in iset:
yield a, b, c, d
iset.remove(a)
[docs] def iterDihedrals(self):
"""Yield dihedrals formed by the atom. Use :meth:`setDihedrals` for setting
dihedrals."""
ag = self._ag
acsi = self.getACSIndex()
for dihedral in self._iterDihedrals():
yield Dihedral(ag, dihedral, acsi)
def _iterImpropers(self):
"""Yield quadruplet of indices for impropered atoms that are within the pointer.
Use :meth:`setImpropers` for setting impropers."""
if self._ag._impropers is None:
raise ValueError('impropers are not set, use `AtomGroup.setImpropers`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 4 >= len(self):
for a, b, c, d in self._ag._iterImpropers():
if a in iset and b in iset and c in iset and d in iset:
yield a, b, c, d
else:
for a, imap in zip(indices, self._ag._imap[indices]):
for b, c, d in imap:
if b > -1 and b in iset and c > -1 and c in iset \
and d > -1 and d in iset:
yield a, b, c, d
iset.remove(a)
[docs] def iterImpropers(self):
"""Yield impropers formed by the atom. Use :meth:`setImpropers` for setting
impropers."""
ag = self._ag
acsi = self.getACSIndex()
for improper in self._iterImpropers():
yield Improper(ag, improper, acsi)
def _iterCrossterms(self):
"""Yield quadruplet of indices for crosstermed atoms that are within the pointer.
Use :meth:`setCrossterms` for setting crossterms."""
if self._ag._crossterms is None:
raise ValueError('crossterms are not set, use `AtomGroup.setCrossterms`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 4 >= len(self):
for a, b, c, d in self._ag._iterCrossterms():
if a in iset and b in iset and c in iset and d in iset:
yield a, b, c, d
else:
for a, cmap in zip(indices, self._ag._cmap[indices]):
for b, c, d in cmap:
if b > -1 and b in iset and c > -1 and c in iset \
and d > -1 and d in iset:
yield a, b, c, d
iset.remove(a)
[docs] def iterCrossterms(self):
"""Yield crossterms formed by the atom. Use :meth:`setCrossterms` for setting
crossterms."""
ag = self._ag
acsi = self.getACSIndex()
for crossterm in self._iterCrossterms():
yield Crossterm(ag, crossterm, acsi)
def _iterDonors(self):
"""Yield pairs of indices for donored atoms that are within the pointer.
Use :meth:`setDonors` for setting donors."""
if self._ag._donors is None:
LOGGER.warning('donors are not set, use `AtomGroup.setDonors`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 2 >= len(self):
for a, b in self._ag._iterDonors():
if a in iset and b in iset:
yield a, b
else:
for a, dmap in zip(indices, self._ag._domap[indices]):
for b in dmap:
if b > -1 and b in iset:
yield a, b
iset.remove(a)
[docs] def iterDonors(self):
"""Yield donors formed by the atom. Use :meth:`setDonors` for setting
donors."""
ag = self._ag
acsi = self.getACSIndex()
for donor in self._iterDonors():
yield Donor(ag, donor, acsi)
def _iterAcceptors(self):
"""Yield pairs of indices for acceptored atoms that are within the pointer.
Use :meth:`setAcceptors` for setting acceptors."""
if self._ag._acceptors is None:
LOGGER.warning('acceptors are not set, use `AtomGroup.setAcceptors`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 2 >= len(self):
for a, b in self._ag._iterAcceptors():
if a in iset and b in iset:
yield a, b
else:
for a, amap in zip(indices, self._ag._acmap[indices]):
for b in amap:
if b > -1 and b in iset:
yield a, b
iset.remove(a)
[docs] def iterAcceptors(self):
"""Yield acceptors formed by the atom. Use :meth:`setAcceptors` for setting
acceptors."""
ag = self._ag
acsi = self.getACSIndex()
for acceptor in self._iterAcceptors():
yield Acceptor(ag, acceptor, acsi)
def _iterNBExclusions(self):
"""Yield pairs of indices for nbexclusioned atoms that are within the pointer.
Use :meth:`setNBExclusions` for setting nbexclusions."""
if self._ag._nbexclusions is None:
LOGGER.warning('nbexclusions are not set, use `AtomGroup.setNBExclusions`')
indices = self._getIndices()
iset = set(indices)
if len(self._ag) / 2 >= len(self):
for a, b in self._ag._iterNBExclusions():
if a in iset and b in iset:
yield a, b
else:
for a, nbemap in zip(indices, self._ag._nbemap[indices]):
for b in nbemap:
if b > -1 and b in iset:
yield a, b
iset.remove(a)
[docs] def iterNBExclusions(self):
"""Yield nbexclusions formed by the atom. Use :meth:`setNBExclusions` for setting
nbexclusions."""
ag = self._ag
acsi = self.getACSIndex()
for nbexclusion in self._iterNBExclusions():
yield NBExclusion(ag, nbexclusion, acsi)