Skip to content

Commit

Permalink
Added dynamically updating atomgroup selections (#175 and #1074) (#1080)
Browse files Browse the repository at this point in the history
* Added dynamically updating atomgroup selections (#175 and #1074)

Cleaned up some selection docs

Updated some uses of itertools.izip to six.zip

Cleaned up some variable names in groups.py that could eventually clash with
the six module.

Made AtomGroup __repr__ fancier by inflecting with the number of
elements.
  • Loading branch information
mnmelo authored Dec 28, 2016
1 parent bc05a22 commit 7c81dd2
Show file tree
Hide file tree
Showing 9 changed files with 625 additions and 77 deletions.
4 changes: 3 additions & 1 deletion package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ The rules for this file:

------------------------------------------------------------------------------
??/??/16 kain88-de, fiona-naughton, richardjgowers, tyler.je.reddy, jdetle
euhruska, orbeckst, rbrtdlg, jbarnoud, wouterboomsma, shanmbic, dotsdl
euhruska, orbeckst, rbrtdlg, jbarnoud, wouterboomsma, shanmbic,
dotsdl, manuel.nuno.melo

* 0.16.0

Enhancements
* Added dynamic selections (addresses Issues #175 and #1074).
* Added 'MemoryReader' class to allow manipulation of trajectory data
in-memory, which can provide substantial speed-ups to certain
calculations.
Expand Down
327 changes: 273 additions & 54 deletions package/MDAnalysis/core/groups.py

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions package/MDAnalysis/core/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def grab_not_keywords(tokens):

_SELECTIONDICT = {}
_OPERATIONS = {}
# These are named args to select_atoms that have a special meaning and must
# not be allowed as names for the 'group' keyword.
_RESERVED_KWARGS=('updating',)


# And and Or are exception and aren't strictly a Selection
Expand Down Expand Up @@ -528,6 +531,10 @@ class SelgroupSelection(Selection):

def __init__(self, parser, tokens):
grpname = tokens.popleft()
if grpname in _RESERVED_KWARGS:
raise TypeError("The '{}' keyword is reserved and cannot be "
"used as a selection group name."
.format(grpname))
try:
self.grp = parser.selgroups[grpname]
except KeyError:
Expand Down
8 changes: 6 additions & 2 deletions package/MDAnalysis/core/universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,18 @@ def transfer_to_memory(self, frame_interval=1, quiet=True):
dimensions=self.trajectory.ts.dimensions,
dt=self.trajectory.ts.dt)

def select_atoms(self, sel, *othersel, **selgroups):
# python 2 doesn't allow an efficient splitting of kwargs in function
# argument signatures.
# In python3-only we'd be able to explicitely define this function with
# something like (sel, *othersels, updating=False, **selgroups)
def select_atoms(self, *args, **kwargs):
"""Select atoms.
SeeAlso
-------
:meth:`MDAnalysis.core.groups.AtomGroup.select_atoms`
"""
return self.atoms.select_atoms(sel, *othersel, **selgroups)
return self.atoms.select_atoms(*args, **kwargs)

@property
def bonds(self):
Expand Down
4 changes: 2 additions & 2 deletions package/MDAnalysis/lib/mdamath.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
.. versionadded:: 0.11.0
"""
import numpy as np
from itertools import izip
from six.moves import zip

from ..exceptions import NoDataError

Expand Down Expand Up @@ -443,7 +443,7 @@ def one_to_many_pointers(Ni, Nj, i2j):
[Ni]])

ptrs = np.zeros((Nj, 2), dtype=np.int32)
for x, y in izip(borders[:-1], borders[1:]):
for x, y in zip(borders[:-1], borders[1:]):
i = sorted_idx[x]
ptrs[i] = x, y

Expand Down
80 changes: 63 additions & 17 deletions package/doc/sphinx/source/documentation_pages/selections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,19 @@ bynum *index-range*
:class:`MDAnalysis.Universe` are consecutively numbered, and the index
runs from 1 up to the total number of atoms.

.. _pre-selections-label:

Preexisting selections and modifiers
------------------------------------

group *group-name*
group `group-name`
selects the atoms in the :class:`AtomGroup` passed to the function as an
argument named *group-name*. Only the atoms common to *group-name* and the
instance :meth:`~select_atoms` was called from will be considered.
*group-name* will be included in the parsing just by comparison of atom
indices. This means that it is up to the user to make sure they were
defined in an appropriate :class:`Universe`.

fullgroup *group-name*
just like the ``group`` keyword with the difference that all the atoms of
*group-name* are included. The resulting selection may therefore have atoms
that were initially absent from the instance :meth:`~select_atoms` was
called from.

.. deprecated:: 0.11
The use of ``fullgroup`` has been deprecated in favor of the equivalent
``global group``.
argument named `group-name`. Only the atoms common to `group-name` and the
instance :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` was called
from will be considered, unless ``group`` is preceded by the ``global``
keyword. `group-name` will be included in the parsing just by comparison of
atom indices. This means that it is up to the user to make sure the
`group-name` group was defined in an appropriate :class:`Universe`.

global *selection*
by default, when issuing
Expand All @@ -234,6 +227,59 @@ global *selection*
:meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` from a
:class:`~MDAnalysis.core.universe.Universe`, ``global`` is ignored.

.. deprecated:: 0.11
The use of ``fullgroup`` has been deprecated in favor of the equivalent
``global group``.

Dynamic selections
==================

By default :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` returns an
:class:`~MDAnalysis.core.groups.AtomGroup`, in which the list of atoms is
constant across trajectory frame changes. If
:meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` is invoked with named
argument ``updating`` set to ``True``, an
:class:`~MDAnalysis.core.groups.UpdatingAtomGroup` instance will be returned
instead. It behaves just like an :class:`~MDAnalysis.core.groups.AtomGroup`
object, with the difference that the selection expressions are re-evaluated
every time the trajectory frame changes (this happens lazily, only when the
:class:`~MDAnalysis.core.groups.UpdatingAtomGroup` object is accessed so that
there is no redundant updating going on)::

# A dynamic selection of corner atoms:
>>> ag_updating = universe.select_atoms("prop x < 5 and prop y < 5 and prop z < 5", updating=True)
>>> ag_updating
<UpdatingAtomGroup with 9 atoms>
>>> universe.trajectory.next()
>>> ag_updating
<UpdatingAtomGroup with 14 atoms>

Using the ``group`` selection keyword for
:ref:`preexisting-selections <pre-selections-label>`, one can
make updating selections depend on
:class:`~MDAnalysis.core.groups.AtomGroup`, or even other
:class:`~MDAnalysis.core.groups.UpdatingAtomGroup`, instances.
Likewise, making an updating selection from an already updating group will
cause later updates to also reflect the updating of the base group::

>>> chained_ag_updating = ag_updating.select_atoms("resid 1:1000", updating=True)
>>> chained_ag_updating
<UpdatingAtomGroup with 3 atoms>
>>> universe.trajectory.next()
>>> chained_ag_updating
<UpdatingAtomGroup with 7 atoms>

Finally, a non-updating selection or a slicing/addition operation made on an
:class:`~MDAnalysis.core.groups.UpdatingAtomGroup` will return a static
:class:`~MDAnalysis.core.groups.AtomGroup`, which will no longer update
across frames::

>>> static_ag = ag_updating.select_atoms("resid 1:1000")
>>> static_ag
<UpdatingAtomGroup with 3 atoms>
>>> universe.trajectory.next()
>>> static_ag
<UpdatingAtomGroup with 3 atoms>


Instant selectors
Expand Down Expand Up @@ -345,4 +391,4 @@ work as one might expect::
>>> print list(universe.select_atoms("segid DMPC and ( resid 3 or resid 2 ) and name P"))
[< Atom 452: name 'P' of type '180' of resid 'DMPC', 2 and 'DMPC'>,
< Atom 570: name 'P' of type '180' of resid 'DMPC', 3 and 'DMPC'>]

10 changes: 10 additions & 0 deletions testsuite/MDAnalysisTests/core/test_atomgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,16 @@ def test_center_wrong_shape(self):

assert_raises(TypeError, self.ag.center, weights)

def test_representations():
u = make_Universe()
for level in (mda.core.groups.ATOMLEVEL, mda.core.groups.RESIDUELEVEL,
mda.core.groups.SEGMENTLEVEL):
singular = level.name
plural = level.name + 's'
group = getattr(u, plural)
assert str(group)[:-1].endswith(plural)
assert str(group[:0])[:-1].endswith(plural)
assert str(group[:1])[:-1].endswith(singular)

class TestSplit(object):
def setUp(self):
Expand Down
Loading

0 comments on commit 7c81dd2

Please sign in to comment.