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