1# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
2# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
3#
4# MDAnalysis --- https://www.mdanalysis.org
5# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
6# (see the file AUTHORS for the full list of names)
7#
8# Released under the GNU Public Licence, v2 or any higher version
9#
10# Please cite your use of MDAnalysis in published work:
11#
12# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
13# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
14# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
15# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
16# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
17#
18# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
19# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
20# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
21#
22"""
23Helper functions --- :mod:`MDAnalysis.lib.util`
24====================================================
25
26Small helper functions that don't fit anywhere else.
27
28.. versionchanged:: 0.11.0
29   Moved mathematical functions into lib.mdamath
30
31
32Files and directories
33---------------------
34
35.. autofunction:: filename
36.. autofunction:: openany
37.. autofunction:: anyopen
38.. autofunction:: greedy_splitext
39.. autofunction:: which
40.. autofunction:: realpath
41.. autofunction:: get_ext
42.. autofunction:: check_compressed_format
43.. autofunction:: format_from_filename_extension
44.. autofunction:: guess_format
45
46Streams
47-------
48
49Many of the readers are not restricted to just reading files. They can
50also use gzip-compressed or bzip2-compressed files (through the
51internal use of :func:`openany`). It is also possible to provide more
52general streams as inputs, such as a :func:`cStringIO.StringIO`
53instances (essentially, a memory buffer) by wrapping these instances
54into a :class:`NamedStream`. This :class:`NamedStream` can then be
55used in place of an ordinary file name (typically, with a
56class:`~MDAnalysis.core.universe.Universe` but it is also possible to
57*write* to such a stream using :func:`MDAnalysis.Writer`).
58
59.. rubric: Examples
60
61In the following example, we use a PDB stored as a string ``pdb_s``::
62
63   import MDAnalysis
64   from MDAnalysis.lib.util import NamedStream
65   import cStringIO
66
67   pdb_s = "TITLE     Lonely Ion\\nATOM      1  NA  NA+     1      81.260  64.982  10.926  1.00  0.00\\n"
68   u = MDAnalysis.Universe(NamedStream(cStringIO.StringIO(pdb_s), "ion.pdb"))
69   print(u)
70   #  <Universe with 1 atoms>
71   print(u.atoms.positions)
72   # [[ 81.26000214  64.98200226  10.92599964]]
73
74It is important to provide a proper pseudo file name with the correct extension
75(".pdb") to :class:`NamedStream` because the file type recognition uses the
76extension of the file name to determine the file format or alternatively
77provide the ``format="pdb"`` keyword argument to the
78:class:`~MDAnalysis.core.universe.Universe`.
79
80The use of streams becomes more interesting when MDAnalysis is used as glue
81between different analysis packages and when one can arrange things so that
82intermediate frames (typically in the PDB format) are not written to disk but
83remain in memory via e.g. :mod:`cStringIO` buffers.
84
85
86.. The following does *not* work because most readers need to
87.. reopen files, which is not possible with http streams. Might
88.. need to implement a buffer.
89..
90.. Read a test LAMMPS data file from the MDAnalysis repository::
91..
92..   import MDAnalysis
93..   from MDAnalysis.lib.util import NamedStream
94..   import urllib2
95..   URI = "https://mdanalysis.googlecode.com/git-history/develop/testsuite/MDAnalysisTests/data/mini.data"
96..   urldata = NamedStream(urllib2.urlopen(URI), "mini.data")
97..   u = MDAnalysis.Universe(urldata)
98
99.. Note::  A remote connection created by :func:`urllib2.urlopen` is not seekable
100           and therefore will often not work as an input. But try it...
101
102.. autoclass:: NamedStream
103   :members:
104
105.. autofunction:: isstream
106
107Containers and lists
108--------------------
109
110.. autofunction:: iterable
111.. autofunction:: asiterable
112.. autofunction:: hasmethod
113.. autoclass:: Namespace
114
115Arrays
116------
117
118.. autofunction:: unique_int_1d(values)
119.. autofunction:: unique_rows
120.. autofunction:: blocks_of
121
122File parsing
123------------
124
125.. autoclass:: FORTRANReader
126   :members:
127.. autodata:: FORTRAN_format_regex
128
129Data manipulation and handling
130------------------------------
131
132.. autofunction:: fixedwidth_bins
133.. autofunction:: get_weights
134.. autofunction:: ltruncate_int
135.. autofunction:: flatten_dict
136
137Strings
138-------
139
140.. autofunction:: convert_aa_code
141.. autofunction:: parse_residue
142.. autofunction:: conv_float
143
144Class decorators
145----------------
146
147.. autofunction:: cached
148
149Function decorators
150-------------------
151
152.. autofunction:: static_variables
153.. autofunction:: warn_if_not_unique
154.. autofunction:: check_coords
155
156Code management
157---------------
158
159.. autofunction:: deprecate
160.. autoclass:: _Deprecate
161.. autofunction:: dedent_docstring
162
163Data format checks
164------------------
165
166.. autofunction:: check_box
167
168.. Rubric:: Footnotes
169
170.. [#NamedStreamClose] The reason why :meth:`NamedStream.close` does
171   not close a stream by default (but just rewinds it to the
172   beginning) is so that one can use the class :class:`NamedStream` as
173   a drop-in replacement for file names, which are often re-opened
174   (e.g. when the same file is used as a topology and coordinate file
175   or when repeatedly iterating through a trajectory in some
176   implementations). The ``close=True`` keyword can be supplied in
177   order to make :meth:`NamedStream.close` actually close the
178   underlying stream and ``NamedStream.close(force=True)`` will also
179   close it.
180"""
181from __future__ import division, absolute_import
182import six
183from six.moves import range, map
184import sys
185
186__docformat__ = "restructuredtext en"
187
188
189import os
190import os.path
191import errno
192from contextlib import contextmanager
193import bz2
194import gzip
195import re
196import io
197import warnings
198import collections
199import functools
200from functools import wraps
201import textwrap
202
203import mmtf
204import numpy as np
205
206from numpy.testing import assert_equal
207import inspect
208
209from ..exceptions import StreamWarning, DuplicateWarning
210from ._cutil import unique_int_1d
211
212
213# Python 3.0, 3.1 do not have the builtin callable()
214try:
215    callable(list)
216except NameError:
217    # http://bugs.python.org/issue10518
218    import collections
219
220    def callable(obj):
221        return isinstance(obj, collections.Callable)
222
223try:
224    from os import PathLike
225except ImportError:
226   class PathLike(object):
227       pass
228
229
230
231def filename(name, ext=None, keep=False):
232    """Return a new name that has suffix attached; replaces other extensions.
233
234    Parameters
235    ----------
236    name : str or NamedStream
237        filename; extension is replaced unless ``keep=True``;
238        `name` can also be a :class:`NamedStream` (and its
239        :attr:`NamedStream.name` will be changed accordingly)
240    ext : None or str
241        extension to use in the new filename
242    keep : bool
243        - ``False``: replace existing extension with `ext`;
244        - ``True``: keep old extension if one existed
245
246
247    .. versionchanged:: 0.9.0
248       Also permits :class:`NamedStream` to pass through.
249    """
250    if ext is not None:
251        if not ext.startswith(os.path.extsep):
252            ext = os.path.extsep + ext
253        root, origext = os.path.splitext(name)
254        if not keep or len(origext) == 0:
255            newname = root + ext
256            if isstream(name):
257                name.name = newname
258            else:
259                name = newname
260    return name if isstream(name) else str(name)
261
262
263@contextmanager
264def openany(datasource, mode='rt', reset=True):
265    """Context manager for :func:`anyopen`.
266
267    Open the `datasource` and close it when the context of the :keyword:`with`
268    statement exits.
269
270    `datasource` can be a filename or a stream (see :func:`isstream`). A stream
271    is reset to its start if possible (via :meth:`~io.IOBase.seek` or
272    :meth:`~cString.StringIO.reset`).
273
274    The advantage of this function is that very different input sources
275    ("streams") can be used for a "file", ranging from files on disk (including
276    compressed files) to open file objects to sockets and strings---as long as
277    they have a file-like interface.
278
279    Parameters
280    ----------
281    datasource : a file or a stream
282    mode : {'r', 'w'} (optional)
283        open in r(ead) or w(rite) mode
284    reset : bool (optional)
285        try to read (`mode` 'r') the stream from the start [``True``]
286
287    Examples
288    --------
289    Open a gzipped file and process it line by line::
290
291        with openany("input.pdb.gz") as pdb:
292            for line in pdb:
293                if line.startswith('ATOM'):
294                    print(line)
295
296    Open a URL and read it::
297
298       import urllib2
299       with openany(urllib2.urlopen("https://www.mdanalysis.org/")) as html:
300           print(html.read())
301
302
303    See Also
304    --------
305    :func:`anyopen`
306    """
307    stream = anyopen(datasource, mode=mode, reset=reset)
308    try:
309        yield stream
310    finally:
311        stream.close()
312
313
314# On python 3, we want to use bz2.open to open and uncompress bz2 files. That
315# function allows to specify the type of the uncompressed file (bytes ot text).
316# The function does not exist in python 2, thus we must use bz2.BZFile to
317# which we cannot tell if the uncompressed file contains bytes or text.
318# Therefore, on python 2 we use a proxy function that removes the type of the
319# uncompressed file from the `mode` argument.
320try:
321    bz2.open
322except AttributeError:
323    # We are on python 2 and bz2.open is not available
324    def bz2_open(filename, mode):
325        """Open and uncompress a BZ2 file"""
326        mode = mode.replace('t', '').replace('b', '')
327        return bz2.BZ2File(filename, mode)
328else:
329    # We are on python 3 so we can use bz2.open
330    bz2_open = bz2.open
331
332
333def anyopen(datasource, mode='rt', reset=True):
334    """Open datasource (gzipped, bzipped, uncompressed) and return a stream.
335
336    `datasource` can be a filename or a stream (see :func:`isstream`). By
337    default, a stream is reset to its start if possible (via
338    :meth:`~io.IOBase.seek` or :meth:`~cString.StringIO.reset`).
339
340    If possible, the attribute ``stream.name`` is set to the filename or
341    "<stream>" if no filename could be associated with the *datasource*.
342
343    Parameters
344    ----------
345    datasource
346        a file (from :class:`file` or :func:`open`) or a stream (e.g. from
347        :func:`urllib2.urlopen` or :class:`cStringIO.StringIO`)
348    mode: {'r', 'w', 'a'} (optional)
349        Open in r(ead), w(rite) or a(ppen) mode. More complicated
350        modes ('r+', 'w+', ...) are not supported; only the first letter of
351        `mode` is used and thus any additional modifiers are silently ignored.
352    reset: bool (optional)
353        try to read (`mode` 'r') the stream from the start
354
355    Returns
356    -------
357    file-like object
358
359    See Also
360    --------
361    :func:`openany`
362      to be used with the :keyword:`with` statement.
363
364
365    .. versionchanged:: 0.9.0
366       Only returns the ``stream`` and tries to set ``stream.name = filename`` instead of the previous
367       behavior to return a tuple ``(stream, filename)``.
368
369    """
370    handlers = {'bz2': bz2_open, 'gz': gzip.open, '': open}
371
372    if mode.startswith('r'):
373        if isstream(datasource):
374            stream = datasource
375            try:
376                filename = str(stream.name)  # maybe that does not always work?
377            except AttributeError:
378                filename = "<stream>"
379            if reset:
380                try:
381                    stream.reset()
382                except (AttributeError, IOError):
383                    try:
384                        stream.seek(0)
385                    except (AttributeError, IOError):
386                        warnings.warn("Stream {0}: not guaranteed to be at the beginning."
387                                      "".format(filename),
388                                      category=StreamWarning)
389        else:
390            stream = None
391            filename = datasource
392            for ext in ('bz2', 'gz', ''):  # file == '' should be last
393                openfunc = handlers[ext]
394                stream = _get_stream(datasource, openfunc, mode=mode)
395                if stream is not None:
396                    break
397            if stream is None:
398                raise IOError(errno.EIO, "Cannot open file or stream in mode={mode!r}.".format(**vars()), repr(filename))
399    elif mode.startswith('w') or mode.startswith('a'):  # append 'a' not tested...
400        if isstream(datasource):
401            stream = datasource
402            try:
403                filename = str(stream.name)  # maybe that does not always work?
404            except AttributeError:
405                filename = "<stream>"
406        else:
407            stream = None
408            filename = datasource
409            name, ext = os.path.splitext(filename)
410            if ext.startswith('.'):
411                ext = ext[1:]
412            if not ext in ('bz2', 'gz'):
413                ext = ''  # anything else but bz2 or gz is just a normal file
414            openfunc = handlers[ext]
415            stream = openfunc(datasource, mode=mode)
416            if stream is None:
417                raise IOError(errno.EIO, "Cannot open file or stream in mode={mode!r}.".format(**vars()), repr(filename))
418    else:
419        raise NotImplementedError("Sorry, mode={mode!r} is not implemented for {datasource!r}".format(**vars()))
420    try:
421        stream.name = filename
422    except (AttributeError, TypeError):
423        pass  # can't set name (e.g. cStringIO.StringIO)
424    return stream
425
426
427def _get_stream(filename, openfunction=open, mode='r'):
428    """Return open stream if *filename* can be opened with *openfunction* or else ``None``."""
429    try:
430        stream = openfunction(filename, mode=mode)
431    except (IOError, OSError) as err:
432        # An exception might be raised due to two reasons, first the openfunction is unable to open the file, in this
433        # case we have to ignore the error and return None. Second is when openfunction can't open the file because
434        # either the file isn't there or the permissions don't allow access.
435        if errno.errorcode[err.errno] in ['ENOENT', 'EACCES']:
436            six.reraise(*sys.exc_info())
437        return None
438    if mode.startswith('r'):
439        # additional check for reading (eg can we uncompress) --- is this needed?
440        try:
441            stream.readline()
442        except IOError:
443            stream.close()
444            stream = None
445        except:
446            stream.close()
447            raise
448        else:
449            stream.close()
450            stream = openfunction(filename, mode=mode)
451    return stream
452
453
454def greedy_splitext(p):
455    """Split extension in path *p* at the left-most separator.
456
457    Extensions are taken to be separated from the filename with the
458    separator :data:`os.extsep` (as used by :func:`os.path.splitext`).
459
460    Arguments
461    ---------
462    p : str
463       path
464
465    Returns
466    -------
467    (root, extension) : tuple
468          where ``root`` is the full path and filename with all
469          extensions removed whereas ``extension`` is the string of
470          all extensions.
471
472    Example
473    -------
474    >>> greedy_splitext("/home/joe/protein.pdb.bz2")
475    ('/home/joe/protein', '.pdb.bz2')
476
477    """
478    path, root = os.path.split(p)
479    extension = ''
480    while True:
481        root, ext = os.path.splitext(root)
482        extension = ext + extension
483        if not ext:
484            break
485    return os.path.join(path, root), extension
486
487
488def hasmethod(obj, m):
489    """Return ``True`` if object *obj* contains the method *m*."""
490    return hasattr(obj, m) and callable(getattr(obj, m))
491
492
493def isstream(obj):
494    """Detect if `obj` is a stream.
495
496    We consider anything a stream that has the methods
497
498    - ``close()``
499
500    and either set of the following
501
502    - ``read()``, ``readline()``, ``readlines()``
503    - ``write()``, ``writeline()``, ``writelines()``
504
505    Parameters
506    ----------
507    obj : stream or str
508
509    Returns
510    -------
511    bool
512        ``True`` if `obj` is a stream, ``False`` otherwise
513
514    See Also
515    --------
516    :mod:`io`
517
518
519    .. versionadded:: 0.9.0
520    """
521    signature_methods = ("close",)
522    alternative_methods = (
523        ("read", "readline", "readlines"),
524        ("write", "writeline", "writelines"))
525
526    # Must have ALL the signature methods
527    for m in signature_methods:
528        if not hasmethod(obj, m):
529            return False
530    # Must have at least one complete set of alternative_methods
531    alternative_results = [
532        np.all([hasmethod(obj, m) for m in alternatives])
533        for alternatives in alternative_methods]
534    return np.any(alternative_results)
535
536
537def which(program):
538    """Determine full path of executable `program` on :envvar:`PATH`.
539
540    (Jay at http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python)
541
542    Parameters
543    ----------
544    programe : str
545       name of the executable
546
547    Returns
548    -------
549    path : str or None
550       absolute path to the executable if it can be found, else ``None``
551
552    """
553
554    def is_exe(fpath):
555        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
556
557    fpath, fname = os.path.split(program)
558    if fpath:
559        real_program = realpath(program)
560        if is_exe(real_program):
561            return real_program
562    else:
563        for path in os.environ["PATH"].split(os.pathsep):
564            exe_file = os.path.join(path, program)
565            if is_exe(exe_file):
566                return exe_file
567    return None
568
569
570@functools.total_ordering
571class NamedStream(io.IOBase, PathLike):
572    """Stream that also provides a (fake) name.
573
574    By wrapping a stream `stream` in this class, it can be passed to
575    code that uses inspection of the filename to make decisions. For
576    instance. :func:`os.path.split` will work correctly on a
577    :class:`NamedStream`.
578
579    The class can be used as a context manager.
580
581    :class:`NamedStream` is derived from :class:`io.IOBase` (to indicate that
582    it is a stream). Many operations that normally expect a string will also
583    work with a :class:`NamedStream`; for instance, most of the functions in
584    :mod:`os.path` will work with the exception of :func:`os.path.expandvars`
585    and :func:`os.path.expanduser`, which will return the :class:`NamedStream`
586    itself instead of a string if no substitutions were made.
587
588    Example
589    -------
590    Wrap a :func:`cStringIO.StringIO` instance to write to::
591
592      import cStringIO
593      import os.path
594      stream = cStringIO.StringIO()
595      f = NamedStream(stream, "output.pdb")
596      print(os.path.splitext(f))
597
598    Wrap a :class:`file` instance to read from::
599
600      stream = open("input.pdb")
601      f = NamedStream(stream, stream.name)
602
603    Use as a context manager (closes stream automatically when the
604    :keyword:`with` block is left)::
605
606      with NamedStream(open("input.pdb"), "input.pdb") as f:
607         # use f
608         print f.closed  # --> False
609         # ...
610      print f.closed     # --> True
611
612    Note
613    ----
614    This class uses its own :meth:`__getitem__` method so if `stream`
615    implements :meth:`stream.__getitem__` then that will be masked and
616    this class should not be used.
617
618    Warning
619    -------
620    By default, :meth:`NamedStream.close` will **not close the
621    stream** but instead :meth:`~NamedStream.reset` it to the
622    beginning. [#NamedStreamClose]_ Provide the ``force=True`` keyword
623    to :meth:`NamedStream.close` to always close the stream.
624
625    """
626
627    def __init__(self, stream, filename, reset=True, close=False):
628        """Initialize the :class:`NamedStream` from a `stream` and give it a `name`.
629
630        The constructor attempts to rewind the stream to the beginning unless
631        the keyword `reset` is set to ``False``. If rewinding fails, a
632        :class:`MDAnalysis.StreamWarning` is issued.
633
634        Parameters
635        ----------
636        stream : stream
637            an open stream (e.g. :class:`file` or :func:`cStringIO.StringIO`)
638        filename : str
639            the filename that should be associated with the stream
640        reset : bool (optional)
641            start the stream from the beginning (either :meth:`reset` or :meth:`seek`)
642            when the class instance is constructed
643        close : bool (optional)
644            close the stream when a :keyword:`with` block exits or when
645            :meth:`close` is called; note that the default is **not to close
646            the stream**
647
648        Notes
649        -----
650        By default, this stream will *not* be closed by :keyword:`with` and
651        :meth:`close` (see there) unless the `close` keyword is set to
652        ``True``.
653
654
655        .. versionadded:: 0.9.0
656        """
657        # constructing the class from an instance of itself has weird behavior
658        # on __del__ and super on python 3. Let's warn the user and ensure the
659        # class works normally.
660        if isinstance(stream, NamedStream):
661            warnings.warn("Constructed NamedStream from a NamedStream",
662                          RuntimeWarning)
663            stream = stream.stream
664        self.stream = stream
665        self.name = filename
666        self.close_stream = close
667        if reset:
668            self.reset()
669
670    def reset(self):
671        """Move to the beginning of the stream"""
672        # try to rewind
673        try:
674            self.stream.reset()  # e.g. StreamIO
675        except (AttributeError, IOError):
676            try:
677                self.stream.seek(0)  # typical file objects
678            except (AttributeError, IOError):
679                warnings.warn("NamedStream {0}: not guaranteed to be at the beginning."
680                              "".format(self.name),
681                              category=StreamWarning)
682
683    # access the stream
684    def __getattr__(self, x):
685        try:
686            return getattr(self.stream, x)
687        except AttributeError:
688            return getattr(self.name, x)
689
690    def __iter__(self):
691        return iter(self.stream)
692
693    def __next__(self):
694        return self.stream.__next__()
695
696    def __enter__(self):
697        # do not call the stream's __enter__ because the stream is already open
698        return self
699
700    def __exit__(self, *args):
701        # NOTE: By default (close=False) we only reset the stream and NOT close it; this makes
702        #       it easier to use it as a drop-in replacement for a filename that might
703        #       be opened repeatedly (at least in MDAnalysis)
704        #try:
705        #    return self.stream.__exit__(*args)
706        #except AttributeError:
707        #    super(NamedStream, self).__exit__(*args)
708        self.close()
709
710    def __fspath__(self):
711        return self.name
712
713    # override more IOBase methods, as these are provided by IOBase and are not
714    # caught with __getattr__ (ugly...)
715    def close(self, force=False):
716        """Reset or close the stream.
717
718        If :attr:`NamedStream.close_stream` is set to ``False`` (the default)
719        then this method will *not close the stream* and only :meth:`reset` it.
720
721        If the *force* = ``True`` keyword is provided, the stream will be
722        closed.
723
724        .. Note:: This ``close()`` method is non-standard. ``del NamedStream``
725                  always closes the underlying stream.
726
727        """
728        if self.close_stream or force:
729            try:
730                return self.stream.close()
731            except AttributeError:
732                return super(NamedStream, self).close()
733        else:
734            self.flush()
735            self.reset()
736
737    def __del__(self):
738        """Always closes the stream."""
739        self.close(force=True)
740
741    @property
742    def closed(self):
743        """``True`` if stream is closed."""
744        try:
745            return self.stream.closed
746        except AttributeError:
747            return super(NamedStream, self).closed
748
749    def seek(self, offset, whence=os.SEEK_SET):
750        """Change the stream position to the given byte `offset` .
751
752        Parameters
753        ----------
754        offset : int
755             `offset` is interpreted relative to the position
756             indicated by `whence`.
757        whence : {0, 1, 2} (optional)
758             Values for `whence` are:
759
760               - :data:`io.SEEK_SET` or 0 – start of the stream (the default); `offset`
761                 should be zero or positive
762               - :data:`io.SEEK_CUR` or 1 – current stream position; `offset` may be
763                 negative
764               - :data:`io.SEEK_END` or 2 – end of the stream; `offset` is usually
765                 negative
766
767        Returns
768        -------
769        int
770            the new absolute position in bytes.
771
772        """
773        try:
774            return self.stream.seek(offset, whence)  # file.seek: no kw
775        except AttributeError:
776            return super(NamedStream, self).seek(offset, whence)
777
778    def tell(self):
779        """Return the current stream position."""
780        try:
781            return self.stream.tell()
782        except AttributeError:
783            return super(NamedStream, self).tell()
784
785    def truncate(self, *size):
786        """Truncate the stream's size to `size`.
787
788        Parameters
789        ----------
790        size : int (optional)
791             The `size` defaults to the current position (if no `size` argument
792             is supplied). The current file position is not changed.
793
794        """
795        try:
796            return self.stream.truncate(*size)
797        except AttributeError:
798            return super(NamedStream, self).truncate(*size)
799
800    def seekable(self):
801        """Return ``True`` if the stream supports random access.
802
803        If ``False``, :meth:`seek`, :meth:`tell` and :meth:`truncate` will
804        raise :exc:`IOError`.
805
806        """
807        try:
808            return self.stream.seekable()
809        except AttributeError:
810            return super(NamedStream, self).seekable()
811
812    def readable(self):
813        """Return ``True`` if the stream can be read from.
814
815        If ``False``, :meth:`read` will raise :exc:`IOError`.
816        """
817        try:
818            return self.stream.readable()
819        except AttributeError:
820            return super(NamedStream, self).readable()
821
822    def writable(self):
823        """Return ``True`` if the stream can be written to.
824
825        If ``False``, :meth:`write` will raise :exc:`IOError`.
826        """
827        try:
828            return self.stream.writable()
829        except AttributeError:
830            return super(NamedStream, self).writable()
831
832    def flush(self):
833        """Flush the write buffers of the stream if applicable.
834
835        This does nothing for read-only and non-blocking streams. For file
836        objects one also needs to call :func:`os.fsync` to write contents to
837        disk.
838        """
839        try:
840            return self.stream.flush()
841        except AttributeError:
842            return super(NamedStream, self).flush()
843
844    def fileno(self):
845        """Return the underlying file descriptor (an integer) of the stream if it exists.
846
847        An :exc:`IOError` is raised if the IO object does not use a file descriptor.
848        """
849        try:
850            return self.stream.fileno()
851        except AttributeError:
852            # IOBase.fileno does not raise IOError as advertised so we do this here
853            raise IOError("This NamedStream does not use a file descriptor.")
854
855    def readline(self):
856        try:
857            return self.stream.readline()
858        except AttributeError:
859            return super(NamedStream, self).readline()
860
861    # fake the important parts of the string API
862    # (other methods such as rfind() are automatically dealt with via __getattr__)
863    def __getitem__(self, x):
864        return self.name[x]
865
866    def __eq__(self, x):
867        return self.name == x
868
869    def __ne__(self, x):
870        return not self == x
871
872    def __lt__(self, x):
873        return self.name < x
874
875    def __len__(self):
876        return len(self.name)
877
878    def __add__(self, x):
879        return self.name + x
880
881    def __radd__(self, x):
882        return x + self.name
883
884    def __mul__(self, x):
885        return self.name * x
886
887    __rmul__ = __mul__
888
889    def __format__(self, format_spec):
890        return self.name.format(format_spec)
891
892    def __str__(self):
893        return self.name
894
895    def __repr__(self):
896        return "<NamedStream({0}, {1})>".format(self.stream, self.name)
897
898
899def realpath(*args):
900    """Join all args and return the real path, rooted at ``/``.
901
902    Expands '~', '~user', and environment variables such as :envvar:`$HOME`.
903
904    Returns ``None`` if any of the args is ``None``.
905    """
906    if None in args:
907        return None
908    return os.path.realpath(os.path.expanduser(os.path.expandvars(os.path.join(*args))))
909
910
911def get_ext(filename):
912    """Return the lower-cased extension of `filename` without a leading dot.
913
914    Parameters
915    ----------
916    filename : str
917
918    Returns
919    -------
920    root : str
921    ext : str
922    """
923    root, ext = os.path.splitext(filename)
924    if ext.startswith(os.extsep):
925        ext = ext[1:]
926    return root, ext.lower()
927
928
929def check_compressed_format(root, ext):
930    """Check if this is a supported gzipped/bzip2ed file format and return UPPERCASE format.
931
932    Parameters
933    ----------
934    root : str
935       path of a file, without extension `ext`
936    ext : str
937       extension (currently only "bz2" and "gz" are recognized as compressed formats)
938
939    Returns
940    -------
941    format : str
942       upper case format extension *if* the compression can be handled by
943       :func:`openany`
944
945    See Also
946    --------
947    openany : function that is used to open and decompress formats on the fly; only
948              compression formats implemented in :func:`openany` are recognized
949
950    """
951    # XYZReader&others are setup to handle both plain and compressed (bzip2, gz) files
952    # ..so if the first file extension is bzip2 or gz, look at the one to the left of it
953    if ext.lower() in ("bz2", "gz"):
954        try:
955            root, ext = get_ext(root)
956        except:
957            raise TypeError("Cannot determine coordinate format for '{0}.{1}'"
958                            "".format(root, ext))
959
960    return ext.upper()
961
962
963def format_from_filename_extension(filename):
964    """Guess file format from the file extension.
965
966    Parameters
967    ----------
968    filename : str
969
970    Returns
971    -------
972    format : str
973
974    Raises
975    ------
976    TypeError
977        if the file format cannot be determined
978    """
979    try:
980        root, ext = get_ext(filename)
981    except:
982        raise TypeError(
983            "Cannot determine file format for file '{0}'.\n"
984            "           You can set the format explicitly with "
985            "'Universe(..., format=FORMAT)'.".format(filename))
986    format = check_compressed_format(root, ext)
987    return format
988
989
990def guess_format(filename):
991    """Return the format of `filename`
992
993    The current heuristic simply looks at the filename extension and can work
994    around compressed format extensions.
995
996    Parameters
997    ----------
998    filename : str or stream
999        path to the file or a stream, in which case ``filename.name`` is looked
1000        at for a hint to the format
1001
1002    Returns
1003    -------
1004    format : str
1005        format specifier (upper case)
1006
1007    Raises
1008    ------
1009    ValueError
1010        if the heuristics are insufficient to guess a supported format
1011
1012
1013    .. versionadded:: 0.11.0
1014       Moved into lib.util
1015
1016    """
1017    if isstream(filename):
1018        # perhaps StringIO or open stream
1019        try:
1020            format = format_from_filename_extension(filename.name)
1021        except AttributeError:
1022            # format is None so we need to complain:
1023            raise ValueError("guess_format requires an explicit format specifier "
1024                             "for stream {0}".format(filename))
1025    else:
1026        # iterator, list, filename: simple extension checking... something more
1027        # complicated is left for the ambitious.
1028        # Note: at the moment the upper-case extension *is* the format specifier
1029        # and list of filenames is handled by ChainReader
1030        format = (format_from_filename_extension(filename)
1031                  if not iterable(filename) else 'CHAIN')
1032
1033    return format.upper()
1034
1035
1036def iterable(obj):
1037    """Returns ``True`` if `obj` can be iterated over and is *not* a  string
1038    nor a :class:`NamedStream`"""
1039    if isinstance(obj, (six.string_types, NamedStream)):
1040        return False  # avoid iterating over characters of a string
1041
1042    if hasattr(obj, 'next'):
1043        return True  # any iterator will do
1044    try:
1045        len(obj)  # anything else that might work
1046    except (TypeError, AttributeError):
1047        return False
1048    return True
1049
1050
1051def asiterable(obj):
1052    """Returns `obj` so that it can be iterated over.
1053
1054    A string is *not* detected as and iterable and is wrapped into a :class:`list`
1055    with a single element.
1056
1057    See Also
1058    --------
1059    iterable
1060
1061    """
1062    if not iterable(obj):
1063        obj = [obj]
1064    return obj
1065
1066#: Regular expresssion (see :mod:`re`) to parse a simple `FORTRAN edit descriptor`_.
1067#: ``(?P<repeat>\d?)(?P<format>[IFELAX])(?P<numfmt>(?P<length>\d+)(\.(?P<decimals>\d+))?)?``
1068#:
1069#: .. _FORTRAN edit descriptor: http://www.cs.mtu.edu/~shene/COURSES/cs201/NOTES/chap05/format.html
1070FORTRAN_format_regex = "(?P<repeat>\d+?)(?P<format>[IFEAX])(?P<numfmt>(?P<length>\d+)(\.(?P<decimals>\d+))?)?"
1071_FORTRAN_format_pattern = re.compile(FORTRAN_format_regex)
1072
1073
1074def strip(s):
1075    """Convert `s` to a string and return it white-space stripped."""
1076    return str(s).strip()
1077
1078
1079class FixedcolumnEntry(object):
1080    """Represent an entry at specific fixed columns.
1081
1082    Reads from line[start:stop] and converts according to
1083    typespecifier.
1084    """
1085    convertors = {'I': int, 'F': float, 'E': float, 'A': strip}
1086
1087    def __init__(self, start, stop, typespecifier):
1088        """
1089        Parameters
1090        ----------
1091        start : int
1092            first column
1093        stop : int
1094            last column + 1
1095        typespecifier : str
1096            'I': int, 'F': float, 'E': float, 'A': stripped string
1097
1098        The start/stop arguments follow standard Python convention in that
1099        they are 0-based and that the *stop* argument is not included.
1100        """
1101        self.start = start
1102        self.stop = stop
1103        self.typespecifier = typespecifier
1104        self.convertor = self.convertors[typespecifier]
1105
1106    def read(self, line):
1107        """Read the entry from `line` and convert to appropriate type."""
1108        try:
1109            return self.convertor(line[self.start:self.stop])
1110        except ValueError:
1111            raise ValueError("{0!r}: Failed to read&convert {1!r}".format(self, line[self.start:self.stop]))
1112
1113    def __len__(self):
1114        """Length of the field in columns (stop - start)"""
1115        return self.stop - self.start
1116
1117    def __repr__(self):
1118        return "FixedcolumnEntry({0:d},{1:d},{2!r})".format(self.start, self.stop, self.typespecifier)
1119
1120
1121class FORTRANReader(object):
1122    """FORTRANReader provides a method to parse FORTRAN formatted lines in a file.
1123
1124    The contents of lines in a file can be parsed according to FORTRAN format
1125    edit descriptors (see `Fortran Formats`_ for the syntax).
1126
1127    Only simple one-character specifiers supported here: *I F E A X* (see
1128    :data:`FORTRAN_format_regex`).
1129
1130    Strings are stripped of leading and trailing white space.
1131
1132    .. _`Fortran Formats`: http://www.webcitation.org/5xbaWMV2x
1133    .. _`Fortran Formats (URL)`:
1134       http://www.cs.mtu.edu/~shene/COURSES/cs201/NOTES/chap05/format.html
1135
1136    """
1137
1138    def __init__(self, fmt):
1139        """Set up the reader with the FORTRAN format string.
1140
1141        The string `fmt` should look like '2I10,2X,A8,2X,A8,3F20.10,2X,A8,2X,A8,F20.10'.
1142
1143        Parameters
1144        ----------
1145        fmt : str
1146           FORTRAN format edit descriptor for a line as described in `Fortran
1147           Formats`_
1148
1149        Example
1150        -------
1151        Parsing of a standard CRD file::
1152
1153           atomformat = FORTRANReader('2I10,2X,A8,2X,A8,3F20.10,2X,A8,2X,A8,F20.10')
1154           for line in open('coordinates.crd'):
1155               serial,TotRes,resName,name,x,y,z,chainID,resSeq,tempFactor = atomformat.read(line)
1156
1157        """
1158        self.fmt = fmt.split(',')
1159        descriptors = [self.parse_FORTRAN_format(descriptor) for descriptor in self.fmt]
1160        start = 0
1161        self.entries = []
1162        for d in descriptors:
1163            if d['format'] != 'X':
1164                for x in range(d['repeat']):
1165                    stop = start + d['length']
1166                    self.entries.append(FixedcolumnEntry(start, stop, d['format']))
1167                    start = stop
1168            else:
1169                start += d['totallength']
1170
1171    def read(self, line):
1172        """Parse `line` according to the format string and return list of values.
1173
1174        Values are converted to Python types according to the format specifier.
1175
1176        Parameters
1177        ----------
1178        line : str
1179
1180        Returns
1181        -------
1182        list
1183            list of entries with appropriate types
1184
1185        Raises
1186        ------
1187        ValueError
1188            Any of the conversions cannot be made (e.g. space for an int)
1189
1190        See Also
1191        --------
1192        :meth:`FORTRANReader.number_of_matches`
1193        """
1194        return [e.read(line) for e in self.entries]
1195
1196    def number_of_matches(self, line):
1197        """Return how many format entries could be populated with legal values."""
1198        # not optimal, I suppose...
1199        matches = 0
1200        for e in self.entries:
1201            try:
1202                e.read(line)
1203                matches += 1
1204            except ValueError:
1205                pass
1206        return matches
1207
1208    def parse_FORTRAN_format(self, edit_descriptor):
1209        """Parse the descriptor.
1210
1211
1212        Parameters
1213        ----------
1214        edit_descriptor : str
1215            FORTRAN format edit descriptor
1216
1217        Returns
1218        -------
1219        dict
1220            dict with totallength (in chars), repeat, length, format, decimals
1221
1222        Raises
1223        ------
1224        ValueError
1225            The `edit_descriptor` is not recognized and cannot be parsed
1226
1227        Note
1228        ----
1229        Specifiers: *L ES EN T TL TR / r S SP SS BN BZ* are *not* supported,
1230        and neither are the scientific notation *Ew.dEe* forms.
1231
1232        """
1233
1234        m = _FORTRAN_format_pattern.match(edit_descriptor.upper())
1235        if m is None:
1236            try:
1237                m = _FORTRAN_format_pattern.match("1" + edit_descriptor.upper())
1238                if m is None:
1239                    raise ValueError  # really no idea what the descriptor is supposed to mean
1240            except:
1241                raise ValueError("unrecognized FORTRAN format {0!r}".format(edit_descriptor))
1242        d = m.groupdict()
1243        if d['repeat'] == '':
1244            d['repeat'] = 1
1245        if d['format'] == 'X':
1246            d['length'] = 1
1247        for k in ('repeat', 'length', 'decimals'):
1248            try:
1249                d[k] = int(d[k])
1250            except ValueError:  # catches ''
1251                d[k] = 0
1252            except TypeError:  # keep None
1253                pass
1254        d['totallength'] = d['repeat'] * d['length']
1255        return d
1256
1257    def __len__(self):
1258        """Returns number of entries."""
1259        return len(self.entries)
1260
1261    def __repr__(self):
1262        return self.__class__.__name__ + "(" + ",".join(self.fmt) + ")"
1263
1264
1265def fixedwidth_bins(delta, xmin, xmax):
1266    """Return bins of width `delta` that cover `xmin`, `xmax` (or a larger range).
1267
1268    The bin parameters are computed such that the bin size `delta` is
1269    guaranteed. In order to achieve this, the range `[xmin, xmax]` can be
1270    increased.
1271
1272    Bins can be calculated for 1D data (then all parameters are simple floats)
1273    or nD data (then parameters are supplied as arrays, with each entry
1274    correpsonding to one dimension).
1275
1276    Parameters
1277    ----------
1278    delta : float or array_like
1279        desired spacing of the bins
1280    xmin : float or array_like
1281        lower bound (left boundary of first bin)
1282    xmax : float or array_like
1283        upper bound (right boundary of last bin)
1284
1285    Returns
1286    -------
1287    dict
1288        The dict contains 'Nbins', 'delta', 'min', and 'max'; these are either
1289        floats or arrays, depending on the input.
1290
1291    Example
1292    -------
1293    Use with :func:`numpy.histogram`::
1294
1295       B = fixedwidth_bins(delta, xmin, xmax)
1296       h, e = np.histogram(data, bins=B['Nbins'], range=(B['min'], B['max']))
1297
1298    """
1299    if not np.all(xmin < xmax):
1300        raise ValueError('Boundaries are not sane: should be xmin < xmax.')
1301    _delta = np.asarray(delta, dtype=np.float_)
1302    _xmin = np.asarray(xmin, dtype=np.float_)
1303    _xmax = np.asarray(xmax, dtype=np.float_)
1304    _length = _xmax - _xmin
1305    N = np.ceil(_length / _delta).astype(np.int_)  # number of bins
1306    dx = 0.5 * (N * _delta - _length)  # add half of the excess to each end
1307    return {'Nbins': N, 'delta': _delta, 'min': _xmin - dx, 'max': _xmax + dx}
1308
1309def get_weights(atoms, weights):
1310    """Check that a `weights` argument is compatible with `atoms`.
1311
1312    Parameters
1313    ----------
1314    atoms : AtomGroup or array_like
1315        The atoms that the `weights` should be applied to. Typically this
1316        is a :class:`AtomGroup` but because only the length is compared,
1317        any sequence for which ``len(atoms)`` is defined is acceptable.
1318    weights : {"mass", None} or array_like
1319        All MDAnalysis functions or classes understand "mass" and will then
1320        use ``atoms.masses``. ``None`` indicates equal weights for all atoms.
1321        Using an ``array_like`` assigns a custom weight to each element of
1322        `atoms`.
1323
1324    Returns
1325    -------
1326    weights : array_like or None
1327         If "mass" was selected, ``atoms.masses`` is returned, otherwise the
1328         value of `weights` (which can be ``None``).
1329
1330    Raises
1331    ------
1332    TypeError
1333        If `weights` is not one of the allowed values or if "mass" is
1334        selected but ``atoms.masses`` is not available.
1335    ValueError
1336        If `weights` is not a 1D array with the same length as
1337        `atoms`, then the exception is raised.  :exc:`TypeError` is
1338        also raised if ``atoms.masses`` is not defined.
1339
1340    """
1341    if not iterable(weights) and weights == "mass":
1342        try:
1343            weights = atoms.masses
1344        except AttributeError:
1345            raise TypeError("weights='mass' selected but atoms.masses is missing")
1346
1347    if iterable(weights):
1348        if len(np.asarray(weights).shape) != 1:
1349            raise ValueError("weights must be a 1D array, not with shape "
1350                            "{0}".format(np.asarray(weights).shape))
1351        elif len(weights) != len(atoms):
1352            raise ValueError("weights (length {0}) must be of same length as "
1353                            "the atoms ({1})".format(
1354                                len(weights), len(atoms)))
1355    elif weights is not None:
1356        raise TypeError("weights must be {'mass', None} or an iterable of the "
1357                        "same size as the atomgroup.")
1358
1359    return weights
1360
1361
1362# String functions
1363# ----------------
1364
1365#: translation table for 3-letter codes --> 1-letter codes
1366#: .. SeeAlso:: :data:`alternative_inverse_aa_codes`
1367canonical_inverse_aa_codes = {
1368    'ALA': 'A', 'CYS': 'C', 'ASP': 'D', 'GLU': 'E',
1369    'PHE': 'F', 'GLY': 'G', 'HIS': 'H', 'ILE': 'I',
1370    'LYS': 'K', 'LEU': 'L', 'MET': 'M', 'ASN': 'N',
1371    'PRO': 'P', 'GLN': 'Q', 'ARG': 'R', 'SER': 'S',
1372    'THR': 'T', 'VAL': 'V', 'TRP': 'W', 'TYR': 'Y'}
1373#: translation table for 1-letter codes --> *canonical* 3-letter codes.
1374#: The table is used for :func:`convert_aa_code`.
1375amino_acid_codes = {one: three for three, one in canonical_inverse_aa_codes.items()}
1376#: non-default charge state amino acids or special charge state descriptions
1377#: (Not fully synchronized with :class:`MDAnalysis.core.selection.ProteinSelection`.)
1378alternative_inverse_aa_codes = {
1379    'HISA': 'H', 'HISB': 'H', 'HSE': 'H', 'HSD': 'H', 'HID': 'H', 'HIE': 'H', 'HIS1': 'H',
1380    'HIS2': 'H',
1381    'ASPH': 'D', 'ASH': 'D',
1382    'GLUH': 'E', 'GLH': 'E',
1383    'LYSH': 'K', 'LYN': 'K',
1384    'ARGN': 'R',
1385    'CYSH': 'C', 'CYS1': 'C', 'CYS2': 'C'}
1386#: lookup table from 3/4 letter resnames to 1-letter codes. Note that non-standard residue names
1387#: for tautomers or different protonation states such as HSE are converted to canonical 1-letter codes ("H").
1388#: The table is used for :func:`convert_aa_code`.
1389#: .. SeeAlso:: :data:`canonical_inverse_aa_codes` and :data:`alternative_inverse_aa_codes`
1390inverse_aa_codes = {}
1391inverse_aa_codes.update(canonical_inverse_aa_codes)
1392inverse_aa_codes.update(alternative_inverse_aa_codes)
1393
1394
1395def convert_aa_code(x):
1396    """Converts between 3-letter and 1-letter amino acid codes.
1397
1398    Parameters
1399    ----------
1400    x : str
1401        1-letter or 3-letter amino acid code
1402
1403    Returns
1404    -------
1405    str
1406        3-letter or 1-letter amino acid code
1407
1408    Raises
1409    ------
1410    ValueError
1411        No conversion can be made; the amino acid code is not defined.
1412
1413    Note
1414    ----
1415    Data are defined in :data:`amino_acid_codes` and :data:`inverse_aa_codes`.
1416    """
1417    if len(x) == 1:
1418        d = amino_acid_codes
1419    else:
1420        d = inverse_aa_codes
1421
1422    try:
1423        return d[x.upper()]
1424    except KeyError:
1425        raise ValueError("No conversion for {0} found (1 letter -> 3 letter or 3/4 letter -> 1 letter)".format(x))
1426
1427
1428#: Regular expression to match and parse a residue-atom selection; will match
1429#: "LYS300:HZ1" or "K300:HZ1" or "K300" or "4GB300:H6O" or "4GB300" or "YaA300".
1430RESIDUE = re.compile("""
1431                 (?P<aa>([ACDEFGHIKLMNPQRSTVWY])   # 1-letter amino acid
1432                        |                          #   or
1433                        ([0-9A-Z][a-zA-Z][A-Z][A-Z]?)    # 3-letter or 4-letter residue name
1434                 )
1435                 \s*                               # white space allowed
1436                 (?P<resid>\d+)                    # resid
1437                 \s*
1438                 (:                                # separator ':'
1439                   \s*
1440                   (?P<atom>\w+)                   # atom name
1441                 )?                                # possibly one
1442            """, re.VERBOSE | re.IGNORECASE)
1443
1444
1445# from GromacsWrapper cbook.IndexBuilder
1446def parse_residue(residue):
1447    """Process residue string.
1448
1449    Parameters
1450    ----------
1451    residue: str
1452        The *residue* must contain a 1-letter or 3-letter or
1453        4-letter residue string, a number (the resid) and
1454        optionally an atom identifier, which must be separate
1455        from the residue with a colon (":"). White space is
1456        allowed in between.
1457
1458    Returns
1459    -------
1460    tuple
1461        `(3-letter aa string, resid, atomname)`; known 1-letter
1462        aa codes are converted to 3-letter codes
1463
1464    Examples
1465    --------
1466     - "LYS300:HZ1" --> ("LYS", 300, "HZ1")
1467     - "K300:HZ1" --> ("LYS", 300, "HZ1")
1468     - "K300" --> ("LYS", 300, None)
1469     - "4GB300:H6O" --> ("4GB", 300, "H6O")
1470     - "4GB300" --> ("4GB", 300, None)
1471
1472    """
1473
1474    # XXX: use _translate_residue() ....
1475    m = RESIDUE.match(residue)
1476    if not m:
1477        raise ValueError("Selection {residue!r} is not valid (only 1/3/4 letter resnames, resid required).".format(**vars()))
1478    resid = int(m.group('resid'))
1479    residue = m.group('aa')
1480    if len(residue) == 1:
1481        resname = convert_aa_code(residue)  # only works for AA
1482    else:
1483        resname = residue  # use 3-letter for any resname
1484    atomname = m.group('atom')
1485    return (resname, resid, atomname)
1486
1487
1488def conv_float(s):
1489    """Convert an object `s` to float if possible.
1490
1491    Function to be passed into :func:`map` or a list comprehension. If
1492    the argument can be interpreted as a float it is converted,
1493    otherwise the original object is passed back.
1494    """
1495    try:
1496        return float(s)
1497    except ValueError:
1498        return s
1499
1500
1501def cached(key):
1502    """Cache a property within a class.
1503
1504    Requires the Class to have a cache dict called ``_cache``.
1505
1506    Example
1507    -------
1508    How to add a cache for a variable to a class by using the `@cached`
1509    decorator::
1510
1511       class A(object):
1512           def__init__(self):
1513               self._cache = dict()
1514
1515           @property
1516           @cached('keyname')
1517           def size(self):
1518               # This code gets ran only if the lookup of keyname fails
1519               # After this code has been ran once, the result is stored in
1520               # _cache with the key: 'keyname'
1521               size = 10.0
1522
1523
1524    .. versionadded:: 0.9.0
1525
1526    """
1527
1528    def cached_lookup(func):
1529        @wraps(func)
1530        def wrapper(self, *args, **kwargs):
1531            try:
1532                return self._cache[key]
1533            except KeyError:
1534                self._cache[key] = ret = func(self, *args, **kwargs)
1535                return ret
1536
1537        return wrapper
1538
1539    return cached_lookup
1540
1541
1542def unique_rows(arr, return_index=False):
1543    """Return the unique rows of an array.
1544
1545    Arguments
1546    ---------
1547    arr : numpy.ndarray
1548        Array of shape ``(n1, m)``.
1549    return_index : bool, optional
1550      If ``True``, returns indices of array that formed answer (see
1551      :func:`numpy.unique`)
1552
1553    Returns
1554    -------
1555    unique_rows : numpy.ndarray
1556         Array of shape ``(n2, m)`` containing only the unique rows of `arr`.
1557    r_idx : numpy.ndarray (optional)
1558          Array containing the corresponding row indices (if `return_index`
1559          is ``True``).
1560
1561    Examples
1562    --------
1563    Remove dupicate rows from an array:
1564
1565    >>> a = np.array([[0, 1], [1, 2], [1, 2], [0, 1], [2, 3]])
1566    >>> b = unique_rows(a)
1567    >>> b
1568    array([[0, 1], [1, 2], [2, 3]])
1569
1570    See Also
1571    --------
1572    numpy.unique
1573
1574    """
1575    # From here, but adapted to handle any size rows
1576    # https://mail.scipy.org/pipermail/scipy-user/2011-December/031200.html
1577
1578    # This seems to fail if arr.flags['OWNDATA'] is False
1579    # this can occur when second dimension was created through broadcasting
1580    # eg: idx = np.array([1, 2])[None, :]
1581    if not arr.flags['OWNDATA']:
1582        arr = arr.copy()
1583
1584    m = arr.shape[1]
1585
1586    if return_index:
1587        u, r_idx = np.unique(arr.view(dtype=np.dtype([(str(i), arr.dtype)
1588                                                      for i in range(m)])),
1589                             return_index=True)
1590        return u.view(arr.dtype).reshape(-1, m), r_idx
1591    else:
1592        u = np.unique(arr.view(
1593            dtype=np.dtype([(str(i), arr.dtype) for i in range(m)])
1594        ))
1595        return u.view(arr.dtype).reshape(-1, m)
1596
1597
1598def blocks_of(a, n, m):
1599    """Extract a view of ``(n, m)`` blocks along the diagonal of the array `a`.
1600
1601    Parameters
1602    ----------
1603    a : numpy.ndarray
1604        Input array, must be C contiguous and at least 2D.
1605    n : int
1606        Size of block in first dimension.
1607    m : int
1608        Size of block in second dimension.
1609
1610    Returns
1611    -------
1612    view : numpy.ndarray
1613        A view of the original array with shape ``(nblocks, n, m)``, where
1614        ``nblocks`` is the number of times the miniblocks of shape ``(n, m)``
1615        fit in the original.
1616
1617    Raises
1618    ------
1619    ValueError
1620        If the supplied `n` and `m` don't divide `a` into an integer number
1621        of blocks or if `a` is not C contiguous.
1622
1623    Examples
1624    --------
1625    >>> arr = np.arange(16).reshape(4, 4)
1626    >>> view = blocks_of(arr, 2, 2)
1627    >>> view[:] = 100
1628    >>> arr
1629    array([[100, 100,   2,   3],
1630           [100, 100,   6,   7],
1631           [  8,   9, 100, 100],
1632           [ 12,  13, 100, 100]])
1633
1634    Notes
1635    -----
1636    `n`, `m` must divide `a` into an identical integer number of blocks. Please
1637    note that if the block size is larger than the input array, this number will
1638    be zero, resulting in an empty view!
1639
1640    Uses strides and therefore requires that the array is C contiguous.
1641
1642    Returns a view, so editing this modifies the original array.
1643
1644
1645    .. versionadded:: 0.12.0
1646
1647    """
1648    # based on:
1649    # http://stackoverflow.com/a/10862636
1650    # but generalised to handle non square blocks.
1651    if not a.flags['C_CONTIGUOUS']:
1652        raise ValueError("Input array is not C contiguous.")
1653
1654    nblocks = a.shape[0] // n
1655    nblocks2 = a.shape[1] // m
1656
1657    if not nblocks == nblocks2:
1658        raise ValueError("Must divide into same number of blocks in both"
1659                         " directions.  Got {} by {}"
1660                         "".format(nblocks, nblocks2))
1661
1662    new_shape = (nblocks, n, m)
1663    new_strides = (n * a.strides[0] + m * a.strides[1],
1664                   a.strides[0], a.strides[1])
1665
1666    return np.lib.stride_tricks.as_strided(a, new_shape, new_strides)
1667
1668class Namespace(dict):
1669    """Class to allow storing attributes in new namespace. """
1670    def __getattr__(self, key):
1671        # a.this causes a __getattr__ call for key = 'this'
1672        try:
1673            return dict.__getitem__(self, key)
1674        except KeyError:
1675            raise AttributeError('"{}" is not known in the namespace.'
1676                                 .format(key))
1677
1678    def __setattr__(self, key, value):
1679        dict.__setitem__(self, key, value)
1680
1681    def __delattr__(self, key):
1682        try:
1683            dict.__delitem__(self, key)
1684        except KeyError:
1685            raise AttributeError('"{}" is not known in the namespace.'
1686                                 .format(key))
1687
1688    def __eq__(self, other):
1689        try:
1690            # this'll allow us to compare if we're storing arrays
1691            assert_equal(self, other)
1692        except AssertionError:
1693            return False
1694        return True
1695
1696
1697def ltruncate_int(value, ndigits):
1698    """Truncate an integer, retaining least significant digits
1699
1700    Parameters
1701    ----------
1702    value : int
1703      value to truncate
1704    ndigits : int
1705      number of digits to keep
1706
1707    Returns
1708    -------
1709    truncated : int
1710      only the `ndigits` least significant digits from `value`
1711
1712    Examples
1713    --------
1714    >>> ltruncate_int(123, 2)
1715    23
1716    >>> ltruncate_int(1234, 5)
1717    1234
1718    """
1719    return int(str(value)[-ndigits:])
1720
1721
1722def flatten_dict(d, parent_key=tuple()):
1723    """Flatten a nested dict `d` into a shallow dict with tuples as keys.
1724
1725    Parameters
1726    ----------
1727    d : dict
1728
1729    Returns
1730    -------
1731    dict
1732
1733    Note
1734    -----
1735    Based on https://stackoverflow.com/a/6027615/
1736    by user https://stackoverflow.com/users/1897/imran
1737
1738    .. versionadded:: 0.18.0
1739    """
1740
1741    items = []
1742    for k, v in d.items():
1743        if type(k) != tuple:
1744            new_key = parent_key + (k, )
1745        else:
1746            new_key = parent_key + k
1747        if isinstance(v, collections.MutableMapping):
1748            items.extend(flatten_dict(v, new_key).items())
1749        else:
1750            items.append((new_key, v))
1751    return dict(items)
1752
1753
1754def static_variables(**kwargs):
1755    """Decorator equipping functions or methods with static variables.
1756
1757    Static variables are declared and initialized by supplying keyword arguments
1758    and initial values to the decorator.
1759
1760    Example
1761    -------
1762
1763    >>> @static_variables(msg='foo calls', calls=0)
1764    ... def foo():
1765    ...     foo.calls += 1
1766    ...     print("{}: {}".format(foo.msg, foo.calls))
1767    ...
1768    >>> foo()
1769    foo calls: 1
1770    >>> foo()
1771    foo calls: 2
1772
1773
1774    .. note:: Based on https://stackoverflow.com/a/279586
1775        by `Claudiu <https://stackoverflow.com/users/15055/claudiu>`_
1776
1777    .. versionadded:: 0.19.0
1778    """
1779    def static_decorator(func):
1780        for kwarg in kwargs:
1781            setattr(func, kwarg, kwargs[kwarg])
1782        return func
1783    return static_decorator
1784
1785
1786# In a lot of Atom/Residue/SegmentGroup methods such as center_of_geometry() and
1787# the like, results are biased if the calling group is not unique, i.e., if it
1788# contains duplicates.
1789# We therefore raise a `DuplicateWarning` whenever an affected method is called
1790# from a non-unique group. Since several of the affected methods involve calls
1791# to other affected methods, simply raising a warning in every affected method
1792# would potentially lead to a massive amount of warnings. This is exactly where
1793# the `warn_if_unique` decorator below comes into play. It ensures that a
1794# warning is only raised once for a method using this decorator, and suppresses
1795# all such warnings that would potentially be raised in methods called by that
1796# method. Of course, as it is generally the case with Python warnings, this is
1797# *not threadsafe*.
1798
1799@static_variables(warned=False)
1800def warn_if_not_unique(groupmethod):
1801    """Decorator triggering a :class:`~MDAnalysis.exceptions.DuplicateWarning`
1802    if the underlying group is not unique.
1803
1804    Assures that during execution of the decorated method only the first of
1805    potentially multiple warnings concerning the uniqueness of groups is shown.
1806
1807    Raises
1808    ------
1809    :class:`~MDAnalysis.exceptions.DuplicateWarning`
1810        If the :class:`~MDAnalysis.core.groups.AtomGroup`,
1811        :class:`~MDAnalysis.core.groups.ResidueGroup`, or
1812        :class:`~MDAnalysis.core.groups.SegmentGroup` of which the decorated
1813        method is a member contains duplicates.
1814
1815
1816    .. versionadded:: 0.19.0
1817    """
1818    @wraps(groupmethod)
1819    def wrapper(group, *args, **kwargs):
1820        # Proceed as usual if the calling group is unique or a DuplicateWarning
1821        # has already been thrown:
1822        if group.isunique or warn_if_not_unique.warned:
1823            return groupmethod(group, *args, **kwargs)
1824        # Otherwise, throw a DuplicateWarning and execute the method.
1825        method_name = ".".join((group.__class__.__name__, groupmethod.__name__))
1826        # Try to get the group's variable name(s):
1827        caller_locals = inspect.currentframe().f_back.f_locals.items()
1828        group_names = []
1829        for name, obj in caller_locals:
1830            try:
1831                if obj is group:
1832                    group_names.append("'{}'".format(name))
1833            except:
1834                pass
1835        if not group_names:
1836            group_name = "'unnamed {}'".format(group.__class__.__name__)
1837        elif len(group_names) == 1:
1838            group_name = group_names[0]
1839        else:
1840            group_name = " a.k.a. ".join(sorted(group_names))
1841        group_repr = repr(group)
1842        msg = ("{}(): {} {} contains duplicates. Results might be biased!"
1843               "".format(method_name, group_name, group_repr))
1844        warnings.warn(message=msg, category=DuplicateWarning, stacklevel=2)
1845        warn_if_not_unique.warned = True
1846        try:
1847            result = groupmethod(group, *args, **kwargs)
1848        finally:
1849            warn_if_not_unique.warned = False
1850        return result
1851    return wrapper
1852
1853
1854def check_coords(*coord_names, **options):
1855    """Decorator for automated coordinate array checking.
1856
1857    This decorator is intended for use especially in
1858    :mod:`MDAnalysis.lib.distances`.
1859    It takes an arbitrary number of positional arguments which must correspond
1860    to names of positional arguments of the decorated function.
1861    It then checks if the corresponding values are valid coordinate arrays.
1862    If all these arrays are single coordinates (i.e., their shape is ``(3,)``),
1863    the decorated function can optionally return a single coordinate (or angle)
1864    instead of an array of coordinates (or angles). This can be used to enable
1865    computations of single observables using functions originally designed to
1866    accept only 2-d coordinate arrays.
1867
1868    The checks performed on each individual coordinate array are:
1869
1870    * Check that coordinate arrays are of type :class:`numpy.ndarray`.
1871    * Check that coordinate arrays have a shape of ``(n, 3)`` (or ``(3,)`` if
1872      single coordinates are allowed; see keyword argument `allow_single`).
1873    * Automatic dtype conversion to ``numpy.float32``.
1874    * Optional replacement by a copy; see keyword argument `enforce_copy` .
1875    * If coordinate arrays aren't C-contiguous, they will be automatically
1876      replaced by a C-contiguous copy.
1877    * Optional check for equal length of all coordinate arrays; see optional
1878      keyword argument `check_lengths_match`.
1879
1880    Parameters
1881    ----------
1882    *coord_names : tuple
1883        Arbitrary number of strings corresponding to names of positional
1884        arguments of the decorated function.
1885    **options : dict, optional
1886        * **enforce_copy** (:class:`bool`, optional) -- Enforce working on a
1887          copy of the coordinate arrays. This is useful to ensure that the input
1888          arrays are left unchanged. Default: ``True``
1889        * **allow_single** (:class:`bool`, optional) -- Allow the input
1890          coordinate array to be a single coordinate with shape ``(3,)``.
1891        * **convert_single** (:class:`bool`, optional) -- If ``True``, single
1892          coordinate arrays will be converted to have a shape of ``(1, 3)``.
1893          Only has an effect if `allow_single` is ``True``. Default: ``True``
1894        * **reduce_result_if_single** (:class:`bool`, optional) -- If ``True``
1895          and *all* input coordinates are single, a decorated function ``func``
1896          will return ``func()[0]`` instead of ``func()``. Only has an effect if
1897          `allow_single` is ``True``. Default: ``True``
1898        * **check_lengths_match** (:class:`bool`, optional) -- If ``True``, a
1899          :class:`ValueError` is raised if not all coordinate arrays contain the
1900          same number of coordinates. Default: ``True``
1901
1902    Raises
1903    ------
1904    ValueError
1905        If the decorator is used without positional arguments (for development
1906        purposes only).
1907
1908        If any of the positional arguments supplied to the decorator doesn't
1909        correspond to a name of any of the decorated function's positional
1910        arguments.
1911
1912        If any of the coordinate arrays has a wrong shape.
1913    TypeError
1914        If any of the coordinate arrays is not a :class:`numpy.ndarray`.
1915
1916        If the dtype of any of the coordinate arrays is not convertible to
1917          ``numpy.float32``.
1918
1919    Example
1920    -------
1921
1922    >>> @check_coords('coords1', 'coords2')
1923    ... def coordsum(coords1, coords2):
1924    ...     assert coords1.dtype == np.float32
1925    ...     assert coords2.flags['C_CONTIGUOUS']
1926    ...     return coords1 + coords2
1927    ...
1928    >>> # automatic dtype conversion:
1929    >>> coordsum(np.zeros(3, dtype=np.int64), np.ones(3))
1930    array([1., 1., 1.], dtype=float32)
1931    >>>
1932    >>> # automatic handling of non-contiguous arrays:
1933    >>> coordsum(np.zeros(3), np.ones(6)[::2])
1934    array([1., 1., 1.], dtype=float32)
1935    >>>
1936    >>> # automatic shape checking:
1937    >>> coordsum(np.zeros(3), np.ones(6))
1938    ValueError: coordsum(): coords2.shape must be (3,) or (n, 3), got (6,).
1939
1940
1941    .. versionadded:: 0.19.0
1942    """
1943    enforce_copy = options.get('enforce_copy', True)
1944    allow_single = options.get('allow_single', True)
1945    convert_single = options.get('convert_single', True)
1946    reduce_result_if_single = options.get('reduce_result_if_single', True)
1947    check_lengths_match = options.get('check_lengths_match',
1948                                     len(coord_names) > 1)
1949    if not coord_names:
1950        raise ValueError("Decorator check_coords() cannot be used without "
1951                         "positional arguments.")
1952
1953    def check_coords_decorator(func):
1954        fname = func.__name__
1955        code = func.__code__
1956        argnames = code.co_varnames
1957        nargs = len(code.co_varnames)
1958        ndefaults = len(func.__defaults__) if func.__defaults__ else 0
1959        # Create a tuple of positional argument names:
1960        nposargs = code.co_argcount - ndefaults
1961        posargnames = argnames[:nposargs]
1962        # The check_coords() decorator is designed to work only for positional
1963        # arguments:
1964        for name in coord_names:
1965            if name not in posargnames:
1966                raise ValueError("In decorator check_coords(): Name '{}' "
1967                                 "doesn't correspond to any positional "
1968                                 "argument of the decorated function {}()."
1969                                 "".format(name, func.__name__))
1970
1971        def _check_coords(coords, argname):
1972            if not isinstance(coords, np.ndarray):
1973                raise TypeError("{}(): Parameter '{}' must be a numpy.ndarray, "
1974                                "got {}.".format(fname, argname, type(coords)))
1975            is_single = False
1976            if allow_single:
1977                if (coords.ndim not in (1, 2)) or (coords.shape[-1] != 3):
1978                    raise ValueError("{}(): {}.shape must be (3,) or (n, 3), "
1979                                     "got {}.".format(fname, argname,
1980                                                      coords.shape))
1981                if coords.ndim == 1:
1982                    is_single = True
1983                    if convert_single:
1984                        coords = coords[None, :]
1985            else:
1986                if (coords.ndim != 2) or (coords.shape[1] != 3):
1987                    raise ValueError("{}(): {}.shape must be (n, 3), got {}."
1988                                     "".format(fname, argname, coords.shape))
1989            try:
1990                coords = coords.astype(np.float32, order='C', copy=enforce_copy)
1991            except ValueError:
1992                raise TypeError("{}(): {}.dtype must be convertible to float32,"
1993                                " got {}.".format(fname, argname, coords.dtype))
1994            return coords, is_single
1995
1996        @wraps(func)
1997        def wrapper(*args, **kwargs):
1998            # Check for invalid function call:
1999            if len(args) != nposargs:
2000                # set marker for testing purposes:
2001                wrapper._invalid_call = True
2002                if len(args) > nargs:
2003                    # too many arguments, invoke call:
2004                    return func(*args, **kwargs)
2005                for name in posargnames[:len(args)]:
2006                    if name in kwargs:
2007                        # duplicate argument, invoke call:
2008                        return func(*args, **kwargs)
2009                for name in posargnames[len(args):]:
2010                    if name not in kwargs:
2011                        # missing argument, invoke call:
2012                        return func(*args, **kwargs)
2013                for name in kwargs:
2014                    if name not in argnames:
2015                        # unexpected kwarg, invoke call:
2016                        return func(*args, **kwargs)
2017                # call is valid, unset test marker:
2018                wrapper._invalid_call = False
2019            args = list(args)
2020            ncoords = []
2021            all_single = allow_single
2022            for name in coord_names:
2023                idx = posargnames.index(name)
2024                if idx < len(args):
2025                    args[idx], is_single = _check_coords(args[idx], name)
2026                    all_single &= is_single
2027                    ncoords.append(args[idx].shape[0])
2028                else:
2029                    kwargs[name], is_single = _check_coords(kwargs[name],
2030                                                            name)
2031                    all_single &= is_single
2032                    ncoords.append(kwargs[name].shape[0])
2033            if check_lengths_match and ncoords:
2034                if ncoords.count(ncoords[0]) != len(ncoords):
2035                    raise ValueError("{}(): {} must contain the same number of "
2036                                     "coordinates, got {}."
2037                                     "".format(fname, ", ".join(coord_names),
2038                                               ncoords))
2039            # If all input coordinate arrays were 1-d, so should be the output:
2040            if all_single and reduce_result_if_single:
2041                return func(*args, **kwargs)[0]
2042            return func(*args, **kwargs)
2043        return wrapper
2044    return check_coords_decorator
2045
2046
2047#------------------------------------------------------------------
2048#
2049# our own deprecate function, derived from numpy (see
2050# https://github.com/MDAnalysis/mdanalysis/pull/1763#issuecomment-403231136)
2051#
2052# From numpy/lib/utils.py 1.14.5 (used under the BSD 3-clause licence,
2053# https://www.numpy.org/license.html#license) and modified
2054
2055def _set_function_name(func, name):
2056    func.__name__ = name
2057    return func
2058
2059class _Deprecate(object):
2060    """
2061    Decorator class to deprecate old functions.
2062
2063    Refer to `deprecate` for details.
2064
2065    See Also
2066    --------
2067    deprecate
2068
2069
2070    .. versionadded:: 0.19.0
2071    """
2072
2073    def __init__(self, old_name=None, new_name=None,
2074                 release=None, remove=None, message=None):
2075        self.old_name = old_name
2076        self.new_name = new_name
2077        if release is None:
2078            raise ValueError("deprecate: provide release in which "
2079                             "feature was deprecated.")
2080        self.release = str(release)
2081        self.remove = str(remove) if remove is not None else remove
2082        self.message = message
2083
2084    def __call__(self, func, *args, **kwargs):
2085        """
2086        Decorator call.  Refer to ``decorate``.
2087
2088        """
2089        old_name = self.old_name
2090        new_name = self.new_name
2091        message = self.message
2092        release = self.release
2093        remove = self.remove
2094
2095        if old_name is None:
2096            try:
2097                old_name = func.__name__
2098            except AttributeError:
2099                old_name = func.__name__
2100        if new_name is None:
2101            depdoc = "`{0}` is deprecated!".format(old_name)
2102        else:
2103            depdoc = "`{0}` is deprecated, use `{1}` instead!".format(
2104                old_name, new_name)
2105
2106        warn_message = depdoc
2107
2108        remove_text = ""
2109        if remove is not None:
2110            remove_text = "`{0}` will be removed in release {1}.".format(
2111                old_name, remove)
2112            warn_message += "\n" + remove_text
2113        if message is not None:
2114            warn_message += "\n" + message
2115
2116        def newfunc(*args, **kwds):
2117            """This function is deprecated."""
2118            warnings.warn(warn_message, DeprecationWarning, stacklevel=2)
2119            return func(*args, **kwds)
2120
2121        newfunc = _set_function_name(newfunc, old_name)
2122
2123        # Build the doc string
2124        # First line: func is deprecated, use newfunc instead!
2125        # Normal docs follows.
2126        # Last: .. deprecated::
2127
2128        # make sure that we do not mess up indentation, otherwise sphinx
2129        # docs do not build properly
2130        try:
2131            doc = dedent_docstring(func.__doc__)
2132        except TypeError:
2133            doc = ""
2134
2135        deprecation_text = dedent_docstring("""\n\n
2136        .. deprecated:: {0}
2137           {1}
2138           {2}
2139        """.format(release,
2140                   message if message else depdoc,
2141                   remove_text))
2142
2143        doc = "{0}\n\n{1}\n{2}\n".format(depdoc, doc, deprecation_text)
2144
2145        newfunc.__doc__ = doc
2146        try:
2147            d = func.__dict__
2148        except AttributeError:
2149            pass
2150        else:
2151            newfunc.__dict__.update(d)
2152        return newfunc
2153
2154def deprecate(*args, **kwargs):
2155    """Issues a DeprecationWarning, adds warning to `old_name`'s
2156    docstring, rebinds ``old_name.__name__`` and returns the new
2157    function object.
2158
2159    This function may also be used as a decorator.
2160
2161    It adds a restructured text ``.. deprecated:: release`` block with
2162    the sphinx deprecated role to the end of the docs. The `message`
2163    is added under the deprecation block and contains the `release` in
2164    which the function was deprecated.
2165
2166    Parameters
2167    ----------
2168    func : function
2169        The function to be deprecated.
2170    old_name : str, optional
2171        The name of the function to be deprecated. Default is None, in
2172        which case the name of `func` is used.
2173    new_name : str, optional
2174        The new name for the function. Default is None, in which case the
2175        deprecation message is that `old_name` is deprecated. If given, the
2176        deprecation message is that `old_name` is deprecated and `new_name`
2177        should be used instead.
2178    release : str
2179        Release in which the function was deprecated. This is given as
2180        a keyword argument for technical reasons but is required; a
2181        :exc:`ValueError` is raised if it is missing.
2182    remove : str, optional
2183        Release for which removal of the feature is planned.
2184    message : str, optional
2185        Additional explanation of the deprecation.  Displayed in the
2186        docstring after the warning.
2187
2188    Returns
2189    -------
2190    old_func : function
2191        The deprecated function.
2192
2193    Examples
2194    --------
2195    When :func:`deprecate` is used as a function as in the following
2196    example,
2197
2198    .. code-block:: python
2199
2200       oldfunc = deprecate(func, release="0.19.0", remove="1.0",
2201                           message="Do it yourself instead.")
2202
2203    then ``oldfunc`` will return a value after printing
2204    :exc:`DeprecationWarning`; ``func`` is still available as it was
2205    before.
2206
2207    When used as a decorator, ``func`` will be changed and issue the
2208    warning and contain the deprecation note in the do string.
2209
2210    .. code-block:: python
2211
2212       @deprecate(release="0.19.0", remove="1.0",
2213                  message="Do it yourself instead.")
2214       def func():
2215           \"\"\"Just pass\"\"\"
2216           pass
2217
2218    The resulting doc string (``help(func)``) will look like:
2219
2220    .. code-block:: reST
2221
2222       `func` is deprecated!
2223
2224       Just pass.
2225
2226       .. deprecated:: 0.19.0
2227          Do it yourself instead.
2228          `func` will be removed in 1.0.
2229
2230    (It is possible but confusing to change the name of ``func`` with
2231    the decorator so it is not recommended to use the `new_func`
2232    keyword argument with the decorator.)
2233
2234    .. versionadded:: 0.19.0
2235
2236    """
2237    # Deprecate may be run as a function or as a decorator
2238    # If run as a function, we initialise the decorator class
2239    # and execute its __call__ method.
2240
2241    if args:
2242        fn = args[0]
2243        args = args[1:]
2244        return _Deprecate(*args, **kwargs)(fn)
2245    else:
2246        return _Deprecate(*args, **kwargs)
2247#
2248#------------------------------------------------------------------
2249
2250def dedent_docstring(text):
2251    """Dedent typical python doc string.
2252
2253    Parameters
2254    ----------
2255    text : str
2256        string, typically something like ``func.__doc__``.
2257
2258    Returns
2259    -------
2260    str
2261        string with the leading common whitespace removed from each
2262        line
2263
2264    See Also
2265    --------
2266    textwrap.dedent
2267
2268
2269    .. versionadded:: 0.19.0
2270    """
2271    lines = text.splitlines()
2272    if len(lines) < 2:
2273        return text.lstrip()
2274
2275    # treat first line as special (typically no leading whitespace!) which messes up dedent
2276    return lines[0].lstrip() + "\n" + textwrap.dedent("\n".join(lines[1:]))
2277
2278
2279def check_box(box):
2280    """Take a box input and deduce what type of system it represents based on
2281    the shape of the array and whether all angles are 90 degrees.
2282
2283    Parameters
2284    ----------
2285    box : array_like
2286        The unitcell dimensions of the system, which can be orthogonal or
2287        triclinic and must be provided in the same format as returned by
2288        :attr:`MDAnalysis.coordinates.base.Timestep.dimensions`:\n
2289        ``[lx, ly, lz, alpha, beta, gamma]``.
2290
2291    Returns
2292    -------
2293    boxtype : {``'ortho'``, ``'tri_vecs'``}
2294        String indicating the box type (orthogonal or triclinic).
2295    checked_box : numpy.ndarray
2296        Array of dtype ``numpy.float32`` containing box information:
2297          * If `boxtype` is ``'ortho'``, `cecked_box` will have the shape ``(3,)``
2298            containing the x-, y-, and z-dimensions of the orthogonal box.
2299          * If  `boxtype` is ``'tri_vecs'``, `cecked_box` will have the shape
2300            ``(3, 3)`` containing the triclinic box vectors in a lower triangular
2301            matrix as returned by
2302            :meth:`~MDAnalysis.lib.mdamath.triclinic_vectors`.
2303
2304    Raises
2305    ------
2306    ValueError
2307        If `box` is not of the form ``[lx, ly, lz, alpha, beta, gamma]``
2308        or contains data that is not convertible to ``numpy.float32``.
2309
2310    See Also
2311    --------
2312    MDAnalysis.lib.mdamath.triclinic_vectors
2313
2314
2315    .. versionchanged: 0.19.0
2316       * Enforced correspondence of `box` with specified format.
2317       * Added automatic conversion of input to :class:`numpy.ndarray` with
2318         dtype ``numpy.float32``.
2319       * Now also returns the box in the format expected by low-level functions
2320         in :mod:`~MDAnalysis.lib.c_distances`.
2321       * Removed obsolete box types ``tri_box`` and ``tri_vecs_bad``.
2322    """
2323    from .mdamath import triclinic_vectors  # avoid circular import
2324    box = np.asarray(box, dtype=np.float32, order='C')
2325    if box.shape != (6,):
2326        raise ValueError("Invalid box information. Must be of the form "
2327                         "[lx, ly, lz, alpha, beta, gamma].")
2328    if np.all(box[3:] == 90.):
2329        return 'ortho', box[:3]
2330    return 'tri_vecs', triclinic_vectors(box)
2331