diff --git a/.coveragerc b/.coveragerc index 101eab8a311..ac1019d2bbb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,7 +3,6 @@ branch = True source = MDAnalysis omit = */migration/* - */analysis/* */visualization/* */MDAnalysis/tests/* diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000000..8b600597796 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,15 @@ +## Contributing to MDAnalysis + +Thanks for contributing to MDAnalysis! + +#### Reporting issues + +If you've found a defect with MDAnalysis we'd love to know so we can fix it. Please follow the Issue template so we can quickly diagnose the problem, in particular the piece of code that causes the problem. + +If your issue isn't a defect with the code and instead you require help using MDAnalysis, drop by the [discussion board](http://help.mdanalysis.org). + +#### Contributing code + +If you're contributing code, please check out the [Style guide](https://github.com/MDAnalysis/mdanalysis/wiki/Style-Guide). + +MDAnalysis devs are most easily reached through the [development board](http://developers.mdanalysis.org). diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..8d285c83339 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ +### Expected behaviour + + +### Actual behaviour + + +### Code to reproduce the behaviour + +``` python +import MDAnalysis as mda + +u = mda.Universe(top, trj) + +.... + +``` + +### Currently version of MDAnalysis: +(run `python -c "import MDAnalysis as mda; print(mda.__version__)"`) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..f12d113f4f8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +Fixes # + +Changes made in this Pull Request: + - + + +PR Checklist +------------ + - [ ] Tests? + - [ ] Docs? + - [ ] CHANGELOG updated? + - [ ] Issue raised/referenced? diff --git a/.gitignore b/.gitignore index d7945c6a70d..488e359bfc6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ doc/html/ *.sw[a-z] # Ignore MDAnalysis log files MDAnalysis.log +# Ignore the authors.py files as they are generated files +authors.py diff --git a/.travis.yml b/.travis.yml index f449d211a1a..08ade58356a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,9 @@ branches: - master - develop -language: python +os: + - linux + - osx env: global: - secure: "HIj3p+p2PV8DBVg/KGUx6n83KwB0ASE5FwOn0SMB9zxnzAqe8sapwdBQdMdq0sXB7xT1spJqRxuxOMVEVn35BNLu7bxMLfa4287C8YXcomnvmv9xruxAsjsIewnNQ80vtPVbQddBPxa4jKbqgPby5QhhAP8KANAqYe44pIV70fY=" @@ -13,39 +15,29 @@ env: - GIT_CI_USER: TravisCI - GIT_CI_EMAIL: TravisCI@mdanalysis.org - MDA_DOCDIR: package/doc/html + matrix: + - SETUP=minimal PYTHON_VERSION=2.7 + - SETUP=full PYTHON_VERSION=2.7 + - SETUP=minimal PYTHON_VERSION=3.3 + - SETUP=full PYTHON_VERSION=3.3 matrix: allow_failures: - - python: "3.3" - env: SETUP=full - include: - - python: "2.7" - env: SETUP=minimal - - python: "2.7" - env: SETUP=full - - python: "3.3" - env: SETUP=full -# command to install dependencies -addons: - apt: - packages: - - gfortran - - libgfortran3 - - libhdf5-serial-dev - - libnetcdf-dev - - liblapack-dev - - libatlas-dev + - env: SETUP=minimal PYTHON_VERSION=3.3 + - env: SETUP=full PYTHON_VERSION=3.3 + before_install: - - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh + - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then wget http://repo.continuum.io/miniconda/Miniconda-latest-MacOSX-x86_64.sh -O miniconda.sh; fi + - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; fi - chmod +x miniconda.sh - - ./miniconda.sh -b -p /home/travis/miniconda - - export PATH=/home/travis/miniconda/bin:$PATH + - ./miniconda.sh -b -p $(pwd)/miniconda + - export PATH=$(pwd)/miniconda/bin:$PATH - conda update --yes conda install: - - if [[ $SETUP == 'full' ]]; then conda create --yes -q -n pyenv python=2.7 numpy=1.9.2 scipy=0.16.0 nose=1.3.7 sphinx=1.3; fi - - if [[ $SETUP == 'minimal' ]]; then conda create --yes -q -n pyenv python=2.7 numpy=1.9.2 nose=1.3.7 sphinx=1.3; fi + - if [[ $SETUP == 'full' ]]; then conda create --yes -q -n pyenv python=$PYTHON_VERSION numpy scipy nose=1.3.7 sphinx=1.3; fi + - if [[ $SETUP == 'minimal' ]]; then conda create --yes -q -n pyenv python=$PYTHON_VERSION numpy nose=1.3.7 sphinx=1.3; fi - source activate pyenv - - if [[ $SETUP == 'full' ]]; then conda install --yes python=$TRAVIS_PYTHON_VERSION cython biopython matplotlib networkx netcdf4; fi - - if [[ $SETUP == 'minimal' ]]; then conda install --yes python=$TRAVIS_PYTHON_VERSION cython biopython networkx; fi + - if [[ $SETUP == 'full' ]]; then conda install --yes python=$PYTHON_VERSION cython biopython matplotlib networkx netcdf4; fi + - if [[ $SETUP == 'minimal' ]]; then conda install --yes python=$PYTHON_VERSION cython biopython networkx; fi # ensure that cython files are rebuilt - find . -name '*.pyx' -exec touch '{}' \; - pip install -v package/ diff --git a/README.rst b/README.rst index ef4d2ff065e..938d77e7d3d 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ |build| |cov| [*]_ -|docs| |devdocs| |usergroup| |developergroup| +|docs| |devdocs| |usergroup| |developergroup| |anaconda| MDAnalysis_ is a Python toolkit to analyze molecular dynamics trajectories generated by a wide range of popular simulation packages @@ -14,18 +14,18 @@ lists of supported `trajectory formats`_ and `topology formats`_.) .. code:: python import MDAnalysis as mda - + # Load simulation results with a single line u = mda.Universe('topol.tpr','traj.trr') - + # Select atoms ag = u.select_atoms('name OH') - + # Atom data made available as Numpy arrays ag.positions ag.velocities ag.forces - + # Iterate through trajectories for ts in u.trajectory: print(ag.center_of_mass()) @@ -61,9 +61,9 @@ MDAnalysis issue tracker.) .. Footnotes -.. [*] **build**: Unit testing is for the whole package; **coverage** is shown for the core library - modules (which excludes `MDAnalysis.analysis`_ and `MDAnalysis.visualization`_ at - the moment). For more details and discussion see issue `#286`_. +.. [*] **build**: Unit testing is for the whole package; **coverage** is + shown for the core library modules and the analysis modules (which + excludes `MDAnalysis.visualization`_ at the moment). .. _trajectory formats: http://docs.mdanalysis.org/documentation_pages/coordinates/init.html#id1 .. _topology formats: http://docs.mdanalysis.org/documentation_pages/topology/init.html#supported-topology-formats @@ -77,11 +77,11 @@ MDAnalysis issue tracker.) .. |usergroup| image:: https://img.shields.io/badge/Google%20Group-Users-lightgrey.svg :alt: User Google Group :target: http://users.mdanalysis.org - + .. |developergroup| image:: https://img.shields.io/badge/Google%20Group-Developers-lightgrey.svg :alt: Developer Google Group :target: http://developers.mdanalysis.org - + .. |docs| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg :alt: Documentation (latest release) :target: http://docs.mdanalysis.org @@ -93,7 +93,11 @@ MDAnalysis issue tracker.) .. |build| image:: https://travis-ci.org/MDAnalysis/mdanalysis.svg?branch=develop :alt: Build Status :target: https://travis-ci.org/MDAnalysis/mdanalysis - + .. |cov| image:: https://coveralls.io/repos/MDAnalysis/mdanalysis/badge.svg?branch=develop :alt: Coverage Status :target: https://coveralls.io/r/MDAnalysis/mdanalysis?branch=develop + +.. |anaconda| image:: https://anaconda.org/mdanalysis/mdanalysis/badges/version.svg + :alt: Anaconda + :target: https://anaconda.org/MDAnalysis/mdanalysis diff --git a/maintainer/change_release.sh b/maintainer/change_release.sh index b8ce92e2382..7d441b5c2c0 100755 --- a/maintainer/change_release.sh +++ b/maintainer/change_release.sh @@ -10,7 +10,7 @@ like '0.7.6' or 0.7.6-dev'. Run from the top directory of the git checkout. " -FILES="package/setup.py package/MDAnalysis/version.py testsuite/setup.py testsuite/MDAnalysisTests/__init__.py" +FILES="package/setup.py package/MDAnalysis/version.py testsuite/setup.py testsuite/MDAnalysisTests/__init__.py maintainer/conda/MDAnalysis/meta.yaml" die () { echo "ERROR: $1" @@ -19,40 +19,40 @@ die () { findcommand() { for name in $*; do - path=$(which $name) - if [ -n "$path" ]; then - echo $path - return 0 - fi + path=$(which $name) + if [ -n "$path" ]; then + echo $path + return 0 + fi done die "None of the commands $* found." 2 } sed_eregex () { - # does this sed understand extended regular expressions? - # - FreeBSD (Mac OS X) sed -E - # - GNU sed --regexp-extended (undocumented: also -E ...) - local SED=$1 - if [ "good" = "$(echo 'bad' | $SED -E 's/(abc)?bad|foo/good/')" ]; then - echo "$SED -E" - return 0 - elif [ "good" = "$(echo 'bad' | $SED --regexp-extended 's/(abc)?bad|foo/good/')" ]; then - echo "$SED --regexp-extended" - return 0 - elif [ "good" = "$(echo 'bad' | $SED 's/(abc)?bad|foo/good/')" ]; then - echo "$SED" - return 0 - fi - echo "false" - return 1 + # does this sed understand extended regular expressions? + # - FreeBSD (Mac OS X) sed -E + # - GNU sed --regexp-extended (undocumented: also -E ...) + local SED=$1 + if [ "good" = "$(echo 'bad' | $SED -E 's/(abc)?bad|foo/good/')" ]; then + echo "$SED -E" + return 0 + elif [ "good" = "$(echo 'bad' | $SED --regexp-extended 's/(abc)?bad|foo/good/')" ]; then + echo "$SED --regexp-extended" + return 0 + elif [ "good" = "$(echo 'bad' | $SED 's/(abc)?bad|foo/good/')" ]; then + echo "$SED" + return 0 + fi + echo "false" + return 1 } while getopts h OPT; do case $OPT in - h) echo "$usage"; - exit 0 - ;; - \?) exit 2;; + h) echo "$usage"; + exit 0 + ;; + \?) exit 2;; esac done @@ -62,20 +62,24 @@ RELEASE=$1 test -n "$RELEASE" || die "Required argument missing. See -h for help." 2 # find a sed with -i and -E -for cmd in gsed sed; do +for cmd in gsed sed; do SED=$(sed_eregex $(findcommand $cmd)) [ "$SED" != "false" ] && break done -[ "$SED" = "false" ] && { echo "ERROR: cannot find suitable sed."; exit 1; } +[ "$SED" = "false" ] && { echo "ERROR: cannot find suitable sed."; exit 1; } # should check for -i but we just hope for the best ... # modern(ish) seds have -i echo "Using sed = $SED" echo "Setting RELEASE/__version__ in MDAnalysis to $RELEASE" -git grep -E -l 'RELEASE.*[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(dev|rc*[0-9])?' $FILES \ - | xargs -I FILE $SED '/RELEASE/s/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(dev|rc*[0-9])?/'${RELEASE}'/' -i.bak FILE -git grep -E -l '__version__ =.*[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(dev|rc*[0-9])?' $FILES \ - | xargs -I FILE $SED '/__version__/s/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(dev|rc*[0-9])?/'${RELEASE}'/' -i.bak FILE -git status +git grep -E -l 'RELEASE.*[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?' $FILES \ + | xargs -I FILE $SED '/RELEASE/s/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?/'${RELEASE}'/' -i.bak FILE + +git grep -E -l '__version__ *=.*[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?' $FILES \ + | xargs -I FILE $SED '/__version__/s/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?/'${RELEASE}'/' -i.bak FILE +git grep -E -l 'version:.*[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?' $FILES \ + | xargs -I FILE $SED '/version:/s/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?(-dev[0-9]*|-rc[0-9]*)?/'${RELEASE}'/' -i.bak FILE + +git status diff --git a/maintainer/conda/MDAnalysis/build.sh b/maintainer/conda/MDAnalysis/build.sh new file mode 100644 index 00000000000..011f106a28d --- /dev/null +++ b/maintainer/conda/MDAnalysis/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +MDA_USE_OPENMP=FALSE pip install package/ +pip install testsuite/ diff --git a/maintainer/conda/MDAnalysis/meta.yaml b/maintainer/conda/MDAnalysis/meta.yaml new file mode 100644 index 00000000000..c5168a29004 --- /dev/null +++ b/maintainer/conda/MDAnalysis/meta.yaml @@ -0,0 +1,73 @@ +package: + name: mdanalysis + # This has to be changed after a release + version: "0.14.0" + +source: + # to build from source you can speficy the path to the code directly. + # path: ../../../ + # This ensures that you will build from a clean checkout + git_url: https://github.com/MDAnalysis/mdanalysis + git_branch: master + # git_tag: release-0.14.0 + +requirements: + build: + - python + - setuptools + - pip + - numpy + - cython + - biopython + - networkx + - griddataformats + - nose + + run: + - python + - numpy + - scipy + - griddataformats + - networkx + - biopython + - matplotlib + - seaborn + - six + - netcdf4 + - nose + +test: + imports: + - MDAnalysis + - MDAnalysis.analysis + # check that distance cython modules have been build + - MDAnalysis.lib.c_distances_openmp + - MDAnalysis.lib.c_distances + + requires: + # this is the same list as the run requirements + - python + - numpy + - scipy + - griddataformats + - networkx + - biopython + - matplotlib + - seaborn + - six + - netcdf4 + - nose + + commands: + # run the testsuite with 2 processes + - python -c 'import MDAnalysisTests; MDAnalysisTests.run(label="full", extra_argv=["-v", "--processes=2", "--process-timeout=120"])' + +about: + home: http://www.mdanalysis.org + license: GPLv2 + license_file: package/LICENSE + summary: 'MDAnalysis is a Python library to analyze molecular dynamics trajectories.' + +# See +# http://docs.continuum.io/conda/build.html for +# more information about meta.yaml diff --git a/package/AUTHORS b/package/AUTHORS index a80cd147fd0..6c05bc71a5a 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -49,6 +49,7 @@ Chronological list of authors - Robert McGibbon - Richard J. Gowers - Alejandro Bernardin + - Lennard van der Feltz 2014 - Matthieu Chavent - Joe Jordan @@ -68,6 +69,9 @@ Chronological list of authors - Balasubramanian - Mattia F. Palermo - Utkarsh Saxena + - Abhinav Gupta + - John Detlefs + - Bart Bruininks External code ------------- diff --git a/package/CHANGELOG b/package/CHANGELOG index 50a821d3919..a38b3ff4373 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,6 +13,79 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ + +05/15/16 jandom, abhinavgupta94, orbeckst, kain88-de, hainm, jbarnoud, + dotsdl, richardjgowers, BartBruininks, jdetle + + * 0.15.0 + +Metadata + + * link download_url to GitHub releases so that Depsy recognizes + contributors (issue #749) + * a __version__ variable is now exposed; it is built by setup.py from the + AUTHORS file (Issue #784) + +API Changes + + * rmsd doesn't superimpose by default anymore. The superposition + is controlled by the 'superposition' keyword now. (see issue #562, #822) + +Enhancements + + * Add conda build scripts (Issue #608) + * Added read-only property giving Universe init kwargs (Issue #292) + * Added 'crdbox' as AMBER Trj format extension (Issue #846) + * Iteration and seeking in PDB files made faster (Issue #848) + +Fixes + * ENT file format added to PDB Readers/Writers/Parsers (Issue #834) + * rmsd now returns proper value when given array of weights (Issue #814) + * change_release now finds number and dev (Issue #776) + * units.py now correctly prints errors for unknown units. + * test_shear_from_matrix doesn't fail for MKL builds anymore (Issue #757) + * HEADER and TITLE now appear just once in the PDB. (Issue #741) (PR #761) + * MOL2 files without substructure section can now be read (Issue #816) + * MOL2 files can be written without substructure section (Issue #816) + * GRO files with an incomplete set of velocities can now be read (Issue #820) + * Fixed Atom.position/velocity/force returning a view onto Timestep array + (Issue #755) + * PDB files can now read a CRYST entry if it happens before model headers + (Issue #849) + * Fixed HistoryReader returning 1 based frame indices (Issue #851) + +Changes + + * Added zero_based indices for HBondsAnalysis. (Issue #807) + * Generalized contact analysis class `Contacts` added. (Issue #702) + * Removed Bio.PDBParser and sloppy structure builder and all of + MDAnalysis.coordinates.pdb (Issue #777) + * PDB parsers/readers/writers replaced by "permissive"/"primitive" + counterparts (formerly known as PrimitivePDBReader); the + 'permissive' keyword for Universe is now ignored and only the + native MDAnalysis PDBReader is being used (Issue #777) + * PDBReader only opens a single file handle in its lifetime, + previously opened & closed handle each frame (Issue #850) + +Deprecations (Issue #599) + * Use of PrimitivePDBReader/Writer/Parser deprecated in favor of PDBReader/ + Writer/Parser (Issue #777) + * Deprecated all `get_*` and `set_*` methods of Groups. + * Deprecation warnings for accessing atom attributes from Residue, + ResidueGroup, Segment, SegmentGroup. Will not be present or will + give per-level results. + * Deprecation warnings for accessing plural residue attributes from + Residue or Segment (will disappear), or from SegmentGroup (will give + per-Segment results). + * Deprecation warnings for accessing plural segment attributes from Segment + (will disappear). + * Deprecated Atom number, pos, centroid, universe setter + * Deprecated AtomGroup serials, write_selection + * Deprecated Residue name, id + * Deprecated Segment id, name + * Deprecated as_Universe function; not needed + * Deprecated ContactAnalysis and ContactAnalysis1 classes + 02/28/16 tyler.je.reddy, kain88-de, jbarnoud, richardjgowers, orbeckst manuel.nuno.melo, Balasubra, Saxenauts, mattihappy diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index f7db3601edd..2a9b4fd6a16 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -84,14 +84,14 @@ Calculate the CA end-to-end distance (in angstroem):: >>> import numpy as np - >>> coord = ca.coordinates() + >>> coord = ca.positions >>> v = coord[-1] - coord[0] # last Ca minus first one >>> np.sqrt(np.dot(v, v,)) 10.938133 Define a function eedist(): >>> def eedist(atoms): - ... coord = atoms.coordinates() + ... coord = atoms.positions ... v = coord[-1] - coord[0] ... return sqrt(dot(v, v,)) ... @@ -139,7 +139,14 @@ import logging import warnings +logger = logging.getLogger("MDAnalysis.__init__") + from .version import __version__ +try: + from .authors import __authors__ +except ImportError: + logger.info('Could not find authors.py, __authors__ will be empty.') + __authors__ = [] # custom exceptions and warnings from .exceptions import ( @@ -155,7 +162,7 @@ del logging # DeprecationWarnings are loud by default -warnings.simplefilter('always', DeprecationWarning) +warnings.simplefilter('once', DeprecationWarning) from . import units diff --git a/package/MDAnalysis/analysis/align.py b/package/MDAnalysis/analysis/align.py index 36db886d360..227752bd52a 100644 --- a/package/MDAnalysis/analysis/align.py +++ b/package/MDAnalysis/analysis/align.py @@ -14,8 +14,7 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -""" -Coordinate fitting and alignment --- :mod:`MDAnalysis.analysis.align` +"""Coordinate fitting and alignment --- :mod:`MDAnalysis.analysis.align` ===================================================================== :Author: Oliver Beckstein, Joshua Adelman @@ -70,21 +69,29 @@ >>> ref = Universe(PDB_small) >>> mobile = Universe(PSF,DCD) >>> rmsd(mobile.atoms.CA.positions, ref.atoms.CA.positions) - 18.858259026820352 + 16.282308620224068 Note that in this example translations have not been removed. In order to look at the pure rotation one needs to superimpose the centres of mass (or geometry) first: - >>> ref0 = ref.atoms.CA.positions - ref.atoms.CA.center_of_mass() - >>> mobile0 = mobile.atoms.CA.positions - mobile.atoms.CA.center_of_mass() - >>> rmsd(mobile0, ref0) - 6.8093965864717951 + >>> rmsd(mobile.atoms.CA.positions, ref.atoms.CA.positions, center=True) + 12.639693690256898 + +This has only done a translational superposition. If you want to also do a +rotational superposition use the superposition keyword. This will calculate a +minimized RMSD between the reference and mobile structure. + + >>> rmsd(mobile.atoms.CA.positions, ref.atoms.CA.positions, + >>> superposition=True) + 6.8093965864717951 The rotation matrix that superimposes *mobile* on *ref* while minimizing the CA-RMSD is obtained with the :func:`rotation_matrix` function :: + >>> mobile0 = mobile.atoms.CA.positions - mobile.atoms.center_of_mass() + >>> ref0 = ref.atoms.CA.positions - ref.atoms.center_of_mass() >>> R, rmsd = rotation_matrix(mobile0, ref0) >>> print rmsd 6.8093965864717951 @@ -155,6 +162,7 @@ .. autofunction:: fasta2select .. autofunction:: get_matching_atoms + """ import os.path @@ -209,8 +217,8 @@ def rotation_matrix(a, b, weights=None): :meth:`MDAnalysis.core.AtomGroup.AtomGroup.rotate` to generate a rotated selection, e.g. :: - >>> R = rotation_matrix(A.select_atoms('backbone').coordinates(), - >>> B.select_atoms('backbone').coordinates())[0] + >>> R = rotation_matrix(A.select_atoms('backbone').positions, + >>> B.select_atoms('backbone').positions)[0] >>> A.atoms.rotate(R) >>> A.atoms.write("rotated.pdb") @@ -348,10 +356,10 @@ def alignto(mobile, reference, select="all", mass_weighted=False, ref_com = ref_atoms.center_of_geometry() mobile_com = mobile_atoms.center_of_geometry() - ref_coordinates = ref_atoms.coordinates() - ref_com - mobile_coordinates = mobile_atoms.coordinates() - mobile_com + ref_coordinates = ref_atoms.positions - ref_com + mobile_coordinates = mobile_atoms.positions - mobile_com - old_rmsd = rms.rmsd(mobile_atoms.coordinates(), ref_atoms.coordinates()) + old_rmsd = rms.rmsd(mobile_coordinates, ref_coordinates) R, new_rmsd = rotation_matrix(mobile_coordinates, ref_coordinates, weights=weights) @@ -486,10 +494,10 @@ def rms_fit_trj(traj, reference, select='all', filename=None, rmsdfile=None, pre # reference centre of mass system ref_com = ref_atoms.center_of_mass() - ref_coordinates = ref_atoms.coordinates() - ref_com + ref_coordinates = ref_atoms.positions - ref_com # allocate the array for selection atom coords - traj_coordinates = traj_atoms.coordinates().copy() + traj_coordinates = traj_atoms.positions.copy() # RMSD timeseries nframes = len(frames) @@ -508,7 +516,7 @@ def rms_fit_trj(traj, reference, select='all', filename=None, rmsdfile=None, pre # shift coordinates for rotation fitting # selection is updated with the time frame x_com = traj_atoms.center_of_mass().astype(np.float32) - traj_coordinates[:] = traj_atoms.coordinates() - x_com + traj_coordinates[:] = traj_atoms.positions - x_com # Need to transpose coordinates such that the coordinate array is # 3xN instead of Nx3. Also qcp requires that the dtype be float64 diff --git a/package/MDAnalysis/analysis/base.py b/package/MDAnalysis/analysis/base.py index bdb2970f0be..581a76f2303 100644 --- a/package/MDAnalysis/analysis/base.py +++ b/package/MDAnalysis/analysis/base.py @@ -92,7 +92,7 @@ def _conclude(self): """ pass - def run(self): + def run(self, **kwargs): """Perform the calculation""" logger.info("Starting preparation") self._prepare() diff --git a/package/MDAnalysis/analysis/contacts.py b/package/MDAnalysis/analysis/contacts.py index 4128404162e..13c06bc9a8b 100644 --- a/package/MDAnalysis/analysis/contacts.py +++ b/package/MDAnalysis/analysis/contacts.py @@ -2,8 +2,8 @@ # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # @@ -14,50 +14,32 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -""" -Native contacts analysis --- :mod:`MDAnalysis.analysis.contacts` +"""Native contacts analysis --- :mod:`MDAnalysis.analysis.contacts` ================================================================ -:Author: Oliver Beckstein -:Year: 2010 -:Copyright: GNU Public License v3 -Analysis of native contacts *q* over a trajectory. +Analysis of native contacts *Q* over a trajectory. Native contacts of a +conformation are contacts that exist in a reference structure and in the +conformation. Contacts in the reference structure are always defined as being +closer then a distance `radius`. The fraction of native contacts for a +conformation can be calculated in different ways. This module supports 3 +different metrics liseted below, as wel as custom metrics. + +1. *Hard Cut*: To count as a contact the atoms *i* and *j* have to be at least + as close as in the reference structure. -* a "contact" exists between two atoms *i* and *j* if the distance between them is - smaller than a given *radius* +2. *Soft Cut*: The atom pair *i* and *j* is assigned based on a soft potential + that is 1 for if the distance is 0, 1./2 if the distance is the same as in + the reference and 0 for large distances. For the exact definition of the + potential and parameters have a look at `soft_cut_q`. -* a "native contact" exists between *i* and *j* if a contact exists and if the - contact also exists between the equivalent atoms in a reference structure or - conformation +3. *Radius Cut*: To count as a contact the atoms *i* and *j* cannot be further + apart then some distance `radius`. -The "fraction of native contacts" *q(t)* is a number between 0 and 1 and +The "fraction of native contacts" *Q(t)* is a number between 0 and 1 and calculated as the total number of native contacts for a given time frame divided by the total number of contacts in the reference structure. -Classes are available for two somewhat different ways to perform a contact -analysis: - -1. Contacts between two groups of atoms are defined with - :class:`ContactAnalysis1`), which allows one to calculate *q(t)* over - time. This is especially useful in order to look at native contacts during - an equilibrium simulation where one can also look at the average matrix of - native contacts (see :meth:`ContactAnalysis1.plot_qavg`). - -2. Contacts are defined within one group in a protein (e.g. all C-alpha atoms) - but relative to *two different conformations* 1 and 2, using - :class:`ContactAnalysis`. This allows one to do a *q1-q2* analysis that - shows how native contacts of state 1 change in comparison to native contacts - of state 2. Transition pathways have been analyzed in terms of these two - variables q1 and q2 that relate to the native contacts in the end states of - the transition. - -.. SeeAlso:: See http://lorentz.dynstr.pasteur.fr/joel/adenylate.php for an - example of contact analysis applied to MinActionPath trajectories of AdK - (although this was *not* performed with MDAnalysis --- it's provided as a - very good illustrative example). - - Examples -------- @@ -68,96 +50,462 @@ when the AdK enzyme opens up; this is one of the example trajectories in MDAnalysis. :: - import MDAnalysis - import MDAnalysis.analysis.contacts - from MDAnalysis.tests.datafiles import PSF,DCD +>>> import MDAnalysis as mda +>>> from MDAnalysis.analysis import contacts +>>> from MDAnalysis.tests.datafiles import PSF,DCD +>>> import matplotlib.pyplot as plt +>>> # example trajectory (transition of AdK from closed to open) +>>> u = mda.Universe(PSF,DCD) +>>> # crude definition of salt bridges as contacts between NH/NZ in ARG/LYS and +>>> # OE*/OD* in ASP/GLU. You might want to think a little bit harder about the +>>> # problem before using this for real work. +>>> sel_basic = "(resname ARG LYS) and (name NH* NZ)" +>>> sel_acidic = "(resname ASP GLU) and (name OE* OD*)" +>>> # reference groups (first frame of the trajectory, but you could also use a +>>> # separate PDB, eg crystal structure) +>>> acidic = u.select_atoms(sel_acidic) +>>> basic = u.select_atoms(sel_basic) +>>> # set up analysis of native contacts ("salt bridges"); salt bridges have a +>>> # distance <6 A +>>> ca1 = contacts.Contacts(u, selection=(sel_acidic, sel_basic), +>>> refgroup=(acidic, basic), radius=6.0) +>>> # iterate through trajectory and perform analysis of "native contacts" Q +>>> ca1.run() +>>> # print number of averave contacts +>>> average_contacts = np.mean(ca1.timeseries[:, 1]) +>>> print('average contacts = {}'.format(average_contacts)) +>>> # plot time series q(t) +>>> f, ax = plt.subplots() +>>> ax.plot(ca1.timeseries[:, 0], ca1.timeseries[:, 1]) +>>> ax.set(xlabel='frame', ylabel='fraction of native contacts', + title='Native Contacts, average = {:.2f}'.format(average_contacts)) +>>> fig.show() - # example trajectory (transition of AdK from closed to open) - u = MDAnalysis.Universe(PSF,DCD) - - # crude definition of salt bridges as contacts between NH/NZ in ARG/LYS and OE*/OD* in ASP/GLU. - # You might want to think a little bit harder about the problem before using this for real work. - sel_basic = "(resname ARG or resname LYS) and (name NH* or name NZ)" - sel_acidic = "(resname ASP or resname GLU) and (name OE* or name OD*)" - - # reference groups (first frame of the trajectory, but you could also use a separate PDB, eg crystal structure) - acidic = u.select_atoms(sel_acidic) - basic = u.select_atoms(sel_basic) - - # set up analysis of native contacts ("salt bridges"); salt bridges have a distance <6 A - CA1 = MDAnalysis.analysis.contacts.ContactAnalysis1(u, selection=(sel_acidic, sel_basic), refgroup=(acidic, - basic), radius=6.0, outfile="qsalt.dat") - - # iterate through trajectory and perform analysis of "native contacts" q - # (force=True ignores any previous results, force=True is useful when testing) - CA1.run(force=True) - - # plot time series q(t) [possibly do "import pylab; pylab.clf()" do clear the figure first...] - CA1.plot(filename="adk_saltbridge_contact_analysis1.pdf", linewidth=3, color="blue") - - # or plot the data in qsalt.dat yourself. - CA1.plot_qavg(filename="adk_saltbridge_contact_analysis1_matrix.pdf") The first graph shows that when AdK opens, about 20% of the salt bridges that existed in the closed state disappear when the enzyme opens. They open in a step-wise fashion (made more clear by the movie `AdK_zipper_cartoon.avi`_). -The output graphs can be made prettier but if you look at the code -itself then you'll quickly figure out what to do. The qavg plot is the -matrix of all contacts, averaged over the trajectory. This plot makes -more sense for an equilibrium trajectory than for the example above -but is is included for illustration. - -See the docs for :class:`ContactAnalysis1` for another example. - - .. AdK_zipper_cartoon.avi: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC2803350/bin/NIHMS150766-supplement-03.avi +Notes +----- +Suggested cutoff distances for different simulations +* For all-atom simulations, cutoff = 4.5 A +* For coarse-grained simulations, cutoff = 6.0 A + Two-dimensional contact analysis (q1-q2) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Analyze a single DIMS transition of AdK between its closed and open conformation and plot the trajectory projected on q1-q2:: - import MDAnalysis.analysis.contacts - from MDAnalysis.tests.datafiles import PSF, DCD - C = MDAnalysis.analysis.contacts.ContactAnalysis(PSF, DCD) - C.run() - C.plot() + +>>> import MDAnalysis as mda +>>> from MDAnalysis.analysis import contacts +>>> from MDAnalysisTests.datafiles import PSF, DCD +>>> u = mda.Universe(PSF, DCD) +>>> q1q2 = contacts.q1q2(u, 'name CA', radius=8) +>>> q1q2.run() +>>> +>>> f, ax = plt.subplots(1, 2, figsize=plt.figaspect(0.5)) +>>> ax[0].plot(q1q2.timeseries[:, 0], q1q2.timeseries[:, 1], label='q1') +>>> ax[0].plot(q1q2.timeseries[:, 0], q1q2.timeseries[:, 2], label='q2') +>>> ax[0].legend(loc='best') +>>> ax[1].plot(q1q2.timeseries[:, 1], q1q2.timeseries[:, 2], '.-') +>>> f.show() Compare the resulting pathway to the `MinActionPath result for AdK`_. .. _MinActionPath result for AdK: http://lorentz.dynstr.pasteur.fr/joel/adenylate.php +Writing your own contact analysis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ..class:`Contacts` has been designed to be extensible for your own +analysis. As an example we will analysis when the acidic and basic groups of +are in contact which each other, this means that at least one of the contacts +formed in the reference is closer then 2.5 Angstrom. For this we define a new +method to determine if any contact is closer then 2.5 Angström that implements +the API ..class:`Contacts` except. + +The first to parameters `r` and `r0` are provided by ..class:`Contacts` the +others can be passed as keyword args using the `kwargs` parameter in +..class:`Contacts`. + +>>> def is_any_closer(r, r0, dist=2.5): +>>> return np.any(r < dist) + +Next we are creating an instance of the Constants class and use the +`is_any_closer` function as an argument to `method` and run the analysus + +>>> # crude definition of salt bridges as contacts between NH/NZ in ARG/LYS and +>>> # OE*/OD* in ASP/GLU. You might want to think a little bit harder about the +>>> # problem before using this for real work. +>>> sel_basic = "(resname ARG LYS) and (name NH* NZ)" +>>> sel_acidic = "(resname ASP GLU) and (name OE* OD*)" +>>> # reference groups (first frame of the trajectory, but you could also use a +>>> # separate PDB, eg crystal structure) +>>> acidic = u.select_atoms(sel_acidic) +>>> basic = u.select_atoms(sel_basic) +>>> nc = contacts.Contacts(u, selection=(sel_acidic, sel_basic), +>>> method=is_any_closer, +>>> refgroup=(acidic, basic), kwargs={'dist': 2.5}) +>>> +>>> nc.run() +>>> +>>> bound = nc.timeseries[:, 1] +>>> frames = nc.timeseries[:, 0] +>>> +>>> f, ax = plt.subplots() +>>> +>>> ax.plot(frames, bound, '.') +>>> ax.set(xlabel='frame', ylabel='is Bound', +>>> ylim=(-0.1, 1.1)) +>>> +>>> f.show() + +Functions +--------- + +.. autofunction:: hard_cut_q +.. autofunction:: soft_cut_q +.. autofunction:: radius_cut_q +.. autofunction:: contact_matrix +.. autofunction:: q1q2 + Classes ------- -.. autoclass:: ContactAnalysis +.. autoclass:: Contacts :members: + + +Deprecated +---------- + .. autoclass:: ContactAnalysis1 :members: +.. autoclass:: ContactAnalysis + :members: """ +from __future__ import division import os import errno import warnings import bz2 from six.moves import zip + import numpy as np +from numpy.lib.utils import deprecate + import logging import MDAnalysis import MDAnalysis.lib.distances from MDAnalysis.lib.util import openany - +from MDAnalysis.analysis.distances import distance_array +from MDAnalysis.core.AtomGroup import AtomGroup +from .base import AnalysisBase logger = logging.getLogger("MDAnalysis.analysis.contacts") +def soft_cut_q(r, r0, beta=5.0, lambda_constant=1.8): + r"""Calculate fraction of native contacts *Q* for a soft cut off + + ..math:: + Q(r, r_0) = \frac{1}{1 + e^{\beta (r - \lambda r_0)}} + + Reasonable values for different simulation types are + + *All Atom*: lambda_constant = 1.8 (unitless) + *Coarse Grained*: lambda_constant = 1.5 (unitless) + + Parameters + ---------- + r: array + Contact distances at time t + r0: array + Contact distances at time t=0, reference distances + beta: float (default 5.0 Angstrom) + Softness of the switching function + lambda_constant: float (default 1.8, unitless) + Reference distance tolerance + + Returns + ------- + Q : float + fraction of native contacts + + References + ---------- + .. [1] RB Best, G Hummer, and WA Eaton, "Native contacts determine protein + folding mechanisms in atomistic simulations" _PNAS_ **110** (2013), + 17874–17879. `10.1073/pnas.1311599110 + `_. + + """ + r = np.asarray(r) + r0 = np.asarray(r0) + result = 1/(1 + np.exp(beta*(r - lambda_constant * r0))) + + return result.sum() / len(r0) + + +def hard_cut_q(r, cutoff): + """Calculate fraction of native contacts *Q* for a hard cut off. The cutoff + can either be a float a ndarray the same shape as `r` + + Parameters + ---------- + r : ndarray + distance matrix + cutoff : ndarray | float + cut off value to count distances. Can either be a float of a ndarray of + the same size as distances + + Returns + ------- + Q : float + fraction of contacts + + """ + r = np.asarray(r) + cutoff = np.asarray(cutoff) + y = r <= cutoff + return y.sum() / r.size + + +def radius_cut_q(r, r0, radius): + """calculate native contacts *Q* based on the single sistance radius + + Parameters + ---------- + r : ndarray + distance array between atoms + r0 : ndarray + unused to fullfill Contacts API + radius : float + Distance between atoms at which a contact is formed + + Returns + ------- + Q : float + fraction of contacts + + References + ---------- + .. [1] Franklin, J., Koehl, P., Doniach, S., & Delarue, M. (2007). + MinActionPath: Maximum likelihood trajectory for large-scale structural + transitions in a coarse-grained locally harmonic energy landscape. + Nucleic Acids Research, 35(SUPPL.2), 477–482. + http://doi.org/10.1093/nar/gkm342 + + """ + return hard_cut_q(r, radius) + + +def contact_matrix(d, radius, out=None): + """calculate contacts from distance matrix + + Parameters + ---------- + d : array-like + distance matrix + radius : float + distance below which a contact is formed. + out: array (optional) + If `out` is supplied as a pre-allocated array of the correct + shape then it is filled instead of allocating a new one in + order to increase performance. + + Returns + ------- + contacts : ndarray + boolean array of formed contacts + """ + if out is not None: + out[:] = d <= radius + else: + out = d <= radius + return out + + +class Contacts(AnalysisBase): + """Calculate contacts based observables. + + The standard methods used in this class calculate the fraction of native + contacts *Q* from a trajectory. By defining your own method it is possible + to calculate other observables that only depend on the distances and a + possible reference distance. + + Attributes + ---------- + timeseries : list + list containing *Q* for all refgroup pairs and analyzed frames + + """ + def __init__(self, u, selection, refgroup, method="hard_cut", radius=4.5, + kwargs=None, start=None, stop=None, step=None,): + """Initialization + + Parameters + ---------- + u : Universe + trajectory + selection : tuple(string, string) + two contacting groups that change over time + refgroup : tuple(AtomGroup, AtomGroup) + two contacting atomgroups in their reference conformation. This + can also be a list of tuples containing different atom groups + radius : float, optional (4.5 Angstroms) + radius within which contacts exist in refgroup + method : string | callable (optional) + Can either be one of ['hard_cut' , 'soft_cut'] or a callable that + implements a API (r, r0, **kwargs). + kwargs : dict, optional + dictionary of additional kwargs passed to `method`. Check + respective functions for reasonable values. + start : int, optional + First frame of trajectory to analyse, Default: 0 + stop : int, optional + Last frame of trajectory to analyse, Default: -1 + step : int, optional + Step between frames to analyse, Default: 1 + + """ + if method == 'hard_cut': + self.fraction_contacts = hard_cut_q + elif method == 'soft_cut': + self.fraction_contacts = soft_cut_q + else: + if not callable(method): + raise ValueError("method has to be callable") + self.fraction_contacts = method + + # setup boilerplate + self.u = u + self._setup_frames(self.u.trajectory, start, stop, step) + + self.selection = selection + self.grA = u.select_atoms(selection[0]) + self.grB = u.select_atoms(selection[1]) + + # contacts formed in reference + self.r0 = [] + self.initial_contacts = [] + + if isinstance(refgroup[0], AtomGroup): + refA, refB = refgroup + self.r0.append(distance_array(refA.positions, refB.positions)) + self.initial_contacts.append(contact_matrix(self.r0[-1], radius)) + else: + for refA, refB in refgroup: + self.r0.append(distance_array(refA.positions, refB.positions)) + self.initial_contacts.append(contact_matrix(self.r0[-1], + radius)) + + self.fraction_kwargs = kwargs if kwargs is not None else {} + self.timeseries = [] + + def _single_frame(self): + # compute distance array for a frame + d = distance_array(self.grA.positions, self.grB.positions) + + y = np.empty(len(self.r0) + 1) + y[0] = self._ts.frame + for i, (initial_contacts, r0) in enumerate(zip(self.initial_contacts, + self.r0)): + # select only the contacts that were formed in the reference state + r = d[initial_contacts] + r0 = r0[initial_contacts] + y[i + 1] = self.fraction_contacts(r, r0, **self.fraction_kwargs) + + if len(y) == 1: + y = y[0] + self.timeseries.append(y) + + def _conclude(self): + self.timeseries = np.array(self.timeseries, dtype=float) + + def save(self, outfile): + """save contacts timeseries + + Parameter + --------- + outfile : str + file to save contacts + + """ + with open(outfile, "w") as f: + f.write("# q1 analysis\n") + np.savetxt(f, self.timeseries) + + +def _new_selections(u_orig, selections, frame): + """create stand alone AGs from selections at frame""" + u = MDAnalysis.Universe(u_orig.filename, u_orig.trajectory.filename) + u.trajectory[frame] + return [u.select_atoms(s) for s in selections] + + +def q1q2(u, selection='all', radius=4.5, + start=None, stop=None, step=None): + """Do a q1-q2 analysis to compare native contacts between the starting + structure and final structure of a trajectory. + + Parameters + ---------- + u : Universe + Universe with a trajectory + selection : string, optional + atoms to do analysis on + radius : float, optional + distance at which contact is formed + start : int, optional + First frame of trajectory to analyse, Default: 0 + stop : int, optional + Last frame of trajectory to analyse, Default: -1 + step : int, optional + Step between frames to analyse, Default: 1 + + Returns + ------- + contacts + Contact Analysis setup for a q1-q2 analysis + + """ + selection = (selection, selection) + first_frame_refs = _new_selections(u, selection, 0) + last_frame_refs = _new_selections(u, selection, -1) + return Contacts(u, selection, + (first_frame_refs, last_frame_refs), + radius=radius, method=radius_cut_q, + start=start, stop=stop, step=step, + kwargs={'radius': radius}) + +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ + + +# What comes now are old deprecated contact Analysis classes + + +# ContactAnalysis needs to be cleaned up and possibly renamed but +# until then it remains because we don't have the functionality +# elsewhere. + +@deprecate(new_name="Contacts", message="This class will be removed in 0.17") class ContactAnalysis(object): """Perform a native contact analysis ("q1-q2"). @@ -171,6 +519,20 @@ class ContactAnalysis(object): The total number of contacts in the reference states 1 and 2 are stored in :attr:`ContactAnalysis.nref` (index 0 and 1). + + The :meth:`ContactAnalysis.run` method calculates the percentage of native + contacts *q1* and *q2* along a trajectory. "Contacts" are defined as the + number of Ca atoms (or per-residue *centroids* of a user defined + *selection*) within *radius* of a primary Ca. *q1* is the fraction of + contacts relative to the reference state 1 (typically the starting + conformation of the trajectory) and *q2* is the fraction of contacts + relative to the conformation 2. + + The timeseries is written to a bzip2-compressed file in `targetdir` + named "basename(trajectory)infix_q1q2.dat.bz2" and is also + accessible as the attribute + :attr:`ContactAnalysis.timeseries`. + """ def __init__(self, topology, trajectory, ref1=None, ref2=None, radius=8.0, @@ -178,49 +540,39 @@ def __init__(self, topology, trajectory, ref1=None, ref2=None, radius=8.0, selection="name CA", centroids=False): """Calculate native contacts from two reference structures. - :Arguments: - *topology* - psf or pdb file - *trajectory* - dcd or xtc/trr file - *ref1* - structure of the reference conformation 1 (pdb); if ``None`` the *first* - frame of the trajectory is chosen - *ref2* - structure of the reference conformation 2 (pdb); if ``None`` the *last* - frame of the trajectory is chosen - *radius* - contacts are deemed any Ca within radius [8 A] - *targetdir* - output files are saved there [.] - *infix* - additional tag string that is inserted into the output filename of the - data file [""] - *selection* + Parameters + ---------- + topology : filename + topology file + trajectory : filename + trajectory + ref1 : filename or ``None``, optional + structure of the reference conformation 1 (pdb); if ``None`` the + *first* frame of the trajectory is chosen + ref2 : filename or ``None``, optional + structure of the reference conformation 2 (pdb); if ``None`` the + *last* frame of the trajectory is chosen + radius : float, optional, default 8 A + contacts are deemed any Ca within radius + targetdir : path, optional, default ``.`` + output files are saved in this directory + infix : string, optional + additional tag string that is inserted into the output filename of + the data file + selection : string, optional, default ``"name CA"`` MDAnalysis selection string that selects the particles of interest; the default is to only select the C-alpha atoms - in *ref1* and *ref*2 ["name CA"] + in `ref1` and `ref2` - .. Note:: If *selection* produces more than one atom per + .. Note:: If `selection` produces more than one atom per residue then you will get multiple contacts per - residue unless you also set *centroids* = ``True`` - *centroids* + residue unless you also set `centroids` = ``True`` + centroids : bool If set to ``True``, use the centroids for the selected atoms on a per-residue basis to compute contacts. This allows, for instance - defining the sidechains as *selection* and then computing distances + defining the sidechains as `selection` and then computing distances between sidechain centroids. - The function calculates the percentage of native contacts *q1* and *q2* - along a trajectory. "Contacts" are defined as the number of Ca atoms (or - per-residue *centroids* of a user defined *selection*) within *radius* of - a primary Ca. *q1* is the fraction of contacts relative to the reference - state 1 (typically the starting conformation of the trajectory) and *q2* - is the fraction of contacts relative to the conformation 2. - - The timeseries is written to a bzip2-compressed file in *targetdir* - named "basename(*trajectory*)*infix*_q1q2.dat.bz2" and is also - accessible as the attribute - :attr:`ContactAnalysis.timeseries`. """ self.topology = topology @@ -241,11 +593,15 @@ def __init__(self, topology, trajectory, ref1=None, ref2=None, radius=8.0, # short circuit if output file already exists: skip everything if self.output_exists(): self._skip = True - return # do not bother reading any data or initializing arrays... !! - # don't bother if trajectory is empty (can lead to segfaults so better catch it) + # do not bother reading any data or initializing arrays... !! + return + + # don't bother if trajectory is empty (can lead to segfaults so better + # catch it) stats = os.stat(trajectory) if stats.st_size == 0: - warnings.warn('trajectory = {trajectory!s} is empty, skipping...'.format(**vars())) + warnings.warn('trajectory = {trajectory!s} is empty, ' + 'skipping...'.format(**vars())) self._skip = True return # under normal circumstances we do not skip @@ -287,14 +643,17 @@ def __init__(self, topology, trajectory, ref1=None, ref2=None, radius=8.0, def get_distance_array(self, g, **kwargs): """Calculate the self_distance_array for atoms in group *g*. - :Keywords: - *results* + Parameters + ---------- + g : AtomGroup + group of atoms to calculate distance array for + results : array, optional passed on to :func:`MDAnalysis.lib.distances.self_distance_array` as a preallocated array - *centroids* - ``True``: calculate per-residue centroids from the selected atoms; - ``False``: consider each atom separately; ``None``: use the class - default for *centroids* [``None``] + centroids : bool, optional, default ``None`` + ``True``: calculate per-residue centroids from the selected + atoms; ``False``: consider each atom separately; ``None``: use + the class default for *centroids* [``None``] """ centroids = kwargs.pop("centroids", None) @@ -303,15 +662,18 @@ def get_distance_array(self, g, **kwargs): coordinates = g.positions else: # centroids per residue (but only including the selected atoms) - coordinates = np.array([residue.centroid() for residue in g.split("residue")]) - return MDAnalysis.lib.distances.self_distance_array(coordinates, **kwargs) + coordinates = np.array([residue.centroid() + for residue in g.split("residue")]) + return MDAnalysis.lib.distances.self_distance_array(coordinates, + **kwargs) def output_exists(self, force=False): """Return True if default output file already exists. Disable with force=True (will always return False) """ - return (os.path.isfile(self.output) or os.path.isfile(self.output_bz2)) and not (self.force or force) + return (os.path.isfile(self.output) or + os.path.isfile(self.output_bz2)) and not (self.force or force) def run(self, store=True, force=False): """Analyze trajectory and produce timeseries. @@ -320,7 +682,8 @@ def run(self, store=True, force=False): store=True) and writes them to a bzip2-compressed data file. """ if self._skip or self.output_exists(force=force): - warnings.warn("File {output!r} or {output_bz2!r} already exists, loading {trajectory!r}.".format(**vars(self))) + warnings.warn("File {output!r} or {output_bz2!r} already exists, " + "loading {trajectory!r}.".format(**vars(self))) try: self.load(self.output) except IOError: @@ -329,7 +692,10 @@ def run(self, store=True, force=False): outbz2 = bz2.BZ2File(self.output_bz2, mode='w', buffering=8192) try: - outbz2.write("# q1-q2 analysis\n# nref1 = {0:d}\n# nref2 = {1:d}\n".format(self.nref[0], self.nref[1])) + outbz2.write("# q1-q2 analysis\n" + "# nref1 = {0:d}\n" + "# nref2 = {1:d}\n".format(self.nref[0], + self.nref[1])) outbz2.write("# frame q1 q2 n1 n2\n") records = [] for ts in self.u.trajectory: @@ -350,11 +716,28 @@ def run(self, store=True, force=False): return self.output_bz2 def qarray(self, d, out=None): - """Return distance array with True for contacts. + """Return array with ``True`` for contacts. + + Note + ---- + This method is typically only used internally. + + Arguments + --------- + d : array + 2D array of distances. The method uses the value of + :attr:`radius` to determine if a ``distance < radius`` + is considered a contact. + out : array, optional + If `out` is supplied as a pre-allocated array of the correct + shape then it is filled instead of allocating a new one in + order to increase performance. + + Returns + ------- + array + contact matrix - If *out* is supplied as a pre-allocated array of the correct - shape then it is filled instead of allocating a new one in - order to increase performance. """ if out is None: out = (d <= self.radius) @@ -363,12 +746,31 @@ def qarray(self, d, out=None): return out def qN(self, q, n, out=None): - """Calculate native contacts relative to state n. + """Calculate native contacts relative to reference state. + + Note + ---- + This method is typically only used internally. + + Arguments + --------- + q : array + contact matrix (see :meth:`Contacts.qarray`) + out : array, optional + If `out` is supplied as a pre-allocated array of the correct + shape then it will contain the contact matrix relative + to the reference state, i.e. only those contacts that + are also seen in the reference state. + + Returns + ------- + contacts : integer + total number of contacts + fraction : float + fraction of contacts relative to the reference state - If *out* is supplied as a pre-allocated array of the correct - shape then it is filled instead of allocating a new one in - order to increase performance. """ + if out is None: out = np.logical_and(q, self.qref[n]) else: @@ -392,29 +794,26 @@ def plot(self, **kwargs): kwargs.setdefault('color', 'black') if self.timeseries is None: - raise ValueError("No timeseries data; do 'ContactAnalysis.run(store=True)' first.") + raise ValueError("No timeseries data; do " + "'ContactAnalysis.run(store=True)' first.") t = self.timeseries plot(t[1], t[2], **kwargs) xlabel(r"$q_1$") ylabel(r"$q_2$") -# ContactAnalysis1 is a (hopefully) temporary hack. It should be unified with ContactAnalysis -# or either should be derived from a base class because many methods are copy&paste with -# minor changes (mostly for going from q1q2 -> q1 only). -# If ContactAnalysis is enhanced to accept two references then this should be even easier. -# It might also be worthwhile making a simpler class that just does the q calculation -# and use it for both reference and trajectory data. - +@deprecate(new_name="Contacts", message="This class will be removed in 0.17") class ContactAnalysis1(object): - """Perform a very flexible native contact analysis with respect to a single reference. + """Perform a very flexible native contact analysis with respect to a single + reference. This analysis class allows one to calculate the fraction of native contacts *q* between two arbitrary groups of atoms with respect to an arbitrary reference structure. For instance, as a reference one could take a crystal structure of a complex, and as the two groups atoms one selects two molecules A and B in the complex. Then the question to be answered by *q* - is, is which percentage of the contacts between A and B persist during the simulation. + is, is which percentage of the contacts between A and B persist during the + simulation. First prepare :class:`~MDAnalysis.core.AtomGroup.AtomGroup` selections for the reference atoms; this example uses some arbitrary selections:: @@ -443,7 +842,8 @@ class ContactAnalysis1(object): Now we are ready to set up the analysis:: - CA1 = ContactAnalysis1(u, selection=(selA,selB), refgroup=(refA,refB), radius=8.0, outfile="q.dat") + CA1 = ContactAnalysis1(u, selection=(selA,selB), refgroup=(refA,refB), + radius=8.0, outfile="q.dat") If the groups do not match in length then a :exc:`ValueError` is raised. @@ -481,14 +881,16 @@ def __init__(self, *args, **kwargs): :Keywords: *selection* - selection string that determines which distances are calculated; if this - is a tuple or list with two entries then distances are calculated between - these two different groups ["name CA or name B*"] + selection string that determines which distances are calculated; if + this is a tuple or list with two entries then distances are + calculated between these two different groups ["name CA or name + B*"] *refgroup* - reference group, either a single :class:`~MDAnalysis.core.AtomGroup.AtomGroup` - (if there is only a single *selection*) or a list of two such groups. - The reference contacts are directly computed from *refgroup* and hence - the atoms in the reference group(s) must be equivalent to the ones produced + reference group, either a single + :class:`~MDAnalysis.core.AtomGroup.AtomGroup` (if there is only a + single *selection*) or a list of two such groups. The reference + contacts are directly computed from *refgroup* and hence the atoms + in the reference group(s) must be equivalent to the ones produced by the *selection* on the input trajectory. *radius* contacts are deemed any atoms within radius [8.0 A] @@ -509,6 +911,9 @@ def __init__(self, *args, **kwargs): The timeseries is written to a file *outfile* and is also accessible as the attribute :attr:`ContactAnalysis1.timeseries`. + + .. deprecated: 0.14.0 + """ # XX or should I use as input @@ -521,8 +926,14 @@ def __init__(self, *args, **kwargs): # - make this selection based on qavg from os.path import splitext - self.selection_strings = self._return_tuple2(kwargs.pop('selection', "name CA or name B*"), "selection") - self.references = self._return_tuple2(kwargs.pop('refgroup', None), "refgroup") + warnings.warn("ContactAnalysis1 is deprecated and will be removed " + "in 1.0. Use Contacts instead.", + category=DeprecationWarning) + + self.selection_strings = self._return_tuple2(kwargs.pop( + 'selection', "name CA or name B*"), "selection") + self.references = self._return_tuple2(kwargs.pop('refgroup', None), + "refgroup") self.radius = kwargs.pop('radius', 8.0) self.targetdir = kwargs.pop('targetdir', os.path.curdir) self.output = kwargs.pop('outfile', "q1.dat.gz") @@ -534,21 +945,24 @@ def __init__(self, *args, **kwargs): self.filenames = args self.universe = MDAnalysis.as_Universe(*args, **kwargs) - self.selections = [self.universe.select_atoms(s) for s in self.selection_strings] + self.selections = [self.universe.select_atoms(s) + for s in self.selection_strings] # sanity checkes for x in self.references: if x is None: raise ValueError("a reference AtomGroup must be supplied") - for ref, sel, s in zip(self.references, self.selections, self.selection_strings): + for ref, sel, s in zip(self.references, + self.selections, + self.selection_strings): if ref.atoms.n_atoms != sel.atoms.n_atoms: - raise ValueError("selection=%r: Number of atoms differ between " - "reference (%d) and trajectory (%d)" % + raise ValueError("selection=%r: Number of atoms differ " + "between reference (%d) and trajectory (%d)" % (s, ref.atoms.n_atoms, sel.atoms.n_atoms)) # compute reference contacts dref = MDAnalysis.lib.distances.distance_array( - self.references[0].coordinates(), self.references[1].coordinates()) + self.references[0].positions, self.references[1].positions) self.qref = self.qarray(dref) self.nref = self.qref.sum() @@ -569,8 +983,8 @@ def _return_tuple2(self, x, name): elif len(t) == 1: return (x, x) else: - raise ValueError("%(name)s must be a single object or a tuple/list with two objects " - "and not %(x)r" % vars()) + raise ValueError("%(name)s must be a single object or a " + "tuple/list with two objects and not %(x)r" % vars()) def output_exists(self, force=False): """Return True if default output file already exists. @@ -579,38 +993,47 @@ def output_exists(self, force=False): """ return os.path.isfile(self.output) and not (self.force or force) - def run(self, store=True, force=False, start=0, stop=None, step=1, **kwargs): + def run(self, store=True, force=False, start=0, stop=None, step=1, + **kwargs): """Analyze trajectory and produce timeseries. Stores results in :attr:`ContactAnalysis1.timeseries` (if store=True) and writes them to a data file. The average q is written to a second data file. *start* - The value of the first frame index in the trajectory to be used (default: index 0) + The value of the first frame index in the trajectory to be used + (default: index 0) *stop* - The value of the last frame index in the trajectory to be used (default: None -- use all frames) + The value of the last frame index in the trajectory to be used + (default: None -- use all frames) *step* - The number of frames to skip during trajectory iteration (default: use every frame) + The number of frames to skip during trajectory iteration (default: + use every frame) + """ if 'start_frame' in kwargs: - warnings.warn("start_frame argument has been deprecated, use start instead --" - "removal targeted for version 0.15.0", DeprecationWarning) + warnings.warn("start_frame argument has been deprecated, use " + "start instead -- removal targeted for version " + "0.15.0", DeprecationWarning) start = kwargs.pop('start_frame') if 'end_frame' in kwargs: - warnings.warn("end_frame argument has been deprecated, use stop instead --" - "removal targeted for version 0.15.0", DeprecationWarning) + warnings.warn("end_frame argument has been deprecated, use " + "stop instead -- removal targeted for version " + "0.15.0", DeprecationWarning) stop = kwargs.pop('end_frame') if 'step_value' in kwargs: - warnings.warn("step_value argument has been deprecated, use step instead --" - "removal targeted for version 0.15.0", DeprecationWarning) + warnings.warn("step_value argument has been deprecated, use " + "step instead -- removal targeted for version " + "0.15.0", DeprecationWarning) step = kwargs.pop('step_value') if self.output_exists(force=force): - warnings.warn("File %r already exists, loading it INSTEAD of trajectory %r. " - "Use force=True to overwrite the output file. " % + warnings.warn("File %r already exists, loading it INSTEAD of " + "trajectory %r. Use force=True to overwrite " + "the output file. " % (self.output, self.universe.trajectory.filename)) self.load(self.output) return None @@ -624,7 +1047,9 @@ def run(self, store=True, force=False, start=0, stop=None, step=1, **kwargs): for ts in self.universe.trajectory[start:stop:step]: frame = ts.frame # use pre-allocated distance array to save a little bit of time - MDAnalysis.lib.distances.distance_array(A.coordinates(), B.coordinates(), result=self.d) + MDAnalysis.lib.distances.distance_array(A.coordinates(), + B.coordinates(), + result=self.d) self.qarray(self.d, out=self.q) n1, q1 = self.qN(self.q, out=self._qtmp) self.qavg += self.q @@ -638,7 +1063,8 @@ def run(self, store=True, force=False, start=0, stop=None, step=1, **kwargs): if n_frames > 0: self.qavg /= n_frames else: - logger.warn("No frames were analyzed. Check values of start, stop, step.") + logger.warn("No frames were analyzed. " + "Check values of start, stop, step.") logger.debug("start={start} stop={stop} step={step}".format(**vars())) np.savetxt(self.outarray, self.qavg, fmt="%8.6f") return self.output @@ -709,7 +1135,8 @@ def plot(self, filename=None, **kwargs): kwargs.setdefault('color', 'black') kwargs.setdefault('linewidth', 2) if self.timeseries is None: - raise ValueError("No timeseries data; do 'ContactAnalysis.run(store=True)' first.") + raise ValueError("No timeseries data; " + "do 'ContactAnalysis.run(store=True)' first.") t = self.timeseries plot(t[0], t[1], **kwargs) xlabel(r"frame number $t$") @@ -719,8 +1146,10 @@ def plot(self, filename=None, **kwargs): savefig(filename) def _plot_qavg_pcolor(self, filename=None, **kwargs): - """Plot :attr:`ContactAnalysis1.qavg`, the matrix of average native contacts.""" - from pylab import pcolor, gca, meshgrid, xlabel, ylabel, xlim, ylim, colorbar, savefig + """Plot :attr:`ContactAnalysis1.qavg`, the matrix of average native + contacts.""" + from pylab import (pcolor, gca, meshgrid, xlabel, ylabel, xlim, ylim, + colorbar, savefig) x, y = self.selections[0].resids, self.selections[1].resids X, Y = meshgrid(x, y) @@ -748,7 +1177,8 @@ def plot_qavg(self, filename=None, **kwargs): suffix determines the file type, e.g. pdf, png, eps, ...). All other keyword arguments are passed on to :func:`pylab.imshow`. """ - from pylab import imshow, xlabel, ylabel, xlim, ylim, colorbar, cm, clf, savefig + from pylab import (imshow, xlabel, ylabel, xlim, ylim, colorbar, cm, + clf, savefig) x, y = self.selections[0].resids, self.selections[1].resids diff --git a/package/MDAnalysis/analysis/density.py b/package/MDAnalysis/analysis/density.py index a58edced141..cdf07418d57 100644 --- a/package/MDAnalysis/analysis/density.py +++ b/package/MDAnalysis/analysis/density.py @@ -80,6 +80,10 @@ :members: :inherited-members: +.. deprecated:: 0.15.0 + The "permissive" flag is not used anymore (and effectively + defaults to True); it will be completely removed in 0.16.0. + """ from __future__ import print_function from six.moves import range @@ -466,7 +470,7 @@ def current_coordinates(): group = u.select_atoms(atomselection) def current_coordinates(): - return group.coordinates() + return group.positions coord = current_coordinates() logger.info("Selected {0:d} atoms out of {1:d} atoms ({2!s}) from {3:d} total.".format(coord.shape[0], len(u.select_atoms(atomselection)), atomselection, len(u.atoms))) @@ -501,7 +505,7 @@ def current_coordinates(): pm = ProgressMeter(u.trajectory.n_frames, interval=interval, quiet=quiet, format="Histogramming %(n_atoms)6d atoms in frame " "%(step)5d/%(numsteps)d [%(percentage)5.1f%%]\r") - start, stop, step = u.trajectory.check_slice_indices(start, stop, step) + start, stop, step = u.trajectory.check_slice_indices(start, stop, step) for ts in u.trajectory[start:stop:step]: if update_selection: group = u.select_atoms(atomselection) @@ -515,8 +519,8 @@ def current_coordinates(): h[:], edges[:] = np.histogramdd(coord, bins=bins, range=arange, normed=False) grid += h # accumulate average histogram - - + + n_frames = len(range(start, stop, step)) grid /= float(n_frames) @@ -596,14 +600,14 @@ def notwithin_coordinates(cutoff=cutoff): ns_w = NS.AtomNeighborSearch(solvent) # build kd-tree on solvent (N_w > N_protein) solvation_shell = ns_w.search_list(protein, cutoff) # solvent within CUTOFF of protein group = MDAnalysis.core.AtomGroup.AtomGroup(set_solvent - set(solvation_shell)) # bulk - return group.coordinates() + return group.positions else: def notwithin_coordinates(cutoff=cutoff): # acts as ' WITHIN OF ' # must update every time step ns_w = NS.AtomNeighborSearch(solvent) # build kd-tree on solvent (N_w > N_protein) group = ns_w.search_list(protein, cutoff) # solvent within CUTOFF of protein - return group.coordinates() + return group.positions else: # slower distance matrix based (calculate all with all distances first) dist = np.zeros((len(solvent), len(protein)), dtype=np.float64) @@ -616,8 +620,8 @@ def notwithin_coordinates(cutoff=cutoff): aggregatefunc = np.any def notwithin_coordinates(cutoff=cutoff): - s_coor = solvent.coordinates() - p_coor = protein.coordinates() + s_coor = solvent.positions + p_coor = protein.positions # Does water i satisfy d[i,j] > r for ALL j? d = MDAnalysis.analysis.distances.distance_array(s_coor, p_coor, box=box, result=dist) return s_coor[aggregatefunc(compare(d, cutoff), axis=1)] @@ -727,11 +731,7 @@ def __init__(self, pdb, delta=1.0, atomselection='resname HOH and name O', :Arguments: pdb - PDB file or :class:`MDAnalysis.Universe`; a PDB is read with the - simpl PDB reader. If the Bio.PDB reader is required, either set - the *permissive_pdb_reader* flag to ``False`` in - :data:`MDAnalysis.core.flags` or supply a Universe - that was created with the `permissive` = ``False`` keyword. + PDB file or :class:`MDAnalysis.Universe`; atomselection selection string (MDAnalysis syntax) for the species to be analyzed delta @@ -751,7 +751,7 @@ def __init__(self, pdb, delta=1.0, atomselection='resname HOH and name O', """ u = MDAnalysis.as_Universe(pdb) group = u.select_atoms(atomselection) - coord = group.coordinates() + coord = group.positions logger.info("Selected {0:d} atoms ({1!s}) out of {2:d} total.".format(coord.shape[0], atomselection, len(u.atoms))) smin = np.min(coord, axis=0) - padding smax = np.max(coord, axis=0) + padding diff --git a/package/MDAnalysis/analysis/distances.py b/package/MDAnalysis/analysis/distances.py index adea4299565..af9d0014648 100644 --- a/package/MDAnalysis/analysis/distances.py +++ b/package/MDAnalysis/analysis/distances.py @@ -25,47 +25,90 @@ :func:`dist` and :func:`between` can take atom groups that do not even have to be from the same :class:`~MDAnalysis.core.AtomGroup.Universe`. -.. SeeAlso:: :mod:`MDAnalysis.lib.distances` and :mod:`MDAnalysis.lib.parallel.distances` +.. SeeAlso:: :mod:`MDAnalysis.lib.distances` """ __all__ = ['distance_array', 'self_distance_array', 'contact_matrix', 'dist'] import numpy as np -from scipy import sparse from MDAnalysis.lib.distances import distance_array, self_distance_array from MDAnalysis.lib.c_distances import contact_matrix_no_pbc, contact_matrix_pbc +import warnings import logging - logger = logging.getLogger("MDAnalysis.analysis.distances") +try: + from scipy import sparse +except ImportError: + sparse = None + msg = "scipy.sparse could not be imported: some functionality will " \ + "not be available in contact_matrix()" + warnings.warn(msg, category=ImportWarning) + logger.warn(msg) + del msg def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): - '''Calculates a matrix of contacts within a numpy array of type float32. + '''Calculates a matrix of contacts. There is a fast, high-memory-usage version for small systems (*returntype* = 'numpy'), and a slower, low-memory-usage version for larger systems (*returntype* = 'sparse'). - If *box* dimensions are passed (``box = [Lx, Ly, Lz]``), then - periodic boundary conditions are applied. Only orthorhombic boxes - are currently supported. + If *box* dimensions are passed then periodic boundary conditions + are applied. + + Parameters + --------- + coord : array + Array of coordinates of shape ``(N, 3)`` and dtype float32. + cutoff : float, optional, default 15 + Particles within `cutoff` are considered to form a contact. + returntype : string, optional, default "numpy" + Select how the contact matrix is returned. + * ``"numpy"``: return as an ``(N. N)`` :class:`numpy.ndarray` + * ``"sparse"``: return as a :class:`scipy.sparse.lil_matrix` + box : array-like or ``None``, optional, default ``None`` + Simulation cell dimensions in the form of + :attr:`MDAnalysis.trajectory.base.Timestep.dimensions` when + periodic boundary conditions should be taken into account for + the calculation of contacts. + + Returns + ------- + array or sparse matrix + The contact matrix is returned in a format determined by the `returntype` + keyword. + + + Note + ---- + :module:`scipy.sparse` is require for using *sparse* matrices; if it cannot + be imported then an `ImportError` is raised. .. versionchanged:: 0.11.0 Keyword *suppress_progmet* and *progress_meter_freq* were removed. + ''' + if returntype == "numpy": adj = (distance_array(coord, coord, box=box) < cutoff) return adj elif returntype == "sparse": - # Initialize square List of Lists matrix of dimensions equal to number of coordinates passed + if sparse is None: + # hack: if we are running with minimal dependencies then scipy was + # not imported and we have to bail here (see scipy import at top) + raise ImportError("For sparse matrix functionality you need to " + "import scipy.") + # Initialize square List of Lists matrix of dimensions equal to number + # of coordinates passed sparse_contacts = sparse.lil_matrix((len(coord), len(coord)), dtype='bool') if box is not None: - # if PBC + # with PBC contact_matrix_pbc(coord, sparse_contacts, box, cutoff) else: - # if no PBC + # without PBC contact_matrix_no_pbc(coord, sparse_contacts, cutoff) return sparse_contacts @@ -75,27 +118,33 @@ def dist(A, B, offset=0): The distance is calculated atom-wise. The residue ids are also returned because a typical use case is to look at CA distances - before and after an alignment. Using the *offset* keyword one can + before and after an alignment. Using the `offset` keyword one can also add a constant offset to the resids which facilitates comparison with PDB numbering. - :Arguments: - *A*, *B* - :class:`~MDAnalysis.core.AtomGroup.AtomGroup` with the - same number of atoms - - :Keywords: - *offset* : integer - The *offset* is added to *resids_A* and *resids_B* (see - below) in order to produce PDB numbers. The default is 0. - - *offset* : tuple - *offset[0]* is added to *resids_A* and *offset[1]* to - *resids_B*. Note that one can actually supply numpy arrays - of the same length as the atom group so that an individual - offset is added to each resid. - - :Returns: NumPy `array([resids_A, resids_B, distances])` + Arguments + --------- + + A, B: AtomGroup instances + :class:`~MDAnalysis.core.AtomGroup.AtomGroup` with the + same number of atoms + offset : integer or tuple, optional, default 0 + An integer `offset` is added to *resids_A* and *resids_B* (see + below) in order to produce PDB numbers. + + If `offset` is :class:`tuple` then ``offset[0]`` is added to + *resids_A* and ``offset[1]`` to *resids_B*. Note that one can + actually supply numpy arrays of the same length as the atom + group so that an individual offset is added to each resid. + + Returns + ------- + resids_A : array + residue ids of the `A` group (possibly changed with `offset`) + resids_B : array + residue ids of the `B` group (possibly changed with `offset`) + distances : array + distances between the atoms """ if A.atoms.n_atoms != B.atoms.n_atoms: @@ -112,20 +161,35 @@ def dist(A, B, offset=0): def between(group, A, B, distance): - """Return sub group of *group* that is within *distance* of both *A* and *B*. + """Return sub group of `group` that is within `distance` of both `A` and `B` - *group*, *A*, and *B* must be - :class:`~MDAnalysis.core.AtomGroup.AtomGroup` instances. Works best - if *group* is bigger than either *A* or *B*. This function is not - aware of periodic boundary conditions. + This function is not aware of periodic boundary conditions. Can be used to find bridging waters or molecules in an interface. Similar to "*group* and (AROUND *A* *distance* and AROUND *B* *distance*)". - .. SeeAlso:: Makes use of :mod:`MDAnalysis.lib.NeighborSearch`. + Parameters + ---------- + + group : AtomGroup + Find members of `group` that are between `A` and `B` + A, B : AtomGroups + `A` and `B` are :class:`~MDAnalysis.core.AtomGroup.AtomGroup` + instances. Works best if `group` is bigger than either `A` or + `B`. + distance : float + maximum distance for an atom to be counted as in the vicinity of + `A` or `B` + + Returns + ------- + AtomGroup + :class:`~MDAnalysis.core.AtomGroup.AtomGroup` of atoms that + fulfill the criterion .. versionadded: 0.7.5 + """ from MDAnalysis.core.AtomGroup import AtomGroup diff --git a/package/MDAnalysis/analysis/gnm.py b/package/MDAnalysis/analysis/gnm.py index 5f4910b9480..100d516a218 100644 --- a/package/MDAnalysis/analysis/gnm.py +++ b/package/MDAnalysis/analysis/gnm.py @@ -210,7 +210,7 @@ def generate_kirchoff(self): the cutoff. Returns the resulting matrix ''' #ca = self.u.select_atoms(self.selection) - positions = self.ca.coordinates() + positions = self.ca.positions natoms = len(positions) @@ -320,7 +320,7 @@ def __init__(self, universe, selection='protein', cutoff=4.5, ReportVector=None, def generate_kirchoff(self): natoms = len(self.ca.atoms) nresidues = len(self.ca.residues) - positions = self.ca.coordinates() + positions = self.ca.positions [res_positions, grid, low_x, low_y, low_z] = generate_grid(positions, self.cutoff) residue_index_map = [resnum for [resnum, residue] in enumerate(self.ca.residues) for atom in residue] matrix = np.zeros((nresidues, nresidues), "float") diff --git a/package/MDAnalysis/analysis/hbonds/hbond_analysis.py b/package/MDAnalysis/analysis/hbonds/hbond_analysis.py index 6b2b84a0d23..33c7a9caab1 100644 --- a/package/MDAnalysis/analysis/hbonds/hbond_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/hbond_analysis.py @@ -59,10 +59,14 @@ results = [ [ # frame 1 [ # hbond 1 - , , , , , + , , , + , , , + , ], [ # hbond 2 - , , , , , + , , , + , , , + , ], .... ], @@ -79,6 +83,9 @@ MDAnalysis simply subtract 1. For instance, to find an atom in :attr:`Universe.atoms` by *index* from the output one would use ``u.atoms[index-1]``. + .. deprecated:: 0.15.0 + This feature is being deprecated in favor of zero-based indices and is targeted + for removal in 0.16.0. Using the :meth:`HydrogenBondAnalysis.generate_table` method one can reformat @@ -206,10 +213,11 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): All protein-water hydrogen bonds can be analysed with :: + import MDAnalysis import MDAnalysis.analysis.hbonds - u = MDAnalysis.Universe(PSF, PDB, permissive=True) - h = MDAnalysis.analysis.hbonds.HydrogenBondAnalysis(u, 'protein', 'resname TIP3', distance=3.0, angle=120.0) + u = MDAnalysis.Universe('topology', 'trajectory') + h = MDAnalysis.analysis.hbonds.HydrogenBondAnalysis(u, 'protein', distance=3.0, angle=120.0) h.run() The results are stored as the attribute @@ -224,6 +232,11 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): H-bonds or the whole protein when looking at ligand-protein interactions. +.. Note:: + + The topology supplied and the trajectory must reflect the same total number + of atoms. + .. TODO: how to analyse the ouput and notes on selection updating @@ -248,10 +261,14 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): results = [ [ # frame 1 [ # hbond 1 - , , , , , + , , , + , , , + , ], [ # hbond 2 - , , , , , + , , , + , , , + , ], .... ], @@ -260,7 +277,6 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): ], ... ] - The time of each step is not stored with each hydrogen bond frame but in :attr:`~HydrogenBondAnalysis.timesteps`. @@ -271,6 +287,8 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): instance, to find an atom in :attr:`Universe.atoms` by *index* one would use ``u.atoms[index-1]``. + + .. attribute:: table A normalised table of the data in @@ -281,14 +299,16 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): 0. "time" 1. "donor_idx" 2. "acceptor_idx" - 3. "donor_resnm" - 4. "donor_resid" - 5. "donor_atom" - 6. "acceptor_resnm" - 7. "acceptor_resid" - 8. "acceptor_atom" - 9. "distance" - 10. "angle" + 3. "donor_index" + 4. "acceptor_index" + 5. "donor_resnm" + 6. "donor_resid" + 7. "donor_atom" + 8. "acceptor_resnm" + 9. "acceptor_resid" + 10. "acceptor_atom" + 11. "distance" + 12. "angle" It takes up more space than :attr:`~HydrogenBondAnalysis.timeseries` but it is easier to @@ -299,7 +319,9 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): The *index* is a 1-based index. To get the :attr:`Atom.index` (the 0-based index typically used in MDAnalysis simply subtract 1. For instance, to find an atom in :attr:`Universe.atoms` by *index* one - would use ``u.atoms[index-1]``. + would use ``u.atoms[idx_zero]``. The 1-based index is deprecated and + targeted for removal in 0.16.0 + .. automethod:: _get_bonded_hydrogens @@ -308,6 +330,12 @@ class HydrogenBondAnalysis_OtherFF(HydrogenBondAnalysis): .. automethod:: _get_bonded_hydrogens_list + .. deprecated:: 0.15.0 + The donor and acceptor indices being 1-based is deprecated in favor of + a zero-based index. This can be accessed by "donor_index" or + "acceptor_index" removal of the 1-based indices is targeted + for version 0.16.0 + """ import six from six.moves import range, zip, map, cPickle @@ -479,8 +507,7 @@ def __init__(self, universe, selection1='protein', selection2='all', selection1_ last trajectory frame for analysis, ``None`` is the last one [``None``] *step* read every *step* between *start* and *stop*, ``None`` selects 1. - Note that not all trajectory readers perform well with a step different - from 1 [``None``] + Note that not all trajectory reader from 1 [``None``] *verbose* If set to ``True`` enables per-frame debug logging. This is disabled by default because it generates a very large amount of output in @@ -532,6 +559,12 @@ def __init__(self, universe, selection1='protein', selection2='all', selection1_ .. _`Issue 138`: https://github.com/MDAnalysis/mdanalysis/issues/138 """ + warnings.warn( + "The donor and acceptor indices being 1-based is deprecated in favor" + " of a zero-based index. These can be accessed by 'donor_index' or" + " 'acceptor_index', removal of the 1-based indices is targeted for" + " version 0.16.0", category=DeprecationWarning) + self._get_bonded_hydrogens_algorithms = { "distance": self._get_bonded_hydrogens_dist, # 0.7.6 default "heuristic": self._get_bonded_hydrogens_list, # pre 0.7.6 @@ -680,7 +713,8 @@ def _get_bonded_hydrogens_dist(self, atom): """ try: return atom.residue.select_atoms( - "(name H* or name 1H* or name 2H* or name 3H* or type H) and around {0:f} name {1!s}".format(self.r_cov[atom.name[0]], atom.name)) + "(name H* 1H* 2H* 3H* or type H) and around {0:f} name {1!s}" + "".format(self.r_cov[atom.name[0]], atom.name)) except NoDataError: return [] @@ -732,7 +766,8 @@ def _update_selection_1(self): self._s1_donors_h = {} self._s1_acceptors = {} if self.selection1_type in ('donor', 'both'): - self._s1_donors = self._s1.select_atoms(' or '.join(['name {0}'.format(name) for name in self.donors])) + self._s1_donors = self._s1.select_atoms( + 'name {0}'.format(' '.join(self.donors))) self._s1_donors_h = {} for i, d in enumerate(self._s1_donors): tmp = self._get_bonded_hydrogens(d) @@ -741,12 +776,13 @@ def _update_selection_1(self): self.logger_debug("Selection 1 donors: {0}".format(len(self._s1_donors))) self.logger_debug("Selection 1 donor hydrogens: {0}".format(len(self._s1_donors_h))) if self.selection1_type in ('acceptor', 'both'): - self._s1_acceptors = self._s1.select_atoms(' or '.join(['name {0}'.format(name) for name in self.acceptors])) + self._s1_acceptors = self._s1.select_atoms( + 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Selection 1 acceptors: {0}".format(len(self._s1_acceptors))) def _update_selection_2(self): self._s2 = self.u.select_atoms(self.selection2) - if self.filter_first and len(self._s2) > 0: + if self.filter_first and self._s2: self.logger_debug('Size of selection 2 before filtering:' ' {} atoms'.format(len(self._s2))) ns_selection_2 = AtomNeighborSearch(self._s2) @@ -760,11 +796,11 @@ def _update_selection_2(self): self._s2_acceptors = {} if self.selection1_type in ('donor', 'both'): self._s2_acceptors = self._s2.select_atoms( - ' or '.join(['name {0!s}'.format(i) for i in self.acceptors])) + 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Selection 2 acceptors: {0:d}".format(len(self._s2_acceptors))) if self.selection1_type in ('acceptor', 'both'): self._s2_donors = self._s2.select_atoms( - ' or '.join(['name {0!s}'.format(i) for i in self.donors])) + 'name {0}'.format(' '.join(self.donors))) self._s2_donors_h = {} for i, d in enumerate(self._s2_donors): tmp = self._get_bonded_hydrogens(d) @@ -802,6 +838,12 @@ def run(self, **kwargs): Accept *quiet* keyword. Analysis will now proceed through frames even if no donors or acceptors were found in a particular frame. + .. deprecated:: 0.15.0 + The donor and acceptor indices being 1-based is deprecated in favor of + a zero-based index. This can be accessed by "donor_index" or + "acceptor_index" removal of the 1-based indices is targeted + for version 0.16.0 + """ logger.info("HBond analysis: starting") logger.debug("HBond analysis: donors %r", self.donors) @@ -868,7 +910,7 @@ def _get_timestep(): if self.update_selection2: self._update_selection_2() - if self.selection1_type in ('donor', 'both') and len(self._s2_acceptors) > 0: + if self.selection1_type in ('donor', 'both') and self._s2_acceptors: self.logger_debug("Selection 1 Donors <-> Acceptors") ns_acceptors = AtomNeighborSearch(self._s2_acceptors) for i, donor_h_set in self._s1_donors_h.items(): @@ -884,10 +926,13 @@ def _get_timestep(): "S1-D: {0!s} <-> S2-A: {1!s} {2:f} A, {3:f} DEG".format(h.index + 1, a.index + 1, dist, angle)) #self.logger_debug("S1-D: %r <-> S2-A: %r %f A, %f DEG" % (h, a, dist, angle)) frame_results.append( - [h.index + 1, a.index + 1, '{0!s}{1!s}:{2!s}'.format(h.resname, repr(h.resid), h.name), - '{0!s}{1!s}:{2!s}'.format(a.resname, repr(a.resid), a.name), dist, angle]) + [h.index + 1, a.index + 1, h.index, a.index, + '{0!s}{1!s}:{2!s}'.format(h.resname, repr(h.resid), h.name), + '{0!s}{1!s}:{2!s}'.format(a.resname, repr(a.resid), a.name), + dist, angle]) + already_found[(h.index + 1, a.index + 1)] = True - if self.selection1_type in ('acceptor', 'both') and len(self._s1_acceptors) > 0: + if self.selection1_type in ('acceptor', 'both') and self._s1_acceptors: self.logger_debug("Selection 1 Acceptors <-> Donors") ns_acceptors = AtomNeighborSearch(self._s1_acceptors) for i, donor_h_set in self._s2_donors_h.items(): @@ -907,24 +952,29 @@ def _get_timestep(): "S1-A: {0!s} <-> S2-D: {1!s} {2:f} A, {3:f} DEG".format(a.index + 1, h.index + 1, dist, angle)) #self.logger_debug("S1-A: %r <-> S2-D: %r %f A, %f DEG" % (a, h, dist, angle)) frame_results.append( - [h.index + 1, a.index + 1, '{0!s}{1!s}:{2!s}'.format(h.resname, repr(h.resid), h.name), - '{0!s}{1!s}:{2!s}'.format(a.resname, repr(a.resid), a.name), dist, angle]) + [h.index + 1, a.index + 1, h.index, a.index, + '{0!s}{1!s}:{2!s}'.format(h.resname, repr(h.resid), h.name), + '{0!s}{1!s}:{2!s}'.format(a.resname, repr(a.resid), a.name), + dist, angle]) + self.timeseries.append(frame_results) logger.info("HBond analysis: complete; timeseries with %d hbonds in %s.timeseries", self.count_by_time().count.sum(), self.__class__.__name__) - def calc_angle(self, d, h, a): + @staticmethod + def calc_angle(d, h, a): """Calculate the angle (in degrees) between two atoms with H at apex.""" - v1 = h.pos - d.pos - v2 = h.pos - a.pos + v1 = h.position - d.position + v2 = h.position - a.position if np.all(v1 == v2): return 0.0 return np.rad2deg(angle(v1, v2)) - def calc_eucl_distance(self, a1, a2): + @staticmethod + def calc_eucl_distance(a1, a2): """Calculate the Euclidean distance between two atoms. """ - return norm(a2.pos - a1.pos) + return norm(a2.position - a1.position) def generate_table(self): """Generate a normalised table of the results. @@ -937,14 +987,16 @@ def generate_table(self): 0. "time" 1. "donor_idx" 2. "acceptor_idx" - 3. "donor_resnm" - 4. "donor_resid" - 5. "donor_atom" - 6. "acceptor_resnm" - 7. "acceptor_resid" - 8. "acceptor_atom" - 9. "distance" - 10. "angle" + 3. "donor_index" + 4. "acceptor_index" + 4. "donor_resnm" + 5. "donor_resid" + 6. "donor_atom" + 7. "acceptor_resnm" + 8. "acceptor_resid" + 9. "acceptor_atom" + 10. "distance" + 11. "angle" .. _recsql: http://pypi.python.org/pypi/RecSQL """ @@ -958,6 +1010,7 @@ def generate_table(self): # build empty output table dtype = [ ("time", float), ("donor_idx", int), ("acceptor_idx", int), + ("donor_index", int), ("acceptor_index", int), ("donor_resnm", "|U4"), ("donor_resid", int), ("donor_atom", "|U4"), ("acceptor_resnm", "|U4"), ("acceptor_resid", int), ("acceptor_atom", "|U4"), ("distance", float), ("angle", float)] @@ -966,9 +1019,10 @@ def generate_table(self): out = np.empty((num_records,), dtype=dtype) cursor = 0 # current row for t, hframe in zip(self.timesteps, self.timeseries): - for donor_idx, acceptor_idx, donor, acceptor, distance, angle in hframe: - out[cursor] = (t, donor_idx, acceptor_idx) + parse_residue(donor) + \ - parse_residue(acceptor) + (distance, angle) + for (donor_idx, acceptor_idx, donor_index, acceptor_index, donor, + acceptor, distance, angle) in hframe: + out[cursor] = (t, donor_idx, acceptor_idx, donor_index, acceptor_index) + \ + parse_residue(donor) + parse_residue(acceptor) + (distance, angle) cursor += 1 assert cursor == num_records, "Internal Error: Not all HB records stored" self.table = out.view(np.recarray) @@ -1025,13 +1079,16 @@ def count_by_type(self): hbonds = defaultdict(int) for hframe in self.timeseries: - for donor_idx, acceptor_idx, donor, acceptor, distance, angle in hframe: + for (donor_idx, acceptor_idx, donor_index, acceptor_index, donor, + acceptor, distance, angle) in hframe: donor_resnm, donor_resid, donor_atom = parse_residue(donor) acceptor_resnm, acceptor_resid, acceptor_atom = parse_residue(acceptor) - # generate unambigous key for current hbond + # generate unambigous key for current hbond \ # (the donor_heavy_atom placeholder '?' is added later) + # idx_zero is redundant for an unambigous key, but included for + # consistency. hb_key = ( - donor_idx, acceptor_idx, + donor_idx, acceptor_idx, donor_index, acceptor_index, donor_resnm, donor_resid, "?", donor_atom, acceptor_resnm, acceptor_resid, acceptor_atom) @@ -1040,7 +1097,8 @@ def count_by_type(self): # build empty output table dtype = [ ('donor_idx', int), ('acceptor_idx', int), - ('donor_resnm', 'U4'), ('donor_resid', int), ('donor_heavy_atom', 'U4'), ('donor_atom', 'U4'), + ("donor_index", int), ("acceptor_index", int), ('donor_resnm', 'U4'), + ('donor_resid', int), ('donor_heavy_atom', 'U4'), ('donor_atom', 'U4'), ('acceptor_resnm', 'U4'), ('acceptor_resid', int), ('acceptor_atom', 'U4'), ('frequency', float) ] @@ -1059,7 +1117,7 @@ def count_by_type(self): # patch in donor heavy atom names (replaces '?' in the key) h2donor = self._donor_lookup_table_byindex() - r.donor_heavy_atom[:] = [h2donor[idx - 1] for idx in r.donor_idx] + r.donor_heavy_atom[:] = [h2donor[idx] for idx in r.donor_index] return r @@ -1082,13 +1140,16 @@ def timesteps_by_type(self): hbonds = defaultdict(list) for (t, hframe) in zip(self.timesteps, self.timeseries): - for donor_idx, acceptor_idx, donor, acceptor, distance, angle in hframe: + for (donor_idx, acceptor_idx, donor_index, acceptor_index, donor, + acceptor, distance, angle) in hframe: donor_resnm, donor_resid, donor_atom = parse_residue(donor) acceptor_resnm, acceptor_resid, acceptor_atom = parse_residue(acceptor) # generate unambigous key for current hbond # (the donor_heavy_atom placeholder '?' is added later) + # idx_zero is redundant for key but added for consistency hb_key = ( donor_idx, acceptor_idx, + donor_index, acceptor_index, donor_resnm, donor_resid, "?", donor_atom, acceptor_resnm, acceptor_resid, acceptor_atom) hbonds[hb_key].append(t) @@ -1100,10 +1161,10 @@ def timesteps_by_type(self): # build empty output table dtype = [ - ('donor_idx', int), ('acceptor_idx', int), - ('donor_resnm', 'U4'), ('donor_resid', int), ('donor_heavy_atom', 'U4'), ('donor_atom', 'U4'), - ('acceptor_resnm', 'U4'), ('acceptor_resid', int), ('acceptor_atom', 'U4'), - ('time', float)] + ('donor_idx', int), ('acceptor_idx', int),('donor_index', int), + ('acceptor_index', int), ('donor_resnm', 'U4'), ('donor_resid', int), + ('donor_heavy_atom', 'U4'), ('donor_atom', 'U4'),('acceptor_resnm', 'U4'), + ('acceptor_resid', int), ('acceptor_atom', 'U4'), ('time', float)] out = np.empty((out_nrows,), dtype=dtype) out_row = 0 @@ -1182,8 +1243,11 @@ def _donor_lookup_table_byindex(self): *index* is the 0-based MDAnalysis index (:attr:`MDAnalysis.core.AtomGroup.Atom.index`). The tables generated by :class:`HydrogenBondAnalysis` contain - 1-based indices. + 1-based indices and zero-based indices. + .. deprecated:: 0.15.0 + The 1-based indices are deprecated in favor of the zero-based indices + given by "idx_zero". """ s1d = self._s1_donors # list of donor Atom instances s1h = self._s1_donors_h # dict indexed by donor position in donor list, containg AtomGroups of H diff --git a/package/MDAnalysis/analysis/helanal.py b/package/MDAnalysis/analysis/helanal.py index 8f0cb6b3987..af987bcd81c 100644 --- a/package/MDAnalysis/analysis/helanal.py +++ b/package/MDAnalysis/analysis/helanal.py @@ -290,7 +290,7 @@ def helanal_trajectory(universe, selection="name CA", start=None, end=None, begi if trajectory.time > finish: break - ca_positions = ca.coordinates() + ca_positions = ca.positions twist, bending_angles, height, rnou, origins, local_helix_axes, local_screw_angles = \ main_loop(ca_positions, ref_axis=ref_axis) @@ -466,7 +466,7 @@ def stats(some_list): return [list_mean, list_sd, list_abdev] -def helanal_main(pdbfile, selection="name CA", start=None, end=None, ref_axis=None, permissive=False): +def helanal_main(pdbfile, selection="name CA", start=None, end=None, ref_axis=None): """Simple HELANAL run on a single frame PDB/GRO. Computed data are returned as a dict and also logged at level INFO to the @@ -503,7 +503,7 @@ def helanal_main(pdbfile, selection="name CA", start=None, end=None, ref_axis=No *MDAnalysis.analysis.helanal* instead of being printed to stdout. """ - universe = MDAnalysis.Universe(pdbfile, permissive=permissive) + universe = MDAnalysis.Universe(pdbfile) if not (start is None and end is None): if start is None: start = universe.atoms[0].resid @@ -515,7 +515,7 @@ def helanal_main(pdbfile, selection="name CA", start=None, end=None, ref_axis=No logger.info("Analysing %d/%d residues", ca.n_atoms, universe.atoms.n_residues) twist, bending_angles, height, rnou, origins, local_helix_axes, local_screw_angles = \ - main_loop(ca.coordinates(), ref_axis=ref_axis) + main_loop(ca.positions, ref_axis=ref_axis) #TESTED- origins are correct #print current_origin diff --git a/package/MDAnalysis/analysis/nuclinfo.py b/package/MDAnalysis/analysis/nuclinfo.py index 2d878226f73..a4244d4e2c7 100644 --- a/package/MDAnalysis/analysis/nuclinfo.py +++ b/package/MDAnalysis/analysis/nuclinfo.py @@ -135,7 +135,7 @@ def wc_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): wc_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " "or (segid {3!s} and resid {4!s} and name {5!s}) " .format(seg1, i, a1, seg2, bp, a2)) - wc = mdamath.norm(wc_dist[0].pos - wc_dist[1].pos) + wc = mdamath.norm(wc_dist[0].position - wc_dist[1].position) return wc @@ -168,7 +168,7 @@ def minor_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): c2o2_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " "or (segid {3!s} and resid {4!s} and name {5!s})" .format(seg1, i, a1, seg2, bp, a2)) - c2o2 = mdamath.norm(c2o2_dist[0].pos - c2o2_dist[1].pos) + c2o2 = mdamath.norm(c2o2_dist[0].position - c2o2_dist[1].position) return c2o2 @@ -208,7 +208,7 @@ def major_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): no_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " "or (segid {3!s} and resid {4!s} and name {5!s}) " .format(seg1, i, a1, seg2, bp, a2)) - major = mdamath.norm(no_dist[0].pos - no_dist[1].pos) + major = mdamath.norm(no_dist[0].position - no_dist[1].position) return major @@ -234,11 +234,11 @@ def phase_cp(universe, seg, i): atom4 = universe.select_atoms(" atom {0!s} {1!s} C3\' ".format(seg, i)) atom5 = universe.select_atoms(" atom {0!s} {1!s} C4\' ".format(seg, i)) - data1 = atom1.coordinates() - data2 = atom2.coordinates() - data3 = atom3.coordinates() - data4 = atom4.coordinates() - data5 = atom5.coordinates() + data1 = atom1.positions + data2 = atom2.positions + data3 = atom3.positions + data4 = atom4.positions + data5 = atom5.positions r0 = (data1 + data2 + data3 + data4 + data5) * (1.0 / 5.0) r1 = data1 - r0 diff --git a/package/MDAnalysis/analysis/rms.py b/package/MDAnalysis/analysis/rms.py index b9eb4c20bc4..035713cc7d5 100644 --- a/package/MDAnalysis/analysis/rms.py +++ b/package/MDAnalysis/analysis/rms.py @@ -1,9 +1,9 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # @@ -128,7 +128,6 @@ import logging import warnings -import MDAnalysis import MDAnalysis.lib.qcprot as qcp from MDAnalysis.exceptions import SelectionError, NoDataError from MDAnalysis.lib.log import ProgressMeter @@ -138,70 +137,100 @@ logger = logging.getLogger('MDAnalysis.analysis.rmsd') -def rmsd(a, b, weights=None, center=False): - """Returns RMSD between two coordinate sets *a* and *b*. +def rmsd(a, b, weights=None, center=False, superposition=False): + """Returns RMSD between two coordinate sets `a` and `b`. - *a* and *b* are arrays of the coordinates of N atoms of shape N*3 + `a` and `b` are arrays of the coordinates of N atoms of shape N*3 as generated by, e.g., :meth:`MDAnalysis.core.AtomGroup.AtomGroup.coordinates`. - An implicit optimal superposition is performed, which minimizes - the RMSD between *a* and *b* although both *a* and *b* must be - centered on the origin before performing the RMSD calculation so - that translations are removed. + Parameters + ---------- + a, b : array_like + coordinates to align + weights : array_like (optional) + 1D array with weights, use to compute weighted average + center : bool (optional) + subtract center of geometry before calculation. With weights given + compute weighted average as center. + superposition : bool (optional) + perform a rotational and translational superposition with the fast QCP + algorithm [Theobald2005]_ before calculating the RMSD + + Returns + ------- + rmsd : float + RMSD between a and b + + Example + ------- + >>> u = Universe(PSF,DCD) + >>> bb = u.select_atoms('backbone') + >>> A = bb.positions.copy() # coordinates of first frame + >>> u.trajectory[-1] # forward to last frame + >>> B = bb.positions.copy() # coordinates of last frame + >>> rmsd(A, B, center=True) + 3.9482355416565049 + + .. versionchanged: 0.8.1 + *center* keyword added + .. versionchanged: 0.14.0 + *superposition* keyword added - One can use the *center* = ``True`` keyword, which subtracts the - center of geometry (for *weights* = ``None``) before the - superposition. With *weights*, a weighted average is computed as - the center. - - The *weights* can be an array of length N, containing e.g. masses - for a weighted RMSD calculation. + """ + a = np.asarray(a, dtype=np.float64) + b = np.asarray(b, dtype=np.float64) + N = b.shape[0] - The function uses Douglas Theobald's fast QCP algorithm - [Theobald2005]_ to calculate the RMSD. + if a.shape != b.shape: + raise ValueError('a and b must have same shape') - Example:: - >>> u = Universe(PSF,DCD) - >>> bb = u.select_atoms('backbone') - >>> A = bb.coordinates() # coordinates of first frame - >>> u.trajectory[-1] # forward to last frame - >>> B = bb.coordinates() # coordinates of last frame - >>> rmsd(A,B) - 6.8342494129169804 - - .. versionchanged: 0.8.1 - *center* keyword added - """ if weights is not None: + if len(weights) != len(a): + raise ValueError('weights must have same length as a/b') # weights are constructed as relative to the mean relative_weights = np.asarray(weights) / np.mean(weights) else: relative_weights = None - if center: + + # superposition only works if structures are centered + if center or superposition: # make copies (do not change the user data!) # weights=None is equivalent to all weights 1 a = a - np.average(a, axis=0, weights=weights) b = b - np.average(b, axis=0, weights=weights) - return qcp.CalcRMSDRotationalMatrix(a.T.astype(np.float64), b.T.astype(np.float64), - a.shape[0], None, relative_weights) + + if superposition: + return qcp.CalcRMSDRotationalMatrix(a.T, b.T, N, None, + relative_weights) + else: + if weights is not None: + return np.sqrt(np.sum(relative_weights[:, np.newaxis] + * (( a - b ) ** 2)) / N) + else: + return np.sqrt(np.sum((a - b) ** 2) / N) def _process_selection(select): """Return a canonical selection dictionary. - :Arguments: - *select* - - any valid selection string for - :meth:`~MDAnalysis.core.AtomGroup.AtomGroup.select_atoms` that produces identical - selections in *mobile* and *reference*; or - - dictionary ``{'mobile':sel1, 'reference':sel2}``. - The :func:`fasta2select` function returns such a - dictionary based on a ClustalW_ or STAMP_ sequence alignment. - - tuple ``(sel1, sel2)`` - - :Returns: dict with keys `reference` and `mobile`; the values are guaranteed to - be iterable (so that one can provide selections that retain order) + Parameters + ---------- + select : str / tuple / dict + str -> Any valid string selection + dict -> {'mobile':sel1, 'reference':sel2} + tuple -> (sel1, sel2) + + Returns + ------- + dict + selections for 'reference' and 'mobile'. Values are guarenteed to be + iterable (so that one can provide selections to retain order) + + Note + ---- + The dictionary input for ``select`` can be generated by + :func:`fasta2select` based on ClustalW_ or STAMP_ sequence alignment. """ if type(select) is str: select = {'reference': select, 'mobile': select} @@ -247,49 +276,54 @@ class RMSD(object): .. versionadded:: 0.7.7 """ - def __init__(self, traj, reference=None, select='all', groupselections=None, filename="rmsd.dat", + def __init__(self, traj, reference=None, select='all', + groupselections=None, filename="rmsd.dat", mass_weighted=False, tol_mass=0.1, ref_frame=0): """Setting up the RMSD analysis. The RMSD will be computed between *select* and *reference* for all frames in the trajectory in *universe*. - :Arguments: - *traj* - universe (:class:`MDAnalysis.Universe` object) that contains a - trajectory - *reference* - reference coordinates; :class:`MDAnalysis.Universe` object; if ``None`` - the *traj* is used (uses the current time step of the object) [``None``] - *select* - The selection to operate on; can be one of: - - 1. any valid selection string for - :meth:`~MDAnalysis.core.AtomGroup.AtomGroup.select_atoms` that produces identical - selections in *mobile* and *reference*; or - 2. a dictionary ``{'mobile':sel1, 'reference':sel2}`` (the - :func:`MDAnalysis.analysis.align.fasta2select` function returns such a - dictionary based on a ClustalW_ or STAMP_ sequence alignment); or - 3. a tuple ``(sel1, sel2)`` - - When using 2. or 3. with *sel1* and *sel2* then these selections can also each be - a list of selection strings (to generate a AtomGroup with defined atom order as - described under :ref:`ordered-selections-label`). - *groupselections* - A list of selections as described for *select*. Each selection describes additional - RMSDs to be computed *after the structures have be superpositioned* according to *select*. - The output contains one additional column for each selection. [``None``] - - .. Note:: Experimental feature. Only limited error checking implemented. - *filename* - If set, *filename* can be used to write the results with :meth:`RMSD.save` [``None``] - *mass_weighted* + Parameters + ---------- + traj : :class:`MDAnalysis.Universe` + universe that contains a trajectory + reference : :class:`MDAnalysis.Universe` (optional) + reference coordinates, if ``None`` current frame of *traj* is used + select : str / dict / tuple (optional) + The selection to operate on; can be one of: + + 1. any valid selection string for + :meth:`~MDAnalysis.core.AtomGroup.AtomGroup.select_atoms` that + produces identical selections in *mobile* and *reference*; or + + 2. a dictionary ``{'mobile':sel1, 'reference':sel2}`` (the + :func:`MDAnalysis.analysis.align.fasta2select` function returns + such a dictionary based on a ClustalW_ or STAMP_ sequence + alignment); or + 3. a tuple ``(sel1, sel2)`` + + When using 2. or 3. with *sel1* and *sel2* then these selections + can also each be a list of selection strings (to generate a + AtomGroup with defined atom order as described under + :ref:`ordered-selections-label`). + groupselections : list (optional) + A list of selections as described for *select*. Each selection + describes additional RMSDs to be computed *after the structures + have be superpositioned* according to *select*. The output contains + one additional column for each selection. [``None``] + + .. Note:: Experimental feature. Only limited error checking + implemented. + filename : str (optional) + write RSMD into file file :meth:`RMSD.save` + mass_weighted : bool (optional) do a mass-weighted RMSD fit - *tol_mass* - Reject match if the atomic masses for matched atoms differ by more than - *tol_mass* [0.1] - *ref_frame* - frame index to select frame from *reference* [0] + tol_mass : float (optional) + Reject match if the atomic masses for matched atoms differ by more + than `tol_mass` + ref_frame : int (optional) + frame index to select frame from `reference` .. _ClustalW: http://www.clustal.org/ .. _STAMP: http://www.compbio.dundee.ac.uk/manuals/stamp.4.2/ @@ -297,6 +331,7 @@ def __init__(self, traj, reference=None, select='all', groupselections=None, fil .. versionadded:: 0.7.7 .. versionchanged:: 0.8 *groupselections* added + """ self.universe = traj if reference is None: @@ -315,11 +350,13 @@ def __init__(self, traj, reference=None, select='all', groupselections=None, fil self.ref_atoms = self.reference.select_atoms(*self.select['reference']) self.traj_atoms = self.universe.select_atoms(*self.select['mobile']) - natoms = self.traj_atoms.n_atoms if len(self.ref_atoms) != len(self.traj_atoms): logger.exception() - raise SelectionError("Reference and trajectory atom selections do not contain " + - "the same number of atoms: N_ref={0:d}, N_traj={1:d}".format(len(self.ref_atoms), len(self.traj_atoms))) + raise SelectionError("Reference and trajectory atom selections do " + "not contain the same number of atoms: " + "N_ref={0:d}, N_traj={1:d}".format( + self.ref_atoms.n_atoms, + self.traj_atoms.n_atoms)) logger.info("RMS calculation for {0:d} atoms.".format(len(self.ref_atoms))) mass_mismatches = (np.absolute(self.ref_atoms.masses - self.traj_atoms.masses) > self.tol_mass) if np.any(mass_mismatches): @@ -337,8 +374,8 @@ def __init__(self, traj, reference=None, select='all', groupselections=None, fil # TODO: # - make a group comparison a class that contains the checks above - # - use this class for the *select* group and the additional *groupselections* groups - # each a dict with reference/mobile + # - use this class for the *select* group and the additional + # *groupselections* groups each a dict with reference/mobile self.groupselections_atoms = [ { 'reference': self.reference.select_atoms(*s['reference']), @@ -346,40 +383,39 @@ def __init__(self, traj, reference=None, select='all', groupselections=None, fil } for s in self.groupselections] # sanity check - for igroup, (sel, atoms) in enumerate(zip(self.groupselections, self.groupselections_atoms)): + for igroup, (sel, atoms) in enumerate(zip(self.groupselections, + self.groupselections_atoms)): if len(atoms['mobile']) != len(atoms['reference']): logger.exception() raise SelectionError( - "Group selection {0}: {1} | {2}: Reference and trajectory atom selections do not contain " + - "the same number of atoms: N_ref={3}, N_traj={4}".format( - igroup, sel['reference'], sel['mobile'], len(atoms['reference']), len(atoms['mobile']))) + "Group selection {0}: {1} | {2}: Reference and trajectory " + "atom selections do not contain the same number of atoms: " + "N_ref={3}, N_traj={4}".format( + igroup, sel['reference'], sel['mobile'], + len(atoms['reference']), len(atoms['mobile']))) self.rmsd = None - def run(self, **kwargs): + def run(self, start=None, stop=None, step=None, + mass_weighted=None, ref_frame=None): """Perform RMSD analysis on the trajectory. A number of parameters can be changed from the defaults. The result is stored as the array :attr:`RMSD.rmsd`. - :Keywords: - *start*, *stop*, *step* - start and stop frame index with step size: analyse - ``trajectory[start:stop:step]`` [``None``] - *mass_weighted* - do a mass-weighted RMSD fit - *tol_mass* - Reject match if the atomic masses for matched atoms differ by more than - *tol_mass* - *ref_frame* + Parameters + ---------- + start, stop, step : int (optional) + start and stop frame index with step size + mass_weighted : bool (optional) + overwrite object default to do a mass-weighted RMSD fit + ref_frame : int frame index to select frame from *reference* - """ - start = kwargs.pop('start', None) - stop = kwargs.pop('stop', None) - step = kwargs.pop('step', None) - mass_weighted = kwargs.pop('mass_weighted', self.mass_weighted) - ref_frame = kwargs.pop('ref_frame', self.ref_frame) + if mass_weighted is None: + mass_weighted = self.mass_weighted + if ref_frame is None: + ref_frame = self.ref_frame natoms = self.traj_atoms.n_atoms trajectory = self.universe.trajectory @@ -395,10 +431,12 @@ def run(self, **kwargs): current_frame = self.reference.trajectory.ts.frame - 1 try: # Move to the ref_frame - # (coordinates MUST be stored in case the ref traj is advanced elsewhere or if ref == mobile universe) + # (coordinates MUST be stored in case the ref traj is advanced + # elsewhere or if ref == mobile universe) self.reference.trajectory[ref_frame] ref_com = self.ref_atoms.center_of_mass() - ref_coordinates = self.ref_atoms.positions - ref_com # makes a copy + # makes a copy + ref_coordinates = self.ref_atoms.positions - ref_com if self.groupselections_atoms: groupselections_ref_coords_T_64 = [ self.reference.select_atoms(*s['reference']).positions.T.astype(np.float64) for s in @@ -409,14 +447,15 @@ def run(self, **kwargs): ref_coordinates_T_64 = ref_coordinates.T.astype(np.float64) # allocate the array for selection atom coords - traj_coordinates = traj_atoms.coordinates().copy() + traj_coordinates = traj_atoms.positions.copy() if self.groupselections_atoms: - # Only carry out a rotation if we want to calculate secondary RMSDs. + # Only carry out a rotation if we want to calculate secondary + # RMSDs. # R: rotation matrix that aligns r-r_com, x~-x~com # (x~: selected coordinates, x: all coordinates) # Final transformed traj coordinates: x' = (x-x~_com)*R + ref_com - rot = np.zeros(9, dtype=np.float64) # allocate space for calculation + rot = np.zeros(9, dtype=np.float64) # allocate space R = np.matrix(rot.reshape(3, 3)) else: rot = None @@ -425,47 +464,52 @@ def run(self, **kwargs): nframes = len(np.arange(0, len(trajectory))[start:stop:step]) rmsd = np.zeros((nframes, 3 + len(self.groupselections_atoms))) - percentage = ProgressMeter(nframes, interval=10, - format="RMSD %(rmsd)5.2f A at frame %(step)5d/%(numsteps)d [%(percentage)5.1f%%]\r") + percentage = ProgressMeter( + nframes, interval=10, format="RMSD %(rmsd)5.2f A at frame " + "%(step)5d/%(numsteps)d [%(percentage)5.1f%%]\r") for k, ts in enumerate(trajectory[start:stop:step]): # shift coordinates for rotation fitting # selection is updated with the time frame x_com = traj_atoms.center_of_mass().astype(np.float32) - traj_coordinates[:] = traj_atoms.coordinates() - x_com + traj_coordinates[:] = traj_atoms.positions - x_com rmsd[k, :2] = ts.frame, trajectory.time if self.groupselections_atoms: - # 1) superposition structures - # Need to transpose coordinates such that the coordinate array is - # 3xN instead of Nx3. Also qcp requires that the dtype be float64 - # (I think we swapped the position of ref and traj in CalcRMSDRotationalMatrix - # so that R acts **to the left** and can be broadcasted; we're saving - # one transpose. [orbeckst]) - rmsd[k, 2] = qcp.CalcRMSDRotationalMatrix(ref_coordinates_T_64, - traj_coordinates.T.astype(np.float64), - natoms, rot, weight) + # 1) superposition structures Need to transpose coordinates + # such that the coordinate array is 3xN instead of Nx3. Also + # qcp requires that the dtype be float64 (I think we swapped + # the position of ref and traj in CalcRMSDRotationalMatrix so + # that R acts **to the left** and can be broadcasted; we're + # saving one transpose. [orbeckst]) + rmsd[k, 2] = qcp.CalcRMSDRotationalMatrix( + ref_coordinates_T_64, + traj_coordinates.T.astype(np.float64), natoms, rot, weight) R[:, :] = rot.reshape(3, 3) - # Transform each atom in the trajectory (use inplace ops to avoid copying arrays) - # (Marginally (~3%) faster than "ts.positions[:] = (ts.positions - x_com) * R + ref_com".) + # Transform each atom in the trajectory (use inplace ops to + # avoid copying arrays) (Marginally (~3%) faster than + # "ts.positions[:] = (ts.positions - x_com) * R + ref_com".) ts.positions -= x_com - ts.positions[:] = ts.positions * R # R acts to the left & is broadcasted N times. + # R acts to the left & is broadcasted N times. + ts.positions[:] = ts.positions * R ts.positions += ref_com # 2) calculate secondary RMSDs for igroup, (refpos, atoms) in enumerate( - zip(groupselections_ref_coords_T_64, self.groupselections_atoms), 3): - rmsd[k, igroup] = qcp.CalcRMSDRotationalMatrix(refpos, - atoms['mobile'].positions.T.astype(np.float64), - atoms['mobile'].n_atoms, None, weight) + zip(groupselections_ref_coords_T_64, + self.groupselections_atoms), 3): + rmsd[k, igroup] = qcp.CalcRMSDRotationalMatrix( + refpos, atoms['mobile'].positions.T.astype(np.float64), + atoms['mobile'].n_atoms, None, weight) else: - # only calculate RMSD by setting the Rmatrix to None - # (no need to carry out the rotation as we already get the optimum RMSD) - rmsd[k, 2] = qcp.CalcRMSDRotationalMatrix(ref_coordinates_T_64, - traj_coordinates.T.astype(np.float64), - natoms, None, weight) + # only calculate RMSD by setting the Rmatrix to None (no need + # to carry out the rotation as we already get the optimum RMSD) + rmsd[k, 2] = qcp.CalcRMSDRotationalMatrix( + ref_coordinates_T_64, + traj_coordinates.T.astype(np.float64), + natoms, None, weight) percentage.echo(ts.frame, rmsd=rmsd[k, 2]) self.rmsd = rmsd @@ -473,10 +517,11 @@ def run(self, **kwargs): def save(self, filename=None): """Save RMSD from :attr:`RMSD.rmsd` to text file *filename*. - If *filename* is not supplied then the default provided to the - constructor is used. - - The data are saved with :func:`np.savetxt`. + Parameter + --------- + filename : str (optional) + if no filename is given the default provided to the constructor is + used. """ filename = filename or self.filename if filename is not None: @@ -502,8 +547,9 @@ class RMSF(object): def __init__(self, atomgroup): """Calculate RMSF of given atoms across a trajectory. - :Arguments: - *atomgroup* + Parameters + ---------- + atomgroup : mda.AtomGroup AtomGroup to obtain RMSF for """ @@ -514,26 +560,27 @@ def run(self, start=0, stop=-1, step=1, progout=10, quiet=False): """Calculate RMSF of given atoms across a trajectory. This method implements an algorithm for computing sums of squares while - avoiding overflows and underflows; please reference: - - .. [Welford1962] B. P. Welford (1962). "Note on a Method for - Calculating Corrected Sums of Squares and Products." Technometrics - 4(3):419-420. - - :Keywords: - *start* - starting frame [0] - *stop* - stopping frame [-1] - *step* - step between frames [1] - *progout* - number of frames to iterate through between updates to progress - output; ``None`` for no updates [10] - *quiet* - if ``True``, suppress all output (implies *progout* = ``None``) - [``False``] - + avoiding overflows and underflows [Welford1962]_. + + Parameters + ---------- + start : int (optional) + starting frame [0] + stop : int (optional) + stopping frame [-1] + step : int (optional) + step between frames [1] + progout : int (optional) + number of frames to iterate through between updates to progress + output; ``None`` for no updates [10] + quiet : bool (optional) + if ``True``, suppress all output (implies *progout* = ``None``) + [``False``] + + References + ---------- + [Welford1962] B. P. Welford (1962). "Note on a Method for Calculating + Corrected Sums of Squares and Products." Technometrics 4(3):419-420. """ sumsquares = np.zeros((self.atomgroup.n_atoms, 3)) means = np.array(sumsquares) diff --git a/package/MDAnalysis/coordinates/CRD.py b/package/MDAnalysis/coordinates/CRD.py index 78ad91443c6..e0a3228b375 100644 --- a/package/MDAnalysis/coordinates/CRD.py +++ b/package/MDAnalysis/coordinates/CRD.py @@ -145,7 +145,7 @@ def write(self, selection, frame=None): frame = 0 # should catch cases when we are analyzing a single PDB (?) atoms = selection.atoms # make sure to use atoms (Issue 46) - coor = atoms.coordinates() # can write from selection == Universe (Issue 49) + coor = atoms.positions # can write from selection == Universe (Issue 49) with util.openany(self.filename, 'w') as self.crd: self._TITLE("FRAME " + str(frame) + " FROM " + str(u.trajectory.filename)) self._TITLE("") diff --git a/package/MDAnalysis/coordinates/DLPoly.py b/package/MDAnalysis/coordinates/DLPoly.py index da6df0d1990..8c85049aac2 100644 --- a/package/MDAnalysis/coordinates/DLPoly.py +++ b/package/MDAnalysis/coordinates/DLPoly.py @@ -57,13 +57,15 @@ class ConfigReader(base.SingleFrameReader): _Timestep = Timestep def _read_first_frame(self): + unitcell = np.zeros((3, 3), dtype=np.float32, order='F') + with open(self.filename, 'r') as inf: self.title = inf.readline().strip() levcfg, imcon, megatm = map(int, inf.readline().split()[:3]) if not imcon == 0: - cellx = list(map(float, inf.readline().split())) - celly = list(map(float, inf.readline().split())) - cellz = list(map(float, inf.readline().split())) + unitcell[0][:] = inf.readline().split() + unitcell[1][:] = inf.readline().split() + unitcell[2][:] = inf.readline().split() ids = [] coords = [] @@ -126,11 +128,8 @@ def _read_first_frame(self): ts._velocities = velocities if has_forces: ts._forces = forces - if not imcon == 0: - ts._unitcell[0][:] = cellx - ts._unitcell[1][:] = celly - ts._unitcell[2][:] = cellz + ts._unitcell = unitcell ts.frame = 0 @@ -168,9 +167,9 @@ def _read_next_timestep(self, ts=None): if not line.startswith('timestep'): raise IOError if not self._imcon == 0: - ts._unitcell[0] = list(map(float, self._file.readline().split())) - ts._unitcell[1] = list(map(float, self._file.readline().split())) - ts._unitcell[2] = list(map(float, self._file.readline().split())) + ts._unitcell[0] = self._file.readline().split() + ts._unitcell[1] = self._file.readline().split() + ts._unitcell[2] = self._file.readline().split() # If ids are given, put them in here # and later sort by them @@ -186,12 +185,11 @@ def _read_next_timestep(self, ts=None): ids.append(idx) # Read in this order for now, then later reorder in place - ts._pos[i] = list(map(float, self._file.readline().split())) + ts._pos[i] = self._file.readline().split() if self._has_vels: - ts._velocities[i] = list(map(float, - self._file.readline().split())) + ts._velocities[i] = self._file.readline().split() if self._has_forces: - ts._forces[i] = list(map(float, self._file.readline().split())) + ts._forces[i] = self._file.readline().split() if ids: ids = np.array(ids) @@ -210,7 +208,7 @@ def _read_next_timestep(self, ts=None): def _read_frame(self, frame): """frame is 0 based, error checking is done in base.getitem""" self._file.seek(self._offsets[frame]) - self.ts.frame = frame # gets +1'd in read_next_frame + self.ts.frame = frame - 1 # gets +1'd in read_next_frame return self._read_next_timestep() @property @@ -255,16 +253,12 @@ def _read_n_frames(self): return n_frames - def rewind(self): - self._reopen() - self.next() - def _reopen(self): self.close() self._file = open(self.filename, 'r') self._file.readline() # header is 2 lines self._file.readline() - self.ts.frame = 0 + self.ts.frame = -1 def close(self): self._file.close() diff --git a/package/MDAnalysis/coordinates/GRO.py b/package/MDAnalysis/coordinates/GRO.py index 4ba180fd3bd..8916e0b6961 100644 --- a/package/MDAnalysis/coordinates/GRO.py +++ b/package/MDAnalysis/coordinates/GRO.py @@ -130,12 +130,15 @@ def _read_first_frame(self): # (dependent upon the GRO file precision) first_atomline = grofile.readline() cs = first_atomline[25:].find('.') + 1 - has_velocities = first_atomline[20:].count('.') > 3 + + # Always try, and maybe add them later + velocities = np.zeros((n_atoms, 3), dtype=np.float32) self.ts = ts = self._Timestep(n_atoms, - velocities=has_velocities, **self._ts_kwargs) + missed_vel = False + grofile.seek(0) for pos, line in enumerate(grofile, start=-2): # 2 header lines, 1 box line at end @@ -144,13 +147,19 @@ def _read_first_frame(self): continue if pos < 0: continue - for i in range(3): - ts._pos[pos, i] = float(line[20 + cs*i: 20 + cs*(i+1)]) - if not has_velocities: - continue - for i, j in enumerate(range(3, 6)): - ts._velocities[pos, i] = float(line[20+cs*j:20+cs*(j+1)]) + ts._pos[pos] = [line[20 + cs*i:20 + cs*(i+1)] for i in range(3)] + try: + velocities[pos] = [line[20 + cs*i:20 + cs*(i+1)] for i in range(3, 6)] + except ValueError: + # Remember that we got this error + missed_vel = True + + if np.any(velocities): + ts.velocities = velocities + if missed_vel: + warnings.warn("Not all velocities were present. " + "Unset velocities set to zero.") self.ts.frame = 0 # 0-based frame number diff --git a/package/MDAnalysis/coordinates/MOL2.py b/package/MDAnalysis/coordinates/MOL2.py index 8ef6720f770..f02168a9c22 100644 --- a/package/MDAnalysis/coordinates/MOL2.py +++ b/package/MDAnalysis/coordinates/MOL2.py @@ -59,6 +59,8 @@ def __init__(self, filename, **kwargs): """Read coordinates from *filename*.""" super(MOL2Reader, self).__init__(filename, **kwargs) + self.n_atoms = None + blocks = [] with util.openany(filename) as f: @@ -93,16 +95,26 @@ def parse_block(self, block): if not len(atom_lines): raise Exception("The mol2 (starting at line {0}) block has no atoms" "".format(block["start_line"])) + elif self.n_atoms is None: + # First time round, remember the number of atoms + self.n_atoms = len(atom_lines) + elif len(atom_lines) != self.n_atoms: + raise ValueError( + "MOL2Reader assumes that the number of atoms remains unchanged" + " between frames; the current " + "frame has {0}, the next frame has {1} atoms" + "".format(self.n_atoms, len(atom_lines))) + if not len(bond_lines): raise Exception("The mol2 (starting at line {0}) block has no bonds" "".format(block["start_line"])) - coords = [] - for a in atom_lines: + coords = np.zeros((self.n_atoms, 3), dtype=np.float32) + for i, a in enumerate(atom_lines): aid, name, x, y, z, atom_type, resid, resname, charge = a.split() - x, y, z = float(x), float(y), float(z) - coords.append((x, y, z)) - coords = np.array(coords, dtype=np.float32) + #x, y, z = float(x), float(y), float(z) + coords[i, :] = x, y, z + return sections, coords def _read_next_timestep(self, ts=None): @@ -124,16 +136,11 @@ def _read_frame(self, frame): sections, coords = self.parse_block(block) - self.ts.data['molecule'] = sections["molecule"] - self.ts.data['substructure'] = sections["substructure"] - - # check if atom number changed - if len(coords) != self.n_atoms: - raise ValueError( - "MOL2Reader assumes that the number of atoms remains unchanged" - " between frames; the current " - "frame has {0}, the next frame has {1} atoms" - "".format(self.n_atoms, len(coords))) + for sect in ['molecule', 'substructure']: + try: + self.ts.data[sect] = sections[sect] + except KeyError: + pass self.ts.positions = np.array(coords, dtype=np.float32) self.ts.unitcell = unitcell @@ -277,10 +284,9 @@ def encode_block(self, obj): atom_lines = "\n".join(atom_lines) try: - substructure = ["@SUBSTRUCTURE\n"] - substructure.extend(ts.data['substructure']) + substructure = ["@SUBSTRUCTURE\n"] + ts.data['substructure'] except KeyError: - raise NotImplementedError("No MOL2 substructure type found in traj") + substructure = "" molecule = ts.data['molecule'] check_sums = molecule[1].split() diff --git a/package/MDAnalysis/coordinates/PDB.py b/package/MDAnalysis/coordinates/PDB.py index 45d02b9a0ea..ec1815a39a5 100644 --- a/package/MDAnalysis/coordinates/PDB.py +++ b/package/MDAnalysis/coordinates/PDB.py @@ -56,44 +56,22 @@ Implementations --------------- -Two different implementations of PDB I/O are available: the -":ref:`permissive`" and the ":ref:`strict`" Reader/Writers. -The default are the "permissive" ones but this can be changed by setting the -flag "permissive_pdb_reader" in :data:`MDAnalysis.core.flags` (see -:ref:`flags-label`) to ``False``:: +PDB I/O is available in the form of the Simple PDB Reader/Writers. - MDAnalysis.core.flags["permissive_pdb_reader"] = False +..deprecated:: 0.15.0 +Readers and writers solely available in the form of +Simple Readers and Writers, see below. -The *default for MDAnalysis* is to use the -":ref:`permissive`" :class:`PrimitivePDBReader` and -:class:`PrimitivePDBWriter`, corresponding to :: - - MDAnalysis.core.flags["permissive_pdb_reader"] = True - -On a case-by-case basis one kind of reader can be selected with the -*permissive* keyword to :class:`~MDAnalysis.core.AtomGroup.Universe`, e.g. :: - - u = MDAnalysis.Universe(PDB, permissive=False) - -would select :class:`PDBReader` instead of the default -:class:`PrimitivePDBReader`. - -.. _permissive: - -Simple (permissive) PDB Reader and Writer +Simple PDB Reader and Writer ----------------------------------------- - A pure-Python implementation for PDB files commonly encountered in MD -simulations comes under the names :class:`PrimitivePDBReader` and -:class:`PrimitivePDBWriter`. It only implements a subset of the `PDB standard`_ +simulations comes under the names :class:`PDBReader` and +:class:`PDBWriter`. It only implements a subset of the `PDB standard`_ (for instance, it does not deal with insertion codes) and also allows some -typical enhancements such as 4-letter resids (introduced by CHARMM/NAMD). The -"primitive PDB Reader/Writer" are the *default* in MDAnalysis (equivalent to -supplying the *permissive* = ``True`` keyword to -:class:`~MDAnalysis.core.AtomGroup.Universe`). +typical enhancements such as 4-letter resids (introduced by CHARMM/NAMD). -The :class:`PrimitivePDBReader` can read multi-frame PDB files and represents -them as a trajectory. The :class:`PrimitivePDBWriter` can write single and +The :class:`PDBReader` can read multi-frame PDB files and represents +them as a trajectory. The :class:`PDBWriter` can write single and multi-frame PDB files as specified by the *multiframe* keyword. By default, it writes single frames. On the other hand, the :class:`MultiPDBWriter` is set up to write a PDB trajectory by default (equivalent to using *multiframe* = @@ -137,10 +115,10 @@ Classes ~~~~~~~ -.. autoclass:: PrimitivePDBReader +.. autoclass:: PDBReader :members: -.. autoclass:: PrimitivePDBWriter +.. autoclass:: PDBWriter :members: .. automethod:: _check_pdb_coordinates @@ -151,67 +129,14 @@ .. autoclass:: MultiPDBWriter :members: -.. _strict: - -Biopython (strict) PDB Reader and Writer ----------------------------------------- -The :mod:`PDB` module can make use of Biopython's :mod:`Bio.PDB` -[Hamelryck2003]_ but replaces the standard PDB file parser with one that uses -the :class:`MDAnalysis.coordinates.pdb.extensions.SloppyStructureBuilder` to -cope with very large pdb files as commonly encountered in MD simulations. The -Biopython-based :class:`PDBReader` has the advantage that it implements the -`PDB standard`_ rigorously but this comes at the cost of flexibility and -performance. It is also difficult to write out selections using this -implementation (:class:`PDBWriter`) and multi frame PDB files are not -implemented. The Biopython Reader/Writer can be selected when loading data into -a :class:`~MDAnalysis.core.AtomGroup.Universe` by providing the keyword -*permissive* = ``False``. - -The Biopython PDB parser :class:`Bio.PDB.PDBParser` is fairly strict and even -in its own permissive mode (which MDAnalysis employs) typically warns about -missing element names with a -:exc:`Bio.PDB.PDBExceptions.PDBConstructionWarning` . Such warnings, however, -are generally harmless and therefore are filtered (and ignored) by MDAnalysis -with the help of :func:`warnings.filterwarnings`. - - -Classes -~~~~~~~ - -.. autoclass:: PDBReader - :members: - -.. autoclass:: PDBWriter - :members: - -References ----------- - -.. [Hamelryck2003] Hamelryck, T., Manderick, B. (2003) PDB parser and structure - class implemented in Python. Bioinformatics, 19, 2308-2310. - http://biopython.org - -.. _PDB standard: http://www.wwpdb.org/documentation/format32/v3.2.html -.. _END: http://www.wwpdb.org/documentation/format32/sect11.html#END +..deprecated:: 0.15.0 + The "permissive" flag is not used anymore (and effectively defaults to True); + it will be completely removed in 0.16.0. """ -from six.moves import range - -try: - # BioPython is overkill but potentially extensible (altLoc etc) - import Bio.PDB - from . import pdb - # disable PDBConstructionWarning from picky builder - import warnings - - warnings.filterwarnings('ignore', - category=Bio.PDB.PDBExceptions.PDBConstructionWarning, - message="Could not assign element|Used element .* for Atom") -except ImportError: - # TODO: fall back to PrimitivePDBReader - raise ImportError("No full-feature PDB I/O functionality. Install biopython.") +from six.moves import range, zip import os import errno @@ -234,167 +159,7 @@ class implemented in Python. Bioinformatics, 19, 2308-2310. # Pairs of residue name / atom name in use to deduce PDB formatted atom names Pair = collections.namedtuple('Atom', 'resname name') - -class PDBReader(base.SingleFrameReader): - """Read a pdb file into a :mod:`BioPython.PDB` structure. - - The coordinates are also supplied as one numpy array and wrapped - into a Timestep object. - - .. Note:: The Biopython.PDB reader does not parse the ``CRYST1`` - record and hence the unitcell dimensions are not set. - Use the :class:`PrimitivePDBReader` instead (i.e. use - the ``primitive=True`` keyword for :class:`Universe`). - - .. versionchanged:: 0.11.0 - * Frames now 0-based instead of 1-based. - * All PDB header metadata parsed by the reader is available in - the dict :attr:`metadata`. - - """ - format = 'PDB' - units = {'time': None, 'length': 'Angstrom'} - - def _read_first_frame(self): - pdb_id = "0UNK" - self.pdb = pdb.extensions.get_structure(self.filename, pdb_id) - pos = np.array([atom.coord for atom in self.pdb.get_atoms()]) - self.n_atoms = pos.shape[0] - self.fixed = 0 # parse B field for fixed atoms? - #self.ts._unitcell[:] = ??? , from CRYST1? --- not implemented in Biopython.PDB - self.ts = self._Timestep.from_coordinates(pos, **self._ts_kwargs) - self.ts.frame = 0 - del pos - if self.convert_units: - self.convert_pos_from_native(self.ts._pos) # in-place ! - # metadata - self.metadata = self.pdb.header - - def Writer(self, filename, **kwargs): - """Returns a strict PDBWriter for *filename*. - - :Arguments: - *filename* - filename of the output PDB file - - :Returns: :class:`PDBWriter` - - .. Note:: - - This :class:`PDBWriter` 's :meth:`~PDBWriter.write` method - always requires a :class:`base.Timestep` as an argument (it is - not optional anymore when the Writer is obtained through - this method of :class:`PDBReader` .) - """ - # This is messy; we cannot get a universe from the Reader, which would - # be also needed to be fed to the PDBWriter (which is a total mess...). - # Hence we ignore the problem and document it in the doc string... --- - # the limitation is simply that PDBWriter.write() must always be called - # with an argument. - kwargs['BioPDBstructure'] = self.pdb # make sure that this Writer is - # always linked to this reader, don't bother with Universe - kwargs.pop('universe', None) - return PDBWriter(filename, **kwargs) - - -class PDBWriter(base.Writer): - """Write out the current time step as a pdb file. - - This is not cleanly implemented at the moment. One must supply a - universe, even though this is nominally an optional argument. The - class behaves slightly differently depending on if the structure - was loaded from a PDB (then the full-fledged :mod:`Bio.PDB` writer is - used) or if this is really only an atom selection (then a less - sophistiocated writer is employed). - - .. Note:: - - The standard PDBWriter can only write the *whole system*. In - order to write a selection, use the :class:`PrimitivePDBWriter` , - which happens automatically when the - :meth:`~MDAnalysis.core.AtomGroup.AtomGroup.write` method of a - :class:`~MDAnalysis.core.AtomGroup.AtomGroup` instance is used. - """ - format = 'PDB' - units = {'time': None, 'length': 'Angstrom'} - - # PDBWriter is a bit more complicated than the DCDWriter in the - # sense that a DCD frame only contains coordinate information. The - # PDB contains atom data as well and hence it MUST access the - # universe. In order to present a unified (and backwards - # compatible) interface we must keep the universe argument an - # optional keyword argument even though it really is required. - - def __init__(self, pdbfilename, universe=None, multi=False, **kwargs): - """pdbwriter = PDBWriter(,universe=universe,**kwargs) - - :Arguments: - pdbfilename filename; if multi=True, embed a %%d formatstring - so that write_next_timestep() can insert the frame number - - universe supply a universe [really REQUIRED; optional only for compatibility] - - multi False: write a single structure to a single pdb - True: write all frames to multiple pdb files - """ - import Bio.PDB.Structure - - self.universe = universe - # hack for PDBReader.Writer() - self.PDBstructure = kwargs.pop('BioPDBstructure', None) - if not self.PDBstructure: - try: - self.PDBstructure = universe.trajectory.pdb - except AttributeError: - pass - self.filename = pdbfilename - self.multi = multi - if self.multi: - raise NotImplementedError('Sorry, multi=True does not work yet.') - if self.PDBstructure is not None \ - and not isinstance(self.PDBstructure, Bio.PDB.Structure.Structure): - raise TypeError('If defined, PDBstructure must be a Bio.PDB.Structure.Structure, eg ' - 'Universe.trajectory.pdb.') - - def write_next_timestep(self, ts=None): - self.write(ts) - - def write(self, ts=None): - """Write timestep as a pdb file. - - If ts=None then we try to get the current one from the universe. - """ - if self.PDBstructure is None: - if self.universe is None: - warnings.warn("PDBWriter: Not writing frame as neither Timestep nor Universe supplied.") - return - # primitive PDB writing (ignores timestep argument) - ppw = PrimitivePDBWriter(self.filename) - ppw.write(self.universe.select_atoms('all')) - ppw.close() - else: - # full fledged PDB writer - # Let's cheat and use universe.pdb.pdb: modify coordinates - # and save... - if ts is None: - try: - ts = self.universe.trajectory.ts - except AttributeError: - warnings.warn("PDBWriter: Not writing frame as neither universe nor timestep supplied.") - return - if not hasattr(ts, '_pos'): - raise TypeError("The PDBWriter can only process a Timestep as " - " optional argument, not e.g. a selection. " - "Use the PrimitivePDBWriter instead and see " - "the docs.") - for a, pos in zip(self.PDBstructure.get_atoms(), ts._pos): - a.set_coord(pos) - io = pdb.extensions.SloppyPDBIO() - io.set_structure(self.PDBstructure) - io.save(self.filename) - - -class PrimitivePDBReader(base.Reader): +class PDBReader(base.Reader): """PDBReader that reads a `PDB-formatted`_ file, no frills. The following *PDB records* are parsed (see `PDB coordinate section`_ for @@ -445,7 +210,7 @@ class PrimitivePDBReader(base.Reader): ============= ============ =========== ============================================= - .. SeeAlso:: :class:`PrimitivePDBWriter`; :class:`PDBReader` + .. SeeAlso:: :class:`PDBWriter`; :class:`PDBReader` implements a larger subset of the header records, which are accessible as :attr:`PDBReader.metadata`. @@ -454,7 +219,7 @@ class PrimitivePDBReader(base.Reader): * New :attr:`title` (list with all TITLE lines). """ - format = 'Permissive_PDB' + format = ['PDB', 'ENT'] units = {'time': None, 'length': 'Angstrom'} def __init__(self, filename, **kwargs): @@ -464,114 +229,94 @@ def __init__(self, filename, **kwargs): If the pdb file contains multiple MODEL records then it is read as a trajectory where the MODEL numbers correspond to - frame numbers. Therefore, the MODEL numbers must be a sequence - of integers (typically starting at 1 or 0). + frame numbers. """ - super(PrimitivePDBReader, self).__init__(filename, **kwargs) + super(PDBReader, self).__init__(filename, **kwargs) try: - self._n_atoms = kwargs['n_atoms'] + self.n_atoms = kwargs['n_atoms'] except KeyError: - raise ValueError("PrimitivePDBReader requires the n_atoms keyword") - - self.model_offset = kwargs.pop("model_offset", 0) - - header = "" - title = [] - compound = [] - remarks = [] - - frames = {} - - self.ts = self._Timestep(self._n_atoms, **self._ts_kwargs) - - pos = 0 # atom position for filling coordinates array - occupancy = np.ones(self._n_atoms) - with util.openany(filename, 'rt') as pdbfile: - for i, line in enumerate(pdbfile): - line = line.strip() # Remove extra spaces - if len(line) == 0: # Skip line if empty - continue - record = line[:6].strip() - - if record == 'END': - break - elif record == 'CRYST1': - self.ts._unitcell[:] = [line[6:15], line[15:24], - line[24:33], line[33:40], - line[40:47], line[47:54]] - continue - elif record == 'HEADER': - # classification = line[10:50] - # date = line[50:59] - # idCode = line[62:66] - header = line[10:66] - continue - elif record == 'TITLE': - l = line[8:80].strip() - title.append(l) - continue - elif record == 'COMPND': - l = line[7:80].strip() - compound.append(l) - continue - elif record == 'REMARK': - content = line[6:].strip() - remarks.append(content) - elif record == 'MODEL': - frames[len(frames)] = i # 0-based indexing - elif line[:6] in ('ATOM ', 'HETATM'): - # skip atom/hetatm for frames other than the first - # they will be read in when next() is called - # on the trajectory reader - if len(frames) > 1: - continue - self.ts._pos[pos] = [line[30:38], - line[38:46], - line[46:54]] - try: - occupancy[pos] = line[54:60] - except ValueError: - pass - pos += 1 - - self.header = header - self.title = title - self.compound = compound - self.remarks = remarks - - if pos != self._n_atoms: - raise ValueError("Read an incorrect number of atoms\n" - "Expected {expected} got {actual}" - "".format(expected=self._n_atoms, actual=pos)) - self.n_atoms = pos + # hackish, but should work and keeps things DRY + # regular MDA usage via Universe doesn't follow this route + from MDAnalysis.topology import PDBParser - self.ts.frame = 0 # 0-based frame number as starting frame - self.ts.data['occupancy'] = occupancy + with PDBParser.PDBParser(self.filename) as p: + top = p.parse() + self.n_atoms = len(top['atoms']) - if self.convert_units: - self.convert_pos_from_native(self.ts._pos) # in-place ! - self.convert_pos_from_native(self.ts._unitcell[:3]) # in-place ! (only lengths) + self.model_offset = kwargs.pop("model_offset", 0) - # No 'MODEL' entries - if len(frames) == 0: - frames[0] = 0 + self.header = header = "" + self.title = title = [] + self.compound = compound = [] + self.remarks = remarks = [] + + self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) + + # Record positions in file of CRYST and MODEL headers + # then build frame offsets to start at the minimum of these + # This allows CRYST to come either before or after MODEL + # This assumes that **either** + # - pdbfile has a single CRYST (NVT) + # - pdbfile has a CRYST for every MODEL (NPT) + models = [] + crysts = [] + + pdbfile = self._pdbfile = util.anyopen(filename, 'rt') + + line = "magical" + while line: + # need to use readline so tell gives end of line + # (rather than end of current chunk) + line = pdbfile.readline() + + if line.startswith('MODEL'): + models.append(pdbfile.tell()) + elif line.startswith('CRYST1'): + # remove size of line to get **start** of CRYST line + crysts.append(pdbfile.tell() - len(line)) + elif line.startswith('HEADER'): + # classification = line[10:50] + # date = line[50:59] + # idCode = line[62:66] + header = line[10:66] + elif line.startswith('TITLE'): + title.append(line[8:80].strip()) + elif line.startswith('COMPND'): + compound.append(line[7:80].strip()) + elif line.startswith('REMARK'): + remarks.append(line[6:].strip()) + + end = pdbfile.tell() # where the file ends + + if not models: + # No model entries + # so read from start of file to read first frame + models.append(0) + if len(crysts) == len(models): + offsets = [min(a, b) for a, b in zip(models, crysts)] + else: + offsets = models + # Position of the start of each frame + self._start_offsets = offsets + # Position of the end of each frame + self._stop_offsets = offsets[1:] + [end] + self.n_frames = len(offsets) - self.frames = frames - self.n_frames = len(frames) if frames else 1 + self._read_frame(0) def Writer(self, filename, **kwargs): - """Returns a permissive (simple) PDBWriter for *filename*. + """Returns a PDBWriter for *filename*. :Arguments: *filename* filename of the output PDB file - :Returns: :class:`PrimitivePDBWriter` + :Returns: :class:`PDBWriter` """ kwargs.setdefault('multiframe', self.n_frames > 1) - return PrimitivePDBWriter(filename, **kwargs) + return PDBWriter(filename, **kwargs) def rewind(self): self._read_frame(0) @@ -579,6 +324,8 @@ def rewind(self): def _reopen(self): # Pretend the current TS is -1 (in 0 based) so "next" is the # 0th frame + self.close() + self._pdbfile = util.anyopen(self.filename, 'rt') self.ts.frame = -1 def _read_next_timestep(self, ts=None): @@ -586,7 +333,7 @@ def _read_next_timestep(self, ts=None): ts = self.ts else: # TODO: cleanup _read_frame() to use a "free" Timestep - raise NotImplementedError("PrimitivePDBReader cannot assign to a timestep") + raise NotImplementedError("PDBReader cannot assign to a timestep") # frame is 1-based. Normally would add 1 to frame before calling # self._read_frame to retrieve the subsequent ts. But self._read_frame # assumes it is being passed a 0-based frame, and adjusts. @@ -595,50 +342,41 @@ def _read_next_timestep(self, ts=None): def _read_frame(self, frame): try: - line = self.frames[frame] - except KeyError: + start = self._start_offsets[frame] + stop = self._stop_offsets[frame] + except IndexError: # out of range of known frames raise IOError - if line is None: - # single frame file, we already have the timestep - return self.ts - # TODO: only open file once and leave the file open; then seek back and - # forth; should improve performance substantially pos = 0 - occupancy = np.ones(self._n_atoms) - with util.openany(self.filename, 'rt') as f: - for i in range(line): - next(f) # forward to frame - for line in f: - if line[:6] == 'ENDMDL': - break - # NOTE - CRYST1 line won't be found if it comes before the - # MODEL line, which is sometimes the case, e.g. output from - # gromacs trjconv - elif line[:6] == 'CRYST1': - self.ts._unitcell[:] = [line[6:15], line[15:24], - line[24:33], line[33:40], - line[40:47], line[47:54]] - continue - elif line[:6] in ('ATOM ', 'HETATM'): - # we only care about coordinates - self.ts._pos[pos] = [line[30:38], - line[38:46], - line[46:54]] - # TODO import bfactors - might these change? - try: - occupancy[pos] = line[54:60] - except ValueError: - # Be tolerant for ill-formated or empty occupancies - pass - pos += 1 - continue + occupancy = np.ones(self.n_atoms) + + # Seek to start and read until start of next frame + self._pdbfile.seek(start) + chunk = self._pdbfile.read(stop - start) + + for line in chunk.splitlines(): + if line[:6] in ('ATOM ', 'HETATM'): + # we only care about coordinates + self.ts._pos[pos] = [line[30:38], + line[38:46], + line[46:54]] + # TODO import bfactors - might these change? + try: + occupancy[pos] = line[54:60] + except ValueError: + # Be tolerant for ill-formated or empty occupancies + pass + pos += 1 + elif line[:6] == 'CRYST1': + self.ts._unitcell[:] = [line[6:15], line[15:24], + line[24:33], line[33:40], + line[40:47], line[47:54]] # check if atom number changed - if pos != self._n_atoms: + if pos != self.n_atoms: raise ValueError("Read an incorrect number of atoms\n" "Expected {expected} got {actual}" - "".format(expected=self._n_atoms, actual=pos+1)) + "".format(expected=self.n_atoms, actual=pos+1)) if self.convert_units: # both happen inplace @@ -648,14 +386,17 @@ def _read_frame(self, frame): self.ts.data['occupancy'] = occupancy return self.ts + def close(self): + self._pdbfile.close() + -class PrimitivePDBWriter(base.Writer): +class PDBWriter(base.Writer): """PDB writer that implements a subset of the `PDB 3.2 standard`_ . PDB format as used by NAMD/CHARMM: 4-letter resnames and segID are allowed, altLoc is written. - The :class:`PrimitivePDBWriter` can be used to either a dump a coordinate + The :class:`PDBWriter` can be used to either dump a coordinate set to a PDB file (operating as a "single frame writer", selected with the constructor keyword *multiframe* = ``False``, the default) or by writing a PDB "movie" (multi frame mode, *multiframe* = ``True``), consisting of @@ -706,7 +447,7 @@ class PrimitivePDBWriter(base.Writer): "{spacegroup:<11s}{zvalue:4d}\n"), 'CONECT': "CONECT{0}\n" } - format = 'PDB' + format = ['PDB', 'ENT'] units = {'time': None, 'length': 'Angstrom'} pdb_coor_limits = {"min": -999.9995, "max": 9999.9995} #: wrap comments into REMARK records that are not longer than @@ -743,7 +484,7 @@ class PrimitivePDBWriter(base.Writer): Pair('PF5', 'FE2'), Pair('UNL', 'UNL')) def __init__(self, filename, bonds="conect", n_atoms=None, start=0, step=1, - remarks="Created by PrimitivePDBWriter", + remarks="Created by PDBWriter", convert_units=None, multiframe=None): """Create a new PDBWriter @@ -808,6 +549,7 @@ def __init__(self, filename, bonds="conect", n_atoms=None, start=0, step=1, self.pdbfile = util.anyopen(self.filename, 'wt') # open file on init self.has_END = False + self.first_frame_done = False def close(self): """Close PDB file and write END record""" @@ -832,10 +574,15 @@ def _write_pdb_header(self): if not self.obj or not hasattr(self.obj, 'universe'): self._write_pdb_title(self) return + if self.first_frame_done == True: + return + self.first_frame_done = True u = self.obj.universe self.HEADER(u.trajectory) + self._write_pdb_title() + self.COMPND(u.trajectory) try: # currently inconsistent: DCDReader gives a string, @@ -901,7 +648,7 @@ def _write_pdb_bonds(self): records for anything smaller than the :class:`Universe` are written. .. versionchanged:: 0.7.6 - Only write CONECT records if :attr:`PrimitivePDBWriter.bonds` ``== True``. + Only write CONECT records if :attr:`PDBWriter.bonds` ``== True``. Raises :exc:`NotImplementedError` if it would produce wrong output. """ @@ -958,12 +705,12 @@ def _update_frame(self, obj): Attributes initialized/updated: - * :attr:`PrimitivePDBWriter.obj` (the entity that provides topology information *and* + * :attr:`PDBWriter.obj` (the entity that provides topology information *and* coordinates, either a :class:`~MDAnalysis.core.AtomGroup.AtomGroup` or a whole :class:`~MDAnalysis.core.AtomGroup.Universe`) - * :attr:`PrimitivePDBWriter.trajectory` (the underlying trajectory + * :attr:`PDBWriter.trajectory` (the underlying trajectory :class:`~MDAnalysis.coordinates.base.Reader`) - * :attr:`PrimitivePDBWriter.timestep` (the underlying trajectory + * :attr:`PDBWriter.timestep` (the underlying trajectory :class:`~MDAnalysis.coordinates.base.Timestep`) Before calling :meth:`write_next_timestep` this method **must** be @@ -972,7 +719,7 @@ def _update_frame(self, obj): """ if isinstance(obj, base.Timestep): - raise TypeError("PrimitivePDBWriter cannot write Timestep objects " + raise TypeError("PDBWriter cannot write Timestep objects " "directly, since they lack topology information (" "atom names and types) required in PDB files") # remember obj for some of other methods --- NOTE: this is an evil/lazy @@ -989,7 +736,7 @@ def _update_frame(self, obj): traj = obj.trajectory if not (ts and traj): - raise AssertionError("PrimitivePDBWriter couldn't extract " + raise AssertionError("PDBWriter couldn't extract " "trajectory and timestep information " "from an object; inheritance problem.") @@ -1005,7 +752,7 @@ def write(self, obj): :class:`~MDAnalysis.core.AtomGroup.Universe`. The last letter of the :attr:`~MDAnalysis.core.AtomGroup.Atom.segid` is - used as the PDB chainID (but see :meth:`~PrimitivePDBWriter.ATOM` for + used as the PDB chainID (but see :meth:`~PDBWriter.ATOM` for details). :Arguments: @@ -1034,18 +781,19 @@ def write_all_timesteps(self, obj): constructor). Thus, if *u* is a Universe then :: u.trajectory[-2] - pdb = PrimitivePDBWriter("out.pdb", u.atoms.n_atoms) + pdb = PDBWriter("out.pdb", u.atoms.n_atoms) pdb.write_all_timesteps(u) will write a PDB trajectory containing the last 2 frames and :: - pdb = PrimitivePDBWriter("out.pdb", u.atoms.n_atoms, start=12, skip=2) + pdb = PDBWriter("out.pdb", u.atoms.n_atoms, start=12, skip=2) pdb.write_all_timesteps(u) will be writing frames 12, 14, 16, ... .. versionchanged:: 0.11.0 Frames now 0-based instead of 1-based + """ self._update_frame(obj) @@ -1076,14 +824,14 @@ def write_next_timestep(self, ts=None, **kwargs): :Keywords: *ts* :class:`base.Timestep` object containing coordinates to be written to trajectory file; - if ``None`` then :attr:`PrimitivePDBWriter.ts`` is tried. + if ``None`` then :attr:`PDBWriter.ts`` is tried. *multiframe* ``False``: write a single frame (default); ``True`` behave as a trajectory writer .. Note:: Before using this method with another :class:`base.Timestep` in the *ts* - argument, :meth:`PrimitivePDBWriter._update_frame` *must* be called + argument, :meth:`PDBWriter._update_frame` *must* be called with the :class:`~MDAnalysis.core.AtomGroup.AtomGroup.Universe` as its argument so that topology information can be gathered. ''' @@ -1130,7 +878,7 @@ def _write_timestep(self, ts, multiframe=False): the moment we do *not* write the NUMMDL_ record.) The *multiframe* = ``False`` keyword signals that the - :class:`PrimitivePDBWriter` is in single frame mode and no MODEL_ + :class:`PDBWriter` is in single frame mode and no MODEL_ records are written. .. _MODEL: http://www.wwpdb.org/documentation/format32/sect9.html#MODEL @@ -1246,9 +994,9 @@ def END(self): """Write END_ record. Only a single END record is written. Calling END multiple times has no - effect. Because :meth:`~PrimitivePDBWriter.close` also calls this + effect. Because :meth:`~PDBWriter.close` also calls this method right before closing the file it is recommended to *not* call - :meth:`~PrimitivePDBWriter.END` explicitly. + :meth:`~PDBWriter.END` explicitly. .. _END: http://www.wwpdb.org/documentation/format32/sect11.html#END @@ -1277,7 +1025,24 @@ def CONECT(self, conect): self.pdbfile.write(self.fmt['CONECT'].format(conect)) -class ExtendedPDBReader(PrimitivePDBReader): +class PrimitivePDBReader(PDBReader): + def __init__(self, filename, *args, **kwargs): + warnings.warn('PrimitivePDBReader is identical to the PDBReader,' + ' it is deprecated in favor of the shorter name' + ' removal targeted for version 0.16.0', + category=DeprecationWarning) + super(PrimitivePDBReader, self).__init__(filename, *args, **kwargs) + + +class PrimitivePDBWriter(PDBWriter): + def __init__(self, filename, *args, **kwargs): + warnings.warn('PrimitivePDBWriter is identical to the Writer,' + 'it is deprecated in favor of the shorter name' + ' removal targeted for version 0.16.0', + category=DeprecationWarning) + super(PrimitivePDBWriter, self).__init__(filename, *args, **kwargs) + +class ExtendedPDBReader(PDBReader): """PDBReader that reads a PDB-formatted file with five-digit residue numbers. This reader does not conform to the `PDB standard`_ because it allows @@ -1286,7 +1051,7 @@ class ExtendedPDBReader(PrimitivePDBReader): insertion code in the PDB standard). PDB files in this format are written by popular programs such as VMD_. - .. SeeAlso:: :class:`PrimitivePDBReader` + .. SeeAlso:: :class:`PDBReader` .. _PDB standard: http://www.wwpdb.org/documentation/format32/sect9.html .. _VMD: http://www.ks.uiuc.edu/Research/vmd/ @@ -1296,7 +1061,7 @@ class ExtendedPDBReader(PrimitivePDBReader): format = "XPDB" -class MultiPDBWriter(PrimitivePDBWriter): +class MultiPDBWriter(PDBWriter): """PDB writer that implements a subset of the `PDB 3.2 standard`_ . PDB format as used by NAMD/CHARMM: 4-letter resnames and segID, altLoc @@ -1315,7 +1080,7 @@ class MultiPDBWriter(PrimitivePDBWriter): .. SeeAlso:: - This class is identical to :class:`PrimitivePDBWriter` with the one + This class is identical to :class:`PDBWriter` with the one exception that it defaults to writing multi-frame PDB files instead of single frames. diff --git a/package/MDAnalysis/coordinates/PDBQT.py b/package/MDAnalysis/coordinates/PDBQT.py index 7c9205528e7..965c9fed2dc 100644 --- a/package/MDAnalysis/coordinates/PDBQT.py +++ b/package/MDAnalysis/coordinates/PDBQT.py @@ -293,7 +293,7 @@ def write(self, selection, frame=None): self.TITLE("FRAME " + str(frame) + " FROM " + str(u.trajectory.filename)) self.CRYST1(self.convert_dimensions_to_unitcell(u.trajectory.ts)) atoms = selection.atoms # make sure to use atoms (Issue 46) - coor = atoms.coordinates() # can write from selection == Universe (Issue 49) + coor = atoms.positions # can write from selection == Universe (Issue 49) # check if any coordinates are illegal (coordinates are already in Angstroem per package default) if not self.has_valid_coordinates(self.pdb_coor_limits, coor): diff --git a/package/MDAnalysis/coordinates/PQR.py b/package/MDAnalysis/coordinates/PQR.py index 6dba0494f68..e7b286b6f3e 100644 --- a/package/MDAnalysis/coordinates/PQR.py +++ b/package/MDAnalysis/coordinates/PQR.py @@ -231,7 +231,7 @@ def write(self, selection, frame=None): frame = 0 # should catch cases when we are analyzing a single frame(?) atoms = selection.atoms # make sure to use atoms (Issue 46) - coordinates = atoms.coordinates() # can write from selection == Universe (Issue 49) + coordinates = atoms.positions # can write from selection == Universe (Issue 49) if self.convert_units: self.convert_pos_to_native(coordinates) # inplace because coordinates is already a copy diff --git a/package/MDAnalysis/coordinates/TRJ.py b/package/MDAnalysis/coordinates/TRJ.py index 68b7a6c810a..93c32331e47 100644 --- a/package/MDAnalysis/coordinates/TRJ.py +++ b/package/MDAnalysis/coordinates/TRJ.py @@ -2,19 +2,19 @@ # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # # Please cite your use of MDAnalysis in published work: + + # # N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # - - """ AMBER trajectories --- :mod:`MDAnalysis.coordinates.TRJ` ======================================================== @@ -66,8 +66,8 @@ are handled by the :class:`TRJReader`. It is also possible to directly read *bzip2* or *gzip* compressed files. -AMBER ASCII trajectories are recognised by the suffix '.trj' or -'.mdcrd' (possibly with an additional '.gz' or '.bz2'). +AMBER ASCII trajectories are recognised by the suffix '.trj', +'.mdcrd' or '.crdbox (possibly with an additional '.gz' or '.bz2'). .. rubric:: Limitations @@ -126,7 +126,8 @@ """ -from __future__ import absolute_import, division, print_function, unicode_literals +from __future__ import (absolute_import, division, print_function, + unicode_literals) import numpy as np import warnings @@ -138,18 +139,19 @@ from . import base from ..lib import util - logger = logging.getLogger("MDAnalysis.coordinates.AMBER") try: import netCDF4 as netcdf except ImportError: - # Just to notify the user; the module will still load. However, NCDFReader and NCDFWriter - # will raise a proper ImportError if they are called without the netCDF4 library present. - # See Issue 122 for a discussion. - logger.debug("Failed to import netCDF4; AMBER NETCDFReader/Writer will not work. " - "Install netCDF4 from https://github.com/Unidata/netcdf4-python.") - logger.debug("See also https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") + # Just to notify the user; the module will still load. However, NCDFReader + # and NCDFWriter will raise a proper ImportError if they are called without + # the netCDF4 library present. See Issue 122 for a discussion. + logger.debug( + "Failed to import netCDF4; AMBER NETCDFReader/Writer will not work. " + "Install netCDF4 from https://github.com/Unidata/netcdf4-python.") + logger.debug( + "See also https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") class Timestep(base.Timestep): @@ -162,7 +164,7 @@ class Timestep(base.Timestep): .. versionchanged:: 0.10.0 Added ability to contain Forces """ - order='C' + order = 'C' class TRJReader(base.Reader): @@ -189,13 +191,12 @@ class TRJReader(base.Reader): Frames now 0-based instead of 1-based kwarg 'delta' renamed to 'dt', for uniformity with other Readers """ - format = ['TRJ', 'MDCRD'] + format = ['TRJ', 'MDCRD', 'CRDBOX'] units = {'time': 'ps', 'length': 'Angstrom'} _Timestep = Timestep def __init__(self, filename, n_atoms=None, **kwargs): super(TRJReader, self).__init__(filename, **kwargs) - # amber trj REQUIRES the number of atoms from the topology if n_atoms is None: raise ValueError("AMBER TRJ reader REQUIRES the n_atoms keyword") self._n_atoms = n_atoms @@ -206,12 +207,14 @@ def __init__(self, filename, n_atoms=None, **kwargs): # FORMAT(10F8.3) (X(i), Y(i), Z(i), i=1,NATOM) self.default_line_parser = util.FORTRANReader("10F8.3") - self.lines_per_frame = int(np.ceil(3.0 * self.n_atoms / len(self.default_line_parser))) + self.lines_per_frame = int(np.ceil(3.0 * self.n_atoms / len( + self.default_line_parser))) # The last line per frame might have fewer than 10 # We determine right away what parser we need for the last # line because it will be the same for all frames. last_per_line = 3 * self.n_atoms % len(self.default_line_parser) - self.last_line_parser = util.FORTRANReader("{0:d}F8.3".format(last_per_line)) + self.last_line_parser = util.FORTRANReader("{0:d}F8.3".format( + last_per_line)) # FORMAT(10F8.3) BOX(1), BOX(2), BOX(3) # is this always on a separate line?? @@ -231,7 +234,7 @@ def _read_next_timestep(self): self.open_trajectory() # Read coordinat frame: - #coordinates = numpy.zeros(3*self.n_atoms, dtype=np.float32) + # coordinates = numpy.zeros(3*self.n_atoms, dtype=np.float32) _coords = [] for number, line in enumerate(self.trjfile): try: @@ -253,9 +256,9 @@ def _read_next_timestep(self): ts._unitcell[:3] = np.array(box, dtype=np.float32) ts._unitcell[3:] = [90., 90., 90.] # assumed - # probably slow ... could be optimized by storing the coordinates in X,Y,Z - # lists or directly filling the array; the array/reshape is not good - # because it creates an intermediate array + # probably slow ... could be optimized by storing the coordinates in + # X,Y,Z lists or directly filling the array; the array/reshape is not + # good because it creates an intermediate array ts._pos[:] = np.array(_coords).reshape(self.n_atoms, 3) ts.frame += 1 return ts @@ -345,8 +348,9 @@ def open_trajectory(self): self.header = self.trjfile.readline() # ignore first line if len(self.header.rstrip()) > 80: # Chimera uses this check - raise OSError("Header of AMBER formatted trajectory has more than 80 chars. " - "This is probably not a AMBER trajectory.") + raise OSError( + "Header of AMBER formatted trajectory has more than 80 chars. " + "This is probably not a AMBER trajectory.") # reset ts ts = self.ts ts.frame = -1 @@ -432,14 +436,16 @@ def __init__(self, filename, n_atoms=None, **kwargs): if not ('AMBER' in self.trjfile.Conventions.split(',') or 'AMBER' in self.trjfile.Conventions.split()): - errmsg = ("NCDF trajectory {0} does not conform to AMBER specifications, " + - "http://ambermd.org/netcdf/nctraj.html ('AMBER' must be one of the tokens " + - "in attribute Conventions)").format(self.filename) + errmsg = ("NCDF trajectory {0} does not conform to AMBER " + "specifications, http://ambermd.org/netcdf/nctraj.html " + "('AMBER' must be one of the tokens in attribute " + "Conventions)".format(self.filename)) logger.fatal(errmsg) raise TypeError(errmsg) if not self.trjfile.ConventionVersion == self.version: - wmsg = "NCDF trajectory format is {0!s} but the reader implements format {1!s}".format( - self.trjfile.ConventionVersion, self.version) + wmsg = ("NCDF trajectory format is {0!s} but the reader " + "implements format {1!s}".format( + self.trjfile.ConventionVersion, self.version)) warnings.warn(wmsg) logger.warn(wmsg) @@ -458,24 +464,25 @@ def __init__(self, filename, n_atoms=None, **kwargs): # - application AMBER # - # checks for not-implemented features (other units would need to be hacked into MDAnalysis.units) + # checks for not-implemented features (other units would need to be + # hacked into MDAnalysis.units) if self.trjfile.variables['time'].units != "picosecond": raise NotImplementedError( - "NETCDFReader currently assumes that the trajectory was written with a time unit of picoseconds and " - "not {0}.".format( - self.trjfile.variables['time'].units)) + "NETCDFReader currently assumes that the trajectory was " + "written with a time unit of picoseconds and " + "not {0}.".format(self.trjfile.variables['time'].units)) if self.trjfile.variables['coordinates'].units != "angstrom": raise NotImplementedError( - "NETCDFReader currently assumes that the trajectory was written with a length unit of Angstroem and " - "not {0}.".format( - self.trjfile.variables['coordinates'].units)) + "NETCDFReader currently assumes that the trajectory was " + "written with a length unit of Angstroem and " + "not {0}.".format(self.trjfile.variables['coordinates'].units)) if hasattr(self.trjfile.variables['coordinates'], 'scale_factor'): raise NotImplementedError("scale_factors are not implemented") if n_atoms is not None: if n_atoms != self.n_atoms: raise ValueError( - "Supplied n_atoms ({0}) != natom from ncdf ({1}). " - "Note: n_atoms can be None and then the ncdf value is used!" + "Supplied n_atoms ({0}) != natom from ncdf ({1}). Note: " + "n_atoms can be None and then the ncdf value is used!" "".format(n_atoms, self.n_atoms)) self.has_velocities = 'velocities' in self.trjfile.variables @@ -513,14 +520,17 @@ def _read_frame(self, frame): ts._unitcell[3:] = self.trjfile.variables['cell_angles'][frame] if self.convert_units: self.convert_pos_from_native(ts._pos) # in-place ! - self.convert_time_from_native(ts.time) # in-place ! (hope this works...) + self.convert_time_from_native( + ts.time) # in-place ! (hope this works...) if self.has_velocities: - self.convert_velocities_from_native(ts._velocities, inplace=True) + self.convert_velocities_from_native(ts._velocities, + inplace=True) if self.has_forces: self.convert_forces_from_native(ts._forces, inplace=True) if self.periodic: - self.convert_pos_from_native(ts._unitcell[:3]) # in-place ! (only lengths) - ts.frame = frame # frame labels are 0-based + self.convert_pos_from_native( + ts._unitcell[:3]) # in-place ! (only lengths) + ts.frame = frame # frame labels are 0-based self._current_frame = frame return ts @@ -597,11 +607,22 @@ class NCDFWriter(base.Writer): format = 'NCDF' version = "1.0" - units = {'time': 'ps', 'length': 'Angstrom', 'velocity': 'Angstrom/ps', + units = {'time': 'ps', + 'length': 'Angstrom', + 'velocity': 'Angstrom/ps', 'force': 'kcal/(mol*Angstrom)'} - def __init__(self, filename, n_atoms, start=0, step=1, dt=1.0, remarks=None, - convert_units=None, zlib=False, cmplevel=1, **kwargs): + def __init__(self, + filename, + n_atoms, + start=0, + step=1, + dt=1.0, + remarks=None, + convert_units=None, + zlib=False, + cmplevel=1, + **kwargs): """Create a new NCDFWriter :Arguments: @@ -636,7 +657,8 @@ def __init__(self, filename, n_atoms, start=0, step=1, dt=1.0, remarks=None, self.n_atoms = n_atoms if convert_units is None: convert_units = flags['convert_lengths'] - self.convert_units = convert_units # convert length and time to base units on the fly? + # convert length and time to base units on the fly? + self.convert_units = convert_units self.start = start # do we use those? self.step = step # do we use those? @@ -672,18 +694,26 @@ def _init_netcdf(self, periodic=True): try: import netCDF4 as netcdf except ImportError: - logger.fatal( - "netcdf4-python with the netCDF and HDF5 libraries must be installed for the AMBER ncdf Writer.") - logger.fatal("See installation instructions at https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") - raise ImportError("netCDF4 package missing.\n" - "netcdf4-python with the netCDF and HDF5 libraries must be installed for the AMBER ncdf " - "Writer.\n" - "See installation instructions at https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") + logger.fatal("netcdf4-python with the netCDF and HDF5 libraries " + "must be installed for the AMBER ncdf Writer." + "See installation instructions at " + "https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") + raise ImportError( + "netCDF4 package missing.\n" + "netcdf4-python with the netCDF and HDF5 libraries must be " + "installed for the AMBER ncdf Writer.\n" + "See installation instructions at " + "https://github.com/MDAnalysis/mdanalysis/wiki/netcdf") if not self._first_frame: - raise IOError(errno.EIO, "Attempt to write to closed file {0}".format(self.filename)) + raise IOError( + errno.EIO, + "Attempt to write to closed file {0}".format(self.filename)) - ncfile = netcdf.Dataset(self.filename, clobber=True, mode='w', format='NETCDF3_64BIT') + ncfile = netcdf.Dataset(self.filename, + clobber=True, + mode='w', + format='NETCDF3_64BIT') # Set global attributes. setattr(ncfile, 'program', 'MDAnalysis.coordinates.TRJ.NCDFWriter') @@ -693,52 +723,68 @@ def _init_netcdf(self, periodic=True): setattr(ncfile, 'application', 'MDAnalysis') # Create dimensions - ncfile.createDimension('frame', None) # unlimited number of steps (can append) - ncfile.createDimension('atom', self.n_atoms) # number of atoms in system + ncfile.createDimension('frame', + None) # unlimited number of steps (can append) + ncfile.createDimension('atom', + self.n_atoms) # number of atoms in system ncfile.createDimension('spatial', 3) # number of spatial dimensions ncfile.createDimension('cell_spatial', 3) # unitcell lengths ncfile.createDimension('cell_angular', 3) # unitcell angles ncfile.createDimension('label', 5) # needed for cell_angular # Create variables. - coords = ncfile.createVariable('coordinates', 'f4', ('frame', 'atom', 'spatial'), - zlib=self.zlib, complevel=self.cmplevel) + coords = ncfile.createVariable('coordinates', + 'f4', ('frame', 'atom', 'spatial'), + zlib=self.zlib, + complevel=self.cmplevel) setattr(coords, 'units', 'angstrom') - spatial = ncfile.createVariable('spatial', 'c', ('spatial',)) + spatial = ncfile.createVariable('spatial', 'c', ('spatial', )) spatial[:] = np.asarray(list('xyz')) - time = ncfile.createVariable('time', 'f4', ('frame',), - zlib=self.zlib, complevel=self.cmplevel) + time = ncfile.createVariable('time', + 'f4', ('frame', ), + zlib=self.zlib, + complevel=self.cmplevel) setattr(time, 'units', 'picosecond') self.periodic = periodic if self.periodic: - cell_lengths = ncfile.createVariable('cell_lengths', 'f8', ('frame', 'cell_spatial'), - zlib=self.zlib, complevel=self.cmplevel) + cell_lengths = ncfile.createVariable('cell_lengths', + 'f8', + ('frame', 'cell_spatial'), + zlib=self.zlib, + complevel=self.cmplevel) setattr(cell_lengths, 'units', 'angstrom') cell_spatial = ncfile.createVariable('cell_spatial', 'c', - ('cell_spatial',)) + ('cell_spatial', )) cell_spatial[:] = np.asarray(list('abc')) - cell_angles = ncfile.createVariable('cell_angles', 'f8', ('frame', 'cell_angular'), - zlib=self.zlib, complevel=self.cmplevel) + cell_angles = ncfile.createVariable('cell_angles', + 'f8', + ('frame', 'cell_angular'), + zlib=self.zlib, + complevel=self.cmplevel) setattr(cell_angles, 'units', 'degrees') cell_angular = ncfile.createVariable('cell_angular', 'c', ('cell_angular', 'label')) - cell_angular[:] = np.asarray([list('alpha'), list('beta '), - list('gamma')]) + cell_angular[:] = np.asarray([list('alpha'), list('beta '), list( + 'gamma')]) # These properties are optional, and are specified on Writer creation if self.has_velocities: - velocs = ncfile.createVariable('velocities', 'f8', ('frame', 'atom', 'spatial'), - zlib=self.zlib, complevel=self.cmplevel) + velocs = ncfile.createVariable('velocities', + 'f8', ('frame', 'atom', 'spatial'), + zlib=self.zlib, + complevel=self.cmplevel) setattr(velocs, 'units', 'angstrom/picosecond') if self.has_forces: - forces = ncfile.createVariable('forces', 'f8', ('frame', 'atom', 'spatial'), - zlib=self.zlib, complevel=self.cmplevel) + forces = ncfile.createVariable('forces', + 'f8', ('frame', 'atom', 'spatial'), + zlib=self.zlib, + complevel=self.cmplevel) setattr(forces, 'units', 'kilocalorie/mole/angstrom') ncfile.sync() @@ -746,7 +792,9 @@ def _init_netcdf(self, periodic=True): self.trjfile = ncfile def is_periodic(self, ts=None): - """Return ``True`` if :class:`Timestep` *ts* contains a valid simulation box""" + """Return ``True`` if :class:`Timestep` *ts* contains a valid + simulation box + """ ts = ts if ts is not None else self.ts return np.all(ts.dimensions > 0) @@ -758,11 +806,13 @@ def write_next_timestep(self, ts=None): ''' if ts is None: if not hasattr(self, "ts") or self.ts is None: - raise IOError("NCDFWriter: no coordinate data to write to trajectory file") + raise IOError( + "NCDFWriter: no coordinate data to write to trajectory file") else: ts = self.ts # self.ts would have to be assigned manually! elif ts.n_atoms != self.n_atoms: - raise IOError("NCDFWriter: Timestep does not have the correct number of atoms") + raise IOError( + "NCDFWriter: Timestep does not have the correct number of atoms") if self.trjfile is None: # first time step: analyze data and open trajectory accordingly @@ -797,7 +847,8 @@ def _write_next_timestep(self, ts): try: time = self.convert_time_to_native(ts.time, inplace=False) except AttributeError: - time = ts.frame * self.convert_time_to_native(self.dt, inplace=False) + time = ts.frame * self.convert_time_to_native(self.dt, + inplace=False) unitcell = self.convert_dimensions_to_unitcell(ts) else: pos = ts._pos @@ -809,17 +860,22 @@ def _write_next_timestep(self, ts): self.trjfile.variables['coordinates'][self.curr_frame, :, :] = pos self.trjfile.variables['time'][self.curr_frame] = time if self.periodic: - self.trjfile.variables['cell_lengths'][self.curr_frame, :] = unitcell[:3] - self.trjfile.variables['cell_angles'][self.curr_frame, :] = unitcell[3:] + self.trjfile.variables['cell_lengths'][ + self.curr_frame, :] = unitcell[:3] + self.trjfile.variables['cell_angles'][ + self.curr_frame, :] = unitcell[3:] if self.has_velocities: if self.convert_units: - velocities = self.convert_velocities_to_native(ts._velocities, inplace=False) + velocities = self.convert_velocities_to_native(ts._velocities, + inplace=False) else: velocities = ts._velocities - self.trjfile.variables['velocities'][self.curr_frame, :, :] = velocities + self.trjfile.variables['velocities'][ + self.curr_frame, :, :] = velocities if self.has_forces: if self.convert_units: - forces = self.convert_forces_to_native(ts._forces, inplace=False) + forces = self.convert_forces_to_native(ts._forces, + inplace=False) else: forces = ts._velocities self.trjfile.variables['forces'][self.curr_frame, :, :] = forces diff --git a/package/MDAnalysis/coordinates/__init__.py b/package/MDAnalysis/coordinates/__init__.py index 106c96fcd19..1b01098ca8d 100644 --- a/package/MDAnalysis/coordinates/__init__.py +++ b/package/MDAnalysis/coordinates/__init__.py @@ -179,10 +179,8 @@ | | | | optional `netcdf4-python`_ module (coordinates and | | | | | velocities). Module :mod:`MDAnalysis.coordinates.TRJ`| +---------------+-----------+-------+------------------------------------------------------+ - | Brookhaven | pdb | r/w | a simplified PDB format (as used in MD simulations) | - | [#a]_ | | | is read by default; the full format can be read by | - | | | | supplying the `permissive=False` flag to | - | | | | :class:`MDAnalysis.Universe`. Multiple frames (MODEL)| + | Brookhaven | pdb/ent | r/w | a relaxed PDB format (as used in MD simulations) | + | [#a]_ | | | is read by default; Multiple frames (MODEL) | | | | | are supported but require the *multiframe* keyword. | | | | | Module :mod:`MDAnalysis.coordinates.PDB` | +---------------+-----------+-------+------------------------------------------------------+ @@ -265,7 +263,7 @@ - 2015-01-15 Timestep._init_unitcell() method added - 2015-06-11 Reworked Timestep init. Base Timestep now does Vels & Forces - 2015-07-21 Major changes to Timestep and Reader API (release 0.11.0) - +- 2016-04-03 Removed references to Strict Readers for PDBS [jdetle] .. _Issue 49: https://github.com/MDAnalysis/mdanalysis/issues/49 .. _Context Manager: http://docs.python.org/2/reference/datamodel.html#context-managers diff --git a/package/MDAnalysis/coordinates/core.py b/package/MDAnalysis/coordinates/core.py index c741d4757fa..d30c851d070 100644 --- a/package/MDAnalysis/coordinates/core.py +++ b/package/MDAnalysis/coordinates/core.py @@ -42,16 +42,13 @@ from ..lib.mdamath import triclinic_box, triclinic_vectors, box_volume -def get_reader_for(filename, permissive=False, format=None): +def get_reader_for(filename, format=None): """Return the appropriate trajectory reader class for *filename*. Parameters ---------- filename : str filename of the input trajectory or coordinate file - permissive : bool - If set to ``True``, a reader is selected that is more tolerant of the - input (currently only implemented for PDB). [``False``] kwargs Keyword arguments for the selected Reader class. @@ -67,8 +64,6 @@ def get_reader_for(filename, permissive=False, format=None): if format is None: format = util.guess_format(filename) format = format.upper() - if permissive and format == 'PDB': - return _READERS['Permissive_PDB'] try: return _READERS[format] except KeyError: @@ -89,9 +84,7 @@ def reader(filename, **kwargs): This function guesses the file format from the extension of *filename* and it will throw a :exc:`TypeError` if the extension is not recognized. - In most cases, no special keyword arguments are necessary. For PDB readers - it might be useful to set the *permissive* = ``True`` flag to - select a simpler but faster reader. + In most cases, no special keyword arguments are necessary. All other keywords are passed on to the underlying Reader classes; see their documentation for details. @@ -100,9 +93,6 @@ def reader(filename, **kwargs): ---------- filename : str or tuple filename (or tuple of filenames) of the input coordinate file - permissive : bool - If set to ``True``, a reader is selected that is more tolerant of the - input (currently only implemented for PDB). [``False``] kwargs Keyword arguments for the selected Reader class. @@ -113,17 +103,19 @@ def reader(filename, **kwargs): .. SeeAlso:: For trajectory formats: :class:`~DCD.DCDReader`, :class:`~XTC.XTCReader`, :class:`~TRR.TRRReader`, :class:`~XYZ.XYZReader`. For single frame formats: - :class:`~CRD.CRDReader`, :class:`~PDB.PDBReader` and - :class:`~PDB.PrimitivePDBReader`, :class:`~GRO.GROReader`, + :class:`~CRD.CRDReader`, and + :class:`~PDB.PDBReader`, :class:`~GRO.GROReader`, + + .. deprecated:: 0.15.0 + The "permissive" flag is not used anymore (and effectively + defaults to True); it will be completely removed in 0.16.0. """ if isinstance(filename, tuple): Reader = get_reader_for(filename[0], - permissive=kwargs.pop('permissive', False), format=filename[1]) return Reader(filename[0], **kwargs) else: - Reader = get_reader_for(filename, - permissive=kwargs.pop('permissive', False)) + Reader = get_reader_for(filename) return Reader(filename, **kwargs) diff --git a/package/MDAnalysis/coordinates/pdb/extensions.py b/package/MDAnalysis/coordinates/pdb/extensions.py deleted file mode 100644 index 1ebb5184e5f..00000000000 --- a/package/MDAnalysis/coordinates/pdb/extensions.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 -# -# MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) -# -# Released under the GNU Public Licence, v2 or any higher version -# -# Please cite your use of MDAnalysis in published work: -# -# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. -# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. -# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 -# - - -# pdb.extensions -# original file: edPDB.xpdb but only kept content needed for MDAnalysis -""" -Extensions to :mod:`Bio.PDB` --- :mod:`pdb.extensions` -====================================================== - -:Author: Oliver Beckstein -:Year: 2009 -:License: Biopython - -Extension to :mod:`Bio.PDB` to handle large pdb files. - -Partly published on http://biopython.org/wiki/Reading_large_PDB_files -and more code at -http://github.com/orbeckst/GromacsWrapper/tree/master/edPDB/ - -Classes -------- - -.. autoclass:: SloppyStructureBuilder -.. autoclass:: SloppyPDBIO - -Functions ---------- - -.. autofunction:: get_structure -.. autofunction:: write_pdb -""" - -import Bio.PDB -import Bio.PDB.StructureBuilder - -import logging - -logger = logging.getLogger('MDAnalysis.pdb.extensions') - - -class SloppyStructureBuilder(Bio.PDB.StructureBuilder.StructureBuilder): - """Cope with resSeq < 10,000 limitation by just incrementing internally. - - Solves the follwing problem with :class:`Bio.PDB.StructureBuilder.StructureBuilder`: - - Q: What's wrong here?? - Some atoms or residues will be missing in the data structure. - WARNING: Residue (' ', 8954, ' ') redefined at line 74803. - PDBConstructionException: Blank altlocs in duplicate residue SOL (' ', 8954, ' ') at line 74803. - - A: resSeq only goes to 9999 --> goes back to 0 (PDB format is not really good here) - - .. warning:: H and W records are probably not handled yet (don't have examples to test) - """ - - def __init__(self, verbose=False): - Bio.PDB.StructureBuilder.StructureBuilder.__init__(self) - self.max_resseq = -1 - self.verbose = verbose - - def init_residue(self, resname, field, resseq, icode): - """ - Initiate a new Residue object. - - Arguments: - o resname - string, e.g. "ASN" - o field - hetero flag, "W" for waters, "H" for - hetero residues, otherwise blanc. - o resseq - int, sequence identifier - o icode - string, insertion code - """ - if field != " ": - if field == "H": - # The hetero field consists of H_ + the residue name (e.g. H_FUC) - field = "H_" + resname - res_id = (field, resseq, icode) - - if resseq > self.max_resseq: - self.max_resseq = resseq - - if field == " ": - fudged_resseq = False - while self.chain.has_id(res_id) or resseq == 0: - # There already is a residue with the id (field, resseq, icode). - # resseq == 0 catches already wrapped residue numbers which do not - # trigger the has_id() test. - # - # Be sloppy and just increment... - # (This code will not leave gaps in resids... I think) - # - # XXX: shouldn't we also do this for hetero atoms and water?? - self.max_resseq += 1 - resseq = self.max_resseq - res_id = (field, resseq, icode) # use max_resseq! - fudged_resseq = True - - if fudged_resseq and self.verbose: - logger.debug("Residues are wrapping (Residue ('{0!s}', {1:d}, '{2!s}') at line {3:d}).".format(field, resseq, icode, self.line_counter) + - ".... assigning new resid {0:d}.\n".format(self.max_resseq)) - residue = Bio.PDB.Residue.Residue(res_id, resname, self.segid) - self.chain.add(residue) - self.residue = residue - - -class SloppyPDBIO(Bio.PDB.PDBIO): - """PDBIO class that can deal with large pdb files as used in MD simulations. - - - resSeq simply wrap and are printed modulo 10,000. - - atom numbers wrap at 99,999 and are printed modulo 100,000 - """ - # directly copied from PDBIO.py - # (has to be copied because of the package layout it is not externally accessible) - _ATOM_FORMAT_STRING = "%s%5i %-4s%c%3s %c%4i%c %8.3f%8.3f%8.3f%6.2f%6.2f %4s%2s%2s\n" - - def _get_atom_line(self, atom, hetfield, segid, atom_number, resname, - resseq, icode, chain_id, element=" ", charge=" "): - """ - Returns an ATOM PDB string that is guaranteed to fit into the ATOM format. - - - Resid (resseq) is wrapped (modulo 10,000) to fit into %4i (4I) format - - Atom number (atom_number) is wrapped (modulo 100,000) to fit into %4i (4I) format - """ - if hetfield != " ": - record_type = "HETATM" - else: - record_type = "ATOM " - name = atom.get_fullname() - altloc = atom.get_altloc() - x, y, z = atom.get_coord() - bfactor = atom.get_bfactor() - occupancy = atom.get_occupancy() - args = ( - record_type, atom_number % 100000, name, altloc, resname, chain_id, - resseq % 10000, icode, x, y, z, occupancy, bfactor, segid, element, charge) - return self._ATOM_FORMAT_STRING % args - - -sloppyparser = Bio.PDB.PDBParser(PERMISSIVE=True, structure_builder=SloppyStructureBuilder()) - - -def get_structure(pdbfile, pdbid='system'): - """Read the *pdbfilename* and return a Bio.PDB structure. - - This function ignores duplicate atom numbers and resids from the - file and simply increments them. - - .. Note:: - - The call signature is reversed compared to the one of - :meth:`Bio.PDB.PDBParser.get_structure`. - """ - return sloppyparser.get_structure(pdbid, pdbfile) - - -def write_pdb(structure, filename, **kwargs): - """Write Bio.PDB molecule *structure* to *filename*. - - :Arguments: - *structure* - Bio.PDB structure instance - *filename* - pdb file - *selection* - Bio.PDB.Selection - """ - selection = kwargs.pop('selection', None) - - io = SloppyPDBIO() # deals with resSeq > 9999 - io.set_structure(structure) - io.save(filename, select=selection) diff --git a/package/MDAnalysis/core/AtomGroup.py b/package/MDAnalysis/core/AtomGroup.py index 18b053efd4d..ca4ee119620 100644 --- a/package/MDAnalysis/core/AtomGroup.py +++ b/package/MDAnalysis/core/AtomGroup.py @@ -69,13 +69,13 @@ The same is mostly true for :class:`Residue` instances although they are derived from :class:`Atom` instances: all :class:`Atom` objects with the same :attr:`Atom.resid` are bundled into a single :class:`Residue` with -:class:`Residue.id` = *resid*. This means that just changing, say, the residue +:class:`Residue.resid` = *resid*. This means that just changing, say, the residue name with a command such as :: >>> r = u.select_atoms("resid 99").residues[0] >>> print(r) - >>> r.name = "UNK" + >>> r.resname = "UNK" >>> print(r) >>> rnew = u.select_atoms("resid 99").residues[0] @@ -457,7 +457,83 @@ # And the return route _SINGULAR_PROPERTIES = {v: k for k, v in _PLURAL_PROPERTIES.items()} -_FIFTEEN_DEPRECATION = "This will be removed in version 0.15.0" +_SIXTEEN_DEPRECATION = "This will be removed in version 0.16.0" + +def warn_atom_property(func): + warnstring = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args, **kwargs): + if isinstance(self, SegmentGroup): + warnings.warn(warnstring.format('segmentgroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Segment): + warnings.warn(warnstring.format('segment', func.__name__), + DeprecationWarning) + elif isinstance(self, ResidueGroup): + warnings.warn(warnstring.format('residuegroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Residue): + warnings.warn(warnstring.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args, **kwargs) + + return outfunc + +def warn_residue_property(func): + warnstring = "In version 0.16.0, use `{}.residues.{}` instead." + warnstring_sing = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args): + if isinstance(self, SegmentGroup): + warnings.warn(warnstring.format('segmentgroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Segment): + warnings.warn(warnstring.format('segment', func.__name__), + DeprecationWarning) + elif isinstance(self, ResidueGroup): + pass + elif isinstance(self, Residue): + warnings.warn(warnstring_sing.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args) + + return outfunc + +def warn_segment_property(func): + warnstring = "In version 0.16.0, use `{}.segments.{}` instead." + warnstring_sing = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args): + if isinstance(self, SegmentGroup): + pass + elif isinstance(self, Segment): + warnings.warn("In version 0.16.0, Use 'segment.residues.{}' instead.".format(func.__name__), + DeprecationWarning) + pass + elif isinstance(self, ResidueGroup): + warnings.warn(warnstring.format('residuegroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Residue): + warnings.warn(warnstring_sing.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args) + + return outfunc @functools.total_ordering @@ -492,7 +568,7 @@ class Atom(object): """ __slots__ = ( - "index", "id", "name", "type", "resname", "resid", "segid", + "index", "name", "type", "resname", "resid", "segid", "mass", "charge", "residue", "segment", "_universe", "radius", "bfactor", "resnum", "serial", "altLoc") @@ -546,11 +622,21 @@ def __add__(self, other): return AtomGroup([self] + other._atoms) @property + @deprecate(message="{}; use `index` property instead".format(_SIXTEEN_DEPRECATION)) def number(self): """The index of this atom""" return self.index @property + def id(self): + """The atom id of this atom""" + if self.serial is not None: + return self.serial + else: + return self.index + + @property + @deprecate(message="{}; use `position` property instead".format(_SIXTEEN_DEPRECATION)) def pos(self): """coordinates of the atom @@ -569,7 +655,7 @@ def position(self): :Returns: a (3,) shape numpy array """ - return self.universe.coord.positions[self.index] # internal numbering starts at 0 + return self.universe.coord.positions[self.index].copy() @position.setter def position(self, coords): @@ -578,7 +664,7 @@ def position(self, coords): @param coords: a 1x3 numpy array of {x,y,z} coordinates, or optionally a single scalar if you should want to set all coordinates to the same value. """ - self.universe.coord.positions[self.index, :] = coords # internal numbering starts at 0 + self.universe.coord.positions[self.index, :] = coords @property def velocity(self): @@ -594,7 +680,7 @@ def velocity(self): # TODO: Remove error checking here (and all similar below) # and add to Timestep try: - return self.universe.coord.velocities[self.index] + return self.universe.coord.velocities[self.index].copy() except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain velocities") @@ -656,7 +742,7 @@ def force(self): .. versionadded:: 0.9.2 """ try: - return self.universe.coord.forces[self.index] + return self.universe.coord.forces[self.index].copy() except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain forces") @@ -671,6 +757,7 @@ def force(self, vals): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain forces") + @deprecate(message="{}; use `position` property instead".format(_SIXTEEN_DEPRECATION)) def centroid(self): """The centroid of an atom is its position, :attr:`Atom.position`.""" # centroid exists for compatibility with AtomGroup @@ -685,6 +772,7 @@ def universe(self): return self._universe @universe.setter + @deprecate(message="{}; Atoms will not be able to leave their Universes.".format(_SIXTEEN_DEPRECATION)) def universe(self, new): self._universe = new @@ -1042,6 +1130,9 @@ def __getitem__(self, item): raise TypeError("Cannot slice with type: {0}".format(type(item))) def __getattr__(self, name): + if isinstance(self, ResidueGroup): + warnings.warn("In version 0.16.0 this will select " + "residue names, not atom names ", DeprecationWarning) try: return self._get_named_atom(name) except SelectionError: @@ -1143,6 +1234,7 @@ def n_segments(self): return len(self.segments) @property + @warn_atom_property @cached('indices') def indices(self): """Array of all :attr:`Atom.index` in the group. @@ -1160,6 +1252,7 @@ def indices(self): return np.array([atom.index for atom in self._atoms]) @property + @warn_atom_property @cached('masses') def masses(self): """Array of atomic masses (as defined in the topology) @@ -1170,9 +1263,10 @@ def masses(self): return np.array([atom.mass for atom in self._atoms]) @masses.setter + @warn_atom_property def masses(self, new): self._clear_caches('masses') - self.set_masses(new) + self.set("mass", new, conversion=float, cache="masses") def total_mass(self): """Total mass of the selection (masses are taken from the topology or guessed).""" @@ -1181,9 +1275,10 @@ def total_mass(self): totalMass = deprecate(total_mass, old_name='totalMass', new_name='total_mass', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) @property + @warn_atom_property def occupancies(self): """Access occupancies of atoms @@ -1202,6 +1297,7 @@ def occupancies(self): raise NoDataError('Timestep does not contain occupancy') @occupancies.setter + @warn_atom_property def occupancies(self, new): try: self.universe.coord.data['occupancy'][self.indices] = new @@ -1211,6 +1307,7 @@ def occupancies(self, new): self.universe.coord.data['occupancy'][self.indices] = new @property + @warn_atom_property def charges(self): """Array of partial charges of the atoms (as defined in the topology) @@ -1220,8 +1317,9 @@ def charges(self): return np.array([atom.charge for atom in self._atoms]) @charges.setter + @warn_atom_property def charges(self, new): - self.set_charges(new) + self.set("charge", new, conversion=float) def total_charge(self): """Sum of all partial charges (must be defined in topology).""" @@ -1230,9 +1328,10 @@ def total_charge(self): totalCharge = deprecate(total_charge, old_name='totalCharge', new_name='total_charge', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) @property + @warn_atom_property def names(self): """Returns an array of atom names. @@ -1244,10 +1343,12 @@ def names(self): return np.array([a.name for a in self._atoms]) @names.setter + @warn_atom_property def names(self, new): - self.set_names(new) + self.set("name", new, conversion=str) @property + @warn_atom_property def types(self): """Returns an array of atom types. @@ -1258,10 +1359,12 @@ def types(self): return np.array([a.type for a in self._atoms]) @types.setter + @warn_atom_property def types(self, new): - self.set_types(new) + self.set("type", new) @property + @warn_atom_property def radii(self): """Array of atomic radii (as defined in the PQR file) @@ -1271,20 +1374,24 @@ def radii(self): return np.array([atom.radius for atom in self._atoms]) @radii.setter + @warn_atom_property def radii(self, new): - self.set_radii(new) + self.set("radius", new, conversion=float) @property + @warn_atom_property def bfactors(self): """Crystallographic B-factors (from PDB) in A**2. """ return np.array([atom.bfactor for atom in self._atoms]) @bfactors.setter + @warn_atom_property def bfactors(self, new): - self.set_bfactors(new) + self.set("bfactor", new, conversion=float) @property + @warn_atom_property def altLocs(self): """numpy array of the altLocs for all atoms in this group @@ -1293,10 +1400,12 @@ def altLocs(self): return np.array([atom.altLoc for atom in self._atoms]) @altLocs.setter + @warn_atom_property def altLocs(self, new): - self.set_altlocs(new) + self.set("altLoc", new, conversion=str) @property + @deprecate(message="{}; use `ids` property instead".format(_SIXTEEN_DEPRECATION)) def serials(self): """numpy array of the serials for all atoms in this group @@ -1305,8 +1414,31 @@ def serials(self): return np.array([atom.serial for atom in self._atoms]) @serials.setter + @deprecate(message="{}; use `ids` property instead".format(_SIXTEEN_DEPRECATION)) def serials(self, new): - self.set_serials(new) + self.set("serial", new, conversion=int) + + @property + @warn_atom_property + def ids(self): + """Array of the atom ids for all atoms in this group. + + Atom ids are defined by the topology file the universe was built from, + and need not start from 0. They are usually unique to each atom, but + need not be. + + """ + out = np.array([atom.serial for atom in self._atoms]) + + if not any(out): + out = np.array([atom.id for atom in self._atoms]) + + return out + + @ids.setter + @warn_atom_property + def ids(self, new): + self.set("serial", new, conversion=int) @property @cached('residues') @@ -1353,6 +1485,7 @@ def segments(self): return SegmentGroup(segments) @property + @warn_residue_property def resids(self): """Returns an array of residue numbers. @@ -1364,10 +1497,24 @@ def resids(self): return np.array([a.resid for a in self._atoms]) @resids.setter + @warn_residue_property def resids(self, new): - self.set_resids(new) + from MDAnalysis.topology.core import build_residues + + self.set("resid", new, conversion=int) + # Note that this also automagically updates THIS AtomGroup; + # the side effect of build_residues(self.atoms) is to update all Atoms!!!! + self._fill_cache('residues', ResidueGroup(build_residues(self.atoms))) + + # make sure to update the whole universe: the Atoms are shared but + # ResidueGroups are not + if self.atoms is not self.universe.atoms: + self.universe.atoms._fill_cache( + 'residues', + ResidueGroup(build_residues(self.universe.atoms))) @property + @warn_residue_property def resnames(self): """Returns an array of residue names. @@ -1379,10 +1526,12 @@ def resnames(self): return np.array([a.resname for a in self._atoms]) @resnames.setter + @warn_residue_property def resnames(self, new): - self.set_resnames(new) + self.set("resname", new, conversion=str) @property + @warn_residue_property def resnums(self): """Returns an array of canonical residue numbers. @@ -1395,10 +1544,12 @@ def resnums(self): return np.array([a.resnum for a in self._atoms]) @resnums.setter + @warn_residue_property def resnums(self, new): - self.set_resnums(new) + self.set("resnum", new) @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -1410,8 +1561,25 @@ def segids(self): return np.array([a.segid for a in self._atoms]) @segids.setter + @warn_segment_property def segids(self, new): - self.set_segids(new) + from MDAnalysis.topology.core import build_segments + + self.set("segid", new, conversion=str) + + # also updates convenience handles for segments in universe + segments = self.universe._build_segments() + + # Note that this also automagically updates THIS AtomGroup; + # the side effect of build_residues(self.atoms) is to update all Atoms!!!! + self._fill_cache('segments', SegmentGroup(segments)) + + # make sure to update the whole universe: the Atoms are shared but + # ResidueGroups are not + if self.atoms is not self.universe.atoms: + self.universe.atoms._fill_cache( + 'segments', + SegmentGroup(segments)) def sequence(self, **kwargs): """Returns the amino acid sequence. @@ -1518,6 +1686,7 @@ def fragments(self): """ return tuple(set(a.fragment for a in self._atoms)) + @warn_atom_property def guess_bonds(self, vdwradii=None): """Guess all the bonds that exist within this AtomGroup and add to Universe. @@ -1564,6 +1733,7 @@ def guess_bonds(self, vdwradii=None): self._clear_caches('dihedrals') @property + @warn_atom_property @cached('bonds') def bonds(self): """All the bonds in this AtomGroup @@ -1581,6 +1751,7 @@ def bonds(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('angles') def angles(self): """All the angles in this AtomGroup @@ -1598,6 +1769,7 @@ def angles(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('dihedrals') def dihedrals(self): """All the dihedrals in this AtomGroup @@ -1615,6 +1787,7 @@ def dihedrals(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('impropers') def impropers(self): """All the improper dihedrals in this AtomGroup @@ -1706,6 +1879,7 @@ def set_occupancies(self, occupancies): """ self.occupancies = occupancies + @deprecate(message="{}; use `names` property instead".format(_SIXTEEN_DEPRECATION)) def set_names(self, name): """Set the atom names to string for *all atoms* in the AtomGroup. @@ -1725,14 +1899,15 @@ def set_names(self, name): set_name = deprecate(set_names, old_name='set_name', new_name='set_names', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resids` property instead".format(_SIXTEEN_DEPRECATION)) def set_resids(self, resid): """Set the resids to integer *resid* for **all atoms** in the :class:`AtomGroup`. If *resid* is a sequence of the same length as the :class:`AtomGroup` then each :attr:`Atom.resid` is set to the corresponding value together - with the :attr:`Residue.id` of the residue the atom belongs to. If + with the :attr:`Residue.resid` of the residue the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -1772,8 +1947,9 @@ def set_resids(self, resid): set_resid = deprecate(set_resids, old_name='set_resid', new_name='set_resids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnums` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnums(self, resnum): """Set the resnums to *resnum* for **all atoms** in the :class:`AtomGroup`. @@ -1803,14 +1979,15 @@ def set_resnums(self, resnum): set_resnum = deprecate(set_resnums, old_name='set_resnum', new_name='set_resnums', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnames` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnames(self, resname): """Set the resnames to string *resname* for **all atoms** in the :class:`AtomGroup`. If *resname* is a sequence of the same length as the :class:`AtomGroup` then each :attr:`Atom.resname` is set to the corresponding value together - with the :attr:`Residue.name` of the residue the atom belongs to. If + with the :attr:`Residue.resname` of the residue the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -1827,14 +2004,15 @@ def set_resnames(self, resname): set_resname = deprecate(set_resnames, old_name='set_resname', new_name='set_resnames', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `segids` property instead".format(_SIXTEEN_DEPRECATION)) def set_segids(self, segid): """Set the segids to *segid* for all atoms in the :class:`AtomGroup`. If *segid* is a sequence of the same length as the :class:`AtomGroup` then each :attr:`Atom.segid` is set to the corresponding value together - with the :attr:`Segment.id` of the residue the atom belongs to. If + with the :attr:`Segment.segid` of the residue the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -1872,8 +2050,9 @@ def set_segids(self, segid): set_segid = deprecate(set_segids, old_name='set_segid', new_name='set_segids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `masses` property instead".format(_SIXTEEN_DEPRECATION)) def set_masses(self, mass): """Set the atom masses to float *mass* for **all atoms** in the AtomGroup. @@ -1893,8 +2072,9 @@ def set_masses(self, mass): set_mass = deprecate(set_masses, old_name='set_mass', new_name='set_masses', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `types` property instead".format(_SIXTEEN_DEPRECATION)) def set_types(self, atype): """Set the atom types to *atype* for **all atoms** in the AtomGroup. @@ -1914,8 +2094,9 @@ def set_types(self, atype): set_type = deprecate(set_types, old_name='set_type', new_name='set_types', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `charges` property instead".format(_SIXTEEN_DEPRECATION)) def set_charges(self, charge): """Set the partial charges to float *charge* for **all atoms** in the AtomGroup. @@ -1935,8 +2116,9 @@ def set_charges(self, charge): set_charge = deprecate(set_charges, old_name='set_charge', new_name='set_charges', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `radii` property instead".format(_SIXTEEN_DEPRECATION)) def set_radii(self, radius): """Set the atom radii to float *radius* for **all atoms** in the AtomGroup. @@ -1956,8 +2138,9 @@ def set_radii(self, radius): set_radius = deprecate(set_radii, old_name='set_radius', new_name='set_radii', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `bfactors` property instead".format(_SIXTEEN_DEPRECATION)) def set_bfactors(self, bfactor): """Set the atom bfactors to float *bfactor* for **all atoms** in the AtomGroup. @@ -1977,8 +2160,9 @@ def set_bfactors(self, bfactor): set_bfactor = deprecate(set_bfactors, old_name='set_bfactor', new_name='set_bfactors', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `altLocs` property instead".format(_SIXTEEN_DEPRECATION)) def set_altLocs(self, altLoc): """Set the altLocs to *altLoc for **all atoms** in the AtomGroup. @@ -1994,8 +2178,9 @@ def set_altLocs(self, altLoc): set_altLoc = deprecate(set_altLocs, old_name='set_altLoc', new_name='set_altLocs', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `serials` property instead".format(_SIXTEEN_DEPRECATION)) def set_serials(self, serial): """Set the serials to *serial* for **all atoms** in the AtomGroup. @@ -2011,7 +2196,7 @@ def set_serials(self, serial): set_serial = deprecate(set_serials, old_name='set_serial', new_name='set_serials', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def center_of_geometry(self, **kwargs): """Center of geometry (also known as centroid) of the selection. @@ -2035,7 +2220,7 @@ def center_of_geometry(self, **kwargs): centerOfGeometry = deprecate(center_of_geometry, old_name='centerOfGeometry', new_name='center_of_geometry', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) centroid = center_of_geometry @@ -2062,7 +2247,7 @@ def center_of_mass(self, **kwargs): centerOfMass = deprecate(center_of_mass, old_name='centerOfMass', new_name='center_of_mass', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def radius_of_gyration(self, **kwargs): """Radius of gyration. @@ -2089,7 +2274,7 @@ def radius_of_gyration(self, **kwargs): radiusOfGyration = deprecate(radius_of_gyration, old_name='radiusOfGyration', new_name='radius_of_gyration', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def shape_parameter(self, **kwargs): """Shape parameter. @@ -2125,7 +2310,7 @@ def shape_parameter(self, **kwargs): shapeParameter = deprecate(shape_parameter, old_name='shapeParameter', new_name='shape_parameter', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def asphericity(self, **kwargs): """Asphericity. @@ -2208,7 +2393,7 @@ def moment_of_inertia(self, **kwargs): momentOfInertia = deprecate(moment_of_inertia, old_name='momentOfInertia', new_name='moment_of_inertia', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def bbox(self, **kwargs): """Return the bounding box of the selection. @@ -2235,7 +2420,7 @@ def bbox(self, **kwargs): if pbc: x = self.pack_into_box(inplace=False) else: - x = self.coordinates() + x = self.positions return np.array([x.min(axis=0), x.max(axis=0)]) def bsphere(self, **kwargs): @@ -2261,7 +2446,7 @@ def bsphere(self, **kwargs): x = self.pack_into_box(inplace=False) centroid = self.center_of_geometry(pbc=True) else: - x = self.coordinates() + x = self.positions centroid = self.center_of_geometry(pbc=False) R = np.sqrt(np.max(np.sum(np.square(x - centroid), axis=1))) return R, centroid @@ -2366,8 +2551,9 @@ def principal_axes(self, **kwargs): principalAxes = deprecate(principal_axes, old_name='principalAxes', new_name='principal_axes', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) def get_positions(self, ts=None, copy=False, dtype=np.float32): """Get a numpy array of the coordinates. @@ -2404,17 +2590,20 @@ def get_positions(self, ts=None, copy=False, dtype=np.float32): ts = self.universe.trajectory.ts return np.array(ts.positions[self.indices], copy=copy, dtype=dtype) - coordinates = get_positions - """Np array of the coordinates. + @deprecate(message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) + def coordinates(self): + """Np array of the coordinates. - .. SeeAlso:: :attr:`~AtomGroup.positions` and :meth:`~AtomGroup.get_positions` + .. SeeAlso:: :attr:`~AtomGroup.positions` and :meth:`~AtomGroup.get_positions` - .. deprecated:: 0.7.6 - In new scripts use :meth:`AtomGroup.get_positions` preferrably. - """ - # coordinates() should NOT be removed as it has been used in many scripts, - # MDAnalysis itself, and in the paper + .. deprecated:: 0.7.6 + In new scripts use :meth:`AtomGroup.get_positions` preferrably. + """ + return self.positions + # coordinates() should NOT be removed as it has been used in many scripts, + # MDAnalysis itself, and in the paper + @deprecate(message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) def set_positions(self, coords, ts=None): """Set the positions for all atoms in the group. @@ -2444,20 +2633,24 @@ def set_positions(self, coords, ts=None): ts = self.universe.trajectory.ts ts.positions[self.indices, :] = coords - positions = property(get_positions, set_positions, - doc=""" - Coordinates of the atoms in the AtomGroup. + @property + def positions(self): + """Coordinates of the atoms in the AtomGroup. - The positions can be changed by assigning an array of the appropriate - shape, i.e. either Nx3 to assign individual coordinates or 3, to assign - the *same* coordinate to all atoms (e.g. ``ag.positions = array([0,0,0])`` - will move all particles to the origin). + The positions can be changed by assigning an array of the appropriate + shape, i.e. either Nx3 to assign individual coordinates or 3, to assign + the *same* coordinate to all atoms (e.g. ``ag.positions = array([0,0,0])`` + will move all particles to the origin). - For more control use the :meth:`~AtomGroup.get_positions` and - :meth:`~AtomGroup.set_positions` methods. + .. versionadded:: 0.7.6 + """ + return self.universe.trajectory.ts.positions[self.indices, :] - .. versionadded:: 0.7.6""") + @positions.setter + def positions(self, coords): + self.universe.trajectory.ts.positions[self.indices, :] = coords + @deprecate(message="{}; use `velocities` property instead".format(_SIXTEEN_DEPRECATION)) def get_velocities(self, ts=None, copy=False, dtype=np.float32): """numpy array of the velocities. @@ -2477,6 +2670,7 @@ def get_velocities(self, ts=None, copy=False, dtype=np.float32): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain velocities") + @deprecate(message="{}; use `velocities` property instead".format(_SIXTEEN_DEPRECATION)) def set_velocities(self, v, ts=None): """Assign the velocities *v* to the timestep. @@ -2496,8 +2690,9 @@ def set_velocities(self, v, ts=None): except AttributeError: raise NoDataError("Timestep does not contain velocities") - velocities = property(get_velocities, set_velocities, doc="""\ - numpy array of the velocities of the atoms in the group. + @property + def velocities(self): + """numpy array of the velocities of the atoms in the group. If the trajectory does not contain velocity information then a :exc:`~MDAnalysis.NoDataError` is raised. @@ -2508,8 +2703,20 @@ def set_velocities(self, v, ts=None): and :meth:`set_velocities`. .. versionchanged:: 0.8 Became an attribute. - """) + """ + try: + return self.universe.trajectory.ts.velocities[self.indices] + except (AttributeError, NoDataError): + raise NoDataError("Timestep does not contain velocities") + @velocities.setter + def velocities(self, new): + try: + self.universe.trajectory.ts.velocities[self.indices] = new + except AttributeError: + raise NoDataError("Timestep does not contain velocities") + + @deprecate(message="{}; use `forces` property instead".format(_SIXTEEN_DEPRECATION)) def get_forces(self, ts=None, copy=False, dtype=np.float32): """ Get a numpy array of the atomic forces (if available). @@ -2548,6 +2755,7 @@ def get_forces(self, ts=None, copy=False, dtype=np.float32): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain forces") + @deprecate(message="{}; use `forces` property instead".format(_SIXTEEN_DEPRECATION)) def set_forces(self, forces, ts=None): """Set the forces for all atoms in the group. @@ -2580,20 +2788,30 @@ def set_forces(self, forces, ts=None): except AttributeError: raise NoDataError("Timestep does not contain forces") - forces = property(get_forces, set_forces, - doc=""" - Forces on the atoms in the AtomGroup. + @property + def forces(self): + """Forces on the atoms in the AtomGroup. - The forces can be changed by assigning an array of the appropriate - shape, i.e. either Nx3 to assign individual force or 3, to assign - the *same* force to all atoms (e.g. ``ag.forces = array([0,0,0])`` - will set all forces to (0.,0.,0.)). + The forces can be changed by assigning an array of the appropriate + shape, i.e. either Nx3 to assign individual force or 3, to assign + the *same* force to all atoms (e.g. ``ag.forces = array([0,0,0])`` + will set all forces to (0.,0.,0.)). - For more control use the :meth:`~AtomGroup.get_forces` and - :meth:`~AtomGroup.set_forces` methods. + For more control use the :meth:`~AtomGroup.get_forces` and + :meth:`~AtomGroup.set_forces` methods. - .. versionadded:: 0.7.7""") + .. versionadded:: 0.7.7""" + try: + return self.universe.trajectory.ts.forces[self.indices] + except (AttributeError, NoDataError): + raise NoDataError("Timestep does not contain forces") + @forces.setter + def forces(self, new): + try: + self.universe.trajectory.ts.forces[self.indices] = new + except (AttributeError, NoDataError): + raise NoDataError("Timestep does not contain forces") def transform(self, M): r"""Apply homogenous transformation matrix *M* to the coordinates. @@ -2643,6 +2861,7 @@ def translate(self, t): sel1, sel2 = t x1, x2 = sel1.centroid(), sel2.centroid() vector = x2 - x1 + except (ValueError, AttributeError): vector = np.asarray(t) # changes the coordinates (in place) @@ -2708,6 +2927,7 @@ def rotateby(self, angle, axis, point=None): n = v / np.linalg.norm(v) if point is None: point = x1 + except (ValueError, AttributeError): n = np.asarray(axis) if point is None: @@ -2748,7 +2968,7 @@ def align_principal_axis(self, axis, vector): align_principalAxis = deprecate(align_principal_axis, old_name='align_principalAxis', new_name='align_principal_axis', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def pack_into_box(self, box=None, inplace=True): r"""Shift all atoms in this group to be within the primary unit cell. @@ -2807,7 +3027,7 @@ def pack_into_box(self, box=None, inplace=True): packIntoBox = deprecate(pack_into_box, old_name='packIntoBox', new_name='pack_into_box', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def wrap(self, compound="atoms", center="com", box=None): """Shift the contents of this AtomGroup back into the unit cell. @@ -3096,7 +3316,7 @@ def select_atoms(self, selstr, *othersel, **selgroups): selectAtoms = deprecate(select_atoms, old_name='selectAtoms', new_name='select_atoms', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def split(self, level): """Split atomgroup into a list of atomgroups by *level*. @@ -3113,6 +3333,12 @@ def split(self, level): if level == "atom": return [AtomGroup([a]) for a in self] + if level in ('resid', 'segid'): + warnings.warn("'resid' or 'segid' are no longer allowed levels " + "in version 0.16.0; instead give " + "'residue' or 'segment', respectively.", + DeprecationWarning) + # more complicated groupings try: # use own list comprehension to avoid sorting/compression by eg self.resids @@ -3209,6 +3435,7 @@ def write(self, filename=None, format="PDB", writer.close() # TODO: This is _almost_ the same code as write() --- should unify! + @deprecate(message="{}; use `write` method instead".format(_SIXTEEN_DEPRECATION)) def write_selection(self, filename=None, format="vmd", filenamefmt="%(trjname)s_%(frame)d", **kwargs): """Write AtomGroup selection to a file to be used in another programme. @@ -3297,9 +3524,9 @@ class Residue(AtomGroup): - ``r['name']`` or ``r[id]`` - returns the atom corresponding to that name :Data: - :attr:`Residue.name` + :attr:`Residue.resname` Three letter residue name. - :attr:`Residue.id` + :attr:`Residue.resid` Numeric (integer) resid, taken from the topology. :attr:`Residue.resnum` Numeric canonical residue id (e.g. as used in the PDB structure). @@ -3318,15 +3545,14 @@ class Residue(AtomGroup): def __init__(self, name, id, atoms, resnum=None): super(Residue, self).__init__(atoms) - self.name = name - self.id = id + self._resname = name + self._resid = id if resnum is not None: - self.resnum = resnum + self._resnum = resnum else: - self.resnum = self.id # TODO: get resnum from topologies that support it + self._resnum = self._resid # TODO: get resnum from topologies that support it self.segment = None - for i, a in enumerate(atoms): - a.id = i + for a in atoms: a.resnum = self.resnum a.residue = self @@ -3337,6 +3563,50 @@ def __init__(self, name, id, atoms, resnum=None): ##if not Residue._cache.has_key(name): ## Residue._cache[name] = dict([(a.name, i) for i, a in enumerate(self._atoms)]) + @property + @deprecate(message="{}; use `resname` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self): + return self._resname + + @name.setter + @deprecate(message="{}; use `resname` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self, value): + self._resname = value + + @property + def resname(self): + return self._resname + + @resname.setter + def resname(self, value): + self._resname = value + + @property + @deprecate(message="{}; use `resid` property instead".format(_SIXTEEN_DEPRECATION)) + def id(self): + return self._resid + + @id.setter + @deprecate(message="{}; use `resid` property instead".format(_SIXTEEN_DEPRECATION)) + def id(self, value): + self._resid = value + + @property + def resid(self): + return self._resid + + @resid.setter + def resid(self, value): + self._resid = value + + @property + def resnum(self): + return self._resnum + + @resnum.setter + def resnum(self, value): + self._resnum = value + def phi_selection(self): """AtomGroup corresponding to the phi protein backbone dihedral C'-N-CA-C. @@ -3345,7 +3615,7 @@ def phi_selection(self): method returns ``None``. """ sel = self.universe.select_atoms( - 'segid {0!s} and resid {1:d} and name C'.format(self.segment.id, self.id - 1)) + \ + 'segid {0!s} and resid {1:d} and name C'.format(self.segment.segid, self.resid - 1)) + \ self['N'] + self['CA'] + self['C'] if len(sel) == 4: # select_atoms doesnt raise errors if nothing found, so check size return sel @@ -3361,7 +3631,7 @@ def psi_selection(self): """ sel = self['N'] + self['CA'] + self['C'] + \ self.universe.select_atoms( - 'segid {0!s} and resid {1:d} and name N'.format(self.segment.id, self.id + 1)) + 'segid {0!s} and resid {1:d} and name N'.format(self.segment.segid, self.resid + 1)) if len(sel) == 4: return sel else: @@ -3379,8 +3649,8 @@ def omega_selection(self): method returns ``None``. """ - nextres = self.id + 1 - segid = self.segment.id + nextres = self.resid + 1 + segid = self.segment.segid sel = self['CA'] + self['C'] + \ self.universe.select_atoms( 'segid {0!s} and resid {1:d} and name N'.format(segid, nextres), @@ -3405,7 +3675,7 @@ def chi1_selection(self): def __repr__(self): return "".format( - name=self.name, id=self.id) + name=self.resname, id=self.resid) class ResidueGroup(AtomGroup): @@ -3472,6 +3742,7 @@ def _set_residues(self, name, value, **kwargs): set = _set_residues @property + @warn_residue_property def resids(self): """Returns an array of residue numbers. @@ -3480,9 +3751,10 @@ def resids(self): .. versionchanged:: 0.11.0 Now a property and returns array of length `len(self)` """ - return np.array([r.id for r in self.residues]) + return np.array([r.resid for r in self.residues]) @property + @warn_residue_property def resnames(self): """Returns an array of residue names. @@ -3491,9 +3763,10 @@ def resnames(self): .. versionchanged:: 0.11.0 Now a property and returns array of length `len(self)` """ - return np.array([r.name for r in self.residues]) + return np.array([r.resname for r in self.residues]) @property + @warn_residue_property def resnums(self): """Returns an array of canonical residue numbers. @@ -3506,6 +3779,7 @@ def resnums(self): return np.array([r.resnum for r in self.residues]) @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -3520,13 +3794,14 @@ def segids(self): # a bit of a hack to use just return np.array([r[0].segid for r in self.residues]) + @deprecate(message="{}; use `resids` property instead".format(_SIXTEEN_DEPRECATION)) def set_resids(self, resid): """Set the resids to integer *resid* for **all residues** in the :class:`ResidueGroup`. If *resid* is a sequence of the same length as the :class:`ResidueGroup` then each :attr:`Atom.resid` is set to the corresponding value together - with the :attr:`Residue.id` of the residue the atom belongs to. If + with the :attr:`Residue.resid` of the residue the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -3554,8 +3829,9 @@ def set_resids(self, resid): set_resid = deprecate(set_resids, old_name='set_resid', new_name='set_resids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnums` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnums(self, resnum): """Set the resnums to *resnum* for **all residues** in the :class:`ResidueGroup`. @@ -3585,15 +3861,16 @@ def set_resnums(self, resnum): set_resnum = deprecate(set_resnums, old_name='set_resnum', new_name='set_resnums', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnames` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnames(self, resname): """Set the resnames to string *resname* for **all residues** in the :class:`ResidueGroup`. If *resname* is a sequence of the same length as the :class:`ResidueGroup` then each :attr:`Atom.resname` is set to the corresponding value together - with the :attr:`Residue.name` of the residue the atom belongs to. If + with the :attr:`Residue.resname` of the residue the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -3610,7 +3887,7 @@ def set_resnames(self, resname): set_resname = deprecate(set_resnames, old_name='set_resname', new_name='set_resnames', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) # All other AtomGroup.set_xxx() methods should just work as # ResidueGroup.set_xxx() because we overrode self.set(); the ones above @@ -3646,14 +3923,14 @@ class Segment(ResidueGroup): , , , , , ]> - :Data: :attr:`Segment.name` is the segid from the topology or the + :Data: :attr:`Segment.segid` is the segid from the topology or the chain identifier when loaded from a PDB """ def __init__(self, name, residues): """Initialize a Segment with segid *name* from a list of :class:`Residue` instances.""" super(Segment, self).__init__(residues) - self.name = name + self._segid = name for res in self.residues: res.segment = self for atom in res: @@ -3661,13 +3938,34 @@ def __init__(self, name, residues): self._cls = ResidueGroup @property + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) def id(self): """Segment id (alias for :attr:`Segment.name`)""" - return self.name + return self._segid @id.setter + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) def id(self, x): - self.name = x + self._segid = x + + @property + def segid(self): + """Segment id (alias for :attr:`Segment.name`)""" + return self._segid + + @segid.setter + def segid(self, x): + self._segid = x + + @property + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self): + return self._segid + + @name.setter + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self, x): + self._segid = x def __getattr__(self, attr): if attr[0] == 'r': @@ -3677,7 +3975,7 @@ def __getattr__(self, attr): # There can be multiple residues with the same name r = [] for res in self.residues: - if (res.name == attr): + if (res.resname == attr): r.append(res) if (len(r) == 0): return super(Segment, self).__getattr__(attr) @@ -3687,7 +3985,7 @@ def __getattr__(self, attr): def __repr__(self): return "".format( - name=self.name) + name=self.segid) class SegmentGroup(ResidueGroup): @@ -3745,6 +4043,7 @@ def _set_segments(self, name, value, **kwargs): set = _set_segments @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -3753,14 +4052,15 @@ def segids(self): .. versionchanged:: 0.11.0 Now a property and returns array of length `len(self)` """ - return np.array([s.name for s in self.segments]) + return np.array([s.segid for s in self.segments]) + @deprecate(message="{}; use `segids` property instead".format(_SIXTEEN_DEPRECATION)) def set_segids(self, segid): """Set the segids to *segid* for all atoms in the :class:`SegmentGroup`. If *segid* is a sequence of the same length as the :class:`SegmentGroup` then each :attr:`Atom.segid` is set to the corresponding value together - with the :attr:`Segment.id` of the segment the atom belongs to. If + with the :attr:`Segment.segid` of the segment the atom belongs to. If *value* is neither of length 1 (or a scalar) nor of the length of the :class:`AtomGroup` then a :exc:`ValueError` is raised. @@ -3783,12 +4083,12 @@ def set_segids(self, segid): set_segid = deprecate(set_segids, old_name='set_segid', new_name='set_segids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def __getattr__(self, attr): if attr.startswith('s') and attr[1].isdigit(): attr = attr[1:] # sNxxx only used for python, the name is stored without s-prefix - seglist = [segment for segment in self.segments if segment.name == attr] + seglist = [segment for segment in self.segments if segment.segid == attr] if len(seglist) == 0: return super(SegmentGroup, self).__getattr__(attr) if len(seglist) > 1: @@ -3881,6 +4181,14 @@ class Universe(object): :attr:`anchor_name` were added to support the pickling/unpickling of :class:`AtomGroup`. Deprecated :meth:`selectAtoms` in favour of :meth:`select_atoms`. + + .. versionchanged:: 0.15.0 + Can read multi-frame PDB files with the :class: + `~MDAnalysis.coordinates.PDB.PDBReader`. + Deprecated :class:`~MDAnalysis.coordinates.PDB.PrimitivePDBReader` in + favor of :class:`~MDAnalysis.coordinates.PDB.PDBReader`. + + """ def __init__(self, *args, **kwargs): @@ -3894,11 +4202,6 @@ def __init__(self, *args, **kwargs): MDAnalysis. A "structure" file (PSF, PDB or GRO, in the sense of a topology) is always required. - *permissive* - currently only relevant for PDB files: Set to ``True`` in order to ignore most errors - and read typical MD simulation PDB files; set to ``False`` to read with the Bio.PDB reader, - which can be useful for real Protein Databank PDB files. ``None`` selects the - MDAnalysis default (which is set in :class:`MDAnalysis.core.flags`) [``None``] *topology_format* provide the file format of the topology file; ``None`` guesses it from the file extension [``None``] @@ -3960,12 +4263,19 @@ def __init__(self, *args, **kwargs): .. versionchanged:: 0.11.0 Added the *is_anchor* and *anchor_name* keywords for finer behavior control when unpickling instances of :class:`MDAnalysis.core.AtomGroup.AtomGroup`. + .. deprecated:: 0.15.0 + The "permissive" flag is not used anymore (and effectively defaults + to True); it will be completely removed in 0.16.0. """ from ..topology.core import get_parser_for from ..topology.base import TopologyReader from ..coordinates.base import ProtoReader + # hold on to copy of kwargs; used by external libraries that + # reinitialize universes + self._kwargs = copy.deepcopy(kwargs) + # managed attribute holding Reader self._trajectory = None @@ -3979,7 +4289,7 @@ def __init__(self, *args, **kwargs): # Cached stuff is handled using util.cached decorator self._cache = dict() - if len(args) == 0: + if not args: # create an empty universe self._topology = dict() self.atoms = AtomGroup([]) @@ -4023,7 +4333,6 @@ def __init__(self, *args, **kwargs): perm = kwargs.get('permissive', MDAnalysis.core.flags['permissive_pdb_reader']) parser = get_parser_for(self.filename, - permissive=perm, format=topology_format) try: with parser(self.filename, universe=self) as p: @@ -4109,10 +4418,10 @@ def _build_segments(self): segments = build_segments(self.atoms) for seg in segments: - if seg.id[0].isdigit(): - name = 's' + seg.id + if seg.segid[0].isdigit(): + name = 's' + seg.segid else: - name = seg.id + name = seg.segid self.__dict__[name] = seg return segments @@ -4133,11 +4442,11 @@ def _init_top(self, cat, Top): guessed = self._topology.get('guessed_' + cat, set()) TopSet = top.TopologyGroup.from_indices(defined, self.atoms, - bondclass=Top, guessed=False, - remove_duplicates=True) + bondclass=Top, guessed=False, + remove_duplicates=True) TopSet += top.TopologyGroup.from_indices(guessed, self.atoms, - bondclass=Top, guessed=True, - remove_duplicates=True) + bondclass=Top, guessed=True, + remove_duplicates=True) return TopSet @@ -4262,6 +4571,13 @@ def universe(self): # which might be undesirable if it has a __del__ method. It is also cleaner than a weakref. return self + @property + def kwargs(self): + """Keyword arguments used to initialize this universe (read-only). + + """ + return copy.deepcopy(self._kwargs) + @property @cached('fragments') def fragments(self): @@ -4506,11 +4822,6 @@ def load_new(self, filename, **kwargs): *filename* the coordinate file (single frame or trajectory) *or* a list of filenames, which are read one after another. - *permissive* - currently only relevant for PDB files: Set to ``True`` in order to ignore most errors - and read typical MD simulation PDB files; set to ``False`` to read with the Bio.PDB reader, - which can be useful for real Protein Databank PDB files. ``None`` selects the - MDAnalysis default (which is set in :class:`MDAnalysis.core.flags`) [``None``] *format* provide the file format of the coordinate or trajectory file; ``None`` guesses it from the file extension. Note that this @@ -4532,6 +4843,10 @@ def load_new(self, filename, **kwargs): not read by the :class:`~MDAnalysis.coordinates.base.ChainReader` but directly by its specialized file format reader, which typically has more features than the :class:`~MDAnalysis.coordinates.base.ChainReader`. + + .. deprecated:: 0.15.0 + The "permissive" flag is not used anymore (and effectively + defaults to True); it will be completely removed in 0.16.0. """ if filename is None: return @@ -4546,7 +4861,6 @@ def load_new(self, filename, **kwargs): logger.debug("Universe.load_new(): loading {0}...".format(filename)) reader_format = kwargs.pop('format', None) - perm = kwargs.get('permissive', MDAnalysis.core.flags['permissive_pdb_reader']) reader = None # Check if we were passed a Reader to use @@ -4564,7 +4878,6 @@ def load_new(self, filename, **kwargs): reader_format='CHAIN' try: reader = get_reader_for(filename, - permissive=perm, format=reader_format) except TypeError as err: raise TypeError( @@ -4611,7 +4924,7 @@ def select_atoms(self, sel, *othersel, **selgroups): selectAtoms = deprecate(select_atoms, old_name='selectAtoms', new_name='select_atoms', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def __repr__(self): return "".format( @@ -4716,6 +5029,7 @@ def _matches_unpickling(self, anchor_name, n_atoms, fname, trajname): return False +@deprecate(message=_SIXTEEN_DEPRECATION) def as_Universe(*args, **kwargs): """Return a universe from the input arguments. @@ -4741,7 +5055,7 @@ def as_Universe(*args, **kwargs): asUniverse = deprecate(as_Universe, old_name='asUniverse', new_name='as_Universe', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def Merge(*args): """Return a :class:`Universe` from two or more :class:`AtomGroup` instances. @@ -4795,7 +5109,7 @@ def Merge(*args): if len(a) == 0: raise ValueError("cannot merge empty AtomGroup") - coords = np.vstack([a.coordinates() for a in args]) + coords = np.vstack([a.positions for a in args]) trajectory = MDAnalysis.coordinates.base.Reader(None) ts = MDAnalysis.coordinates.base.Timestep.from_coordinates(coords) setattr(trajectory, "ts", ts) diff --git a/package/MDAnalysis/core/__init__.py b/package/MDAnalysis/core/__init__.py index 50c3d147183..c053ccdb10c 100644 --- a/package/MDAnalysis/core/__init__.py +++ b/package/MDAnalysis/core/__init__.py @@ -1,5 +1,5 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org # Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein @@ -386,26 +386,7 @@ def __doc__(self): 'Bio.PDB': False, 'biopython': False, False: False, }, """ - Select the default reader for PDB Brookhaven databank files. - - >>> flags['%(name)s'] = value - - The Bio.PDB reader (value=``False``) can deal with 'proper' PDB - files from the Protein Databank that contain special PDB features - such as insertion codes and it can auto-correct some common - mistakes; see :mod:`Bio.PDB` for details. However, Bio.PDB has been - known to read some simulation system PDB files **incompletely**; a - sure sign of problems is a warning that an atom has appeared twice - in a residue. - - Therefore, the default for the PDB reader is ``True``, which - selects the "primitive" (or "permissive") reader - :class:`MDAnalysis.coordinates.PDB.PrimitivePDBReader`, which - essentially just reads ATOM and HETATM lines and puts atoms in a - list. - - One can manually switch between the two by providing the *permissive* - keyword to :class:`MDAnalysis.Universe`. + This flag is deprecated and will be removed in 0.16.0. """ ), _Flag( diff --git a/package/MDAnalysis/core/topologyobjects.py b/package/MDAnalysis/core/topologyobjects.py index 19b8b642e32..bedc73fcef7 100644 --- a/package/MDAnalysis/core/topologyobjects.py +++ b/package/MDAnalysis/core/topologyobjects.py @@ -1,5 +1,5 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org # Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein @@ -230,8 +230,8 @@ def angle(self): .. versionadded:: 0.9.0 """ - a = self[0].pos - self[1].pos - b = self[2].pos - self[1].pos + a = self[0].position - self[1].position + b = self[2].position - self[1].position return np.rad2deg( np.arccos(np.dot(a, b) / (norm(a) * norm(b)))) diff --git a/package/MDAnalysis/lib/NeighborSearch.py b/package/MDAnalysis/lib/NeighborSearch.py index f2e6ff6779e..5e3fdf82fab 100644 --- a/package/MDAnalysis/lib/NeighborSearch.py +++ b/package/MDAnalysis/lib/NeighborSearch.py @@ -27,7 +27,6 @@ from MDAnalysis.core.AtomGroup import AtomGroup - class AtomNeighborSearch(object): """This class can be used to find all atoms/residues/segements within the radius of a given query position. @@ -39,33 +38,33 @@ class AtomNeighborSearch(object): def __init__(self, atom_group, bucket_size=10): """ - :Arguments: - *atom_list* - list of atoms (:class: `~MDAnalysis.core.AtomGroup.AtomGroup`) - *bucket_size* + + Parameters + ---------- + atom_list : AtomGroup + list of atoms + bucket_size : int Number of entries in leafs of the KDTree. If you suffer poor performance you can play around with this number. Increasing the `bucket_size` will speed up the construction of the KDTree but slow down the search. """ self.atom_group = atom_group - if not hasattr(atom_group, 'coordinates'): - raise TypeError('atom_group must have a coordinates() method' - '(eq a AtomGroup from a selection)') self.kdtree = KDTree(dim=3, bucket_size=bucket_size) - self.kdtree.set_coords(atom_group.coordinates()) + self.kdtree.set_coords(atom_group.positions) def search(self, atoms, radius, level='A'): """ Return all atoms/residues/segments that are within *radius* of the atoms in *atoms*. - :Arguments: - *atoms* - list of atoms (:class: `~MDAnalysis.core.AtomGroup.AtomGroup`) - *radius* - float. Radius for search in Angstrom. - *level* (optional) + Parameters + ---------- + atoms : AtomGroup + list of atoms + radius : float + Radius for search in Angstrom. + level : str char (A, R, S). Return atoms(A), residues(R) or segments(S) within *radius* of *atoms*. """ @@ -77,13 +76,14 @@ def search(self, atoms, radius, level='A'): return self._index2level(unique_idx, level) def _index2level(self, indices, level): - """ Convert list of atom_indices in a AtomGroup to either the - Atoms or segments/residues containing these atoms. + """Convert list of atom_indices in a AtomGroup to either the + Atoms or segments/residues containing these atoms. - :Arguments: - *indices* + Parameters + ---------- + indices list of atom indices - *level* + level : str char (A, R, S). Return atoms(A), residues(R) or segments(S) within *radius* of *atoms*. """ diff --git a/package/MDAnalysis/topology/ExtendedPDBParser.py b/package/MDAnalysis/topology/ExtendedPDBParser.py index b0285a36292..bbd8c40ae1b 100644 --- a/package/MDAnalysis/topology/ExtendedPDBParser.py +++ b/package/MDAnalysis/topology/ExtendedPDBParser.py @@ -21,7 +21,7 @@ This topology parser uses a PDB file to build a minimum internal structure representation (list of atoms). The only difference from -:mod:`~MDAnalysis.topology.PrimitivePDBParser` is that this parser reads a +:mod:`~MDAnalysis.topology.PDBParser` is that this parser reads a non-standard PDB-like format in which residue numbers can be five digits instead of four. @@ -37,7 +37,7 @@ .. SeeAlso:: - * :mod:`MDAnalysis.topology.PrimitivePDBParser` + * :mod:`MDAnalysis.topology.PDBParser` * :class:`MDAnalysis.coordinates.PDB.ExtendedPDBReader` * :class:`MDAnalysis.core.AtomGroup.Universe` @@ -51,10 +51,10 @@ """ from __future__ import absolute_import -from . import PrimitivePDBParser +from . import PDBParser -class ExtendedPDBParser(PrimitivePDBParser.PrimitivePDBParser): +class ExtendedPDBParser(PDBParser.PDBParser): """Parser that obtains a list of atoms from an non-standard "extended" PDB file. Extended PDB files (MDAnalysis format specifier *XPDB*) may contain residue diff --git a/package/MDAnalysis/topology/GROParser.py b/package/MDAnalysis/topology/GROParser.py index af25d61a8fb..e2c3068b90a 100644 --- a/package/MDAnalysis/topology/GROParser.py +++ b/package/MDAnalysis/topology/GROParser.py @@ -71,7 +71,7 @@ def parse(self): charge = guess_atom_charge(name) # segid = "SYSTEM" # ignore coords and velocities, they can be read by coordinates.GRO - except: + except (ValueError, IndexError): raise IOError("Couldn't read the following line of the .gro file:\n" "{0}".format(line)) else: diff --git a/package/MDAnalysis/topology/PDBParser.py b/package/MDAnalysis/topology/PDBParser.py index 0e2e3e59973..7a38420ea66 100644 --- a/package/MDAnalysis/topology/PDBParser.py +++ b/package/MDAnalysis/topology/PDBParser.py @@ -1,9 +1,10 @@ + # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # @@ -14,20 +15,29 @@ # """ -PDB topology parser -=================== +PDB Topology Parser +========================================================================= + +This topology parser uses a standard PDB file to build a minimum +internal structure representation (list of atoms). -Use a PDB file to build a minimum internal structure representation. +The topology reader reads a PDB file line by line and ignores atom +numbers but only reads residue numbers up to 9,999 correctly. If you +have systems containing at least 10,000 residues then you need to use +a different file format (e.g. the "extended" PDB, *XPDB* format, see +:mod:`~MDAnalysis.topology.ExtendedPDBParser`) that can handle residue +numbers up to 99,999. -.. Note:: Only atoms and their names are read; no bond connectivity of - (partial) charges are deduced. Masses are guessed and set to - 0 if unknown. +.. Note:: -.. SeeAlso:: :mod:`MDAnalysis.coordinates.PDB` and :mod:`Bio.PDB` + The parser processes atoms and their names. Masses are guessed and set to 0 + if unknown. Partial charges are not set. -.. SeeAlso:: :mod:`MDAnalysis.topology.PrimitivePDBParser` (which - *can* guess conectivity but does not support all subleties of the full - PDB format) +.. SeeAlso:: + + * :mod:`MDAnalysis.topology.ExtendedPDBParser` + * :class:`MDAnalysis.coordinates.PDB.PDBReader` + * :class:`MDAnalysis.core.AtomGroup.Universe` Classes ------- @@ -37,62 +47,172 @@ :inherited-members: """ -from __future__ import absolute_import +from __future__ import absolute_import, print_function -try: - # BioPython is overkill but potentially extensible (altLoc etc) - import Bio.PDB -except ImportError: - raise ImportError("Bio.PDB from biopython not found." - "Required for PDB topology parser.") +import numpy as np +import warnings -from .base import TopologyReader from ..core.AtomGroup import Atom -from ..coordinates.pdb.extensions import get_structure -from .core import guess_atom_type, guess_atom_mass, guess_atom_charge +from .core import get_atom_mass, guess_atom_element +from ..lib.util import openany +from .base import TopologyReader class PDBParser(TopologyReader): - """Read minimum topology information from a PDB file.""" - format = 'PDB' + """Parser that obtains a list of atoms from a standard PDB file. - def parse(self): - """Parse atom information from PDB file *pdbfile*. + See Also + -------- + :class:`MDAnalysis.coordinates.PDB.PDBReader` - Only reads the list of atoms. + .. versionadded:: 0.8 + """ + format = ['PDB','ENT'] - This functions uses the :class:`Bio.PDB.PDBParser` as used by - :func:`MDAnalysis.coordinates.pdb.extensions.get_structure`. + def parse(self): + """Parse atom information from PDB file *filename*. - :Returns: MDAnalysis internal *structure* dict + Returns + ------- + MDAnalysis internal *structure* dict + + See Also + -------- + The *structure* dict is defined in `MDAnalysis.topology` and the file + is read with :class:`MDAnalysis.coordinates.PDB.PDBReader`. - .. SeeAlso:: The *structure* dict is defined in `MDAnalysis.topology`. """ + structure = {} + atoms = self._parseatoms() + structure['atoms'] = atoms - structure = {'atoms': atoms} + bonds = self._parsebonds(atoms) + structure['bonds'] = bonds return structure def _parseatoms(self): - # use Sloppy PDB parser to cope with big PDBs! - pdb = get_structure(self.filename, "0UNK") - + iatom = 0 atoms = [] - # translate Bio.PDB atom objects to MDAnalysis Atom. - for iatom, atom in enumerate(pdb.get_atoms()): - residue = atom.parent - chain_id = residue.parent.id - atomname = atom.name - atomtype = guess_atom_type(atomname) - resname = residue.resname - resid = int(residue.id[1]) - # no empty segids (or Universe throws IndexError) - segid = residue.get_segid().strip() or chain_id or "SYSTEM" - mass = guess_atom_mass(atomname) - charge = guess_atom_charge(atomname) - bfactor = atom.bfactor - # occupancy = atom.occupancy - atoms.append(Atom(iatom, atomname, atomtype, resname, resid, segid, - mass, charge, bfactor=bfactor, universe=self._u)) + + with openany(self.filename, 'rt') as f: + resid_prev = 0 # resid looping hack + for i, line in enumerate(f): + line = line.strip() # Remove extra spaces + if not line: # Skip line if empty + continue + record = line[:6].strip() + + if record.startswith('END'): + break + elif line[:6] in ('ATOM ', 'HETATM'): + try: + serial = int(line[6:11]) + except ValueError: + # serial can become '***' when they get too high + self._wrapped_serials = True + serial = None + name = line[12:16].strip() + altLoc = line[16:17].strip() + resName = line[17:21].strip() + # empty chainID is a single space ' '! + chainID = line[21:22].strip() + if self.format == "XPDB": # fugly but keeps code DRY + # extended non-standard format used by VMD + resSeq = int(line[22:27]) + resid = resSeq + else: + resSeq = int(line[22:26]) + resid = resSeq + + while resid - resid_prev < -5000: + resid += 10000 + resid_prev = resid + # insertCode = _c(27, 27, str) # not used + # occupancy = float(line[54:60]) + try: + tempFactor = float(line[60:66]) + except ValueError: + tempFactor = 0.0 + segID = line[66:76].strip() + element = line[76:78].strip() + + segid = segID.strip() or chainID.strip() or "SYSTEM" + + elem = guess_atom_element(name) + + atomtype = element or elem + mass = get_atom_mass(elem) + # charge = guess_atom_charge(name) + charge = 0.0 + + atom = Atom(iatom, name, atomtype, resName, resid, + segid, mass, charge, + bfactor=tempFactor, serial=serial, + altLoc=altLoc, universe=self._u, + resnum=resSeq) + iatom += 1 + atoms.append(atom) + return atoms + + def _parsebonds(self, atoms): + # Could optimise this by saving lines in the main loop + # then doing post processing after all Atoms have been read + # ie do one pass through the file only + # Problem is that in multiframe PDB, the CONECT is at end of file, + # so the "break" call happens before bonds are reached. + + # If the serials wrapped, this won't work + if hasattr(self, '_wrapped_serials'): + warnings.warn("Invalid atom serials were present, bonds will not" + " be parsed") + return tuple([]) + + # Mapping between the atom array indicies a.index and atom ids + # (serial) in the original PDB file + mapping = dict((a.serial, a.index) for a in atoms) + + bonds = set() + with openany(self.filename, "rt") as f: + lines = (line for line in f if line[:6] == "CONECT") + for line in lines: + atom, atoms = _parse_conect(line.strip()) + for a in atoms: + bond = tuple([mapping[atom], mapping[a]]) + bonds.add(bond) + + bonds = tuple(bonds) + + return bonds + + +def _parse_conect(conect): + """parse a CONECT record from pdbs + + Parameters + ---------- + conect : str + white space striped CONECT record + + Returns + ------- + atom_id : int + atom index of bond + bonds : set + atom ids of bonded atoms + + Raises + ------ + RuntimeError + Raised if ``conect`` is not a valid CONECT record + """ + atom_id = np.int(conect[6:11]) + n_bond_atoms = len(conect[11:]) // 5 + if len(conect[11:]) % n_bond_atoms != 0: + raise RuntimeError("Bond atoms aren't aligned proberly for CONECT " + "record: {}".format(conect)) + bond_atoms = (int(conect[11 + i * 5: 16 + i * 5]) for i in + range(n_bond_atoms)) + return atom_id, bond_atoms diff --git a/package/MDAnalysis/topology/PrimitivePDBParser.py b/package/MDAnalysis/topology/PrimitivePDBParser.py index 4a5fd9eb841..ae2cac77b4a 100644 --- a/package/MDAnalysis/topology/PrimitivePDBParser.py +++ b/package/MDAnalysis/topology/PrimitivePDBParser.py @@ -35,7 +35,7 @@ .. SeeAlso:: * :mod:`MDAnalysis.topology.ExtendedPDBParser` - * :class:`MDAnalysis.coordinates.PDB.PrimitivePDBReader` + * :class:`MDAnalysis.coordinates.PDB.PDBReader` * :class:`MDAnalysis.core.AtomGroup.Universe` Classes @@ -45,149 +45,32 @@ :members: :inherited-members: +..deprecated:: 0.15.0 + PDBParser has been replaced with PrimitivePDBParser. """ + from __future__ import absolute_import, print_function import numpy as np import warnings +from . import PDBParser from ..core.AtomGroup import Atom from .core import get_atom_mass, guess_atom_element from ..lib.util import openany from .base import TopologyReader -class PrimitivePDBParser(TopologyReader): - """Parser that obtains a list of atoms from a standard PDB file. - - See Also - -------- - :class:`MDAnalysis.coordinates.PDB.PrimitivePDBReader` - - .. versionadded:: 0.8 - """ - format = 'Permissive_PDB' - - def parse(self): - """Parse atom information from PDB file *filename*. - - Returns - ------- - MDAnalysis internal *structure* dict - - See Also - -------- - The *structure* dict is defined in `MDAnalysis.topology` and the file - is read with :class:`MDAnalysis.coordinates.PDB.PrimitivePDBReader`. - - """ - structure = {} - - atoms = self._parseatoms() - structure['atoms'] = atoms - - bonds = self._parsebonds(atoms) - structure['bonds'] = bonds - - return structure - - def _parseatoms(self): - iatom = 0 - atoms = [] - - with openany(self.filename) as f: - resid_prev = 0 # resid looping hack - for i, line in enumerate(f): - line = line.strip() # Remove extra spaces - if len(line) == 0: # Skip line if empty - continue - record = line[:6].strip() - - if record.startswith('END'): - break - elif line[:6] in ('ATOM ', 'HETATM'): - try: - serial = int(line[6:11]) - except ValueError: - # serial can become '***' when they get too high - self._wrapped_serials = True - serial = None - name = line[12:16].strip() - altLoc = line[16:17].strip() - resName = line[17:21].strip() - # empty chainID is a single space ' '! - chainID = line[21:22].strip() - if self.format == "XPDB": # fugly but keeps code DRY - # extended non-standard format used by VMD - resSeq = int(line[22:27]) - resid = resSeq - else: - resSeq = int(line[22:26]) - resid = resSeq - - while resid - resid_prev < -5000: - resid += 10000 - resid_prev = resid - # insertCode = _c(27, 27, str) # not used - # occupancy = float(line[54:60]) - try: - tempFactor = float(line[60:66]) - except ValueError: - tempFactor = 0.0 - segID = line[66:76].strip() - element = line[76:78].strip() - - segid = segID.strip() or chainID.strip() or "SYSTEM" - - elem = guess_atom_element(name) - - atomtype = element or elem - mass = get_atom_mass(elem) - # charge = guess_atom_charge(name) - charge = 0.0 - - atom = Atom(iatom, name, atomtype, resName, resid, - segid, mass, charge, - bfactor=tempFactor, serial=serial, - altLoc=altLoc, universe=self._u, - resnum=resSeq) - iatom += 1 - atoms.append(atom) - - return atoms - - def _parsebonds(self, atoms): - # Could optimise this by saving lines in the main loop - # then doing post processing after all Atoms have been read - # ie do one pass through the file only - # Problem is that in multiframe PDB, the CONECT is at end of file, - # so the "break" call happens before bonds are reached. - - # If the serials wrapped, this won't work - if hasattr(self, '_wrapped_serials'): - warnings.warn("Invalid atom serials were present, bonds will not" - " be parsed") - return tuple([]) - - # Mapping between the atom array indicies a.index and atom ids - # (serial) in the original PDB file - mapping = dict((a.serial, a.index) for a in atoms) - - bonds = set() - with openany(self.filename, "r") as f: - lines = (line for line in f if line[:6] == "CONECT") - for line in lines: - atom, atoms = _parse_conect(line.strip()) - for a in atoms: - bond = tuple([mapping[atom], mapping[a]]) - bonds.add(bond) - - bonds = tuple(bonds) - - return bonds +class PrimitivePDBParser(PDBParser.PDBParser): + def __init__(self, *args, **kwargs): + warnings.warn('PrimitivePDBParser is identical to the PDBParser,' + ' it is deprecated in favor of the shorter name', + category=DeprecationWarning) + super(PDBParser.PDBParser, self).__init__(*args, **kwargs) def _parse_conect(conect): + """parse a CONECT record from pdbs Parameters diff --git a/package/MDAnalysis/topology/__init__.py b/package/MDAnalysis/topology/__init__.py index 4c1f5ed2150..22ed68401c1 100644 --- a/package/MDAnalysis/topology/__init__.py +++ b/package/MDAnalysis/topology/__init__.py @@ -1,5 +1,5 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- http://www.MDAnalysis.org # Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein @@ -40,13 +40,9 @@ either standard or EXTended format; :mod:`MDAnalysis.topology.CRDParser` - Brookhaven [#a]_ pdb a simplified PDB format (as used in MD simulations) - is read by default; the full format can be read by - supplying the `permissive=False` flag to - :class:`MDAnalysis.Universe`; - :mod:`MDAnalysis.topology.PrimitivePDBParser` and - :mod:`MDAnalysis.topology.PDBParser` - + Brookhaven [#a]_ pdb/ent a simplified PDB format (as used in MD simulations) + is read by default + XPDB [#a]_ pdb Extended PDB format (can use 5-digit residue numbers). To use, specify the format "XPBD" explicitly: diff --git a/package/MDAnalysis/topology/core.py b/package/MDAnalysis/topology/core.py index 40b08e8c94e..2a8dd8356c9 100644 --- a/package/MDAnalysis/topology/core.py +++ b/package/MDAnalysis/topology/core.py @@ -112,7 +112,7 @@ def build_residues(atoms): return residues -def get_parser_for(filename, permissive=False, format=None): +def get_parser_for(filename, format=None): """Return the appropriate topology parser for *filename*. Automatic detection is disabled when an explicit *format* is @@ -125,9 +125,6 @@ def get_parser_for(filename, permissive=False, format=None): if format is None: format = util.guess_format(filename) format = format.upper() - if format == 'PDB' and permissive: - return _PARSERS['Permissive_PDB'] - try: return _PARSERS[format] except KeyError: diff --git a/package/MDAnalysis/units.py b/package/MDAnalysis/units.py index 771c0bf03ce..64e9c24004a 100644 --- a/package/MDAnalysis/units.py +++ b/package/MDAnalysis/units.py @@ -224,7 +224,7 @@ #: hence a number of values are pre-stored in :data:`water`. densityUnit_factor = { 'Angstrom^{-3}': 1 / 1.0, 'A^{-3}': 1 / 1.0, - '\u212b^{-3}': 1 / 1.0, b'\xe2\x84\xab^{-3}': 1 / 1.0, # Unicode and UTF-8 encoded + '\u212b^{-3}': 1 / 1.0, 'nm^{-3}': 1 / 1e-3, 'nanometer^{-3}': 1 / 1e-3, 'Molar': 1 / (1e-27 * constants['N_Avogadro']), 'SPC': 1 / (1e-24 * constants['N_Avogadro'] * water['SPC'] / water['MolarMass']), @@ -247,7 +247,7 @@ #: For *speed*, the basic unit is Angstrom/ps. speedUnit_factor = { - 'Angstrom/ps': 1.0, 'A/ps': 1.0, '\u212b/ps': 1.0, b'\xe2\x84\xab/ps': 1.0, + 'Angstrom/ps': 1.0, 'A/ps': 1.0, '\u212b/ps': 1.0, 'Angstrom/picosecond': 1.0, 'angstrom/picosecond': 1.0, # 1 'Angstrom/AKMA': 4.888821e-2, @@ -269,7 +269,7 @@ #: For *force* the basic unit is kJ/(mol*Angstrom). forceUnit_factor = { 'kJ/(mol*Angstrom)': 1.0, 'kJ/(mol*A)': 1.0, - 'kJ/(mol*\u212b)': 1.0, b'kJ/(mol*\xe2\x84\xab)': 1.0, + 'kJ/(mol*\u212b)': 1.0, 'kJ/(mol*nm)': 10.0, 'Newton': 1e13/constants['N_Avogadro'], 'N': 1e13/constants['N_Avogadro'], @@ -343,16 +343,14 @@ def convert(x, u1, u2): try: ut1 = unit_types[u1] except KeyError: - raise ValueError(("unit '{0}' not recognized.\n" + - len("ValueError: ")*[" "] + - "It must be one of {1}.").format(u1, unit_types)) + raise ValueError("unit '{0}' not recognized.\n" + "It must be one of {1}.".format(u1, ", ".join(unit_types))) try: ut2 = unit_types[u2] except KeyError: - raise ValueError(("unit '{0}' not recognized.\n" + - len("ValueError: ")*" " + - "It must be one of {1}.").format(u2, unit_types)) + raise ValueError("unit '{0}' not recognized.\n" + "It must be one of {1}.".format(u2, ", ".join(unit_types))) if ut1 != ut2: raise ValueError("Cannot convert between unit types " - "{0[ut1]} --> {0[ut2]}".format(vars())) + "{0} --> {1}".format(u1, u2)) return x * get_conversion_factor(ut1, u1, u2) diff --git a/package/MDAnalysis/version.py b/package/MDAnalysis/version.py index 886567d53e2..dfea541f7a5 100644 --- a/package/MDAnalysis/version.py +++ b/package/MDAnalysis/version.py @@ -59,4 +59,5 @@ # e.g. with lib.log #: Release of MDAnalysis as a string, using `semantic versioning`_. -__version__ = "0.14.0" # NOTE: keep in sync with RELEASE in setup.py +__version__ = "0.15.0" # NOTE: keep in sync with RELEASE in setup.py + diff --git a/package/MDAnalysis/visualization/streamlines_3D.py b/package/MDAnalysis/visualization/streamlines_3D.py index dc633339abb..296349fd410 100644 --- a/package/MDAnalysis/visualization/streamlines_3D.py +++ b/package/MDAnalysis/visualization/streamlines_3D.py @@ -42,7 +42,7 @@ def determine_container_limits(coordinate_file_path, trajectory_file_path, buffe container for the system and return these limits.''' universe_object = MDAnalysis.Universe(coordinate_file_path, trajectory_file_path) all_atom_selection = universe_object.select_atoms('all') # select all particles - all_atom_coordinate_array = all_atom_selection.coordinates() + all_atom_coordinate_array = all_atom_selection.positions x_min, x_max, y_min, y_max, z_min, z_max = [ all_atom_coordinate_array[..., 0].min(), all_atom_coordinate_array[..., 0].max(), all_atom_coordinate_array[..., 1].min(), @@ -253,9 +253,9 @@ def produce_coordinate_arrays_single_process(coordinate_file_path, trajectory_fi if ts.frame > end_frame: break # stop here if ts.frame == start_frame: - start_frame_relevant_particle_coordinate_array_xyz = relevant_particles.coordinates() + start_frame_relevant_particle_coordinate_array_xyz = relevant_particles.positions elif ts.frame == end_frame: - end_frame_relevant_particle_coordinate_array_xyz = relevant_particles.coordinates() + end_frame_relevant_particle_coordinate_array_xyz = relevant_particles.positions else: continue return (start_frame_relevant_particle_coordinate_array_xyz, end_frame_relevant_particle_coordinate_array_xyz) diff --git a/package/doc/sphinx/source/conf.py b/package/doc/sphinx/source/conf.py index bfb6a470edc..6d022370323 100644 --- a/package/doc/sphinx/source/conf.py +++ b/package/doc/sphinx/source/conf.py @@ -78,17 +78,8 @@ def __getattr__(cls, name): # (take the list from AUTHORS) # Ordering: (1) Naveen (2) Elizabeth, then all contributors in alphabetical order # (last) Oliver -authors = u"""Naveen Michaud-Agrawal, Elizabeth J. Denning, Joshua - Adelman, Balasubramanian, Jonathan Barnoud, Christian Beckstein (logo), Alejandro - Bernardin, Sébastien Buchoux, David Caplan, Matthieu Chavent, - Xavier Deupi, Jan Domański, David L. Dotson, Lennard van der - Feltz, Philip Fowler, Joseph Goose, Richard J. Gowers, Lukas - Grossar, Benjamin Hall, Joe Jordan, Max Linke, Jinju Lu, Robert - McGibbon, Alex Nesterenko, Manuel Nuno Melo, Hai Nguyen, - Caio S. Souza, Mattia F. Palermo, Danny Parton, Joshua L. Phillips, Tyler Reddy, - Paul Rigor, Sean L. Seyler, Andy Somogyi, Lukas Stelzl, - Gorman Stock, Isaac Virshup, Zhuyi Xue, Carlos Yáñez S., - and Oliver Beckstein""" +author_list = __import__('MDAnalysis').__authors__ +authors = u', '.join(author_list[:-1]) + u', and ' + author_list[-1] project = u'MDAnalysis' copyright = u'2005-2015, ' + authors diff --git a/package/doc/sphinx/source/documentation_pages/analysis/base.rst b/package/doc/sphinx/source/documentation_pages/analysis/base.rst index 84fca34e6f6..4eda92ceac6 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis/base.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis/base.rst @@ -1,2 +1,4 @@ .. automodule:: MDAnalysis.analysis.base + :members: + diff --git a/package/doc/sphinx/source/documentation_pages/analysis/lineardensity.rst b/package/doc/sphinx/source/documentation_pages/analysis/lineardensity.rst index d46d87dff90..2e4b4de2e59 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis/lineardensity.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis/lineardensity.rst @@ -1,2 +1,3 @@ .. automodule:: MDAnalysis.analysis.lineardensity + :members: diff --git a/package/setup.py b/package/setup.py index d92f838b785..561a0fb81be 100755 --- a/package/setup.py +++ b/package/setup.py @@ -39,10 +39,12 @@ from __future__ import print_function from setuptools import setup, Extension, find_packages from distutils.ccompiler import new_compiler +import codecs import os import sys import shutil import tempfile +import warnings # Make sure I have the right Python version. if sys.version_info[:2] < (2, 7): @@ -68,7 +70,7 @@ cmdclass = {} # NOTE: keep in sync with MDAnalysis.__version__ in version.py -RELEASE = "0.14.0" +RELEASE = "0.15.0" is_release = not 'dev' in RELEASE @@ -351,7 +353,82 @@ def extensions(config): "failed/disabled Cython build.".format(source)) return extensions, cython_generated + +def dynamic_author_list(): + """Generate __authors__ from AUTHORS + + This function generates authors.py that contains the list of the + authors from the AUTHORS file. This avoids having that list maintained in + several places. Note that AUTHORS is sorted chronologically while we want + __authors__ in authors.py to be sorted alphabetically. + + The authors are written in AUTHORS as bullet points under the + "Chronological list of authors" title. + """ + authors = [] + with codecs.open('AUTHORS', encoding='utf-8') as infile: + # An author is a bullet point under the title "Chronological list of + # authors". We first want move the cursor down to the title of + # interest. + for line_no, line in enumerate(infile, start=1): + if line[:-1] == "Chronological list of authors": + break + else: + # If we did not break, it means we did not find the authors. + raise IOError('EOF before the list of authors') + # Skip the next line as it is the title underlining + line = next(infile) + line_no += 1 + if line[:4] != '----': + raise IOError('Unexpected content on line {0}, ' + 'should be a string of "-".'.format(line_no)) + # Add each bullet point as an author until the next title underlining + for line in infile: + if line[:4] in ('----', '====', '~~~~'): + # The previous line was a title, hopefully it did not start as + # a bullet point so it got ignored. Since we hit a title, we + # are done reading the list of authors. + break + elif line.strip()[:2] == '- ': + # This is a bullet point, so it should be an author name. + name = line.strip()[2:].strip() + authors.append(name) + + # So far, the list of authors is sorted chronologically. We want it + # sorted alphabetically of the last name. + authors.sort(key=lambda name: name.split()[-1]) + # Move Naveen and Elizabeth first, and Oliver last. + authors.remove('Naveen Michaud-Agrawal') + authors.remove('Elizabeth J. Denning') + authors.remove('Oliver Beckstein') + authors = (['Naveen Michaud-Agrawal', 'Elizabeth J. Denning'] + + authors + ['Oliver Beckstein']) + + # Write the authors.py file. + out_path = 'MDAnalysis/authors.py' + with codecs.open(out_path, 'w', encoding='utf-8') as outfile: + # Write the header + header = '''\ +#-*- coding:utf-8 -*- + +# This file is generated from the AUTHORS file during the installation process. +# Do not edit it as your changes will be overwritten. +''' + print(header, file=outfile) + + # Write the list of authors as a python list + template = u'__authors__ = [\n{}\n]' + author_string = u',\n'.join(u' u"{}"'.format(name) + for name in authors) + print(template.format(author_string), file=outfile) + + if __name__ == '__main__': + try: + dynamic_author_list() + except (OSError, IOError): + warnings.warn('Cannot write the list of authors.') + with open("SUMMARY.txt") as summary: LONG_DESCRIPTION = summary.read() CLASSIFIERS = [ @@ -381,6 +458,7 @@ def extensions(config): maintainer='Richard Gowers', maintainer_email='mdnalysis-discussion@googlegroups.com', url='http://www.mdanalysis.org', + download_url='https://github.com/MDAnalysis/mdanalysis/releases', provides=['MDAnalysis'], license='GPL 2', packages=find_packages(), diff --git a/testsuite/AUTHORS b/testsuite/AUTHORS index 435a176773a..c7744f31d26 100644 --- a/testsuite/AUTHORS +++ b/testsuite/AUTHORS @@ -66,6 +66,10 @@ Chronological list of authors - Hai Nguyen 2016 - Balasubramanian + - Abhinav Gupta + - Pedro Reis + - Fiona B. Naughton + External code ------------- diff --git a/testsuite/CHANGELOG b/testsuite/CHANGELOG index 218eda337b4..0a2a501cb72 100644 --- a/testsuite/CHANGELOG +++ b/testsuite/CHANGELOG @@ -13,8 +13,21 @@ Also see https://github.com/MDAnalysis/mdanalysis/wiki/MDAnalysisTests and https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests ------------------------------------------------------------------------------ -02/28/16 manuel.nuno.melo +05/15/16 orbeckst, jbarnoud, pedrishi, fiona-naughton, jdetle + * 0.15.0 + - removed biopython PDB parser for coordinates and topology (Issue #777) + - Added test for weighted rmsd (issue #814) + - metadata update: link download_url to GitHub releases so that + Depsy recognizes contributors (issue #749) and added + @richardjgowers as maintainer + - a __version__ variable is now exposed; it is built by setup.py from the + AUTHORS file (Issue #784) + - Removed all bare assert (Issue #724) + - added tests for GRO format + - added tempdir module + +02/28/16 manuel.nuno.melo * 0.14.0 - Added the cleanup plugin (--with-mda_cleanup) to delete offset files @@ -50,7 +63,7 @@ and https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests - MDAnalysis and MDAnalysisTests packages MUST have the same release number (they need to stay in sync); MDAnalysisTests will NOT run if a release mismatch is detected - - see Issue #87 and + - see Issue #87 and https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests @@ -59,7 +72,7 @@ and https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests * 0.7.4 - Split off test data trajectories and structures from - MDAnalaysis/tests/data into separate package. (Issue 28) + MDAnalaysis/tests/data into separate package. (Issue 28) - Numbering matches the earliest MDAnalysis release for which the data is needed. Any later releases of MDAnalysis will also use these test data diff --git a/testsuite/LICENSE b/testsuite/LICENSE index 8437675a953..541d854e50c 100644 --- a/testsuite/LICENSE +++ b/testsuite/LICENSE @@ -484,7 +484,7 @@ pyqcprot (src/pyqcprot) is released under the following 'BSD 3-clause' licence: ----------------------------------------------------------------------------- PyQCPROT - Author(s) of Original Implementation: + Author(s) of Original Implementation: Douglas L. Theobald Department of Biochemistry MS 009 @@ -494,7 +494,7 @@ PyQCPROT USA dtheobald@brandeis.edu - + Pu Liu Johnson & Johnson Pharmaceutical Research and Development, L.L.C. 665 Stockton Drive @@ -504,7 +504,7 @@ PyQCPROT pliu24@its.jnj.com For the original code written in C see: - http://theobald.brandeis.edu/qcp/ + http://theobald.brandeis.edu/qcp/ Author of Python Port: @@ -512,10 +512,10 @@ PyQCPROT Department of Biological Sciences University of Pittsburgh Pittsburgh, PA 15260 - + jla65@pitt.edu - + If you use this QCP rotation calculation method in a publication, please reference: @@ -525,25 +525,25 @@ PyQCPROT Acta Crystallographica A 61(4):478-480. Pu Liu, Dmitris K. Agrafiotis, and Douglas L. Theobald (2010) - "Fast determination of the optimal rotational matrix for macromolecular + "Fast determination of the optimal rotational matrix for macromolecular superpositions." - J. Comput. Chem. 31, 1561-1563. + J. Comput. Chem. 31, 1561-1563. - Copyright (c) 2009-2010, Pu Liu and Douglas L. Theobald + Copyright (c) 2009-2010, Pu Liu and Douglas L. Theobald Copyright (c) 2011 Joshua L. Adelman All rights reserved. - Redistribution and use in source and binary forms, with or without modification, are permitted + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials + * Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior written + * Neither the name of the nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS @@ -582,3 +582,24 @@ are distributed under the same license as the 'Atom' logo. ========================================================================== +tempdir is released under the following MIT licence: + +Copyright (c) 2010-2016 Thomas Fenzl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/__init__.py b/testsuite/MDAnalysisTests/__init__.py index ed2132009f4..9e11e3b50e3 100644 --- a/testsuite/MDAnalysisTests/__init__.py +++ b/testsuite/MDAnalysisTests/__init__.py @@ -97,17 +97,24 @@ .. _NumPy: http://www.numpy.org/ .. _nose: - http://somethingaboutorange.com/mrl/projects/nose/0.11.3/index.html + http://nose.readthedocs.org/en/latest/ .. _nose commandline options: - http://somethingaboutorange.com/mrl/projects/nose/0.11.3/usage.html#extended-usage + http://nose.readthedocs.org/en/latest/man.html?highlight=command%20line .. _SciPy testing guidelines: http://projects.scipy.org/numpy/wiki/TestingGuidelines#id11 .. _Charmm: http://www.charmm.org .. _Gromacs: http://www.gromacs.org """ +import logging +logger = logging.getLogger("MDAnalysisTests.__init__") -__version__ = "0.14.0" # keep in sync with RELEASE in setup.py +__version__ = "0.15.0" # keep in sync with RELEASE in setup.py +try: + from MDAnalysisTests.authors import __authors__ +except ImportError: + logger.info('Could not find authors.py, __authors__ will be empty.') + __authors__ = [] # Do NOT import MDAnalysis at this level. Tests should do it themselves. # If MDAnalysis is imported here coverage accounting might fail because all the import diff --git a/testsuite/MDAnalysisTests/analysis/test_align.py b/testsuite/MDAnalysisTests/analysis/test_align.py index 1312a9dc0d6..cc1115a7df8 100644 --- a/testsuite/MDAnalysisTests/analysis/test_align.py +++ b/testsuite/MDAnalysisTests/analysis/test_align.py @@ -25,11 +25,10 @@ import numpy as np from nose.plugins.attrib import attr -import tempdir from os import path from MDAnalysisTests.datafiles import PSF, DCD, FASTA -from MDAnalysisTests import executable_not_found, parser_not_found +from MDAnalysisTests import executable_not_found, parser_not_found, tempdir class TestRotationMatrix(object): @@ -87,18 +86,20 @@ def tearDown(self): def test_rmsd(self): self.universe.trajectory[0] # ensure first frame bb = self.universe.select_atoms('backbone') - first_frame = bb.coordinates(copy=True) + first_frame = bb.positions self.universe.trajectory[-1] - last_frame = bb.coordinates() + last_frame = bb.positions assert_almost_equal(rms.rmsd(first_frame, first_frame), 0.0, 5, err_msg="error: rmsd(X,X) should be 0") # rmsd(A,B) = rmsd(B,A) should be exact but spurious failures in the # 9th decimal have been observed (see Issue 57 comment #1) so we relax # the test to 6 decimals. - rmsd = rms.rmsd(first_frame, last_frame) - assert_almost_equal(rms.rmsd(last_frame, first_frame), rmsd, 6, + rmsd = rms.rmsd(first_frame, last_frame, superposition=True) + assert_almost_equal(rms.rmsd(last_frame, first_frame, + superposition=True), + rmsd, 6, err_msg="error: rmsd() is not symmetric") - assert_almost_equal(rmsd, 6.8342494129169804, 5, + assert_almost_equal(rmsd, 6.820321761927005, 5, err_msg="RMSD calculation between 1st and last " "AdK frame gave wrong answer") @@ -114,13 +115,13 @@ def test_rms_fit_trj(self): # RMSD against the reference frame # calculated on Mac OS X x86 with MDA 0.7.2 r689 # VMD: 6.9378711 - self._assert_rmsd(fitted, 0, 6.92913674516568) + self._assert_rmsd(fitted, 0, 6.929083044751061) self._assert_rmsd(fitted, -1, 0.0) def _assert_rmsd(self, fitted, frame, desired): fitted.trajectory[frame] - rmsd = rms.rmsd(self.reference.atoms.coordinates(), - fitted.atoms.coordinates()) + rmsd = rms.rmsd(self.reference.atoms.positions, + fitted.atoms.positions, superposition=True) assert_almost_equal(rmsd, desired, decimal=5, err_msg="frame {0:d} of fit does not have " "expected RMSD".format(frame)) diff --git a/testsuite/MDAnalysisTests/analysis/test_contacts.py b/testsuite/MDAnalysisTests/analysis/test_contacts.py index 7be5d8fa8b4..712b5cf6ac4 100644 --- a/testsuite/MDAnalysisTests/analysis/test_contacts.py +++ b/testsuite/MDAnalysisTests/analysis/test_contacts.py @@ -2,8 +2,8 @@ # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # @@ -15,63 +15,325 @@ # from __future__ import print_function -import MDAnalysis -import MDAnalysis.analysis.contacts -from MDAnalysis import SelectionError +import MDAnalysis as mda +from MDAnalysis.analysis import contacts +from MDAnalysis.analysis.distances import distance_array -from numpy.testing import (TestCase, dec, - assert_almost_equal, assert_raises, assert_equal) +from numpy.testing import (dec, assert_almost_equal, assert_equal, raises, + assert_array_equal, assert_array_almost_equal) import numpy as np -import nose -from nose.plugins.attrib import attr import os -import tempdir -from MDAnalysisTests.datafiles import PSF, DCD -from MDAnalysisTests import executable_not_found, parser_not_found +from MDAnalysisTests.datafiles import (PSF, + DCD, + contacts_villin_folded, + contacts_villin_unfolded, + contacts_file, ) -class TestContactAnalysis1(TestCase): - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def setUp(self): - self.universe = MDAnalysis.Universe(PSF, DCD) +from MDAnalysisTests import parser_not_found, tempdir + + +def test_soft_cut_q(): + # just check some of the extremal points + assert_equal(contacts.soft_cut_q([0], [0]), .5) + assert_almost_equal(contacts.soft_cut_q([100], [0]), 0) + assert_almost_equal(contacts.soft_cut_q([-100], [0]), 1) + + +def test_soft_cut_q_folded(): + u = mda.Universe(contacts_villin_folded) + + contacts_data = np.genfromtxt(contacts_file) + # indices have been stored 1 indexed + indices = contacts_data[:, :2].astype(int) - 1 + + r = np.linalg.norm(u.atoms.positions[indices[:, 0]] - + u.atoms.positions[indices[:, 1]], axis=1) + r0 = contacts_data[:, 2] + + beta = 5.0 + lambda_constant = 1.8 + Q = 1 / (1 + np.exp(beta * (r - lambda_constant * r0))) + + assert_almost_equal(Q.mean(), 1.0, decimal=3) + + +def test_soft_cut_q_unfolded(): + u = mda.Universe(contacts_villin_unfolded) + + contacts_data = np.genfromtxt(contacts_file) + # indices have been stored 1 indexed + indices = contacts_data[:, :2].astype(int) - 1 + + r = np.linalg.norm(u.atoms.positions[indices[:, 0]] - + u.atoms.positions[indices[:, 1]], axis=1) + r0 = contacts_data[:, 2] + + beta = 5.0 + lambda_constant = 1.8 + Q = 1 / (1 + np.exp(beta * (r - lambda_constant * r0))) + + assert_almost_equal(Q.mean(), 0.0, decimal=1) + + +def test_hard_cut_q(): + # just check some extremal points + assert_equal(contacts.hard_cut_q([1], 2), 1) + assert_equal(contacts.hard_cut_q([2], 1), 0) + assert_equal(contacts.hard_cut_q([2, 0.5], 1), 0.5) + assert_equal(contacts.hard_cut_q([2, 3], [3, 4]), 1) + assert_equal(contacts.hard_cut_q([4, 5], [3, 4]), 0) + + +def test_radius_cut_q(): + # check some extremal points + assert_equal(contacts.radius_cut_q([1], None, 2), 1) + assert_equal(contacts.radius_cut_q([2], None, 1), 0) + assert_equal(contacts.radius_cut_q([2, 0.5], None, 1), 0.5) + + +def test_contact_matrix(): + d = np.arange(5) + radius = np.ones(5) * 2.5 + + out = contacts.contact_matrix(d, radius) + assert_array_equal(out, [True, True, True, False, False]) + + # check in-place update + out = np.empty(out.shape) + contacts.contact_matrix(d, radius, out=out) + assert_array_equal(out, [True, True, True, False, False]) + + +def test_new_selection(): + u = mda.Universe(PSF, DCD) + selections = ('all', ) + sel = contacts._new_selections(u, selections, -1)[0] + u.trajectory[-1] + assert_array_equal(sel.positions, u.atoms.positions) + + +def soft_cut(ref, u, selA, selB, radius=4.5, beta=5.0, lambda_constant=1.8): + """ + Reference implementation for testing + """ + # reference groups A and B from selection strings + refA, refB = ref.select_atoms(selA), ref.select_atoms(selB) + + # 2D float array, reference distances (r0) + dref = distance_array(refA.positions, refB.positions) + + # 2D bool array, select reference distances that are less than the cutoff + # radius + mask = dref < radius + + # group A and B in a trajectory + grA, grB = u.select_atoms(selA), u.select_atoms(selB) + results = [] + + for ts in u.trajectory: + d = distance_array(grA.positions, grB.positions) + r, r0 = d[mask], dref[mask] + x = 1 / (1 + np.exp(beta * (r - lambda_constant * r0))) + + # average/normalize and append to results + results.append((ts.time, x.sum() / mask.sum())) + + return np.asarray(results) + + +class TestContacts(object): + @dec.skipif( + parser_not_found('DCD'), + 'DCD parser not available. Are you using python 3?') + def __init__(self): + self.universe = mda.Universe(PSF, DCD) self.trajectory = self.universe.trajectory + self.sel_basic = "(resname ARG LYS) and (name NH* NZ)" + self.sel_acidic = "(resname ASP GLU) and (name OE* OD*)" + def tearDown(self): + # reset trajectory + self.universe.trajectory[0] del self.universe - del self.trajectory - - def _run_ContactAnalysis1(self, **runkwargs): - sel_basic = "(resname ARG or resname LYS) and (name NH* or name NZ)" - sel_acidic = "(resname ASP or resname GLU) and (name OE* or name OD*)" - acidic = self.universe.select_atoms(sel_acidic) - basic = self.universe.select_atoms(sel_basic) - outfile = 'qsalt.dat' - CA1 = MDAnalysis.analysis.contacts.ContactAnalysis1( + + def _run_Contacts(self, **kwargs): + acidic = self.universe.select_atoms(self.sel_acidic) + basic = self.universe.select_atoms(self.sel_basic) + Contacts = contacts.Contacts( self.universe, - selection=(sel_acidic, sel_basic), refgroup=(acidic, basic), - radius=6.0, outfile=outfile) - kwargs = runkwargs.copy() - kwargs['force'] = True - CA1.run(**kwargs) - return CA1 + selection=(self.sel_acidic, self.sel_basic), + refgroup=(acidic, basic), + radius=6.0, + **kwargs) + Contacts.run() + return Contacts def test_startframe(self): - """test_startframe: TestContactAnalysis1: start frame set to 0 (resolution of Issue #624)""" - with tempdir.in_tempdir(): - CA1 = self._run_ContactAnalysis1() - self.assertEqual(CA1.timeseries.shape[1], self.universe.trajectory.n_frames) + """test_startframe: TestContactAnalysis1: start frame set to 0 (resolution of + Issue #624) + + """ + CA1 = self._run_Contacts() + assert_equal(len(CA1.timeseries), self.universe.trajectory.n_frames) def test_end_zero(self): """test_end_zero: TestContactAnalysis1: stop frame 0 is not ignored""" - with tempdir.in_tempdir(): - CA1 = self._run_ContactAnalysis1(stop=0) - self.assertEqual(len(CA1.timeseries), 0) + CA1 = self._run_Contacts(stop=0) + assert_equal(len(CA1.timeseries), 0) def test_slicing(self): start, stop, step = 10, 30, 5 + CA1 = self._run_Contacts(start=start, stop=stop, step=step) + frames = np.arange(self.universe.trajectory.n_frames)[start:stop:step] + assert_equal(len(CA1.timeseries), len(frames)) + + @staticmethod + def test_villin_folded(): + # one folded, one unfolded + f = mda.Universe(contacts_villin_folded) + u = mda.Universe(contacts_villin_unfolded) + sel = "protein and not name H*" + + grF = f.select_atoms(sel) + + q = contacts.Contacts(u, + selection=(sel, sel), + refgroup=(grF, grF), + method="soft_cut") + q.run() + + results = soft_cut(f, u, sel, sel) + assert_almost_equal(q.timeseries[:, 1], results[:, 1]) + + @staticmethod + def test_villin_unfolded(): + + # both folded + f = mda.Universe(contacts_villin_folded) + u = mda.Universe(contacts_villin_folded) + sel = "protein and not name H*" + + grF = f.select_atoms(sel) + + q = contacts.Contacts(u, + selection=(sel, sel), + refgroup=(grF, grF), + method="soft_cut") + q.run() + + results = soft_cut(f, u, sel, sel) + assert_almost_equal(q.timeseries[:, 1], results[:, 1]) + + def test_hard_cut_method(self): + ca = self._run_Contacts() + expected = [1., 0.58252427, 0.52427184, 0.55339806, 0.54368932, + 0.54368932, 0.51456311, 0.46601942, 0.48543689, 0.52427184, + 0.46601942, 0.58252427, 0.51456311, 0.48543689, 0.48543689, + 0.48543689, 0.46601942, 0.51456311, 0.49514563, 0.49514563, + 0.45631068, 0.47572816, 0.49514563, 0.50485437, 0.53398058, + 0.50485437, 0.51456311, 0.51456311, 0.49514563, 0.49514563, + 0.54368932, 0.50485437, 0.48543689, 0.55339806, 0.45631068, + 0.46601942, 0.53398058, 0.53398058, 0.46601942, 0.52427184, + 0.45631068, 0.46601942, 0.47572816, 0.46601942, 0.45631068, + 0.47572816, 0.45631068, 0.48543689, 0.4368932, 0.4368932, + 0.45631068, 0.50485437, 0.41747573, 0.4368932, 0.51456311, + 0.47572816, 0.46601942, 0.46601942, 0.47572816, 0.47572816, + 0.46601942, 0.45631068, 0.44660194, 0.47572816, 0.48543689, + 0.47572816, 0.42718447, 0.40776699, 0.37864078, 0.42718447, + 0.45631068, 0.4368932, 0.4368932, 0.45631068, 0.4368932, + 0.46601942, 0.45631068, 0.48543689, 0.44660194, 0.44660194, + 0.44660194, 0.42718447, 0.45631068, 0.44660194, 0.48543689, + 0.48543689, 0.44660194, 0.4368932, 0.40776699, 0.41747573, + 0.48543689, 0.45631068, 0.46601942, 0.47572816, 0.51456311, + 0.45631068, 0.37864078, 0.42718447] + assert_equal(len(ca.timeseries), len(expected)) + assert_array_almost_equal(ca.timeseries[:, 1], expected) + + @staticmethod + def _is_any_closer(r, r0, dist=2.5): + return np.any(r < dist) + + def test_own_method(self): + ca = self._run_Contacts(method=self._is_any_closer) + + bound_expected = [1., 1., 0., 1., 1., 0., 0., 1., 0., 1., 1., 0., 0., + 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 0., 1., + 0., 1., 1., 0., 1., 1., 1., 0., 0., 0., 0., 1., 0., + 0., 1., 0., 1., 1., 1., 0., 1., 0., 0., 1., 1., 1., + 0., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1., 0., 0., + 1., 0., 1., 1., 1., 1., 1., 1., 0., 1., 1., 0., 1., + 0., 0., 1., 1., 0., 0., 1., 1., 1., 0., 1., 0., 0., + 1., 0., 1., 1., 1., 1., 1.] + assert_array_equal(ca.timeseries[:, 1], bound_expected) + + @staticmethod + def _weird_own_method(r, r0): + return 'aaa' + + @raises(ValueError) + def test_own_method_no_array_cast(self): + self._run_Contacts(method=self._weird_own_method, stop=2) + + @raises(ValueError) + def test_non_callable_method(self): + self._run_Contacts(method=2, stop=2) + + def test_save(self): with tempdir.in_tempdir(): - CA1 = self._run_ContactAnalysis1(start=start, stop=stop, step=step) - frames = np.arange(self.universe.trajectory.n_frames)[start:stop:step] - self.assertEqual(CA1.timeseries.shape[1], len(frames)) + ca = self._run_Contacts() + ca.save('testfile.npy') + saved = np.genfromtxt('testfile.npy') + assert_array_almost_equal(ca.timeseries, saved) + + +def test_q1q2(): + u = mda.Universe(PSF, DCD) + q1q2 = contacts.q1q2(u, 'name CA', radius=8) + q1q2.run() + + q1_expected = [1., 0.98092643, 0.97366031, 0.97275204, 0.97002725, + 0.97275204, 0.96276113, 0.96730245, 0.9582198, 0.96185286, + 0.95367847, 0.96276113, 0.9582198, 0.95186194, 0.95367847, + 0.95095368, 0.94187103, 0.95186194, 0.94277929, 0.94187103, + 0.9373297, 0.93642144, 0.93097184, 0.93914623, 0.93278837, + 0.93188011, 0.9373297, 0.93097184, 0.93188011, 0.92643052, + 0.92824705, 0.92915531, 0.92643052, 0.92461399, 0.92279746, + 0.92643052, 0.93278837, 0.93188011, 0.93369664, 0.9346049, + 0.9373297, 0.94096276, 0.9400545, 0.93642144, 0.9373297, + 0.9373297, 0.9400545, 0.93006358, 0.9400545, 0.93823797, + 0.93914623, 0.93278837, 0.93097184, 0.93097184, 0.92733878, + 0.92824705, 0.92279746, 0.92824705, 0.91825613, 0.92733878, + 0.92643052, 0.92733878, 0.93278837, 0.92733878, 0.92824705, + 0.93097184, 0.93278837, 0.93914623, 0.93097184, 0.9373297, + 0.92915531, 0.93188011, 0.93551317, 0.94096276, 0.93642144, + 0.93642144, 0.9346049, 0.93369664, 0.93369664, 0.93278837, + 0.93006358, 0.93278837, 0.93006358, 0.9346049, 0.92824705, + 0.93097184, 0.93006358, 0.93188011, 0.93278837, 0.93006358, + 0.92915531, 0.92824705, 0.92733878, 0.92643052, 0.93188011, + 0.93006358, 0.9346049, 0.93188011] + assert_array_almost_equal(q1q2.timeseries[:, 1], q1_expected) + + q2_expected = [0.94649446, 0.94926199, 0.95295203, 0.95110701, 0.94833948, + 0.95479705, 0.94926199, 0.9501845, 0.94926199, 0.95387454, + 0.95202952, 0.95110701, 0.94649446, 0.94095941, 0.94649446, + 0.9400369, 0.94464945, 0.95202952, 0.94741697, 0.94649446, + 0.94188192, 0.94188192, 0.93911439, 0.94464945, 0.9400369, + 0.94095941, 0.94372694, 0.93726937, 0.93819188, 0.93357934, + 0.93726937, 0.93911439, 0.93911439, 0.93450185, 0.93357934, + 0.93265683, 0.93911439, 0.94372694, 0.93911439, 0.94649446, + 0.94833948, 0.95110701, 0.95110701, 0.95295203, 0.94926199, + 0.95110701, 0.94926199, 0.94741697, 0.95202952, 0.95202952, + 0.95202952, 0.94741697, 0.94741697, 0.94926199, 0.94280443, + 0.94741697, 0.94833948, 0.94833948, 0.9400369, 0.94649446, + 0.94741697, 0.94926199, 0.95295203, 0.94926199, 0.9501845, + 0.95664207, 0.95756458, 0.96309963, 0.95756458, 0.96217712, + 0.95756458, 0.96217712, 0.96586716, 0.96863469, 0.96494465, + 0.97232472, 0.97140221, 0.9695572, 0.97416974, 0.9695572, + 0.96217712, 0.96771218, 0.9704797, 0.96771218, 0.9695572, + 0.97140221, 0.97601476, 0.97693727, 0.98154982, 0.98431734, + 0.97601476, 0.9797048, 0.98154982, 0.98062731, 0.98431734, + 0.98616236, 0.9898524, 1.] + assert_array_almost_equal(q1q2.timeseries[:, 2], q2_expected) diff --git a/testsuite/MDAnalysisTests/analysis/test_density.py b/testsuite/MDAnalysisTests/analysis/test_density.py index 0deefe5dded..7d0d9524148 100644 --- a/testsuite/MDAnalysisTests/analysis/test_density.py +++ b/testsuite/MDAnalysisTests/analysis/test_density.py @@ -18,7 +18,6 @@ from six.moves import zip import numpy as np import os -import tempdir from numpy.testing import TestCase, assert_equal, assert_almost_equal, dec @@ -28,7 +27,7 @@ ## import MDAnalysis.analysis.density from MDAnalysisTests.datafiles import TPR, XTC -from MDAnalysisTests import module_not_found +from MDAnalysisTests import module_not_found, tempdir class TestDensity(TestCase): @@ -140,6 +139,3 @@ def test_density_from_Universe_update_selection(self): self.selections['dynamic'], self.references['dynamic']['meandensity'], update_selections=True) - - - diff --git a/testsuite/MDAnalysisTests/analysis/test_gnm.py b/testsuite/MDAnalysisTests/analysis/test_gnm.py new file mode 100644 index 00000000000..3e605e51590 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/test_gnm.py @@ -0,0 +1,134 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- http://www.MDAnalysis.org +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# +from __future__ import print_function + +import MDAnalysis +import MDAnalysis.analysis.gnm + +from numpy.testing import (TestCase, assert_equal, assert_almost_equal) +import numpy as np + +from nose.plugins.attrib import attr + +from MDAnalysisTests.datafiles import GRO, XTC +from MDAnalysisTests import tempdir + +class TestGNM(TestCase): + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.universe = MDAnalysis.Universe(GRO, XTC) + + def tearDown(self): + del self.universe + del self.tmpdir + + def test_gnm(self): + gnm = MDAnalysis.analysis.gnm.GNMAnalysis(self.universe, ReportVector="output.txt") + gnm.run() + result = gnm.results + assert_equal(len(result), 10) + time, eigenvalues, eigenvectors = zip(*result) + assert_almost_equal(time, range(0, 1000, 100), decimal=4) + assert_almost_equal(eigenvalues, + [ 2.0287113e-15, 4.1471575e-15, 1.8539533e-15, 4.3810359e-15, + 3.9607304e-15, 4.1289113e-15, 2.5501084e-15, 4.0498182e-15, + 4.2058769e-15, 3.9839431e-15]) + + def test_gnm_run_skip(self): + gnm = MDAnalysis.analysis.gnm.GNMAnalysis(self.universe) + gnm.run(skip=3) + result = gnm.results + assert_equal(len(result), 4) + time, eigenvalues, eigenvectors = zip(*result) + assert_almost_equal(time, range(0, 1200, 300), decimal=4) + assert_almost_equal(eigenvalues, + [ 2.0287113e-15, 4.3810359e-15, 2.5501084e-15, 3.9839431e-15]) + + def test_generate_kirchoff(self): + gnm = MDAnalysis.analysis.gnm.GNMAnalysis(self.universe) + gnm.run() + gen = gnm.generate_kirchoff() + assert_almost_equal(gen[0], + [ 7,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + @attr('slow') + def test_closeContactGNMAnalysis(self): + gnm = MDAnalysis.analysis.gnm.closeContactGNMAnalysis(self.universe) + gnm.run() + + result = gnm.results + assert_equal(len(result), 10) + time, eigenvalues, eigenvectors = zip(*result) + assert_almost_equal(time, range(0, 1000, 100), decimal=4) + assert_almost_equal(eigenvalues, + [ 0.1502614, 0.1426407, 0.1412389, 0.1478305, 0.1425449, + 0.1563304, 0.156915 , 0.1503619, 0.1572592, 0.1542063]) + + gen = gnm.generate_kirchoff() + assert_almost_equal(gen[0], + [ 16.326744128018923, -2.716098853586913, -1.94736842105263, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -0.05263157894736842, 0.0, 0.0, 0.0, -3.3541953679557905, 0.0, -1.4210526315789465, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -1.0423368771244421, -1.3006649542861801, -0.30779350562554625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.927172649945531, -0.7509392614826383, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -2.263157894736841, -0.24333213169614382]) + + @attr('slow') + def test_closeContactGNMAnalysis_noMassWeight(self): + gnm = MDAnalysis.analysis.gnm.closeContactGNMAnalysis(self.universe, MassWeight=False) + gnm.run() + + result = gnm.results + assert_equal(len(result), 10) + time, eigenvalues, eigenvectors = zip(*result) + assert_almost_equal(time, range(0, 1000, 100), decimal=4) + assert_almost_equal(eigenvalues, + [ 2.4328739, 2.2967251, 2.2950061, 2.4110916, 2.3271343, + 2.5213111, 2.5189955, 2.4481649, 2.5224835, 2.4824345]) + + gen = gnm.generate_kirchoff() + assert_almost_equal(gen[0], + [ 303.0, -58.0, -37.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, + 0.0, 0.0, 0.0, -67.0, 0.0, -27.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -17.0, -15.0, + -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -14.0, -15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -43.0, -3.0]) diff --git a/testsuite/MDAnalysisTests/analysis/test_hbonds.py b/testsuite/MDAnalysisTests/analysis/test_hbonds.py index db244f14fb4..0fe206c2280 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hbonds.py +++ b/testsuite/MDAnalysisTests/analysis/test_hbonds.py @@ -26,7 +26,7 @@ import itertools import warnings -from MDAnalysisTests.datafiles import PDB_helix +from MDAnalysisTests.datafiles import PDB_helix, GRO, XTC class TestHydrogenBondAnalysis(TestCase): @@ -59,6 +59,12 @@ def test_helix_backbone(self): self.values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds") assert_equal(h.timesteps, [0.0]) + def test_zero_vs_1based(self): + h = self._run() + if h.timeseries[0]: + assert_equal((int(h.timeseries[0][0][0])-int(h.timeseries[0][0][2])),1) + assert_equal((int(h.timeseries[0][0][1])-int(h.timeseries[0][0][3])),1) + def test_generate_table(self): h = self._run() h.generate_table() @@ -68,8 +74,12 @@ def test_generate_table(self): assert_array_equal(h.table.donor_resid, self.values['donor_resid']) assert_array_equal(h.table.acceptor_resnm, self.values['acceptor_resnm']) - # TODO: Expand tests because the following ones are a bit superficial - # because we should really run them on a trajectory + @staticmethod + def test_true_traj(): + u = MDAnalysis.Universe(GRO, XTC) + h = MDAnalysis.analysis.hbonds.HydrogenBondAnalysis(u,'protein','resname ASP', distance=3.0, angle=120.0) + h.run() + assert_equal(len(h.timeseries), 10) def test_count_by_time(self): h = self._run() @@ -191,6 +201,3 @@ def run_HBA_dynamic_selections(*args): yield run_HBA_dynamic_selections, s1, s2, s1type finally: self._tearDown() # manually tear down (because with yield cannot use TestCase) - - - diff --git a/testsuite/MDAnalysisTests/analysis/test_helanal.py b/testsuite/MDAnalysisTests/analysis/test_helanal.py index 523cf315c3d..edd68a2caf7 100644 --- a/testsuite/MDAnalysisTests/analysis/test_helanal.py +++ b/testsuite/MDAnalysisTests/analysis/test_helanal.py @@ -15,7 +15,6 @@ # import os import re -import tempdir import numpy as np from numpy.testing import (dec, assert_raises, assert_, @@ -27,7 +26,7 @@ from MDAnalysis import FinishTimeException from MDAnalysisTests.datafiles import (GRO, XTC, PSF, DCD, PDB_small, HELANAL_BENDING_MATRIX) -from MDAnalysisTests import parser_not_found +from MDAnalysisTests import parser_not_found, tempdir # reference data from a single PDB file: # data = MDAnalysis.analysis.helanal.helanal_main(PDB_small, @@ -149,5 +148,3 @@ def test_xtc_striding(): # MDAnalysis.analysis.helanal.helanal_trajectory(u, selection=sel, finish=5) # except IndexError: # self.fail("IndexError consistent with Issue 188.") - - diff --git a/testsuite/MDAnalysisTests/analysis/test_hole.py b/testsuite/MDAnalysisTests/analysis/test_hole.py index 76f438d7987..e59e6077e5c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hole.py +++ b/testsuite/MDAnalysisTests/analysis/test_hole.py @@ -27,10 +27,9 @@ from nose.plugins.attrib import attr import errno -import tempdir from MDAnalysisTests.datafiles import PDB_HOLE, XTC_HOLE -from MDAnalysisTests import executable_not_found +from MDAnalysisTests import executable_not_found, tempdir def rlimits_missing(): # return True if resources module not accesible (ie setting of rlimits) @@ -110,5 +109,3 @@ def _restore_rlimits(self): def tearDown(self): self._restore_rlimits() del self.universe - - diff --git a/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py b/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py new file mode 100644 index 00000000000..6877ff12da1 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py @@ -0,0 +1,42 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- http://www.MDAnalysis.org +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# + +from numpy.testing import ( + assert_, + assert_almost_equal, +) + +import MDAnalysis as mda +from MDAnalysis.analysis import nuclinfo + +from MDAnalysisTests.datafiles import NUCL + + +class TestNuclinfo(object): + def setUp(self): + self.u = mda.Universe(NUCL) + + def tearDown(self): + del self.u + + def test_wc_pair_1(self): + val = nuclinfo.wc_pair(self.u, 1, 2, seg1='RNAA', seg2='RNAA') + + assert_almost_equal(val, 4.449, decimal=3) + + def test_wc_pair_2(self): + val = nuclinfo.wc_pair(self.u, 22, 23, seg1='RNAA', seg2='RNAA') + assert_almost_equal(val, 4.601, decimal=3) diff --git a/testsuite/MDAnalysisTests/analysis/test_psa.py b/testsuite/MDAnalysisTests/analysis/test_psa.py index 379f9e3c719..f36df5bd8dc 100644 --- a/testsuite/MDAnalysisTests/analysis/test_psa.py +++ b/testsuite/MDAnalysisTests/analysis/test_psa.py @@ -19,13 +19,11 @@ import MDAnalysis.analysis.psa from numpy.testing import (TestCase, dec, assert_array_less, - assert_array_almost_equal) + assert_array_almost_equal, assert_) import numpy as np -import tempdir - from MDAnalysisTests.datafiles import PSF, DCD, DCD2 -from MDAnalysisTests import parser_not_found +from MDAnalysisTests import parser_not_found, tempdir class TestPSAnalysis(TestCase): @@ -73,4 +71,4 @@ def test_reversal_hausdorff(self): def test_reversal_frechet(self): err_msg = "Frechet distances did not increase after path reversal" - assert self.frech_matrix[1,2] >= self.frech_matrix[0,1], err_msg + assert_(self.frech_matrix[1,2] >= self.frech_matrix[0,1], err_msg) diff --git a/testsuite/MDAnalysisTests/analysis/test_rms.py b/testsuite/MDAnalysisTests/analysis/test_rms.py index 5627ff583ed..48aba6b3523 100644 --- a/testsuite/MDAnalysisTests/analysis/test_rms.py +++ b/testsuite/MDAnalysisTests/analysis/test_rms.py @@ -18,15 +18,115 @@ from six.moves import range import MDAnalysis -import MDAnalysis.analysis.rms +import MDAnalysis as mda +from MDAnalysis.analysis import rms, align -from numpy.testing import TestCase, assert_almost_equal +from numpy.testing import TestCase, assert_almost_equal, raises, assert_ import numpy as np import os -import tempdir -from MDAnalysisTests.datafiles import GRO, XTC, rmsfArray +from MDAnalysisTests.datafiles import GRO, XTC, rmsfArray, PSF, DCD +from MDAnalysisTests import tempdir + + +class TestRMSD(object): + def __init__(self): + shape = (5, 3) + # vectors with length one + ones = np.ones(shape) / np.sqrt(3) + self.a = ones * np.arange(1, 6)[:, np.newaxis] + self.b = self.a + ones + + self.u = mda.Universe(PSF, DCD) + self.u2 = mda.Universe(PSF, DCD) + + self.p_first = self.u.select_atoms('protein') + self.p_last = self.u2.select_atoms('protein') + + def setUp(self): + self.u.trajectory[2] + self.u2.trajectory[-2] + # reset coordinates + self.u.trajectory[0] + self.u2.trajectory[-1] + + def test_no_center(self): + rmsd = rms.rmsd(self.a, self.b, center=False) + assert_almost_equal(rmsd, 1.0) + + def test_center(self): + rmsd = rms.rmsd(self.a, self.b, center=True) + assert_almost_equal(rmsd, 0.0) + + def test_list(self): + rmsd = rms.rmsd(self.a.tolist(), + self.b.tolist(), + center=False) + assert_almost_equal(rmsd, 1.0) + + def test_superposition(self): + bb = self.u.atoms.select_atoms('backbone') + a = bb.positions.copy() + self.u.trajectory[-1] + b = bb.positions.copy() + rmsd = rms.rmsd(a, b, superposition=True) + assert_almost_equal(rmsd, 6.820321761927005) + + def test_weights(self): + weights = np.zeros(len(self.a)) + weights[0] = 1 + weights[1] = 1 + weighted = rms.rmsd(self.a, self.b, weights=weights) + firstCoords = rms.rmsd(self.a[:2], self.b[:2]) + assert_almost_equal(weighted, firstCoords) + + def test_weights_and_superposition_1(self): + weights = np.ones(len(self.u.trajectory[0])) + weighted = rms.rmsd(self.u.trajectory[0], self.u.trajectory[1], + weights=weights, superposition=True) + firstCoords = rms.rmsd(self.u.trajectory[0], self.u.trajectory[1], + superposition=True) + assert_almost_equal(weighted, firstCoords, decimal=5) + + def test_weights_and_superposition_2(self): + weights = np.zeros(len(self.u.trajectory[0])) + weights[:100] = 1 + weighted = rms.rmsd(self.u.trajectory[0], self.u.trajectory[-1], + weights=weights, superposition=True) + firstCoords = rms.rmsd(self.u.trajectory[0][:100], self.u.trajectory[-1][:100], + superposition=True) + #very close to zero, change significant decimal places to 5 + assert_almost_equal(weighted, firstCoords, decimal = 5) + + @staticmethod + @raises(ValueError) + def test_unequal_shape(): + a = np.ones((4, 3)) + b = np.ones((5, 3)) + rms.rmsd(a, b) + + @raises(ValueError) + def test_wrong_weights(self): + w = np.ones(2) + rms.rmsd(self.a, self.b, w) + + def test_with_superposition_smaller(self): + A = self.p_first.positions + B = self.p_last.positions + rmsd = rms.rmsd(A, B) + rmsd_superposition = rms.rmsd(A, B, center=True, superposition=True) + print(rmsd, rmsd_superposition) + # by design the super positioned rmsd is smaller + assert_(rmsd > rmsd_superposition) + + def test_with_superposition_equal(self): + align.alignto(self.p_first, self.p_last) + A = self.p_first.positions + B = self.p_last.positions + rmsd = rms.rmsd(A, B) + rmsd_superposition = rms.rmsd(A, B, center=True, superposition=True) + assert_almost_equal(rmsd, rmsd_superposition) class TestRMSF(TestCase): diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 58698310fe3..074f0aed55b 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -3,7 +3,6 @@ from six.moves import zip, range from nose.plugins.attrib import attr from unittest import TestCase -import tempdir from numpy.testing import (assert_equal, assert_raises, assert_almost_equal, assert_array_almost_equal, raises, assert_allclose, assert_) @@ -14,6 +13,7 @@ from MDAnalysis.lib.mdamath import triclinic_vectors from MDAnalysisTests.coordinates.reference import RefAdKSmall +from MDAnalysisTests import tempdir class _SingleFrameReader(TestCase, RefAdKSmall): @@ -22,13 +22,6 @@ class _SingleFrameReader(TestCase, RefAdKSmall): def tearDown(self): del self.universe - def test_flag_permissive_pdb_reader(self): - """test_flag_permissive_pdb_reader: permissive_pdb_reader==True enables - primitive PDB reader""" - assert_equal(mda.core.flags['permissive_pdb_reader'], True, - "'permissive_pdb_reader' flag should be True as " - "MDAnalysis default") - def test_load_file(self): U = self.universe assert_equal(len(U.atoms), self.ref_n_atoms, diff --git a/testsuite/MDAnalysisTests/coordinates/test_coordinates.py b/testsuite/MDAnalysisTests/coordinates/test_coordinates.py index 6daa6d5755f..9bbc5adfaee 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_coordinates.py +++ b/testsuite/MDAnalysisTests/coordinates/test_coordinates.py @@ -22,13 +22,12 @@ from nose.plugins.attrib import attr from numpy.testing import (assert_allclose, assert_equal, assert_array_equal, assert_almost_equal, dec) -import tempdir from unittest import TestCase from MDAnalysisTests.datafiles import (PDB, INPCRD, XYZ_five, PSF, CRD, DCD, GRO, XTC, TRR, PDB_small, PDB_closed) from MDAnalysisTests.plugins.knownfailure import knownfailure -from MDAnalysisTests import parser_not_found +from MDAnalysisTests import parser_not_found, tempdir class TestINPCRDReader(TestCase): @@ -123,12 +122,12 @@ def test_frame_numbering(self): def test_frame(self): self.trajectory[0] - coord0 = self.universe.atoms.coordinates().copy() + coord0 = self.universe.atoms.positions.copy() # forward to frame where we repeat original dcd again: # dcd:0..97 crd:98 dcd:99..196 self.trajectory[99] assert_array_equal( - self.universe.atoms.coordinates(), coord0, + self.universe.atoms.positions, coord0, "coordinates at frame 1 and 100 should be the same!") def test_time(self): diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 7c08f4a7bae..0e901ee87fb 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -7,14 +7,13 @@ from numpy.testing import (assert_equal, assert_array_equal, assert_raises, assert_almost_equal, assert_array_almost_equal, assert_allclose, dec) -import tempdir from unittest import TestCase from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, CRD, PRMncdf, NCDF) from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD) from MDAnalysisTests.coordinates.base import BaseTimestepTest -from MDAnalysisTests import module_not_found +from MDAnalysisTests import module_not_found, tempdir @attr('issue') @@ -226,8 +225,8 @@ def test_single_frame(self): w = mda.Universe(PSF, self.outfile) assert_equal(w.trajectory.n_frames, 1, "single frame trajectory has wrong number of frames") - assert_almost_equal(w.atoms.coordinates(), - u.atoms.coordinates(), + assert_almost_equal(w.atoms.positions, + u.atoms.positions, 3, err_msg="coordinates do not match") @@ -242,8 +241,8 @@ def test_with_statement(self): assert_equal(w.trajectory.n_frames, 1, "with_statement: single frame trajectory has wrong " "number of frames") - assert_almost_equal(w.atoms.coordinates(), - u.atoms.coordinates(), + assert_almost_equal(w.atoms.positions, + u.atoms.positions, 3, err_msg="with_statement: coordinates do not match") @@ -287,8 +286,8 @@ def test_issue59(self): dcd.trajectory.rewind() assert_array_almost_equal( - xtc.atoms.coordinates(), - dcd.atoms.coordinates(), + xtc.atoms.positions, + dcd.atoms.positions, 3, err_msg="XTC -> DCD: DCD coordinates are messed up (Issue 59)") @@ -304,16 +303,16 @@ def test_OtherWriter(self): dcd.trajectory.rewind() assert_array_almost_equal( - dcd.atoms.coordinates(), - xtc.atoms.coordinates(), + dcd.atoms.positions, + xtc.atoms.positions, 2, err_msg="DCD -> XTC: coordinates are messed up (frame {0:d})".format( dcd.trajectory.frame)) xtc.trajectory[3] dcd.trajectory[3] assert_array_almost_equal( - dcd.atoms.coordinates(), - xtc.atoms.coordinates(), + dcd.atoms.positions, + xtc.atoms.positions, 2, err_msg="DCD -> XTC: coordinates are messed up (frame {0:d})".format( dcd.trajectory.frame)) @@ -566,5 +565,3 @@ def test_ts_order_define(self): assert_allclose(self.ts._unitcell, np.array([10, 80, 11, 85, 90, 12])) self.ts._ts_order = old self.ts.dimensions = np.zeros(6) - - diff --git a/testsuite/MDAnalysisTests/coordinates/test_dlpoly.py b/testsuite/MDAnalysisTests/coordinates/test_dlpoly.py index 37ad809038e..9b464dcc729 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dlpoly.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dlpoly.py @@ -44,7 +44,7 @@ class TestConfigReader(_DLPConfig): f = DLP_CONFIG def test_read(self): - assert self.rd.title == "DL_POLY: Potassium Chloride Test Case" + assert_equal(self.rd.title, "DL_POLY: Potassium Chloride Test Case") class TestConfigOrder(_DLPConfig): @@ -116,19 +116,19 @@ def tearDown(self): def test_len(self): assert_equal(len(self.u.trajectory), 3) - assert_equal([ts.frame for ts in self.u.trajectory], [1, 2, 3]) + assert_equal([ts.frame for ts in self.u.trajectory], [0, 1, 2]) def test_getting(self): ts = self.u.trajectory[1] - assert_equal(ts.frame, 2) + assert_equal(ts.frame, 1) def test_slicing(self): nums = [ts.frame for ts in self.u.trajectory[::2]] - assert_equal(nums, [1, 3]) + assert_equal(nums, [0, 2]) def test_slicing_2(self): nums = [ts.frame for ts in self.u.trajectory[1::-2]] - assert_equal(nums, [2]) + assert_equal(nums, [1]) def test_position(self): ref = np.array([[-7.595541651, -7.898808509, -7.861763110 diff --git a/testsuite/MDAnalysisTests/coordinates/test_gro.py b/testsuite/MDAnalysisTests/coordinates/test_gro.py index b446f5a6a13..af628b01aa5 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_gro.py +++ b/testsuite/MDAnalysisTests/coordinates/test_gro.py @@ -5,13 +5,17 @@ from nose.plugins.attrib import attr from numpy.testing import (assert_equal, assert_almost_equal, dec, - assert_array_almost_equal, assert_raises) + assert_array_almost_equal, assert_raises, + ) from unittest import TestCase -import tempdir -from MDAnalysisTests.datafiles import (GRO, GRO_velocity, GRO_large) +from MDAnalysisTests.datafiles import ( + GRO, GRO_velocity, GRO_large, + GRO_incomplete_vels, +) from MDAnalysisTests.coordinates.reference import RefAdK from MDAnalysisTests.coordinates.base import BaseTimestepTest +from MDAnalysisTests import tempdir class TestGROReader(TestCase, RefAdK): @@ -159,6 +163,25 @@ def test_volume(self): err_msg="wrong volume for unitcell (rhombic dodecahedron)") +class TestGROIncompleteVels(object): + def setUp(self): + self.u = mda.Universe(GRO_incomplete_vels) + + def tearDown(self): + del self.u + + def test_load(self): + assert_equal(len(self.u.atoms), 4) + + def test_velocities(self): + assert_array_almost_equal(self.u.atoms[0].velocity, + np.array([ 79.56, 124.08, 49.49]), + decimal=3) + assert_array_almost_equal(self.u.atoms[2].velocity, + np.array([0.0, 0.0, 0.0]), + decimal=3) + + class TestGROWriter(TestCase, tempdir.TempDir): def setUp(self): self.universe = mda.Universe(GRO) @@ -184,8 +207,8 @@ def tearDown(self): def test_writer(self): self.universe.atoms.write(self.outfile) u = mda.Universe(self.outfile) - assert_almost_equal(u.atoms.coordinates(), - self.universe.atoms.coordinates(), self.prec, + assert_almost_equal(u.atoms.positions, + self.universe.atoms.positions, self.prec, err_msg="Writing GRO file with GROWriter does " "not reproduce original coordinates") @@ -206,7 +229,7 @@ def test_check_coordinate_limits_min(self): # modify coordinates so we need our own copy or we could mess up # parallel tests u = mda.Universe(GRO) - u.atoms[2000].pos[1] = -999.9995 * 10 # nm -> A + u.atoms[2000].position = -999.9995 * 10 # nm -> A assert_raises(ValueError, u.atoms.write, self.outfile2) del u @@ -219,7 +242,7 @@ def test_check_coordinate_limits_max(self): # parallel tests u = mda.Universe(GRO) # nm -> A ; [ob] 9999.9996 not caught - u.atoms[1000].pos[1] = 9999.9999 * 10 + u.atoms[1000].position = 9999.9999 * 10 assert_raises(ValueError, u.atoms.write, self.outfile2) del u @@ -230,7 +253,7 @@ def test_check_coordinate_limits_max_noconversion(self): # modify coordinates so we need our own copy or we could mess up # parallel tests u = mda.Universe(GRO, convert_units=False) - u.atoms[1000].pos[1] = 9999.9999 + u.atoms[1000].position = 9999.9999 assert_raises(ValueError, u.atoms.write, self.outfile2, convert_units=False) del u @@ -279,7 +302,7 @@ def test_write_velocities(self): u.atoms.write(self.outfile) u2 = mda.Universe(self.outfile) - + assert_array_almost_equal(u.atoms.velocities, u2.atoms.velocities) diff --git a/testsuite/MDAnalysisTests/coordinates/test_lammps.py b/testsuite/MDAnalysisTests/coordinates/test_lammps.py index 79c7edc9317..15d66bb9550 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_lammps.py +++ b/testsuite/MDAnalysisTests/coordinates/test_lammps.py @@ -5,13 +5,14 @@ from numpy.testing import (assert_equal, assert_almost_equal, assert_raises, assert_) -import tempdir from unittest import TestCase from MDAnalysisTests.coordinates.reference import (RefLAMMPSData, RefLAMMPSDataMini, RefLAMMPSDataDCD) from MDAnalysis.tests.datafiles import LAMMPScnt +from MDAnalysisTests import tempdir + def test_datareader_ValueError(): from MDAnalysis.coordinates.LAMMPS import DATAReader diff --git a/testsuite/MDAnalysisTests/coordinates/test_mol2.py b/testsuite/MDAnalysisTests/coordinates/test_mol2.py index be5ba432f97..83472b99174 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_mol2.py +++ b/testsuite/MDAnalysisTests/coordinates/test_mol2.py @@ -15,13 +15,20 @@ # from six.moves import range -import tempdir import os -from numpy.testing import * - -from MDAnalysisTests.datafiles import mol2_molecules, mol2_molecule, mol2_broken_molecule +from numpy.testing import ( + assert_equal,assert_raises, assert_array_equal, + assert_array_almost_equal, TestCase, + assert_, +) + +from MDAnalysisTests.datafiles import ( + mol2_molecules, mol2_molecule, mol2_broken_molecule, + mol2_zinc, +) from MDAnalysis import Universe import MDAnalysis as mda +from MDAnalysisTests import tempdir class TestMol2(TestCase): @@ -120,3 +127,30 @@ def test_reverse_traj(self): def test_n_frames(self): assert_equal(self.universe.trajectory.n_frames, 200, "wrong number of frames in traj") + + +class TestMOL2NoSubstructure(object): + """MOL2 file without substructure + + """ + n_atoms = 45 + + def test_load(self): + r = mda.coordinates.MOL2.MOL2Reader(mol2_zinc, n_atoms=self.n_atoms) + assert_(r.n_atoms == 45) + + def test_universe(self): + u = mda.Universe(mol2_zinc) + assert_(len(u.atoms) == self.n_atoms) + + def test_write_nostructure(self): + mytempdir = tempdir.TempDir() + outfile = os.path.join(mytempdir.name, 'test.mol2') + + u = mda.Universe(mol2_zinc) + with mda.Writer(outfile) as W: + W.write(u.atoms) + + u2 = mda.Universe(outfile) + + assert_(len(u.atoms) == len(u2.atoms)) diff --git a/testsuite/MDAnalysisTests/coordinates/test_netcdf.py b/testsuite/MDAnalysisTests/coordinates/test_netcdf.py index f4be39e50f6..64305727bed 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_netcdf.py +++ b/testsuite/MDAnalysisTests/coordinates/test_netcdf.py @@ -7,7 +7,6 @@ from numpy.testing import (assert_equal, assert_array_almost_equal, assert_array_equal, assert_almost_equal, assert_raises, dec) -import tempdir from unittest import TestCase from MDAnalysisTests import module_not_found @@ -15,6 +14,8 @@ GRO, TRR, XYZ_mini) from MDAnalysisTests.coordinates.test_trj import _TRJReaderTest from MDAnalysisTests.coordinates.reference import (RefVGV, RefTZ2) +from MDAnalysisTests import tempdir + class _NCDFReaderTest(_TRJReaderTest): @dec.skipif(module_not_found("netCDF4"), "Test skipped because netCDF is not available.") diff --git a/testsuite/MDAnalysisTests/coordinates/test_pdb.py b/testsuite/MDAnalysisTests/coordinates/test_pdb.py index 6d5746fc14e..4eae966a3e4 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_pdb.py +++ b/testsuite/MDAnalysisTests/coordinates/test_pdb.py @@ -8,8 +8,7 @@ from nose.plugins.attrib import attr from numpy.testing import (assert_equal, assert_, dec, assert_array_almost_equal, - assert_almost_equal, assert_raises) -import tempdir + assert_almost_equal, assert_raises, assert_) from unittest import TestCase from MDAnalysisTests.coordinates.reference import (RefAdKSmall, Ref4e43, @@ -17,39 +16,46 @@ from MDAnalysisTests.coordinates.base import _SingleFrameReader from MDAnalysisTests.datafiles import (PDB, PDB_small, PDB_multiframe, XPDB_small, PSF, DCD, CONECT, CRD, - INC_PDB, PDB_xlserial, ALIGN) + INC_PDB, PDB_xlserial, ALIGN, ENT, + PDB_cm, PDB_cm_gz, PDB_cm_bz2, + PDB_mc, PDB_mc_gz, PDB_mc_bz2) from MDAnalysisTests.plugins.knownfailure import knownfailure -from MDAnalysisTests import parser_not_found - +from MDAnalysisTests import parser_not_found, tempdir class TestPDBReader(_SingleFrameReader): def setUp(self): - # use permissive=False instead of changing the global flag as this # can lead to race conditions when testing in parallel - self.universe = mda.Universe(RefAdKSmall.filename, permissive=False) + self.universe = mda.Universe(RefAdKSmall.filename) # 3 decimals in PDB spec # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM self.prec = 3 - def test_uses_Biopython(self): + + def test_uses_PDBReader(self): from MDAnalysis.coordinates.PDB import PDBReader assert_(isinstance(self.universe.trajectory, PDBReader), - "failed to choose Biopython PDBReader") + "failed to choose PDBReader") + - @knownfailure("Biopython PDB reader does not parse CRYST1", AssertionError) def test_dimensions(self): assert_almost_equal( self.universe.trajectory.ts.dimensions, RefAdKSmall.ref_unitcell, self.prec, - "Biopython reader failed to get unitcell dimensions from CRYST1") + "PDBReader failed to get unitcell dimensions from CRYST1") + + def test_ENT(self): + from MDAnalysis.coordinates.PDB import PDBReader + self.universe = mda.Universe(ENT) + assert_(isinstance(self.universe.trajectory, PDBReader), + "failed to choose PDBReader") class _PDBMetadata(TestCase, Ref4e43): - permissive = True + def setUp(self): - self.universe = mda.Universe(self.filename, permissive=self.permissive) + self.universe = mda.Universe(self.filename) def tearDown(self): del self.universe @@ -107,27 +113,6 @@ def test_REMARK(self): err_msg="REMARK line {0} do not match".format(lineno)) -class TestPrimitivePDBReader_Metadata(_PDBMetadata): - permissive = True - - -class TestPrimitivePDBReader(_SingleFrameReader): - def setUp(self): - self.universe = mda.Universe(PDB_small, permissive=True) - # 3 decimals in PDB spec - # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM - self.prec = 3 - - def test_missing_natoms(self): - from MDAnalysis.coordinates.PDB import PrimitivePDBReader - - assert_raises(ValueError, PrimitivePDBReader, 'something.pdb') - - def test_wrong_natoms(self): - from MDAnalysis.coordinates.PDB import PrimitivePDBReader - - assert_raises(ValueError, PrimitivePDBReader, PDB_small, n_atoms=4000) - class TestExtendedPDBReader(_SingleFrameReader): def setUp(self): @@ -146,26 +131,12 @@ def test_long_resSeq(self): assert_equal(u[4].resid, 10000, "can't read a five digit resid") -class TestPSF_PrimitivePDBReader(TestPrimitivePDBReader): - def setUp(self): - self.universe = mda.Universe(PSF, PDB_small, permissive=True) - # 3 decimals in PDB spec - # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM - self.prec = 3 - - def test_dimensions(self): - assert_almost_equal(self.universe.trajectory.ts.dimensions, - RefAdKSmall.ref_unitcell, self.prec, - "Primitive PDB reader failed to get unitcell " - "dimensions from CRYST1") - - -class TestPrimitivePDBWriter(TestCase): +class TestPDBWriter(TestCase): @dec.skipif(parser_not_found('DCD'), 'DCD parser not available. Are you using python 3?') def setUp(self): - self.universe = mda.Universe(PSF, PDB_small, permissive=True) - self.universe2 = mda.Universe(PSF, DCD, permissive=True) + self.universe = mda.Universe(PSF, PDB_small) + self.universe2 = mda.Universe(PSF, DCD) # 3 decimals in PDB spec # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM self.prec = 3 @@ -184,10 +155,10 @@ def tearDown(self): def test_writer(self): "Test writing from a single frame PDB file to a PDB file." "" self.universe.atoms.write(self.outfile) - u = mda.Universe(PSF, self.outfile, permissive=True) - assert_almost_equal(u.atoms.coordinates(), - self.universe.atoms.coordinates(), self.prec, - err_msg="Writing PDB file with PrimitivePDBWriter " + u = mda.Universe(PSF, self.outfile) + assert_almost_equal(u.atoms.positions, + self.universe.atoms.positions, self.prec, + err_msg="Writing PDB file with PDBWriter " "does not reproduce original coordinates") @attr('issue') @@ -215,7 +186,7 @@ def test_write_single_frame_AtomGroup(self): assert_equal(u2.trajectory.n_frames, 1, err_msg="Output PDB should only contain a single frame") - assert_almost_equal(u2.atoms.coordinates(), u.atoms.coordinates(), + assert_almost_equal(u2.atoms.positions, u.atoms.positions, self.prec, err_msg="Written coordinates do not " "agree with original coordinates from frame %d" % u.trajectory.frame) @@ -226,10 +197,9 @@ def test_check_coordinate_limits_min(self): with ValueError (Issue 57)""" # modify coordinates so we need our own copy or we could mess up # parallel tests - u = mda.Universe(PSF, PDB_small, permissive=True) - u.atoms[2000].pos[1] = -999.9995 + u = mda.Universe(PSF, PDB_small) + u.atoms[2000].position = -999.9995 assert_raises(ValueError, u.atoms.write, self.outfile) - del u @attr('issue') def test_check_coordinate_limits_max(self): @@ -237,17 +207,38 @@ def test_check_coordinate_limits_max(self): with ValueError (Issue 57)""" # modify coordinates so we need our own copy or we could mess up # parallel tests - u = mda.Universe(PSF, PDB_small, permissive=True) + u = mda.Universe(PSF, PDB_small) # OB: 9999.99951 is not caught by '<=' ?!? - u.atoms[1000].pos[1] = 9999.9996 + u.atoms[1000].position = 9999.9996 assert_raises(ValueError, u.atoms.write, self.outfile) del u + @attr('issue') + def test_check_header_title_multiframe(self): + """Check whether HEADER and TITLE are written just once in a multi- + frame PDB file (Issue 741)""" + u = mda.Universe(PSF, DCD) + pdb = mda.Writer(self.outfile, multiframe=True) + protein = u.select_atoms("protein and name CA") + for ts in u.trajectory[:5]: + pdb.write(protein) + pdb.close() + + with open(self.outfile) as f: + got_header = 0 + got_title = 0 + for line in f: + if line.startswith('HEADER'): + got_header += 1 + assert_(got_header <= 1, "There should be only one HEADER.") + elif line.startswith('TITLE'): + got_title += 1 + assert_(got_title <= 1, "There should be only one TITLE.") + class TestMultiPDBReader(TestCase): def setUp(self): self.multiverse = mda.Universe(PDB_multiframe, - permissive=True, guess_bonds=True) self.multiverse.build_topology() self.conect = mda.Universe(CONECT, guess_bonds=True) @@ -407,9 +398,9 @@ class TestMultiPDBWriter(TestCase): @dec.skipif(parser_not_found('DCD'), 'DCD parser not available. Are you using python 3?') def setUp(self): - self.universe = mda.Universe(PSF, PDB_small, permissive=True) - self.multiverse = mda.Universe(PDB_multiframe, permissive=True) - self.universe2 = mda.Universe(PSF, DCD, permissive=True) + self.universe = mda.Universe(PSF, PDB_small) + self.multiverse = mda.Universe(PDB_multiframe) + self.universe2 = mda.Universe(PSF, DCD) # 3 decimals in PDB spec # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM self.prec = 3 @@ -600,7 +591,7 @@ def tearDown(self): del self.u def test_natoms(self): - assert len(self.u.atoms) == 3 + assert_equal(len(self.u.atoms), 3) def test_coords(self): assert_array_almost_equal(self.u.atoms.positions, @@ -619,16 +610,16 @@ def test_dims(self): dtype=np.float32)) def test_names(self): - assert all(self.u.atoms.names == 'CA') + assert_(all(self.u.atoms.names == 'CA')) def test_residues(self): - assert len(self.u.residues) == 3 + assert_equal(len(self.u.residues), 3) def test_resnames(self): - assert len(self.u.atoms.resnames) == 3 - assert 'VAL' in self.u.atoms.resnames - assert 'LYS' in self.u.atoms.resnames - assert 'PHE' in self.u.atoms.resnames + assert_equal(len(self.u.atoms.resnames), 3) + assert_('VAL' in self.u.atoms.resnames) + assert_('LYS' in self.u.atoms.resnames) + assert_('PHE' in self.u.atoms.resnames) def test_reading_trajectory(self): for ts in self.u.trajectory: @@ -674,7 +665,6 @@ def test_serials(self): # Does not implement Reader.remarks, Reader.header, Reader.title, # Reader.compounds because the PDB header data in trajectory.metadata are # already parsed; should perhaps update the PrimitivePDBReader to do the same. -# [orbeckst] class TestPDBReader_Metadata(_PDBMetadata): permissive = False class TestPSF_CRDReader(_SingleFrameReader): @@ -685,17 +675,16 @@ def setUp(self): class TestPSF_PDBReader(TestPDBReader): def setUp(self): - # mda.core.flags['permissive_pdb_reader'] = False - self.universe = mda.Universe(PSF, PDB_small, permissive=False) + self.universe = mda.Universe(PSF, PDB_small) # 3 decimals in PDB spec # http://www.wwpdb.org/documentation/format32/sect9.html#ATOM self.prec = 3 - def test_uses_Biopython(self): + def test_uses_PDBReader(self): from MDAnalysis.coordinates.PDB import PDBReader assert_(isinstance(self.universe.trajectory, PDBReader), - "failed to choose Biopython PDBReader") + "failed to choose PDBReader") class TestPDBWriterOccupancies(object): @@ -765,3 +754,72 @@ def _test_PDB_atom_name(atom, ref_atom_name): ) for atom, ref_name in test_cases: yield _test_PDB_atom_name, atom, ref_name + + +class TestCrystModelOrder(object): + """Check offset based reading of pdb files + + Checks + - len + - seeking around + + # tests that cryst can precede or follow model header + # allow frames to follow either of these formats: + + # Case 1 (PDB_mc) + # MODEL + # ... + # ENDMDL + # CRYST + + # Case 2 (PDB_cm) + # CRYST + # MODEL + # ... + # ENDMDL + """ + boxsize = [80, 70, 60] + position = [10, 20, 30] + + def test_order(self): + for pdbfile in [PDB_cm, PDB_cm_bz2, PDB_cm_gz, + PDB_mc, PDB_mc_bz2, PDB_mc_gz]: + yield self._check_order, pdbfile + yield self._check_seekaround, pdbfile + yield self._check_rewind, pdbfile + + @staticmethod + def _check_len(pdbfile): + u = mda.Universe(pdbfile) + assert_(len(u.trajectory) == 3) + + def _check_order(self, pdbfile): + u = mda.Universe(pdbfile) + + for ts, refbox, refpos in zip( + u.trajectory, self.boxsize, self.position): + assert_almost_equal(u.dimensions[0], refbox) + assert_almost_equal(u.atoms[0].position[0], refpos) + + def _check_seekaround(self, pdbfile): + u = mda.Universe(pdbfile) + + for frame in [2, 0, 2, 1]: + u.trajectory[frame] + assert_almost_equal(u.dimensions[0], self.boxsize[frame]) + assert_almost_equal(u.atoms[0].position[0], self.position[frame]) + + def _check_rewind(self, pdbfile): + u = mda.Universe(pdbfile) + + u.trajectory[2] + u.trajectory.rewind() + assert_almost_equal(u.dimensions[0], self.boxsize[0]) + assert_almost_equal(u.atoms[0].position[0], self.position[0]) + + +def test_standalone_pdb(): + # check that PDBReader works without n_atoms kwarg + r = mda.coordinates.PDB.PDBReader(PDB_cm) + + assert_(r.n_atoms == 4) diff --git a/testsuite/MDAnalysisTests/coordinates/test_pdbqt.py b/testsuite/MDAnalysisTests/coordinates/test_pdbqt.py index 29132c60c81..f44895dcd25 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_pdbqt.py +++ b/testsuite/MDAnalysisTests/coordinates/test_pdbqt.py @@ -18,10 +18,10 @@ from MDAnalysis.tests.datafiles import PDBQT_input, PDBQT_querypdb from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch -from numpy.testing import * +from numpy.testing import assert_equal, TestCase import os -import tempdir +from MDAnalysisTests import tempdir class TestPDBQT(TestCase): diff --git a/testsuite/MDAnalysisTests/coordinates/test_pqr.py b/testsuite/MDAnalysisTests/coordinates/test_pqr.py index a8c463a5e5b..e18f4951e65 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_pqr.py +++ b/testsuite/MDAnalysisTests/coordinates/test_pqr.py @@ -3,11 +3,11 @@ from numpy.testing import (assert_almost_equal, assert_equal) from unittest import TestCase -import tempdir from MDAnalysisTests.coordinates.reference import (RefAdKSmall) from MDAnalysisTests.coordinates.base import _SingleFrameReader from MDAnalysisTests.datafiles import (PQR) +from MDAnalysisTests import tempdir class TestPQRReader(_SingleFrameReader): @@ -61,8 +61,8 @@ def test_writer_noChainID(self): self.universe.atoms.write(self.outfile) u = mda.Universe(self.outfile) assert_equal(u.segments.segids[0], 'SYSTEM') - assert_almost_equal(u.atoms.coordinates(), - self.universe.atoms.coordinates(), self.prec, + assert_almost_equal(u.atoms.positions, + self.universe.atoms.positions, self.prec, err_msg="Writing PQR file with PQRWriter does " "not reproduce original coordinates") assert_almost_equal(u.atoms.charges, self.universe.atoms.charges, @@ -78,8 +78,8 @@ def test_write_withChainID(self): self.universe.atoms.write(self.outfile) u = mda.Universe(self.outfile) assert_equal(u.segments.segids[0], 'A') - assert_almost_equal(u.atoms.coordinates(), - self.universe.atoms.coordinates(), self.prec, + assert_almost_equal(u.atoms.positions, + self.universe.atoms.positions, self.prec, err_msg="Writing PQR file with PQRWriter does " "not reproduce original coordinates") assert_almost_equal(u.atoms.charges, self.universe.atoms.charges, diff --git a/testsuite/MDAnalysisTests/coordinates/test_reader_api.py b/testsuite/MDAnalysisTests/coordinates/test_reader_api.py index 000ee9dfc1b..35ba9220773 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_reader_api.py +++ b/testsuite/MDAnalysisTests/coordinates/test_reader_api.py @@ -16,7 +16,7 @@ from MDAnalysis.coordinates.base import Timestep, SingleFrameReader, Reader -from numpy.testing import * +from numpy.testing import assert_equal, assert_raises import numpy as np """ diff --git a/testsuite/MDAnalysisTests/coordinates/test_trj.py b/testsuite/MDAnalysisTests/coordinates/test_trj.py index 33050aa4d21..d163b39447e 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_trj.py +++ b/testsuite/MDAnalysisTests/coordinates/test_trj.py @@ -52,8 +52,8 @@ def test_initial_frame_is_0(self): def test_starts_with_first_frame(self): """Test that coordinate arrays are filled as soon as the trajectory has been opened.""" - assert_(np.any(self.universe.atoms.coordinates() > 0), - "Reader does not populate coordinates() right away.") + assert_(np.any(self.universe.atoms.positions > 0), + "Reader does not populate positions right away.") def test_rewind(self): trj = self.universe.trajectory @@ -63,8 +63,8 @@ def test_rewind(self): "failed to forward to frame 2 (frameindex 2)") trj.rewind() assert_equal(trj.ts.frame, 0, "failed to rewind to first frame") - assert_(np.any(self.universe.atoms.coordinates() > 0), - "Reader does not populate coordinates() after rewinding.") + assert_(np.any(self.universe.atoms.positions > 0), + "Reader does not populate positions after rewinding.") def test_full_slice(self): trj_iter = self.universe.trajectory[:] diff --git a/testsuite/MDAnalysisTests/coordinates/test_trz.py b/testsuite/MDAnalysisTests/coordinates/test_trz.py index 6029578ea51..d799396f3a2 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_trz.py +++ b/testsuite/MDAnalysisTests/coordinates/test_trz.py @@ -4,7 +4,6 @@ from numpy.testing import (assert_equal, assert_array_almost_equal, assert_almost_equal, assert_raises) -import tempdir import numpy as np from unittest import TestCase @@ -14,6 +13,7 @@ from MDAnalysisTests.coordinates.reference import RefTRZ from MDAnalysisTests.coordinates.base import BaseTimestepTest from MDAnalysisTests.datafiles import (TRZ_psf, TRZ, two_water_gro) +from MDAnalysisTests import tempdir class TestTRZReader(TestCase, RefTRZ): @@ -240,8 +240,8 @@ def test_write_trajectory(self): u_ag = mda.Universe(self.outfile) - assert_array_almost_equal(self.ag.coordinates(), - u_ag.atoms.coordinates(), + assert_array_almost_equal(self.ag.positions, + u_ag.atoms.positions, self.prec, err_msg="Writing AtomGroup timestep failed.") @@ -255,4 +255,3 @@ class TestTRZTimestep(BaseTimestepTest): 0., 11., 0., 0., 0., 12.]) uni_args = (TRZ_psf, TRZ) - diff --git a/testsuite/MDAnalysisTests/coordinates/test_xdr.py b/testsuite/MDAnalysisTests/coordinates/test_xdr.py index 3cda447b8df..6eb46be3bc3 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_xdr.py +++ b/testsuite/MDAnalysisTests/coordinates/test_xdr.py @@ -12,7 +12,6 @@ from numpy.testing import (assert_equal, assert_array_almost_equal, dec, assert_almost_equal, assert_raises, assert_array_equal) -import tempdir from unittest import TestCase @@ -27,6 +26,7 @@ from MDAnalysisTests.coordinates.base import (BaseReaderTest, BaseReference, BaseWriterTest, assert_timestep_almost_equal) +from MDAnalysisTests import tempdir import MDAnalysis.core.AtomGroup from MDAnalysis.coordinates import XDR @@ -155,7 +155,7 @@ def test_coordinates(self): ca = U.select_atoms('name CA and resid 122') # low precision match (2 decimals in A, 3 in nm) because the above are # the trr coords - assert_array_almost_equal(ca.coordinates(), ca_Angstrom, 2, + assert_array_almost_equal(ca.positions, ca_Angstrom, 2, err_msg="coords of Ca of resid 122 do not " "match for frame 3") @@ -231,8 +231,8 @@ def test_Writer(self): assert_equal(u.trajectory.n_frames, 2) # prec = 6: TRR test fails; here I am generous and take self.prec = # 3... - assert_almost_equal(u.atoms.coordinates(), - self.universe.atoms.coordinates(), self.prec) + assert_almost_equal(u.atoms.positions, + self.universe.atoms.positions, self.prec) @dec.slow def test_EOFraisesIOErrorEIO(self): @@ -337,7 +337,7 @@ def test_coordinates(self): ca = U.select_atoms('name CA and resid 122') # low precision match because we also look at the trr: only 3 decimals # in nm in xtc! - assert_array_almost_equal(ca.coordinates(), ca_nm, 3, + assert_array_almost_equal(ca.positions, ca_nm, 3, err_msg="native coords of Ca of resid 122 " "do not match for frame 3 with " "convert_units=False") @@ -532,8 +532,8 @@ def _single_frame(self, filename): assert_equal(w.trajectory.n_frames, 1, "single frame trajectory has wrong number of frames") assert_almost_equal( - w.atoms.coordinates(), - u.atoms.coordinates(), + w.atoms.positions, + u.atoms.positions, self.prec, err_msg="coordinates do not match for {0!r}".format(filename)) diff --git a/testsuite/MDAnalysisTests/data/contacts/2F4K_qlist5_remap.dat b/testsuite/MDAnalysisTests/data/contacts/2F4K_qlist5_remap.dat new file mode 100644 index 00000000000..8ba2ba3a650 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/contacts/2F4K_qlist5_remap.dat @@ -0,0 +1,307 @@ + 1 217 4.245 + 5 217 4.299 + 20 217 3.735 + 21 211 4.215 + 21 213 3.815 + 21 217 2.646 + 7 64 4.262 + 7 72 4.160 + 7 74 4.398 + 7 76 4.333 + 7 217 4.216 + 12 70 4.408 + 12 71 4.346 + 12 72 4.150 + 12 74 3.864 + 12 76 4.277 + 12 86 4.144 + 12 134 4.261 + 12 156 4.398 + 12 547 4.185 + 16 64 4.029 + 31 72 4.026 + 31 76 4.405 + 32 72 2.824 + 32 74 3.642 + 32 76 3.301 + 35 211 4.434 + 43 92 4.160 + 43 99 4.482 + 43 102 4.443 + 44 92 3.038 + 44 94 3.923 + 44 96 3.573 + 44 99 3.395 + 44 102 3.645 + 40 208 4.155 + 40 211 4.207 + 41 205 4.072 + 41 208 3.173 + 41 211 3.051 + 41 213 4.273 + 58 114 4.116 + 59 114 3.010 + 59 116 3.902 + 59 118 3.697 + 70 124 4.106 + 70 134 4.373 + 71 124 2.970 + 71 126 3.868 + 71 128 3.624 + 71 134 3.311 + 90 140 4.108 + 90 154 4.463 + 90 183 4.167 + 91 140 2.920 + 91 142 3.771 + 91 158 4.376 + 91 144 3.628 + 91 147 4.063 + 91 154 3.587 + 91 160 4.139 + 91 183 3.971 + 76 205 4.307 + 79 154 4.047 + 79 205 3.880 + 80 144 4.464 + 80 154 3.924 + 80 183 3.505 + 80 196 3.961 + 80 197 4.286 + 80 198 3.678 + 80 200 3.908 + 80 202 4.448 + 80 205 3.961 + 86 154 3.918 + 86 156 4.030 + 86 205 4.147 + 86 213 4.060 + 86 214 3.805 + 86 217 4.370 + 82 144 4.138 + 82 147 4.227 + 82 154 3.736 + 82 183 3.749 + 82 196 3.771 + 82 197 3.714 + 82 198 3.630 + 82 200 3.556 + 82 202 4.370 + 82 205 4.320 + 82 247 3.893 + 82 250 4.227 + 82 251 4.437 + 88 154 3.703 + 88 156 3.728 + 88 205 4.480 + 88 214 4.014 + 84 147 4.277 + 84 154 3.622 + 84 156 4.014 + 84 198 4.489 + 84 200 3.972 + 84 247 3.777 + 84 250 3.675 + 84 251 3.946 + 84 257 4.082 + 92 183 3.914 + 94 160 4.358 + 94 167 4.215 + 94 182 4.270 + 94 183 3.398 + 112 160 3.747 + 112 162 4.234 + 112 167 4.455 + 112 183 4.430 + 113 160 2.755 + 113 162 3.092 + 113 165 3.808 + 113 167 3.764 + 113 183 4.450 + 96 183 3.972 + 99 182 3.950 + 99 183 3.550 + 99 184 4.115 + 99 186 3.967 + 99 192 4.245 + 102 192 4.064 + 138 522 4.457 + 139 516 4.211 + 139 519 3.803 + 139 522 3.368 + 139 525 3.913 + 130 513 3.897 + 130 516 3.881 + 130 519 4.332 + 130 547 3.800 + 134 547 4.443 + 147 251 4.472 + 148 251 4.408 + 148 253 4.137 + 148 438 4.070 + 148 516 4.010 + 150 253 4.167 + 150 451 4.006 + 150 452 3.798 + 150 438 4.056 + 150 453 4.261 + 150 455 4.387 + 150 513 4.178 + 150 516 3.754 + 156 547 4.152 + 152 547 4.025 + 171 237 3.952 + 174 237 4.329 + 174 243 4.145 + 174 245 4.432 + 174 247 4.221 + 177 241 4.317 + 177 243 3.777 + 177 245 3.658 + 177 247 3.804 + 177 250 4.443 + 177 251 4.063 + 177 296 3.767 + 178 296 4.133 + 178 443 3.986 + 196 243 4.132 + 197 243 2.955 + 197 245 3.895 + 197 247 3.586 + 220 263 4.154 + 221 263 3.007 + 221 265 3.901 + 221 267 3.608 + 231 273 4.388 + 232 273 3.289 + 232 275 4.209 + 232 277 3.901 + 242 287 3.421 + 242 289 4.448 + 242 291 4.453 + 242 294 3.827 + 261 394 4.411 + 262 389 3.459 + 262 392 3.622 + 262 393 4.469 + 262 394 3.440 + 251 443 4.139 + 257 389 4.030 + 257 392 4.231 + 257 393 4.247 + 253 384 4.003 + 253 397 4.403 + 253 398 3.918 + 253 386 4.318 + 253 438 4.069 + 253 443 3.991 + 259 384 4.287 + 259 386 3.710 + 259 389 3.682 + 259 392 3.979 + 259 393 3.777 + 259 466 4.157 + 255 384 3.692 + 255 397 3.986 + 255 398 3.588 + 255 386 3.606 + 255 389 3.958 + 255 460 4.111 + 255 466 4.008 + 265 392 4.499 + 265 394 3.917 + 271 394 3.893 + 272 394 3.560 + 287 394 4.377 + 304 389 4.422 + 304 394 3.904 + 305 389 3.915 + 305 392 3.817 + 305 394 2.859 + 291 367 4.401 + 291 389 4.079 + 296 370 4.098 + 296 443 3.702 + 318 382 4.204 + 318 389 4.284 + 318 394 4.254 + 319 382 3.019 + 319 384 4.057 + 319 386 3.959 + 319 389 3.578 + 319 392 4.253 + 319 394 4.214 + 337 399 4.120 + 337 406 4.356 + 338 399 2.921 + 338 401 3.855 + 338 403 3.631 + 338 406 3.371 + 327 406 4.186 + 329 406 3.867 + 329 409 4.212 + 329 410 3.829 + 361 416 4.055 + 361 420 4.345 + 362 416 2.998 + 362 418 3.748 + 362 420 3.302 + 380 434 4.077 + 380 441 4.476 + 381 434 2.908 + 381 436 3.811 + 381 438 3.555 + 381 441 3.395 + 381 443 3.771 + 370 443 4.362 + 373 441 4.463 + 376 441 4.157 + 376 443 4.443 + 376 447 4.229 + 397 453 4.161 + 397 460 4.377 + 398 453 3.002 + 398 455 3.936 + 398 457 3.710 + 398 460 3.370 + 398 463 3.925 + 398 466 4.420 + 401 488 4.395 + 414 472 4.221 + 414 488 4.054 + 415 472 3.077 + 415 474 4.016 + 415 476 3.703 + 415 479 3.463 + 415 482 4.063 + 415 485 4.487 + 415 488 3.641 + 403 488 3.739 + 411 488 3.852 + 432 494 4.110 + 432 498 4.370 + 433 494 2.932 + 433 496 3.694 + 433 498 3.326 + 451 509 4.142 + 451 516 4.409 + 452 509 2.978 + 452 511 3.838 + 452 513 3.541 + 452 516 3.327 + 455 556 4.086 + 470 531 4.197 + 470 538 4.117 + 470 555 4.471 + 470 556 3.691 + 471 531 3.064 + 471 533 3.699 + 471 536 3.811 + 471 538 3.000 + 471 540 3.877 + 471 555 3.862 + 471 556 3.220 + 471 542 4.353 + 457 555 4.381 + 457 556 3.736 + 463 561 4.488 diff --git a/testsuite/MDAnalysisTests/data/contacts/villin_folded.gro.bz2 b/testsuite/MDAnalysisTests/data/contacts/villin_folded.gro.bz2 new file mode 100644 index 00000000000..168bd8bbede Binary files /dev/null and b/testsuite/MDAnalysisTests/data/contacts/villin_folded.gro.bz2 differ diff --git a/testsuite/MDAnalysisTests/data/contacts/villin_unfolded.gro.bz2 b/testsuite/MDAnalysisTests/data/contacts/villin_unfolded.gro.bz2 new file mode 100644 index 00000000000..272acffd5db Binary files /dev/null and b/testsuite/MDAnalysisTests/data/contacts/villin_unfolded.gro.bz2 differ diff --git a/testsuite/MDAnalysisTests/data/cryst_then_model.pdb b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb new file mode 100644 index 00000000000..e56ffd99349 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb @@ -0,0 +1,24 @@ +REMARK For testing reading of CRYST +REMARK This has MODELs then CRYST entries +CRYST1 80.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +MODEL 1 +ATOM 1 H2 TIP3 2390 10.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +CRYST1 70.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +MODEL 2 +ATOM 1 H2 TIP3 2390 20.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +CRYST1 60.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +MODEL 3 +ATOM 1 H2 TIP3 2390 30.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +END \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.bz2 b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.bz2 new file mode 100644 index 00000000000..c338e4ae667 Binary files /dev/null and b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.bz2 differ diff --git a/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.gz b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.gz new file mode 100644 index 00000000000..c338e4ae667 Binary files /dev/null and b/testsuite/MDAnalysisTests/data/cryst_then_model.pdb.gz differ diff --git a/testsuite/MDAnalysisTests/data/empty_atom.gro b/testsuite/MDAnalysisTests/data/empty_atom.gro new file mode 100644 index 00000000000..c6d3fa08b75 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/empty_atom.gro @@ -0,0 +1,4 @@ +#Empty atom line for testing exception + 1 + + 10.00000 10.00000 10.00000 diff --git a/testsuite/MDAnalysisTests/data/grovels.gro b/testsuite/MDAnalysisTests/data/grovels.gro new file mode 100644 index 00000000000..ff303578e25 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/grovels.gro @@ -0,0 +1,7 @@ +Incomplete velocities +4 + 1248DOPC NC3 1 7.956 12.408 4.949 7.956 12.408 4.949 + 1248DOPC PO4 2 7.736 12.290 4.671 7.956 12.408 4.949 + 1248DOPC GL1 3 7.760 12.150 4.329 + 1248DOPC GL2 4 7.896 11.896 4.235 7.956 12.408 4.949 + 22.28307 22.28307 23.34569 diff --git a/testsuite/MDAnalysisTests/data/missing_atomname.gro b/testsuite/MDAnalysisTests/data/missing_atomname.gro new file mode 100644 index 00000000000..bb364d8407c --- /dev/null +++ b/testsuite/MDAnalysisTests/data/missing_atomname.gro @@ -0,0 +1,4 @@ +#Missing atom name for testing exception raise + 1 + 1RES 1 0.000 0.000 0.000 + 10.00000 10.00000 10.00000 diff --git a/testsuite/MDAnalysisTests/data/model_then_cryst.pdb b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb new file mode 100644 index 00000000000..d18e77aa96a --- /dev/null +++ b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb @@ -0,0 +1,24 @@ +REMARK For testing reading of CRYST +REMARK This has MODELs then CRYST entries +MODEL 1 +ATOM 1 H2 TIP3 2390 10.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +CRYST1 80.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +MODEL 2 +ATOM 1 H2 TIP3 2390 20.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +CRYST1 70.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +MODEL 3 +ATOM 1 H2 TIP3 2390 30.000 44.891 14.267 1.00 0.00 TIP3 +ATOM 2 OH2 TIP3 2391 67.275 48.893 23.568 1.00 0.00 TIP3 +ATOM 3 H1 TIP3 2391 66.641 48.181 23.485 1.00 0.00 TIP3 +ATOM 4 H2 TIP3 2391 66.986 49.547 22.931 1.00 0.00 TIP3 +ENDMDL +CRYST1 60.000 80.017 80.017 90.00 90.00 90.00 P 1 1 +END \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.bz2 b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.bz2 new file mode 100644 index 00000000000..ffdbcb62e63 Binary files /dev/null and b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.bz2 differ diff --git a/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.gz b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.gz new file mode 100644 index 00000000000..ffdbcb62e63 Binary files /dev/null and b/testsuite/MDAnalysisTests/data/model_then_cryst.pdb.gz differ diff --git a/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 b/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 new file mode 100644 index 00000000000..cdb22b96156 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 @@ -0,0 +1,100 @@ +@MOLECULE +ZINC00856218 + 45 47 0 0 0 +SMALL +USER_CHARGES + +@ATOM + 1 C1 -0.0173 1.4248 0.0099 C.3 1 <0> 0.0247 + 2 O1 0.0021 -0.0041 0.0020 O.3 1 <0> -0.3173 + 3 C2 -1.1998 -0.6372 0.0101 C.ar 1 <0> 0.1400 + 4 C3 -2.3735 0.1054 0.0195 C.ar 1 <0> -0.2092 + 5 C4 -3.5949 -0.5325 0.0272 C.ar 1 <0> -0.0372 + 6 C5 -3.6515 -1.9288 0.0256 C.ar 1 <0> -0.0654 + 7 C6 -2.4681 -2.6718 0.0162 C.ar 1 <0> -0.0517 + 8 C7 -1.2512 -2.0252 0.0031 C.ar 1 <0> -0.1554 + 9 C8 -4.9558 -2.6158 0.0344 C.2 1 <0> 0.1247 + 10 N1 -6.1039 -2.0314 0.0383 N.2 1 <0> -0.2180 + 11 N2 -7.1882 -2.9065 0.0465 N.am 1 <0> -0.4682 + 12 C9 -6.6839 -4.2889 0.0485 C.3 1 <0> 0.1455 + 13 H1 -7.0145 -4.8191 -0.8446 H 1 <0> 0.1012 + 14 C10 -5.1522 -4.1150 0.0337 C.3 1 <0> -0.1212 + 15 C11 -7.1318 -5.0151 1.2907 C.ar 1 <0> -0.0960 + 16 C12 -7.1732 -4.3504 2.5022 C.ar 1 <0> -0.0694 + 17 C13 -7.5842 -5.0152 3.6420 C.ar 1 <0> -0.1494 + 18 C14 -7.9546 -6.3474 3.5702 C.ar 1 <0> 0.0999 + 19 C15 -7.9127 -7.0120 2.3563 C.ar 1 <0> -0.1524 + 20 C16 -7.5063 -6.3438 1.2169 C.ar 1 <0> -0.0709 + 21 F1 -8.3561 -6.9983 4.6838 F 1 <0> -0.1406 + 22 C17 -8.4868 -2.5457 0.0523 C.2 1 <0> 0.5494 + 23 O2 -9.3524 -3.3956 0.0598 O.2 1 <0> -0.5129 + 24 C18 -8.8634 -1.0866 0.0494 C.3 1 <0> -0.0958 + 25 C19 -10.3876 -0.9533 0.0571 C.3 1 <0> -0.1702 + 26 C20 -10.7643 0.5059 0.0542 C.2 1 <0> 0.4921 + 27 O3 -9.8943 1.3600 0.0466 O.co2 1 <0> -0.6986 + 28 O4 -11.9390 0.8322 0.0594 O.co2 1 <0> -0.7025 + 29 H2 1.0053 1.8021 0.0021 H 1 <0> 0.1001 + 30 H3 -0.5445 1.7859 -0.8732 H 1 <0> 0.0585 + 31 H4 -0.5275 1.7763 0.9067 H 1 <0> 0.0584 + 32 H5 -2.3288 1.1845 0.0211 H 1 <0> 0.1341 + 33 H6 -4.5071 0.0457 0.0349 H 1 <0> 0.1415 + 34 H7 -2.5071 -3.7511 0.0149 H 1 <0> 0.1350 + 35 H8 -0.3361 -2.5986 -0.0083 H 1 <0> 0.1350 + 36 H9 -4.7239 -4.5563 -0.8663 H 1 <0> 0.1062 + 37 H10 -4.7105 -4.5589 0.9259 H 1 <0> 0.1045 + 38 H11 -6.8840 -3.3114 2.5580 H 1 <0> 0.1276 + 39 H12 -7.6162 -4.4958 4.5884 H 1 <0> 0.1363 + 40 H13 -8.2010 -8.0513 2.2992 H 1 <0> 0.1365 + 41 H14 -7.4778 -6.8608 0.2691 H 1 <0> 0.1338 + 42 H15 -8.4520 -0.6033 0.9355 H 1 <0> 0.0959 + 43 H16 -8.4615 -0.6093 -0.8444 H 1 <0> 0.0966 + 44 H17 -10.7991 -1.4365 -0.8291 H 1 <0> 0.0626 + 45 H18 -10.7895 -1.4305 0.9509 H 1 <0> 0.0624 +@BOND + 1 1 2 1 + 2 1 29 1 + 3 1 30 1 + 4 1 31 1 + 5 2 3 1 + 6 3 8 ar + 7 3 4 ar + 8 4 5 ar + 9 4 32 1 + 10 5 6 ar + 11 5 33 1 + 12 6 7 ar + 13 6 9 1 + 14 7 8 ar + 15 7 34 1 + 16 8 35 1 + 17 9 14 1 + 18 9 10 2 + 19 10 11 1 + 20 11 12 1 + 21 11 22 am + 22 12 13 1 + 23 12 14 1 + 24 12 15 1 + 25 14 36 1 + 26 14 37 1 + 27 15 20 ar + 28 15 16 ar + 29 16 17 ar + 30 16 38 1 + 31 17 18 ar + 32 17 39 1 + 33 18 19 ar + 34 18 21 1 + 35 19 20 ar + 36 19 40 1 + 37 20 41 1 + 38 22 23 2 + 39 22 24 1 + 40 24 25 1 + 41 24 42 1 + 42 24 43 1 + 43 25 26 1 + 44 25 44 1 + 45 25 45 1 + 46 26 27 2 + 47 26 28 1 diff --git a/testsuite/MDAnalysisTests/data/testENT.ent b/testsuite/MDAnalysisTests/data/testENT.ent new file mode 100644 index 00000000000..8fa1e34cb0a --- /dev/null +++ b/testsuite/MDAnalysisTests/data/testENT.ent @@ -0,0 +1,9 @@ +HEADER RIBONUCLEASE INHIBITOR 09-MAY-94 1BTA +ATOM 1 N LYS A 1 -8.655 5.770 8.371 1.00 1.40 N +ATOM 2 CA LYS A 1 -7.542 5.187 9.163 1.00 0.52 C +ATOM 3 C LYS A 1 -6.210 5.619 8.561 1.00 0.39 C +ATOM 4 O LYS A 1 -6.156 6.468 7.693 1.00 0.33 O +ATOM 5 CB LYS A 1 -7.641 3.666 9.159 1.00 1.53 C +TER 6 LYS A 1 +MASTER 97 1 0 4 3 0 0 6 1434 1 0 7 +END diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index badf3f9f857..824cc407ed2 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -48,7 +48,11 @@ "ALIGN", # Various way to align atom names in PDB files "NUCL", # nucleic acid (PDB) "INC_PDB", # incomplete PDB file (Issue #396) + # for testing cryst before/after model headers + "PDB_cm", "PDB_cm_bz2", "PDB_cm_gz", + "PDB_mc", "PDB_mc_bz2", "PDB_mc_gz", "PDB", "GRO", "XTC", "TRR", "TPR", "GRO_velocity", # Gromacs (AdK) + "GRO_incomplete_vels", "GRO_large", #atom number truncation at > 100,000 particles, Issue 550 "PDB_xvf", "TPR_xvf", "TRR_xvf", # Gromacs coords/veloc/forces (cobrotoxin, OPLS-AA, Gromacs 4.5.5 tpr) "PDB_xlserial", @@ -85,7 +89,9 @@ "TRR_multi_frame", "merge_protein", "merge_ligand", "merge_water", "mol2_molecules", "mol2_molecule", "mol2_broken_molecule", + "mol2_zinc", "capping_input", "capping_output", "capping_ace", "capping_nma", + "contacts_villin_folded", "contacts_villin_unfolded", "contacts_file", "LAMMPSdata", "trz4data", "LAMMPSdata_mini", "LAMMPSdata2", "LAMMPSdcd2", "LAMMPScnt", "LAMMPScnt2", # triclinic box @@ -109,10 +115,16 @@ "COORDINATES_TRR", "COORDINATES_TOPOLOGY", "NUCLsel", + "GRO_empty_atom", "GRO_missing_atomname", # for testing GROParser exception raise + "ENT" #for testing ENT file extension ] from pkg_resources import resource_filename +ENT = resource_filename(__name__, 'data/testENT.ent') +GRO_missing_atomname = resource_filename(__name__, 'data/missing_atomname.gro') +GRO_empty_atom = resource_filename(__name__, 'data/empty_atom.gro') + COORDINATES_XYZ = resource_filename(__name__, 'data/coordinates/test.xyz') COORDINATES_XYZ_BZ2 = resource_filename( __name__, 'data/coordinates/test.xyz.bz2') @@ -143,12 +155,19 @@ ALIGN = resource_filename(__name__, 'data/align.pdb') NUCL = resource_filename(__name__, 'data/1k5i.pdb') INC_PDB = resource_filename(__name__, 'data/incomplete.pdb') +PDB_cm = resource_filename(__name__, 'data/cryst_then_model.pdb') +PDB_cm_gz = resource_filename(__name__, 'data/cryst_then_model.pdb.gz') +PDB_cm_bz2 = resource_filename(__name__, 'data/cryst_then_model.pdb.bz2') +PDB_mc = resource_filename(__name__, 'data/model_then_cryst.pdb') +PDB_mc_gz = resource_filename(__name__, 'data/model_then_cryst.pdb.gz') +PDB_mc_bz2 = resource_filename(__name__, 'data/model_then_cryst.pdb.bz2') PDB_multiframe = resource_filename(__name__, 'data/nmr_neopetrosiamide.pdb') PDB_helix = resource_filename(__name__, 'data/A6PA6_alpha.pdb') PDB_conect = resource_filename(__name__, 'data/conect_parsing.pdb') GRO = resource_filename(__name__, 'data/adk_oplsaa.gro') GRO_velocity = resource_filename(__name__, 'data/sample_velocity_file.gro') +GRO_incomplete_vels = resource_filename(__name__, 'data/grovels.gro') GRO_large = resource_filename(__name__, 'data/bigbox.gro.bz2') PDB = resource_filename(__name__, 'data/adk_oplsaa.pdb') XTC = resource_filename(__name__, 'data/adk_oplsaa.xtc') @@ -256,12 +275,18 @@ mol2_molecules = resource_filename(__name__, "data/mol2/Molecules.mol2") mol2_molecule = resource_filename(__name__, "data/mol2/Molecule.mol2") mol2_broken_molecule = resource_filename(__name__, "data/mol2/BrokenMolecule.mol2") +# MOL2 file without substructure field +mol2_zinc = resource_filename(__name__, "data/mol2/zinc_856218.mol2") capping_input = resource_filename(__name__, "data/capping/aaqaa.gro") capping_output = resource_filename(__name__, "data/capping/maestro_aaqaa_capped.pdb") capping_ace = resource_filename(__name__, "data/capping/ace.pdb") capping_nma = resource_filename(__name__, "data/capping/nma.pdb") +contacts_villin_folded = resource_filename(__name__, "data/contacts/villin_folded.gro.bz2") +contacts_villin_unfolded = resource_filename(__name__, "data/contacts/villin_unfolded.gro.bz2") +contacts_file = resource_filename(__name__, "data/contacts/2F4K_qlist5_remap.dat") + trz4data = resource_filename(__name__, "data/lammps/datatest.trz") LAMMPSdata = resource_filename(__name__, "data/lammps/datatest.data") LAMMPSdata_mini = resource_filename(__name__, "data/lammps/mini.data") diff --git a/testsuite/MDAnalysisTests/tempdir.py b/testsuite/MDAnalysisTests/tempdir.py new file mode 100644 index 00000000000..3e865d58632 --- /dev/null +++ b/testsuite/MDAnalysisTests/tempdir.py @@ -0,0 +1,91 @@ +# License +# ------- +# MIT License +# +# Copyright (c) 2010-2016 Thomas Fenzl +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +import os +import tempfile +import shutil +from functools import wraps + + +class TempDir(object): + """ class for temporary directories +creates a (named) directory which is deleted after use. +All files created within the directory are destroyed +Might not work on windows when the files are still opened +""" + def __init__(self, suffix="", prefix="tmp", basedir=None): + self.name = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=basedir) + + def __del__(self): + try: + if self.name: + self.dissolve() + except AttributeError: + pass + + def __enter__(self): + return self.name + + def __exit__(self, *errstuff): + self.dissolve() + + def dissolve(self): + """remove all files and directories created within the tempdir""" + if self.name: + shutil.rmtree(self.name) + self.name = "" + + def __str__(self): + if self.name: + return "temporary directory at: {}".format(self.name,) + else: + return "dissolved temporary directory" + + +class in_tempdir(object): + """Create a temporary directory and change to it. """ + + def __init__(self, *args, **kwargs): + self.tmpdir = TempDir(*args, **kwargs) + + def __enter__(self): + self.old_path = os.getcwd() + os.chdir(self.tmpdir.name) + return self.tmpdir.name + + def __exit__(self, *errstuff): + os.chdir(self.old_path) + self.tmpdir.dissolve() + + +def run_in_tempdir(*args, **kwargs): + """Make a function execute in a new tempdir. + Any time the function is called, a new tempdir is created and destroyed. + """ + def change_dird(fnc): + @wraps(fnc) + def wrapper(*funcargs, **funckwargs): + with in_tempdir(*args, **kwargs): + return fnc(*funcargs, **funckwargs) + return wrapper + return change_dird diff --git a/testsuite/MDAnalysisTests/test_altloc.py b/testsuite/MDAnalysisTests/test_altloc.py index a8c90fe39b7..5bef06f99e5 100644 --- a/testsuite/MDAnalysisTests/test_altloc.py +++ b/testsuite/MDAnalysisTests/test_altloc.py @@ -14,10 +14,10 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # from MDAnalysis import Universe -import tempdir import os from numpy.testing import TestCase, assert_equal from MDAnalysisTests.datafiles import PDB_full +from MDAnalysisTests import tempdir class TestAltloc(TestCase): diff --git a/testsuite/MDAnalysisTests/test_atomgroup.py b/testsuite/MDAnalysisTests/test_atomgroup.py index 836967c6852..b434769d900 100644 --- a/testsuite/MDAnalysisTests/test_atomgroup.py +++ b/testsuite/MDAnalysisTests/test_atomgroup.py @@ -34,10 +34,9 @@ from nose.plugins.attrib import attr import os -import tempdir import itertools -from MDAnalysisTests import parser_not_found +from MDAnalysisTests import parser_not_found, tempdir class TestAtom(TestCase): @@ -131,8 +130,8 @@ def test_undefined_occupancy(self): def test_set_undefined_occupancy(self): self.universe.atoms[0].occupancy = .5 - assert self.universe.atoms[0].occupancy == .5 - assert self.universe.atoms[1].occupancy == 1 + assert_equal(self.universe.atoms[0].occupancy, .5) + assert_equal(self.universe.atoms[1].occupancy, 1) class TestAtomComparisons(object): @@ -316,7 +315,7 @@ def test_center_of_mass(self): def test_coordinates(self): assert_array_almost_equal( - self.ag.coordinates()[1000:2000:200], + self.ag.positions[1000:2000:200], np.array([[3.94543672, -12.4060812, -7.26820087], [13.21632767, 5.879035, -14.67914867], [12.07735443, -9.00604534, 4.09301519], @@ -580,7 +579,7 @@ def test_packintobox(self): # Provide arbitrary box ag.pack_into_box(box=np.array([5., 5., 5.], dtype=np.float32)) assert_array_almost_equal( - ag.coordinates(), + ag.positions, np.array([[3.94543672, 2.5939188, 2.73179913], [3.21632767, 0.879035, 0.32085133], [2.07735443, 0.99395466, 4.09301519], @@ -788,7 +787,7 @@ def test_positions(self): pos = ag.positions + 3.14 ag.positions = pos # should work - assert_almost_equal(ag.coordinates(), pos, + assert_almost_equal(ag.positions, pos, err_msg="failed to update atoms 12:42 position " "to new position") @@ -802,7 +801,7 @@ def test_set_positions(self): ag = self.universe.select_atoms("bynum 12:42") pos = ag.get_positions() + 3.14 ag.set_positions(pos) - assert_almost_equal(ag.coordinates(), pos, + assert_almost_equal(ag.positions, pos, err_msg="failed to update atoms 12:42 position " "to new position") @@ -1531,7 +1530,7 @@ def universe_from_tmp(self): def test_write_atoms(self): self.universe.atoms.write(self.outfile) u2 = self.universe_from_tmp() - assert_array_almost_equal(self.universe.atoms.coordinates(), u2.atoms.coordinates(), self.precision, + assert_array_almost_equal(self.universe.atoms.positions, u2.atoms.positions, self.precision, err_msg="atom coordinate mismatch between original and {0!s} file".format(self.ext)) def test_write_empty_atomgroup(self): @@ -1544,7 +1543,7 @@ def test_write_selection(self): u2 = self.universe_from_tmp() CA2 = u2.select_atoms('all') # check EVERYTHING, otherwise we might get false positives! assert_equal(len(u2.atoms), len(CA.atoms), "written CA selection does not match original selection") - assert_almost_equal(CA2.coordinates(), CA.coordinates(), self.precision, + assert_almost_equal(CA2.positions, CA.positions, self.precision, err_msg="CA coordinates do not agree with original") def test_write_Residue(self): @@ -1553,7 +1552,7 @@ def test_write_Residue(self): u2 = self.universe_from_tmp() G2 = u2.select_atoms('all') # check EVERYTHING, otherwise we might get false positives! assert_equal(len(u2.atoms), len(G.atoms), "written R206 Residue does not match original ResidueGroup") - assert_almost_equal(G2.coordinates(), G.coordinates(), self.precision, + assert_almost_equal(G2.positions, G.positions, self.precision, err_msg="Residue R206 coordinates do not agree with original") def test_write_ResidueGroup(self): @@ -1562,7 +1561,7 @@ def test_write_ResidueGroup(self): u2 = self.universe_from_tmp() G2 = u2.select_atoms('all') # check EVERYTHING, otherwise we might get false positives! assert_equal(len(u2.atoms), len(G.atoms), "written LEU ResidueGroup does not match original ResidueGroup") - assert_almost_equal(G2.coordinates(), G.coordinates(), self.precision, + assert_almost_equal(G2.positions, G.positions, self.precision, err_msg="ResidueGroup LEU coordinates do not agree with original") def test_write_Segment(self): @@ -1571,7 +1570,7 @@ def test_write_Segment(self): u2 = self.universe_from_tmp() G2 = u2.select_atoms('all') # check EVERYTHING, otherwise we might get false positives! assert_equal(len(u2.atoms), len(G.atoms), "written s4AKE segment does not match original segment") - assert_almost_equal(G2.coordinates(), G.coordinates(), self.precision, + assert_almost_equal(G2.positions, G.positions, self.precision, err_msg="segment s4AKE coordinates do not agree with original") def test_write_Universe(self): @@ -1581,7 +1580,7 @@ def test_write_Universe(self): W.close() u2 = self.universe_from_tmp() assert_equal(len(u2.atoms), len(U.atoms), "written 4AKE universe does not match original universe in size") - assert_almost_equal(u2.atoms.coordinates(), U.atoms.coordinates(), self.precision, + assert_almost_equal(u2.atoms.positions, U.atoms.positions, self.precision, err_msg="written universe 4AKE coordinates do not agree with original") @@ -1661,20 +1660,6 @@ def test_load_new(self): u.load_new(PDB_small) assert_equal(len(u.trajectory), 1, "Failed to load_new(PDB)") - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def test_load_new_strict(self): - u = MDAnalysis.Universe(PSF, DCD) - u.load_new(PDB_small, permissive=False) - assert_equal(len(u.trajectory), 1, "Failed to load_new(PDB, permissive=False)") - - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def test_load_new_permissive(self): - u = MDAnalysis.Universe(PSF, DCD) - u.load_new(PDB_small, permissive=True) - assert_equal(len(u.trajectory), 1, "Failed to load_new(PDB, permissive=True)") - @dec.skipif(parser_not_found('DCD'), 'DCD parser not available. Are you using python 3?') def test_load_new_TypeError(self): @@ -1724,6 +1709,19 @@ def test_set_dimensions(self): u.dimensions = np.array([10, 11, 12, 90, 90, 90]) assert_allclose(u.dimensions, box) + @staticmethod + def test_universe_kwargs(): + u = MDAnalysis.Universe(PSF, PDB_small, fake_kwarg=True) + assert_equal(len(u.atoms), 3341, "Loading universe failed somehow") + + assert_(u.kwargs['fake_kwarg'] is True) + + # initialize new universe from pieces of existing one + u2 = MDAnalysis.Universe(u.filename, u.trajectory.filename, + **u.kwargs) + + assert_(u2.kwargs['fake_kwarg'] is True) + assert_equal(u.kwargs, u2.kwargs) class TestPBCFlag(TestCase): @dec.skipif(parser_not_found('TRZ'), @@ -2121,11 +2119,13 @@ def _check_universe(self, u): assert_equal(len(u.atoms[3].bonds), 2) assert_equal(len(u.atoms[4].bonds), 1) assert_equal(len(u.atoms[5].bonds), 1) + assert_('guess_bonds' in u.kwargs) def test_universe_guess_bonds(self): """Test that making a Universe with guess_bonds works""" u = MDAnalysis.Universe(two_water_gro, guess_bonds=True) self._check_universe(u) + assert_(u.kwargs['guess_bonds'] is True) def test_universe_guess_bonds_no_vdwradii(self): """Make a Universe that has atoms with unknown vdwradii.""" @@ -2136,6 +2136,8 @@ def test_universe_guess_bonds_with_vdwradii(self): u = MDAnalysis.Universe(two_water_gro_nonames, guess_bonds=True, vdwradii=self.vdw) self._check_universe(u) + assert_(u.kwargs['guess_bonds'] is True) + assert_equal(self.vdw, u.kwargs['vdwradii']) def test_universe_guess_bonds_off(self): u = MDAnalysis.Universe(two_water_gro_nonames, guess_bonds=False) @@ -2143,6 +2145,7 @@ def test_universe_guess_bonds_off(self): assert_equal(len(u.bonds), 0) assert_equal(len(u.angles), 0) assert_equal(len(u.dihedrals), 0) + assert_(u.kwargs['guess_bonds'] is False) def _check_atomgroup(self, ag, u): """Verify that the AtomGroup made bonds correctly, diff --git a/testsuite/MDAnalysisTests/test_atomselections.py b/testsuite/MDAnalysisTests/test_atomselections.py index 1b610a5578c..73a21feb91b 100644 --- a/testsuite/MDAnalysisTests/test_atomselections.py +++ b/testsuite/MDAnalysisTests/test_atomselections.py @@ -139,7 +139,7 @@ def test_atom(self): assert_equal(len(sel), 1) assert_equal(sel.resnames, ['GLY']) assert_array_almost_equal( - sel.coordinates(), + sel.positions, np.array([[20.38685226, -3.44224262, -5.92158318]], dtype=np.float32)) diff --git a/package/MDAnalysis/coordinates/pdb/__init__.py b/testsuite/MDAnalysisTests/test_authors.py similarity index 51% rename from package/MDAnalysis/coordinates/pdb/__init__.py rename to testsuite/MDAnalysisTests/test_authors.py index 91c033d99c8..d799980c410 100644 --- a/package/MDAnalysis/coordinates/pdb/__init__.py +++ b/testsuite/MDAnalysisTests/test_authors.py @@ -1,9 +1,9 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 # # MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein -# and contributors (see AUTHORS for the full list) +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) # # Released under the GNU Public Licence, v2 or any higher version # @@ -13,8 +13,16 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +from numpy.testing import assert_ +import MDAnalysis -"""Helper module for handling PDB files.""" +def test_package_authors(): + assert_(len(MDAnalysis.__authors__) > 0, + 'Could not find the list of authors') -from . import extensions + +def test_testsuite_authors(): + from MDAnalysisTests import __authors__ + assert_(len(__authors__) > 0, + 'Could not find the list of authors') diff --git a/testsuite/MDAnalysisTests/test_distances.py b/testsuite/MDAnalysisTests/test_distances.py index fe78bb4e61e..87d92d47a19 100644 --- a/testsuite/MDAnalysisTests/test_distances.py +++ b/testsuite/MDAnalysisTests/test_distances.py @@ -106,9 +106,9 @@ def tearDown(self): def test_simple(self): U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions self.trajectory[10] - x1 = U.atoms.coordinates(copy=True) + x1 = U.atoms.positions d = MDAnalysis.lib.distances.distance_array(x0, x1, backend=self.backend) assert_equal(d.shape, (3341, 3341), "wrong shape (should be (Natoms,Natoms))") assert_almost_equal(d.min(), 0.11981228170520701, self.prec, @@ -119,9 +119,9 @@ def test_simple(self): def test_outarray(self): U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions self.trajectory[10] - x1 = U.atoms.coordinates(copy=True) + x1 = U.atoms.positions natoms = len(U.atoms) d = np.zeros((natoms, natoms), np.float64) MDAnalysis.lib.distances.distance_array(x0, x1, result=d, backend=self.backend) @@ -135,9 +135,9 @@ def test_periodic(self): # boring with the current dcd as that has no PBC U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions self.trajectory[10] - x1 = U.atoms.coordinates(copy=True) + x1 = U.atoms.positions d = MDAnalysis.lib.distances.distance_array(x0, x1, box=U.coord.dimensions, backend=self.backend) assert_equal(d.shape, (3341, 3341), "should be square matrix with Natoms entries") @@ -173,7 +173,7 @@ def tearDown(self): def test_simple(self): U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions d = MDAnalysis.lib.distances.self_distance_array(x0, backend=self.backend) N = 3341 * (3341 - 1) / 2 assert_equal(d.shape, (N,), "wrong shape (should be (Natoms*(Natoms-1)/2,))") @@ -185,7 +185,7 @@ def test_simple(self): def test_outarray(self): U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions natoms = len(U.atoms) N = natoms * (natoms - 1) / 2 d = np.zeros((N,), np.float64) @@ -200,7 +200,7 @@ def test_periodic(self): # boring with the current dcd as that has no PBC U = self.universe self.trajectory.rewind() - x0 = U.atoms.coordinates(copy=True) + x0 = U.atoms.positions natoms = len(U.atoms) N = natoms * (natoms - 1) / 2 d = MDAnalysis.lib.distances.self_distance_array(x0, box=U.coord.dimensions, @@ -575,7 +575,7 @@ def test_ortho_PBC(self): from MDAnalysis.lib.distances import apply_PBC U = MDAnalysis.Universe(PSF, DCD) - atoms = U.atoms.coordinates() + atoms = U.atoms.positions box1 = np.array([2.5, 2.5, 3.5], dtype=np.float32) box2 = np.array([2.5, 2.5, 3.5, 90., 90., 90.], dtype=np.float32) @@ -592,7 +592,7 @@ def test_tric_PBC(self): from MDAnalysis.lib.distances import apply_PBC U = MDAnalysis.Universe(TRIC) - atoms = U.atoms.coordinates() + atoms = U.atoms.positions box1 = U.dimensions box2 = MDAnalysis.coordinates.core.triclinic_vectors(box1) diff --git a/testsuite/MDAnalysisTests/test_log.py b/testsuite/MDAnalysisTests/test_log.py index d6b4731d835..13f82819797 100644 --- a/testsuite/MDAnalysisTests/test_log.py +++ b/testsuite/MDAnalysisTests/test_log.py @@ -19,7 +19,6 @@ import sys import os -import tempdir import logging from numpy.testing import TestCase, assert_ @@ -29,6 +28,8 @@ import MDAnalysis import MDAnalysis.lib.log +from MDAnalysisTests import tempdir + class TestLogging(TestCase): name = "MDAnalysis" diff --git a/testsuite/MDAnalysisTests/test_modelling.py b/testsuite/MDAnalysisTests/test_modelling.py index a2d1979b730..1a54f38acc7 100644 --- a/testsuite/MDAnalysisTests/test_modelling.py +++ b/testsuite/MDAnalysisTests/test_modelling.py @@ -22,7 +22,7 @@ import MDAnalysis.core.AtomGroup from MDAnalysis.core.AtomGroup import Atom, AtomGroup from MDAnalysis import NoDataError -from MDAnalysisTests import parser_not_found +from MDAnalysisTests import parser_not_found, tempdir import numpy as np from numpy.testing import (TestCase, dec, assert_equal, assert_raises, assert_, @@ -30,7 +30,6 @@ from nose.plugins.attrib import attr import os -import tempdir from MDAnalysis import Universe, Merge from MDAnalysis.analysis.align import alignto diff --git a/testsuite/MDAnalysisTests/test_nuclinfo.py b/testsuite/MDAnalysisTests/test_nuclinfo.py index f5d1e6e8816..59c202aa94b 100644 --- a/testsuite/MDAnalysisTests/test_nuclinfo.py +++ b/testsuite/MDAnalysisTests/test_nuclinfo.py @@ -29,9 +29,8 @@ from MDAnalysis.analysis import nuclinfo from MDAnalysis.tests.datafiles import NUCL -from numpy.testing import * +from numpy.testing import assert_almost_equal ,assert_array_almost_equal, TestCase -del test from nose.plugins.attrib import attr diff --git a/testsuite/MDAnalysisTests/test_persistence.py b/testsuite/MDAnalysisTests/test_persistence.py index ac6f880bce8..46aad0e4729 100644 --- a/testsuite/MDAnalysisTests/test_persistence.py +++ b/testsuite/MDAnalysisTests/test_persistence.py @@ -30,7 +30,6 @@ import gc import shutil import warnings -import tempdir class TestAtomGroupPickle(TestCase): diff --git a/testsuite/MDAnalysisTests/test_streamio.py b/testsuite/MDAnalysisTests/test_streamio.py index d4a79f89738..4bf917f4bba 100644 --- a/testsuite/MDAnalysisTests/test_streamio.py +++ b/testsuite/MDAnalysisTests/test_streamio.py @@ -27,11 +27,10 @@ import MDAnalysis.tests.datafiles as datafiles from MDAnalysisTests.coordinates.reference import RefAdKSmall from MDAnalysisTests.plugins.knownfailure import knownfailure +from MDAnalysisTests import tempdir import os -import tempdir - class TestIsstream(TestCase): def test_hasmethod(self): @@ -334,10 +333,10 @@ def test_PrimitivePDBReader(self): u = MDAnalysis.Universe(streamData.as_NamedStream('PDB')) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) - @knownfailure() + def test_PDBReader(self): try: - u = MDAnalysis.Universe(streamData.as_NamedStream('PDB'), permissive=False) + u = MDAnalysis.Universe(streamData.as_NamedStream('PDB')) except Exception as err: raise AssertionError("StreamIO not supported:\n>>>>> {0}".format(err)) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) diff --git a/testsuite/MDAnalysisTests/test_topology.py b/testsuite/MDAnalysisTests/test_topology.py index 5e9f7e6e406..6442a58f881 100644 --- a/testsuite/MDAnalysisTests/test_topology.py +++ b/testsuite/MDAnalysisTests/test_topology.py @@ -166,14 +166,9 @@ def tearDown(self): del self.universe def test_correct_parser(self): - """Check that get_parser returns the intended parser""" - try: - perm = self.perm - except AttributeError: - perm = False - ret = get_parser_for(self.topology, permissive=perm) - - assert_equal(self.parser, ret) + """Check that get_parser returns the intended parser""" + ret = get_parser_for(self.topology) + assert_equal(self.parser, ret) def test_parser(self): """Check that the parser works as intended, @@ -1005,12 +1000,6 @@ class RefPDB(object): ref_n_atoms = 3341 ref_numresidues = 214 - -class RefPDB_Perm(RefPDB): - perm = True - parser = MDAnalysis.topology.PrimitivePDBParser.PrimitivePDBParser - - class TestPDB(_TestTopology, RefPDB): """Testing PDB topology parsing (PrimitivePDB)""" @staticmethod @@ -1047,10 +1036,6 @@ def test_conect_topo_parser(self): p.parse() -class TestPDB_Perm(_TestTopology, RefPDB_Perm): - pass - - class RefXPDB(object): topology = PDB parser = MDAnalysis.topology.ExtendedPDBParser.ExtendedPDBParser diff --git a/testsuite/MDAnalysisTests/test_tprparser.py b/testsuite/MDAnalysisTests/test_tprparser.py index 34ba9cbd75b..a8cd25ea606 100644 --- a/testsuite/MDAnalysisTests/test_tprparser.py +++ b/testsuite/MDAnalysisTests/test_tprparser.py @@ -18,7 +18,7 @@ TPR450, TPR451, TPR452, TPR453, TPR454, TPR455, TPR455Double, \ TPR460, TPR461, TPR502, TPR504, TPR505, TPR510, TPR510_bonded -from numpy.testing import TestCase, dec +from numpy.testing import TestCase, dec, assert_ from test_topology import _TestTopology import MDAnalysis.topology.TPRParser @@ -246,8 +246,8 @@ def _test_is_in_topology(name, elements, topology_section, topology_path): """ universe = MDAnalysis.Universe(topology_path) for element in elements: - assert element in universe._topology[topology_section], \ - 'Interaction type "{0}" not found'.format(name) + assert_(element in universe._topology[topology_section],\ + 'Interaction type "{0}" not found'.format(name)) def test_all_bonds(): diff --git a/testsuite/MDAnalysisTests/test_transformations.py b/testsuite/MDAnalysisTests/test_transformations.py index 7979689985c..7f19daf6f5f 100644 --- a/testsuite/MDAnalysisTests/test_transformations.py +++ b/testsuite/MDAnalysisTests/test_transformations.py @@ -137,7 +137,7 @@ def test_rotation_matrix(self): class TestRotationMatrixNP(_RotationMatrix): f = staticmethod(t._py_rotation_matrix) - + class TestRotationMatrixCy(_RotationMatrix): f = staticmethod(t.rotation_matrix) @@ -297,8 +297,11 @@ def test_shear_from_matrix(): # direct = np.random.random(3) - 0.5 # point = np.random.random(3) - 0.5 # normal = np.cross(direct, np.random.random(3)) + # In this random configuration the test will fail about 0.05% of all times. + # Then we hit some edge-cases of the algorithm. The edge cases for these + # values are slightly different for the linalg library used (MKL/LAPACK). # So here are some of my random numbers - angle = 2.8965075413405783 + angle = 2.8969075413405783 direct = np.array([-0.31117458, -0.41769518, -0.01188556]) point = np.array([-0.0035982, -0.40997482, 0.42241425]) normal = np.cross(direct, np.array([ 0.08122421, 0.4747914 , 0.19851859])) @@ -587,7 +590,7 @@ def test_quaternion_slerp(self): class TestQuaternionSlerpNP(_QuaternionSlerp): f = staticmethod(t._py_quaternion_slerp) - + class TestQuaternionSlerpCy(_QuaternionSlerp): f = staticmethod(t.quaternion_slerp) diff --git a/testsuite/MDAnalysisTests/test_units.py b/testsuite/MDAnalysisTests/test_units.py index 09f2465e946..27e63c69630 100644 --- a/testsuite/MDAnalysisTests/test_units.py +++ b/testsuite/MDAnalysisTests/test_units.py @@ -17,40 +17,37 @@ import six import numpy as np -from numpy.testing import * +from numpy.testing import assert_equal, assert_almost_equal, assert_raises,TestCase from MDAnalysis import units from MDAnalysis.core import flags class TestDefaultUnits(TestCase): - def testLength(self): + @staticmethod + def test_length(): assert_equal(flags['length_unit'], 'Angstrom', u"The default length unit should be Angstrom (in core.flags)") - - def testTime(self): + @staticmethod + def test_time(): assert_equal(flags['time_unit'], 'ps', u"The default length unit should be pico seconds (in core.flags)") - - def testConvertGromacsTrajectories(self): + @staticmethod + def test_convert_gromacs_trajectories(): assert_equal(flags['convert_lengths'], True, u"The default behaviour should be to auto-convert Gromacs trajectories") class TestUnitEncoding(TestCase): - def testUnicode(self): + @staticmethod + def test_unicode(): try: assert_equal(units.lengthUnit_factor[u"\u212b"], 1.0) except KeyError: raise AssertionError("Unicode symbol for Angtrom not supported") - def testUTF8Encoding(self): - try: - assert_equal(units.lengthUnit_factor[b'\xe2\x84\xab'.decode('utf-8')], 1.0) - except KeyError: - raise AssertionError("UTF-8-encoded symbol for Angtrom not supported") - - def testUnicodeEncodingWithSymbol(self): + @staticmethod + def test_unicode_encoding_with_symbol(): try: assert_equal(units.lengthUnit_factor[u"Å"], 1.0) except KeyError: @@ -84,23 +81,34 @@ def _assert_almost_equal_convert(value, u1, u2, ref): err_msg="Conversion {0} --> {1} failed".format(u1, u2)) # generate individual test cases using nose's test generator mechanism - def testLength(self): + def test_length(self): nm = 12.34567 A = nm * 10. yield self._assert_almost_equal_convert, nm, 'nm', 'A', A yield self._assert_almost_equal_convert, A, 'Angstrom', 'nm', nm - def testTime(self): + def test_time(self): yield self._assert_almost_equal_convert, 1, 'ps', 'AKMA', 20.45482949774598 yield self._assert_almost_equal_convert, 1, 'AKMA', 'ps', 0.04888821 - def testEnergy(self): + def test_energy(self): yield self._assert_almost_equal_convert, 1, 'kcal/mol', 'kJ/mol', 4.184 yield self._assert_almost_equal_convert, 1, 'kcal/mol', 'eV', 0.0433641 - def testForce(self): + def test_force(self): yield self._assert_almost_equal_convert, 1, 'kJ/(mol*A)', 'J/m', 1.66053892103219e-11 yield self._assert_almost_equal_convert, 2.5, 'kJ/(mol*nm)', 'kJ/(mol*A)', 0.25 yield self._assert_almost_equal_convert, 1, 'kcal/(mol*Angstrom)', 'kJ/(mol*Angstrom)', 4.184 + @staticmethod + def test_unit_unknown(): + nm = 12.34567 + assert_raises(ValueError, units.convert, nm, 'Stone', 'nm') + assert_raises(ValueError, units.convert, nm, 'nm', 'Stone') + + @staticmethod + def test_unit_unconvertable(): + nm = 12.34567 + A = nm * 10. + assert_raises(ValueError, units.convert, A, 'A', 'ps') diff --git a/testsuite/MDAnalysisTests/test_util.py b/testsuite/MDAnalysisTests/test_util.py index eb1a346b474..05053dbce90 100644 --- a/testsuite/MDAnalysisTests/test_util.py +++ b/testsuite/MDAnalysisTests/test_util.py @@ -531,22 +531,22 @@ class TestFixedwidthBins(object): def test_keys(self): ret = util.fixedwidth_bins(0.5, 1.0, 2.0) for k in ['Nbins', 'delta', 'min', 'max']: - assert k in ret + assert_(k in ret) def test_VE(self): assert_raises(ValueError, util.fixedwidth_bins, 0.1, 5.0, 4.0) def test_usage_1(self): ret = util.fixedwidth_bins(0.1, 4.0, 5.0) - assert ret['Nbins'] == 10 - assert ret['delta'] == 0.1 - assert ret['min'] == 4.0 - assert ret['max'] == 5.0 + assert_equal(ret['Nbins'], 10) + assert_equal(ret['delta'], 0.1) + assert_equal(ret['min'], 4.0) + assert_equal(ret['max'], 5.0) def test_usage_2(self): ret = util.fixedwidth_bins(0.4, 4.0, 5.0) - assert ret['Nbins'] == 3 - assert ret['delta'] == 0.4 + assert_equal(ret['Nbins'], 3) + assert_equal(ret['delta'], 0.4) assert_almost_equal(ret['min'], 3.9) assert_almost_equal(ret['max'], 5.1) @@ -603,24 +603,24 @@ def _check_get_ext(self, f, fn): """Check that get_ext works""" a, b = util.get_ext(fn) - assert a == 'file' - assert b == f.lower() + assert_equal(a, 'file') + assert_equal(b, f.lower()) def _check_compressed(self, f, fn): """Check that format suffixed by compressed extension works""" a = util.format_from_filename_extension(fn) - assert a == f + assert_equal(a, f) def _check_guess_format(self, f, fn): a = util.guess_format(fn) - assert a == f + assert_equal(a, f) def _check_get_parser(self, fn, P): a = mda.topology.core.get_parser_for(fn) - assert a == P + assert_equal(a, P) def _check_get_parser_invalid(self, fn): assert_raises(ValueError, mda.topology.core.get_parser_for, fn) @@ -628,7 +628,7 @@ def _check_get_parser_invalid(self, fn): def _check_get_reader(self, fn, R): a = mda.coordinates.core.get_reader_for(fn) - assert a == R + assert_equal(a, R) def _check_get_reader_invalid(self, fn): assert_raises(ValueError, mda.coordinates.core.get_reader_for, fn) diff --git a/testsuite/MDAnalysisTests/test_velocities_forces.py b/testsuite/MDAnalysisTests/test_velocities_forces.py index 4a9b9cd1e27..d2e62e66b2d 100644 --- a/testsuite/MDAnalysisTests/test_velocities_forces.py +++ b/testsuite/MDAnalysisTests/test_velocities_forces.py @@ -16,7 +16,10 @@ import MDAnalysis import numpy as np -from numpy.testing import * +from numpy.testing import ( + assert_equal, assert_almost_equal, TestCase, + assert_array_equal, +) from nose.plugins.attrib import attr from MDAnalysis.tests.datafiles import GRO_velocity, PDB_xvf, TRR_xvf @@ -51,6 +54,30 @@ def test_atom_velocity_set(self): assert_equal(self.a.velocity, ref) assert_equal(self.u.atoms.velocities[0], ref) + def test_pos_iteration(self): + ag = self.u.atoms[[0]] + + val = np.array([self.a.position for ts in self.u.trajectory]) + ref = np.array([ag.positions[0] for ts in self.u.trajectory]) + + assert_array_equal(val, ref) + + def test_vel_iteration(self): + ag = self.u.atoms[[0]] + + val = np.array([self.a.velocity for ts in self.u.trajectory]) + ref = np.array([ag.velocities[0] for ts in self.u.trajectory]) + + assert_array_equal(val, ref) + + def test_for_iteration(self): + ag = self.u.atoms[[0]] + + val = np.array([self.a.force for ts in self.u.trajectory]) + ref = np.array([ag.forces[0] for ts in self.u.trajectory]) + + assert_array_equal(val, ref) + class TestGROVelocities(TestCase): def setUp(self): diff --git a/testsuite/MDAnalysisTests/topology/test_gro.py b/testsuite/MDAnalysisTests/topology/test_gro.py index 32b5653217e..3ac15411938 100644 --- a/testsuite/MDAnalysisTests/topology/test_gro.py +++ b/testsuite/MDAnalysisTests/topology/test_gro.py @@ -14,12 +14,15 @@ # from numpy.testing import ( assert_, + assert_raises, ) import MDAnalysis as mda from MDAnalysisTests.datafiles import ( two_water_gro_widebox, + GRO_empty_atom, + GRO_missing_atomname, ) @@ -31,3 +34,14 @@ def test_atoms(self): with parser(two_water_gro_widebox) as p: s = p.parse() assert_(len(s['atoms']) == 6) + +def test_parse_empty_atom_IOerror(): + parser = mda.topology.GROParser.GROParser + with parser(GRO_empty_atom) as p: + assert_raises(IOError, p.parse) + +def test_parse_missing_atomname_IOerror(): + parser = mda.topology.GROParser.GROParser + with parser(GRO_missing_atomname) as p: + assert_raises(IOError, p.parse) + diff --git a/testsuite/setup.py b/testsuite/setup.py index c01e7d2401e..e2c594571fa 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -36,22 +36,98 @@ Google groups forbids any name that contains the string `anal'.) """ from __future__ import print_function -from setuptools import setup, Extension, find_packages +from setuptools import setup, find_packages +import codecs import sys -import os -import glob +import warnings + + +def dynamic_author_list(): + """Generate __authors__ from AUTHORS + + This function generates authors.py that contains the list of the + authors from the AUTHORS file. This avoids having that list maintained in + several places. Note that AUTHORS is sorted chronologically while we want + __authors__ in authors.py to be sorted alphabetically. + + The authors are written in AUTHORS as bullet points under the + "Chronological list of authors" title. + """ + authors = [] + with codecs.open('AUTHORS', encoding='utf-8') as infile: + # An author is a bullet point under the title "Chronological list of + # authors". We first want move the cursor down to the title of + # interest. + for line_no, line in enumerate(infile, start=1): + if line[:-1] == "Chronological list of authors": + break + else: + # If we did not break, it means we did not find the authors. + raise IOError('EOF before the list of authors') + # Skip the next line as it is the title underlining + line = next(infile) + line_no += 1 + if line[:4] != '----': + raise IOError('Unexpected content on line {0}, ' + 'should be a string of "-".'.format(line_no)) + # Add each bullet point as an author until the next title underlining + for line in infile: + if line[:4] in ('----', '====', '~~~~'): + # The previous line was a title, hopefully it did not start as + # a bullet point so it got ignored. Since we hit a title, we + # are done reading the list of authors. + break + elif line.strip()[:2] == '- ': + # This is a bullet point, so it should be an author name. + name = line.strip()[2:].strip() + authors.append(name) + + # So far, the list of authors is sorted chronologically. We want it + # sorted alphabetically of the last name. + authors.sort(key=lambda name: name.split()[-1]) + # Move Naveen and Elizabeth first, and Oliver last. + authors.remove('Naveen Michaud-Agrawal') + authors.remove('Elizabeth J. Denning') + authors.remove('Oliver Beckstein') + authors = (['Naveen Michaud-Agrawal', 'Elizabeth J. Denning'] + + authors + ['Oliver Beckstein']) + + # Write the authors.py file. + out_path = 'MDAnalysisTests/authors.py' + with codecs.open(out_path, 'w', encoding='utf-8') as outfile: + # Write the header + header = '''\ +#-*- coding:utf-8 -*- + +# This file is generated from the AUTHORS file during the installation process. +# Do not edit it as your changes will be overwritten. +''' + print(header, file=outfile) + + # Write the list of authors as a python list + template = u'__authors__ = [\n{}\n]' + author_string = u',\n'.join(u' u"{}"'.format(name) + for name in authors) + print(template.format(author_string), file=outfile) + # Make sure I have the right Python version. if sys.version_info[:2] < (2, 7): - print("MDAnalysis requires Python 2.7 or better. Python {0:d}.{1:d} detected".format(* - sys.version_info[:2])) + print("MDAnalysis requires Python 2.7 or better. " + "Python {0:d}.{1:d} detected".format(*sys.version_info[:2])) print("Please upgrade your version of Python.") sys.exit(-1) if __name__ == '__main__': - RELEASE = "0.14.0" # this must be in-sync with MDAnalysis + try: + dynamic_author_list() + except (OSError, IOError): + warnings.warn('Cannot write the list of authors.') + + RELEASE = "0.15.0" # this must be in-sync with MDAnalysis + LONG_DESCRIPTION = \ """MDAnalysis is a tool for analyzing molecular dynamics trajectories. @@ -79,47 +155,53 @@ setup(name='MDAnalysisTests', version=RELEASE, - description='Python tools to support analysis of trajectories (test cases)', + description='MDAnalysis http://mdanalysis.org testsuite', author='Naveen Michaud-Agrawal', author_email='naveen.michaudagrawal@gmail.com', + maintainer='Richard Gowers', + maintainer_email='mdnalysis-discussion@googlegroups.com', url='http://www.mdanalysis.org', + download_url='https://github.com/MDAnalysis/mdanalysis/releases', license='GPL 2', packages=find_packages(), package_dir={'MDAnalysisTests': 'MDAnalysisTests', 'MDAnalysisTests.plugins': 'MDAnalysisTests/plugins'}, package_data={'MDAnalysisTests': - [ - 'data/*.psf', 'data/*.dcd', 'data/*.pdb', - 'data/tprs/*.tpr', 'data/tprs/all_bonded/*.tpr', - 'data/tprs/all_bonded/*.gro', 'data/tprs/all_bonded/*.top', - 'data/tprs/all_bonded/*.mdp', 'data/*.tpr', - 'data/*.gro', 'data/*.xtc', 'data/*.trr', 'data/*npy', - 'data/*.crd', 'data/*.xyz', - 'data/Amber/*.bz2', - 'data/Amber/*.prmtop', 'data/Amber/*.top', - 'data/Amber/*.parm7', - 'data/Amber/*.trj', 'data/Amber/*.mdcrd', - 'data/Amber/*.ncdf', 'data/Amber/*.nc', - 'data/Amber/*.inpcrd', - 'data/*.pqr', 'data/*.pdbqt', 'data/*.bz2', - 'data/*.fasta', - 'data/*.dat', - 'data/*.dms', - 'data/merge/2zmm/*.pdb', - 'data/*.trz', - 'data/mol2/*.mol2', - 'data/capping/*.gro', 'data/capping/*.pdb', - 'data/lammps/*.data', 'data/lammps/*.data.bz2', - 'data/lammps/*.data2', - 'data/lammps/*.dcd', 'data/lammps/*.trz', - 'data/lammps/*.inp', - 'data/gms/*.xyz', 'data/gms/*.gms', 'data/gms/*.gms.gz', - 'data/*.inpcrd', - 'data/dlpoly/CONFIG*', - 'data/dlpoly/HISTORY*', - 'data/*.xml', - 'data/coordinates/*', - ], + ['data/*.psf', 'data/*.dcd', 'data/*.pdb', + 'data/tprs/*.tpr', 'data/tprs/all_bonded/*.tpr', + 'data/tprs/all_bonded/*.gro', + 'data/tprs/all_bonded/*.top', + 'data/tprs/all_bonded/*.mdp', 'data/*.tpr', + 'data/*.gro', 'data/*.xtc', 'data/*.trr', 'data/*npy', + 'data/*.crd', 'data/*.xyz', + 'data/Amber/*.bz2', + 'data/Amber/*.prmtop', 'data/Amber/*.top', + 'data/Amber/*.parm7', + 'data/Amber/*.trj', 'data/Amber/*.mdcrd', + 'data/Amber/*.ncdf', 'data/Amber/*.nc', + 'data/Amber/*.inpcrd', + 'data/*.pqr', 'data/*.pdbqt', 'data/*.bz2', 'data/*.gz', + 'data/*.ent', + 'data/*.fasta', + 'data/*.dat', + 'data/*.dms', + 'data/merge/2zmm/*.pdb', + 'data/*.trz', + 'data/mol2/*.mol2', + 'data/contacts/*.gro.bz2', 'data/contacts/*.dat', + 'data/capping/*.gro', 'data/capping/*.pdb', + 'data/lammps/*.data', 'data/lammps/*.data.bz2', + 'data/lammps/*.data2', + 'data/lammps/*.dcd', 'data/lammps/*.trz', + 'data/lammps/*.inp', + 'data/gms/*.xyz', 'data/gms/*.gms', + 'data/gms/*.gms.gz', + 'data/*.inpcrd', + 'data/dlpoly/CONFIG*', + 'data/dlpoly/HISTORY*', + 'data/*.xml', + 'data/coordinates/*', + ], }, classifiers=CLASSIFIERS, long_description=LONG_DESCRIPTION, @@ -127,7 +209,8 @@ 'MDAnalysis=={0!s}'.format(RELEASE), # same as this release! 'numpy>=1.5', 'nose>=1.3.7', - 'tempdir', ], - zip_safe=False, # had 'KeyError' as zipped egg (2MB savings are not worth the trouble) + # had 'KeyError' as zipped egg (2MB savings are not worth the + # trouble) + zip_safe=False, )