diff --git a/package/CHANGELOG b/package/CHANGELOG index 66827ab51e0..f1aba435b37 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -15,19 +15,20 @@ The rules for this file: ------------------------------------------------------------------------------ -mm/dd/17 richardjgowers, rathann, jbarnoud +mm/dd/17 richardjgowers, rathann, jbarnoud, orbeckst * 0.16.2 -Enhancements +Deprecations + * deprecated core.Timeseries module for 0.17.0 (Issue #1383) + * deprecated instant selectors for 1.0 (Issue #1377) + * deprecated the core.flag registry for 1.0 (Issue #782) Fixes * fixed GROWriter truncating long resids from the wrong end (Issue #1395) * Fixed dtype of numpy arrays to accomodate 32 bit architectures (Issue #1362) * Groups are hashable on python 3 (Issue #1397) -Changes - 06/03/17 utkbansal, kain88-de, xiki-tempula, kaplajon, wouterboomsma, richardjgowers, Shtkddud123, QuantumEntangledAndy, orbeckst, diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index d324854bf16..17ba62e4aaf 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -143,7 +143,7 @@ """ from __future__ import absolute_import -__all__ = ['Timeseries', 'Universe', 'as_Universe', 'Writer', 'collection', +__all__ = ['Universe', 'as_Universe', 'Writer', 'collection', 'fetch_mmtf'] import logging @@ -192,14 +192,46 @@ from . import units # Bring some often used objects into the current namespace -from .core import Timeseries from .core.universe import Universe, as_Universe, Merge from .coordinates.core import writer as Writer # After Universe import from .coordinates.MMTF import fetch_mmtf -collection = Timeseries.TimeseriesCollection() +# REMOVE in 0.17.0 +class _MockTimeseriesCollection(object): + """When accessed the first time, emit warning and raise NotImplementedError. + + This breaks existing code that relies on MDAnalysis.collection by + replacing :: + + collection = Timeseries.TimeseriesCollection() + + with:: + + collection = _MockTimeseriesCollection() + + """ + + def __getattr__(self, name): + self._warn_and_die() + + def __getitem__(self, index): + self._warn_and_die() + + def _warn_and_die(self): + import logging + msg = "collection = Timeseries.TimeseriesCollection() will be removed in 0.17.0\n" \ + "and MDAnalysis.collection has been disabled. If you want to use it, \n" \ + "instantiate a collection yourself:\n\n" \ + " from MDAnalysis.core.Timeseries import TimeseriesCollection\n" \ + " collection = TimeseriesCollection()\n\n" \ + "Note that release 0.16.2 is the LAST RELEASE with TimeseriesCollection." + logging.getLogger("MDAnalysis").warn(msg) + warnings.warn(msg, DeprecationWarning) + raise NotImplementedError("TimeseriesCollection will be REMOVED IN THE NEXT RELEASE 0.17.0") +collection = _MockTimeseriesCollection() +del _MockTimeseriesCollection from .migration.ten2eleven import ten2eleven diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 0f5a0eb9693..70b81d52ae0 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -81,6 +81,7 @@ import os import errno import numpy as np +from numpy.lib.utils import deprecate import struct import types import warnings @@ -578,37 +579,39 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, # XXX needs to be implemented return self._read_timeseries(atom_numbers, start, stop, step, format) + @deprecate(message="This method will be removed in 0.17") def correl(self, timeseries, start=None, stop=None, step=None, skip=None): - """Populate a :class:`~MDAnalysis.core.Timeseries.TimeseriesCollection` object - with time series computed from the trajectory. - - Calling this method will iterate through the whole trajectory and - perform the calculations prescribed in `timeseries`. - - Parameters - ---------- - timeseries : :class:`MDAnalysis.core.Timeseries.TimeseriesCollection` - The :class:`MDAnalysis.core.Timeseries.TimeseriesCollection` that defines what kind - of computations should be performed on the data in this trajectory. - start : int (optional) - Begin reading the trajectory at frame index `start` (where 0 is the index - of the first frame in the trajectory); the default ``None`` starts - at the beginning. - stop : int (optional) - End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. - The trajectory is read to the end with the default ``None``. - step : int (optional) - Step size for reading; the default ``None`` is equivalent to 1 and means to - read every frame. + """ +Populate a :class:`~MDAnalysis.core.Timeseries.TimeseriesCollection` object +with time series computed from the trajectory. + +Calling this method will iterate through the whole trajectory and +perform the calculations prescribed in `timeseries`. + +Parameters +---------- +timeseries : :class:`MDAnalysis.core.Timeseries.TimeseriesCollection` + The :class:`MDAnalysis.core.Timeseries.TimeseriesCollection` that defines what kind + of computations should be performed on the data in this trajectory. +start : int (optional) + Begin reading the trajectory at frame index `start` (where 0 is the index + of the first frame in the trajectory); the default ``None`` starts + at the beginning. +stop : int (optional) + End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. + The trajectory is read to the end with the default ``None``. +step : int (optional) + Step size for reading; the default ``None`` is equivalent to 1 and means to + read every frame. - Note - ---- - The `correl` functionality is only implemented for DCD trajectories and - the :class:`DCDReader`. +Note +---- +The `correl` functionality is only implemented for DCD trajectories and +the :class:`DCDReader`. - .. deprecated:: 0.16.0 - `skip` has been deprecated in favor of the standard keyword `step`. +.. deprecated:: 0.16.0 + `skip` has been deprecated in favor of the standard keyword `step`. """ if skip is not None: diff --git a/package/MDAnalysis/coordinates/__init__.py b/package/MDAnalysis/coordinates/__init__.py index 45c13eada4d..fc9cfc82c81 100644 --- a/package/MDAnalysis/coordinates/__init__.py +++ b/package/MDAnalysis/coordinates/__init__.py @@ -538,10 +538,6 @@ class can choose an appropriate reader automatically. ``timeseries(atomGroup, [start[,stop[,skip[,format]]]])`` returns a subset of coordinate data - ``correl(timeseriesCollection[,start[,stop[,skip]]])`` - populate a :class:`MDAnalysis.core.Timeseries.TimeseriesCollection` object - with observable timeseries computed from the trajectory - Attributes .......... diff --git a/package/MDAnalysis/coordinates/memory.py b/package/MDAnalysis/coordinates/memory.py index 7a3bccab5ee..15a29b7a700 100644 --- a/package/MDAnalysis/coordinates/memory.py +++ b/package/MDAnalysis/coordinates/memory.py @@ -106,6 +106,8 @@ universe2 = mda.Universe(PSF, coordinates, format=MemoryReader, order='afc') +.. _create-in-memory-trajectory-with-AnalysisFromFunction: + .. rubric:: Creating an in-memory trajectory with :func:`~MDAnalysis.analysis.base.AnalysisFromFunction` diff --git a/package/MDAnalysis/core/Timeseries.py b/package/MDAnalysis/core/Timeseries.py index f6c191667d6..024b0cb18ca 100644 --- a/package/MDAnalysis/core/Timeseries.py +++ b/package/MDAnalysis/core/Timeseries.py @@ -21,10 +21,19 @@ # -""" -Compute observable timeseries from trajectories --- :mod:`MDAnalysis.core.Timeseries` +"""Compute observable timeseries from trajectories --- :mod:`MDAnalysis.core.Timeseries` ======================================================================================= +.. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in the + 0.17 release. See issue `#1372 + `_ for more details. + To extract coordinates efficiently one can use + :class:`MDAnalysis.analysis.base.AnalysisFromFunction` as shown in + :ref:`Creating an in-memory trajectory with AnalysisFromFunction + `. + + The collection of timeseries (such as :class:`Atom`, :class:`Bond`, :class:`Dihedral`...) can be computed from a trajectory in one go, foregoing the need to iterate through the trajectory frame by frame in python. Inspired @@ -61,8 +70,17 @@ from __future__ import division, absolute_import import warnings +from numpy.lib.utils import deprecate + from . import groups +with warnings.catch_warnings(): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn(('The Timeseries module and TimeseriesCollection will be ' + 'removed in the 0.17 release. See issue #1372 ' + 'https://github.com/MDAnalysis/mdanalysis/issues/1373'), + DeprecationWarning) + class TimeseriesCollection(object): '''A collection of timeseries objects. @@ -81,8 +99,17 @@ class TimeseriesCollection(object): collection.clear() - clear the collection collection[i] - access the i'th timeseries len(collection) - return the number of Timeseries added to the collection + + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self): self.timeseries = [] @@ -186,9 +213,17 @@ def _getAuxData(self): class Timeseries(object): - '''Base timeseries class - define subclasses for specific timeseries computations + '''Base timeseries class - define subclasses for specific timeseries computations. + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, code, atoms, dsize): if isinstance(atoms, groups.AtomGroup): self.atoms = atoms.atoms @@ -253,8 +288,16 @@ class Atom(Timeseries): can be a single :class:`~MDAnalysis.core.groups.Atom` object, a list of :class:`~MDAnalysis.core.groups.Atom` objects, or an :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, code, atoms): if code not in ('x', 'y', 'z', 'v', 'w'): raise ValueError("Bad code") @@ -281,10 +324,18 @@ class Bond(Timeseries): t = Bond(atoms) - *atoms* must contain 2 :class:`~MDAnalysis.core.groups.Atom` instances, either as a list or an - :class:`~MDAnalysis.core.groups.AtomGroup` + *atoms* must contain 2 :class:`~MDAnalysis.core.groups.Atom` instances, either as a list or an + :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): if not len(atoms) == 2: raise ValueError("Bond timeseries requires a 2 atom selection") @@ -296,10 +347,18 @@ class Angle(Timeseries): t = Angle(atoms) - atoms must contain 3 :class:`~MDAnalysis.core.groups.Atom` instances, either as a list or an - :class:`~MDAnalysis.core.groups.AtomGroup` + atoms must contain 3 :class:`~MDAnalysis.core.groups.Atom` instances, + either as a list or an :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): if not len(atoms) == 3: raise ValueError("Angle timeseries requires a 3 atom selection") @@ -311,10 +370,18 @@ class Dihedral(Timeseries): t = Dihedral(atoms) - atoms must contain 4 :class:`~MDAnalysis.core.groups.Atom` objects, either as a list or an - :class:`~MDAnalysis.core.groups.AtomGroup` + atoms must contain 4 :class:`~MDAnalysis.core.groups.Atom` objects, either + as a list or an :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): if not len(atoms) == 4: raise ValueError("Dihedral timeseries requires a 4 atom selection") @@ -326,11 +393,19 @@ class Distance(Timeseries): t = Distance(code, atoms) - code is one of 'd' (distance vector), or 'r' (scalar distance) - atoms must contain 2 :class:`~MDAnalysis.core.groups.Atom` objects, either as a list or an - :class:`~MDAnalysis.core.groups.AtomGroup` + code is one of 'd' (distance vector), or 'r' (scalar distance) atoms must + contain 2 :class:`~MDAnalysis.core.groups.Atom` objects, either as a list + or an :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, code, atoms): if code not in ('d', 'r'): raise ValueError("Bad code") @@ -348,10 +423,19 @@ class CenterOfGeometry(Timeseries): t = CenterOfGeometry(atoms) - *atoms* can be a list of :class:`~MDAnalysis.core.groups.Atom` - objects, or a :class:`~MDAnalysis.core.groups.AtomGroup` + *atoms* can be a list of :class:`~MDAnalysis.core.groups.Atom` objects, or + a :class:`~MDAnalysis.core.groups.AtomGroup` + + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): Timeseries.__init__(self, 'm', atoms, 3) @@ -364,10 +448,18 @@ class CenterOfMass(Timeseries): t = CenterOfMass(atoms) - *atoms* can be a list of :class:`~MDAnalysis.core.groups.Atom` - objects or a :class:`~MDAnalysis.core.groups.AtomGroup` + *atoms* can be a list of :class:`~MDAnalysis.core.groups.Atom` objects or a + :class:`~MDAnalysis.core.groups.AtomGroup` + + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. + ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): Timeseries.__init__(self, 'm', atoms, 3) @@ -380,41 +472,47 @@ class WaterDipole(Timeseries): d = WaterDipole(atoms) - *atoms* must contain 3 :class:`~MDAnalysis.core.groups.Atom` - objects, either as a list or an - :class:`~MDAnalysis.core.groups.AtomGroup`; the first one *must* be - the oxygen, the other two are the hydrogens. + *atoms* must contain 3 :class:`~MDAnalysis.core.groups.Atom` objects, + either as a list or an :class:`~MDAnalysis.core.groups.AtomGroup`; the + first one *must* be the oxygen, the other two are the hydrogens. + + The vector ``d``, multiplied by the partial charge on the oxygen atom + (e.g. *q* = -0.0.834 for TIP3P water), gives the actual dipole moment. - The vector ``d``, multiplied by the partial charge on the oxygen atom - (e.g. *q* = -0.0.834 for TIP3P water), gives the actual dipole moment. + The vector is calculated from the positions of the oxygen atom + (:math:`\mathbf{x}_{\text{O}}`) and the two hydrogen atoms + (:math:`\mathbf{x}_{\text{H}_1}`, :math:`\mathbf{x}_{\text{H}_2}`) as - The vector is calculated from the positions of the oxygen atom - (:math:`\mathbf{x}_{\text{O}}`) and the two hydrogen atoms - (:math:`\mathbf{x}_{\text{H}_1}`, :math:`\mathbf{x}_{\text{H}_2}`) as + .. math:: - .. math:: + \mathbf{d} = \mathbf{x}_{\text{O}} - \frac{1}{2}(\mathbf{x}_{\text{H}_1} + \mathbf{x}_{\text{H}_2}) - \mathbf{d} = \mathbf{x}_{\text{O}} - \frac{1}{2}(\mathbf{x}_{\text{H}_1} + \mathbf{x}_{\text{H}_2}) + and the dipole moment vector is - and the dipole moment vector is + .. math:: - .. math:: + \boldsymbol{\mu} = q_{\text{O}} \mathbf{d} - \boldsymbol{\mu} = q_{\text{O}} \mathbf{d} + .. Note:: - .. Note:: + This will only work for water models that have half of the oxygen charge + on each hydrogen. The vector :math:`\mathbf{d}` has the opposite + direction of the dipole moment; multiplying with the oxygen charge + (:math:`q_{\text{O}}<0`) will flip the direction and produce the correct + orientation. - This will only work for water models that have half of the oxygen - charge on each hydrogen. The vector :math:`\mathbf{d}` has the - opposite direction of the dipole moment; multiplying with the oxygen - charge (:math:`q_{\text{O}}<0`) will flip the direction and produce - the correct orientation. + There are no sanity checks; *if the first atom in a water + molecule is not oxygen then results will be wrong.* - There are no sanity checks; *if the first atom in a water - molecule is not oxygen then results will be wrong.* + .. deprecated:: 0.16.2 + The Timeseries functionality (in particular correl) will be removed in + the 0.17 release. See issue `#1372 + `_ for more + details. Use :class:`MDAnalysis.analysis.base.AnalysisFromFunction` instead. ''' + @deprecate(message="This class will be removed in 0.17") def __init__(self, atoms): if not len(atoms) == 3: raise ValueError("WaterDipole timeseries requires a 3 atom selection") diff --git a/package/MDAnalysis/core/__init__.py b/package/MDAnalysis/core/__init__.py index 4d2c0bc71ad..6c3f4a4fb10 100644 --- a/package/MDAnalysis/core/__init__.py +++ b/package/MDAnalysis/core/__init__.py @@ -30,8 +30,6 @@ :class:`~MDAnalysis.core.groups.AtomGroup` and return another :class:`~MDAnalysis.core.groups.AtomGroup`. -:mod:`~MDAnalysis.Timeseries` are a convenient way to analyse trajectories. - To get started, load the Universe:: u = Universe(topology_file, trajectory_file) @@ -56,6 +54,12 @@ Flags ----- +.. deprecated:: 0.16.2 + The flags registry will be removed in release 1.0. + Use keyword arguments for functions to obtain the desired behavior. + See issue `#782 `_ + for more details. + (This is an advanced topic and can probably be skipped by most people.) There are a number flags that influence how MDAnalysis behaves. They are accessible @@ -92,8 +96,9 @@ __all__ = ['AtomGroup', 'Selection', 'Timeseries'] -# set up flags for core routines (more convoluted than strictly necessary but should -# be clean to add more flags if needed) +# set up flags for core routines (more convoluted than strictly necessary but +# should be clean to add more flags if needed) + class Flags(dict): """Global registry of flags. Acts like a dict for item access. @@ -109,6 +114,13 @@ class Flags(dict): New flags are added with the :meth:`Flags.register` method which takes a new :class:`Flag` instance as an argument. + + .. deprecated:: 0.16.2 + The flags registry will be removed in release 1.0. + Use keyword arguments for functions to obtain the desired behavior. + See issue `#782 `_ + for more details. + """ def __init__(self, *args): @@ -180,7 +192,14 @@ def __getitem__(self, key): class Flag(object): - """A Flag, essentially a variable that knows its default and legal values.""" + """A Flag, essentially a variable that knows its default and legal values. + + .. deprecated:: 0.16.2 + The flags registry will be removed in release 1.0. + Use keyword arguments for functions to obtain the desired behavior. + See issue `#782 `_ + for more details. + """ def __init__(self, name, default, mapping=None, doc=None): """Create a new flag which will be registered with Flags. @@ -429,5 +448,4 @@ class flagsDocs(object): from . import groups from . import selection -from . import Timeseries from . import AtomGroup diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index f9c2da1ebee..f2717469aac 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -101,6 +101,8 @@ import os import warnings +from numpy.lib.utils import deprecate + import MDAnalysis from .. import _ANCHOR_UNIVERSES from ..lib import util @@ -342,7 +344,7 @@ def wrapped(self, other): class GroupBase(_MutableBase): """Base class from which a Universe's Group class is built. - Instances of :class:`GroupBase` provide the following operations that + Instances of :class:`GroupBase` provide the following operations that conserve element repetitions and order: +-------------------------------+------------+----------------------------+ @@ -989,7 +991,7 @@ def wrap(self, compound="atoms", center="com", box=None): "Please use one of 'group' 'residues' 'segments'" "or 'fragments'".format(compound)) -# TODO: ADD TRY-EXCEPT FOR MASSES PRESENCE + # TODO: ADD TRY-EXCEPT FOR MASSES PRESENCE if center.lower() in ('com', 'centerofmass'): centers = np.vstack([o.atoms.center_of_mass() for o in objects]) elif center.lower() in ('cog', 'centroid', 'centerofgeometry'): @@ -1352,9 +1354,10 @@ def is_strict_superset(self, other): class AtomGroup(GroupBase): """A group of atoms. - An AtomGroup is an ordered collection of atoms. Typically, an AtomGroup is - generated from a selection, or by indexing/slcing the AtomGroup of all - atoms in the Universe at :attr:`MDAnalysis.core.universe.Universe.atoms`. + An :class:`AtomGroup` is an ordered collection of atoms. Typically, an + :class:`AtomGroup` is generated from a selection, or by indexing/slicing + the :class:`AtomGroup` of all atoms in the :class:`Universe` at + :attr:`MDAnalysis.core.universe.Universe.atoms`. An AtomGroup can be indexed and sliced like a list:: @@ -1385,23 +1388,6 @@ class AtomGroup(GroupBase): atoms is crucial (for instance, in order to define angles or dihedrals). - Atoms can also be accessed in a Pythonic fashion by using the atom name as - an attribute. For instance, :: - - ag.CA - - will provide a :class:`AtomGroup` of all CA atoms in the - group. These *instant selector* attributes are auto-generated for - each atom name encountered in the group. - - .. note:: - - The name-attribute instant selector access to atoms is mainly - meant for quick interactive work. Thus it either returns a - single :class:`Atom` if there is only one matching atom, *or* a - new :class:`AtomGroup` for multiple matches. This makes it - difficult to use the feature consistently in scripts. - AtomGroups can be compared and combined using group operators. For instance, AtomGroups can be concatenated using `+` or :meth:`concatenate`:: @@ -1487,12 +1473,44 @@ class AtomGroup(GroupBase): AtomGroup instances are always bound to a :class:`MDAnalysis.core.universe.Universe`. They cannot exist in isolation. + + .. rubric:: Deprecated functionality + + *Instant selectors* will be removed in the 1.0 release. See issue `#1377 + `_ for more details. + + Atoms can also be accessed in a Pythonic fashion by using the atom name as + an attribute. For instance, :: + + ag.CA + + will provide a :class:`AtomGroup` of all CA atoms in the + group. These *instant selector* attributes are auto-generated for + each atom name encountered in the group. + + Notes + ----- + The name-attribute instant selector access to atoms is mainly + meant for quick interactive work. Thus it either returns a + single :class:`Atom` if there is only one matching atom, *or* a + new :class:`AtomGroup` for multiple matches. This makes it + difficult to use the feature consistently in scripts. + + See Also -------- :class:`MDAnalysis.core.universe.Universe` + + .. deprecated:: 0.16.2 + *Instant selectors* of AtomGroup will be removed in the 1.0 release. + See :ref:`Instant selectors ` for details and alternatives. + """ def __getitem__(self, item): + # DEPRECATED in 0.16.2 + # REMOVE in 1.0 + # # u.atoms['HT1'] access, otherwise default if isinstance(item, string_types): try: @@ -1502,6 +1520,9 @@ def __getitem__(self, item): return super(AtomGroup, self).__getitem__(item) def __getattr__(self, attr): + # DEPRECATED in 0.16.2 + # REMOVE in 1.0 + # # is this a known attribute failure? if attr in ('fragments',): # TODO: Generalise this to cover many attributes # eg: @@ -2178,6 +2199,11 @@ class ResidueGroup(GroupBase): ResidueGroups can be compared and combined using group operators. See the list of these operators on :class:`GroupBase`. + + .. deprecated:: 0.16.2 + *Instant selectors* of Segments will be removed in the 1.0 release. + See :ref:`Instant selectors ` for details and alternatives. + """ @property @@ -2292,6 +2318,11 @@ class SegmentGroup(GroupBase): SegmentGroups can be compared and combined using group operators. See the list of these operators on :class:`GroupBase`. + + .. deprecated:: 0.16.2 + *Instant selectors* of Segments will be removed in the 1.0 release. + See :ref:`Instant selectors ` for details and alternatives. + """ @property @@ -2460,7 +2491,7 @@ def ix(self): @property def ix_array(self): """Unique index of this component as an array. - + This method gives a consistent API between components and groups. See Also @@ -2637,6 +2668,10 @@ class Segment(ComponentBase): ComponentBase, so this class only includes ad-hoc methods specific to Segments. + .. deprecated:: 0.16.2 + *Instant selectors* of Segments will be removed in the 1.0 release. + See :ref:`Instant selectors ` for details and alternatives. + """ def __repr__(self): me = ' will be removed in 1.0. " + "Use Segment.residues[N-1] instead.", + DeprecationWarning) + return rg # Resname accesss if hasattr(self.residues, 'resnames'): try: diff --git a/package/MDAnalysis/core/topologyattrs.py b/package/MDAnalysis/core/topologyattrs.py index 9976b9b5b88..82aae50632a 100644 --- a/package/MDAnalysis/core/topologyattrs.py +++ b/package/MDAnalysis/core/topologyattrs.py @@ -41,6 +41,9 @@ import itertools import numbers import numpy as np +import warnings + +from numpy.lib.utils import deprecate from . import flags from ..lib.util import cached, convert_aa_code, iterable @@ -417,6 +420,14 @@ def _get_named_atom(group, name): no atoms are found, a :exc:`SelectionError` is raised. .. versionadded:: 0.9.2 + + .. deprecated:: 0.16.2 + *Instant selectors* will be removed in the 1.0 release. + Use ``AtomGroup.select_atoms('name ')`` instead. + See issue `#1377 + `_ for + more details. + """ # There can be more than one atom with the same name atomlist = group.atoms.unique[group.atoms.unique.names == name] @@ -425,10 +436,12 @@ def _get_named_atom(group, name): "No atoms with name '{0}'".format(name)) elif len(atomlist) == 1: # XXX: keep this, makes more sense for names - return atomlist[0] - else: - # XXX: but inconsistent (see residues and Issue 47) - return atomlist + atomlist = atomlist[0] + warnings.warn("Instant selector AtomGroup[''] or AtomGroup. " + "is deprecated and will be removed in 1.0. " + "Use AtomGroup.select_atoms('name ') instead.", + DeprecationWarning) + return atomlist # AtomGroup already has a getattr # transplants[AtomGroup].append( @@ -1057,6 +1070,13 @@ def getattr__(residuegroup, resname): # This transplant is hardcoded for now to allow for multiple getattr things #transplants[Segment].append(('__getattr__', getattr__)) + + @deprecate(message="Instant selector ResidueGroup. " + "or Segment. " + "is deprecated and will be removed in 1.0. " + "Use ResidueGroup[ResidueGroup.resnames == ''] " + "or Segment.residues[Segment.residues == ''] " + "instead.") def _get_named_residue(group, resname): """Get all residues with name *resname* in the current ResidueGroup or Segment. @@ -1068,6 +1088,15 @@ def _get_named_residue(group, resname): .. versionadded:: 0.9.2 + .. deprecated:: 0.16.2 + *Instant selectors* will be removed in the 1.0 release. + Use ``ResidueGroup[ResidueGroup.resnames == '']`` + or ``Segment.residues[Segment.residues == '']`` + instead. + See issue `#1377 + `_ for + more details. + """ # There can be more than one residue with the same name residues = group.residues.unique[ @@ -1244,6 +1273,10 @@ def getattr__(segmentgroup, segid): transplants[SegmentGroup].append( ('__getattr__', getattr__)) + @deprecate(message="Instant selector SegmentGroup. " + "is deprecated and will be removed in 1.0. " + "Use SegmentGroup[SegmentGroup.segids == ''] " + "instead.") def _get_named_segment(group, segid): """Get all segments with name *segid* in the current SegmentGroup. @@ -1254,6 +1287,13 @@ def _get_named_segment(group, segid): .. versionadded:: 0.9.2 + .. deprecated:: 0.16.2 + *Instant selectors* will be removed in the 1.0 release. + Use ``SegmentGroup[SegmentGroup.segids == '']`` instead. + See issue `#1377 + `_ for + more details. + """ # Undo adding 's' if segid started with digit if segid.startswith('s') and len(segid) >= 2 and segid[1].isdigit(): diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 1ce8cbf3f95..2b6fe1d11e9 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -47,6 +47,11 @@ Quick segid selection --------------------- +.. deprecated:: 0.16.2 + Instant selectors will be removed in the 1.0 release. See issue `#1377 + `_ for more details. + + If the loaded topology provided segids, then these are made accessible as attributes of the Universe. If the segid starts with a number such as '4AKE', the letter 's' will be prepended to the segid. @@ -313,6 +318,10 @@ def _generate_from_topology(self): # Update Universe namespace with segids # Many segments can have same segid, so group together first + # + # DEPRECATED in 0.16.2 + # REMOVE in 1.0 + # See https://github.com/MDAnalysis/mdanalysis/issues/1377 try: # returns dict of segid:segment segids = self.segments.groupby('segids') @@ -492,7 +501,7 @@ def transfer_to_memory(self, start=None, stop=None, step=None, # object, to provide fast access and allow coordinates # to be manipulated if step is None: - step = 1 + step = 1 self.trajectory = MemoryReader( coordinates, dimensions=self.trajectory.ts.dimensions, diff --git a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst index 7da649d8683..edd239805de 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst @@ -4,29 +4,39 @@ Analysis modules **************** -The :mod:`MDAnalysis.analysis` module contains code to carry out -specific analysis functionality. It is based on the core functionality -(i.e. trajectory I/O, selections etc). The analysis modules can be -used as examples for how to use MDAnalysis but also as working code -for research projects; typically all contributed code has been used by -the authors in their own work. +The :mod:`MDAnalysis.analysis` module contains code to carry out specific +analysis functionality. It is based on the core functionality (i.e. trajectory +I/O, selections etc). The analysis modules can be used as examples for how to +use MDAnalysis but also as working code for research projects; typically all +contributed code has been used by the authors in their own work. -Please see the individual module documentation for additional -references and citation information. +Please see the individual module documentation for additional references and +citation information. -These modules are not imported by default; in order to use them one -has to import them from :mod:`MDAnalysis.analysis`, for instance :: +These modules are not imported by default; in order to use them one has to +import them from :mod:`MDAnalysis.analysis`, for instance :: import MDAnalysis.analysis.align -.. Note:: - - Some of the modules require additional Python packages such as :mod:`scipy` - from the SciPy_ package. These package are *not automatically installed* - (although one can add the ``[analysis]`` requirement to the :program:`pip` - command line to force their installation. - -.. _scipy: http://www.scipy.org/ +.. rubric:: Additional dependencies + +Some of the modules in :mod:`MDAnalysis.analysis` require additional Python +packages to enable full functionality. For example, +:mod:`MDAnalysis.analysis.encore` provides more options if `scikit-learn`_ is +installed. These package are *not automatically installed* with +:program:`pip`(although one can add the ``[analysis]`` requirement to the +:program:`pip` command line to force their installation). If you install +MDAnalysis with :program:`conda` (see :ref:`installation-instructions`) then a +*full set of dependencies* is automatically installed. + +Other modules require external programs. For instance, the +:mod:`MDAnalysis.analysis.hole` module requires an installation of the HOLE_ +suite of programs. You will need to install these external dependencies by +following their installation instructions before you can use the corresponding +MDAnalysis module. + +.. _scikit-learn: http://scikit-learn.org/ +.. _HOLE: http://www.smartsci.uk/hole/ Building blocks for Analysis diff --git a/package/doc/sphinx/source/documentation_pages/selections.rst b/package/doc/sphinx/source/documentation_pages/selections.rst index 77851225050..48b221ec9fd 100644 --- a/package/doc/sphinx/source/documentation_pages/selections.rst +++ b/package/doc/sphinx/source/documentation_pages/selections.rst @@ -281,10 +281,21 @@ across frames:: >>> static_ag +.. _instance-selectors: Instant selectors ================= +.. deprecated:: 0.16.2 + *Instant selectors* will be removed in the 1.0 release in order to + streamline the MDAnalysis user interface. They do not seem to be + widely used anymore, can produce cryptic error messages, and are + not considered "Pythonic" (and therefore not very intuitive for new + users). See issue `#1377 + `_ for more + details. + + For interactive work it becomes rather tedious to type common selection strings repeatedly. MDAnalysis automatically generates a number of *instant selectors* as attributes of the :class:`~MDAnalysis.core.universe.Universe` and number of @@ -298,6 +309,11 @@ other levels of the structural hierarchy, namely for Segment selector ---------------- +.. deprecated:: 0.16.2 + Use ``SegmentGroup[SegmentGroup.segids == '']`` instead. Note that this + *always* returns a :class:`SegmentGroup` and *never* a :class:`Segment` + (unlike the instant selector). + - ``universe.`` or ``universe.s`` (if ** starts with a number) - returns a :class:`~MDAnalysis.core.groups.Segment` @@ -309,6 +325,9 @@ Segment selector Resid selector -------------- +.. deprecated:: 0.16.2 + Use ``Segment.residues[N-1]`` instead. + - ``seg.r`` selects residue with number ```` - returns a :class:`~MDAnalysis.core.groups.Residue` - works for :class:`~MDAnalysis.core.groups.Segment` and :class:`~MDAnalysis.core.groups.SegmentGroup` @@ -319,6 +338,12 @@ Resid selector Residue name selector --------------------- +.. deprecated:: 0.16.2 + Use ``ResidueGroup[ResidueGroup.resnames == '']`` or + ``Segment.residues[Segment.residues == '']`` instead. Note that this + *always* returns a :class:`ResidueGroup` and *never* a :class:`Residue` + (unlike the instant selector). + - ``seg.`` selects residues with residue name ```` - returns a :class:`~MDAnalysis.core.groups.ResidueGroup` - works for :class:`~MDAnalysis.core.groups.Segment` and :class:`~MDAnalysis.core.groups.SegmentGroup` @@ -335,6 +360,11 @@ Residue name selector Atom name selector ------------------ +.. deprecated:: 0.16.2 + Use ``AtomGroup.select_atoms('name ')`` instead. Note that this + *always* returns an :class:`AtomGroup` and *never* an :class:`Atom` (unlike + the instant selector). + - ``g.`` selects a single atom or a group of atoms with name ```` - returns diff --git a/package/doc/sphinx/source/index.rst b/package/doc/sphinx/source/index.rst index 20df327041f..666117666c4 100644 --- a/package/doc/sphinx/source/index.rst +++ b/package/doc/sphinx/source/index.rst @@ -57,36 +57,70 @@ members agree and adhere to --- please read it. http://groups.google.com/group/mdnalysis-discussion .. _`Code of Conduct`: http://www.mdanalysis.org/pages/conduct/ +.. _installation-instructions: Installing MDAnalysis ===================== -To `install the latest release`_ using `pip`_: +The easiest approach to `install the latest release`_ is to use a package that +can be installed either with pip_ or conda_. + +pip +--- + +Installation with `pip`_ and a *minimal set of dependencies*: .. code-block:: bash pip install --upgrade MDAnalysis -Alternatively, to install with conda_ do +To install with a *full set of dependencies* (which includes everything needed +for :mod:`MDAnalysis.analysis`), add the ``[analysis]`` tag: + +.. code-block:: bash + + pip install --upgrade MDAnalysis[analysis] + + +conda +----- + +First installation with conda_: .. code-block:: bash conda config --add channels conda-forge conda install mdanalysis -and to upgrade +which will automatically install a *full set of dependencies*. + +To upgrade later: .. code-block:: bash conda update mdanalysis +Tests +----- + +If you want to `run the tests`_ or use example files to follow some of the +examples in the documentation or the tutorials_, also install the +``MDAnalysisTests`` package: + +.. code-block:: bash + + pip install --upgrade MDAnalysisTests # with pip + conda install mdanalysistests # with conda + .. _install the latest release: http://www.mdanalysis.org/pages/installation_quick_start/ .. _pip: http://www.pip-installer.org/en/latest/index.html .. _conda: http://conda.pydata.org/docs/ - +.. _run the tests: http://wiki.mdanalysis.org/UnitTests +.. _tutorials: http://www.mdanalysis.org/pages/learning_MDAnalysis/ + Source Code =========== diff --git a/testsuite/MDAnalysisTests/__init__.py b/testsuite/MDAnalysisTests/__init__.py index 81f330c43f2..2397ed8598d 100644 --- a/testsuite/MDAnalysisTests/__init__.py +++ b/testsuite/MDAnalysisTests/__init__.py @@ -98,7 +98,7 @@ A number of plugins external to nose are automatically loaded. The `knownfailure` plugin provides the `@knownfailure()` decorator, which can be used to mark tests -that are expected to fail. If used with default arguments the parentheses can be +that are expected to fail. If used with default arguments the parentheses can be excluded. .. _NumPy: http://www.numpy.org/ @@ -148,6 +148,7 @@ module_not_found, parser_not_found, in_dir, + assert_nowarns, ) from MDAnalysisTests.core.util import make_Universe diff --git a/testsuite/MDAnalysisTests/core/test_groups.py b/testsuite/MDAnalysisTests/core/test_groups.py index 004b6804729..e962b045fb7 100644 --- a/testsuite/MDAnalysisTests/core/test_groups.py +++ b/testsuite/MDAnalysisTests/core/test_groups.py @@ -30,12 +30,13 @@ assert_array_equal, assert_equal, assert_raises, + assert_warns, ) import operator import six import MDAnalysis as mda -from MDAnalysisTests import make_Universe, parser_not_found +from MDAnalysisTests import make_Universe, parser_not_found, assert_nowarns from MDAnalysisTests.datafiles import PSF, DCD from MDAnalysis.core import groups from MDAnalysis.core.topology import Topology @@ -986,3 +987,41 @@ class TestAtomGroup(object): def test_PDB_atom_repr(): u = make_Universe(extras=('altLocs', 'names', 'types', 'resnames', 'resids', 'segids')) assert_equal("", u.atoms[0].__repr__()) + + +class TestInstantSelectorDeprecationWarnings(object): + def setUp(self): + self.u = make_Universe(("resids", "resnames", "segids", "names")) + + def test_AtomGroup_warn_getitem(self): + name = self.u.atoms[0].name + assert_warns(DeprecationWarning, lambda x: self.u.atoms[x], name) + + def test_AtomGroup_nowarn_getitem_index(self): + assert_nowarns(DeprecationWarning, lambda x: self.u.atoms[x], 0) + + def test_AtomGroup_nowarn_segids_attribute(self): + assert_nowarns(DeprecationWarning, lambda x: getattr(self.u.atoms, x), "segids") + + def test_AtomGroup_warn_getattr(self): + name = self.u.atoms[0].name + assert_warns(DeprecationWarning, lambda x: getattr(self.u.atoms, x), name) + + def test_ResidueGroup_warn_getattr_resname(self): + name = self.u.residues[0].resname + assert_warns(DeprecationWarning, lambda x: getattr(self.u.residues, x), name) + + def test_Segment_warn_getattr_resname(self): + name = self.u.residues[0].resname + assert_warns(DeprecationWarning, lambda x: getattr(self.u.segments[0], x), name) + + def test_Segment_warn_getattr_rRESNUM(self): + assert_warns(DeprecationWarning, lambda x: getattr(self.u.segments[0], x), 'r1') + + def test_SegmentGroup_warn_getattr(self): + name = self.u.segments[0].segid + assert_warns(DeprecationWarning, lambda x: getattr(self.u.segments, x), name) + + def test_SegmentGroup_nowarn_getitem(self): + assert_nowarns(DeprecationWarning, lambda x: self.u.segments[x], 0) + diff --git a/testsuite/MDAnalysisTests/util.py b/testsuite/MDAnalysisTests/util.py index 9454255eb92..fc78dd31671 100644 --- a/testsuite/MDAnalysisTests/util.py +++ b/testsuite/MDAnalysisTests/util.py @@ -40,6 +40,7 @@ import mock import os +from numpy.testing import assert_warns def block_import(package): """Block import of a given package @@ -148,3 +149,50 @@ def in_dir(dirname): os.chdir(dirname) yield dirname os.chdir(old_path) + + +def assert_nowarns(warning_class, *args, **kwargs): + """Fail if the given callable throws the specified warning. + + A warning of class warning_class should NOT be thrown by the callable when + invoked with arguments args and keyword arguments kwargs. + If a different type of warning is thrown, it will not be caught. + + Parameters + ---------- + warning_class : class + The class defining the warning that `func` is expected to throw. + func : callable + The callable to test. + \*args : Arguments + Arguments passed to `func`. + \*\*kwargs : Kwargs + Keyword arguments passed to `func`. + + Returns + ------- + True + if no `AssertionError` is raised + + Note + ---- + numpy.testing.assert_warn returns the value returned by `func`; we would + need a second func evaluation so in order to avoid it, only True is + returned if no assertion is raised. + + SeeAlso + ------- + numpy.testing.assert_warn + + """ + func = args[0] + args = args[1:] + try: + value = assert_warns(DeprecationWarning, func, *args, **kwargs) + except AssertionError: + # a warning was NOT emitted: all good + return True + else: + # There was a warning even though we do not want to see one. + raise AssertionError("function {0} raises warning of class {1}".format( + func.__name__, warning_class.__name__)) diff --git a/testsuite/MDAnalysisTests/utils/test_deprecated.py b/testsuite/MDAnalysisTests/utils/test_deprecated.py index 3a852a0f88f..c7acee6ffa9 100644 --- a/testsuite/MDAnalysisTests/utils/test_deprecated.py +++ b/testsuite/MDAnalysisTests/utils/test_deprecated.py @@ -25,6 +25,8 @@ # will be removed in 1.0) from __future__ import absolute_import +from numpy.testing import assert_raises + class TestImports(object): def test_core_units(self): try: @@ -75,3 +77,12 @@ def test_analysis_x3dna(self): except ImportError: raise AssertionError("MDAnalysis.analysis.x3dna not available") +def test_collections_NotImplementedError(): + import MDAnalysis + with assert_raises(NotImplementedError): + MDAnalysis.collection.clear() + + + + + diff --git a/testsuite/MDAnalysisTests/utils/test_failure.py b/testsuite/MDAnalysisTests/utils/test_failure.py index c05c5c6e534..6cba555d0b5 100644 --- a/testsuite/MDAnalysisTests/utils/test_failure.py +++ b/testsuite/MDAnalysisTests/utils/test_failure.py @@ -30,4 +30,4 @@ def test_failure(): # Have a file open to trigger an output from the open_files plugin. f = open('./failure.txt', 'w') if u'MDA_FAILURE_TEST' in os.environ: - assert False + raise AssertionError("the MDA_FAILURE_TEST environment variable is set")