Source code for MDAnalysis.lib.mdamath

# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- https://www.mdanalysis.org
# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
# (see the file AUTHORS for the full list of names)
#
# Released under the GNU Public Licence, v2 or any higher version
#
# Please cite your use of MDAnalysis in published work:
#
# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
#
# 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
#

"""
Mathematical helper functions --- :mod:`MDAnalysis.lib.mdamath`
===============================================================

Helper functions for common mathematical operations

.. autofunction:: normal
.. autofunction:: norm
.. autofunction:: angle
.. autofunction:: dihedral
.. autofunction:: stp
.. autofunction:: triclinic_box
.. autofunction:: triclinic_vectors
.. autofunction:: box_volume
.. autofunction:: make_whole

.. versionadded:: 0.11.0
"""
from __future__ import division, absolute_import
from six.moves import zip
import numpy as np

from ..exceptions import NoDataError
from . import util
from ._cutil import make_whole

# geometric functions
[docs]def norm(v): r"""Calculate the norm of a vector v. .. math:: v = \sqrt{\mathbf{v}\cdot\mathbf{v}} This version is faster then numpy.linalg.norm because it only works for a single vector and therefore can skip a lot of the additional fuss linalg.norm does. Parameters ---------- v : array_like 1D array of shape (N) for a vector of length N Returns ------- float norm of the vector """ return np.sqrt(np.dot(v, v))
[docs]def normal(vec1, vec2): r"""Returns the unit vector normal to two vectors. .. math:: \hat{\mathbf{n}} = \frac{\mathbf{v}_1 \times \mathbf{v}_2}{|\mathbf{v}_1 \times \mathbf{v}_2|} If the two vectors are collinear, the vector :math:`\mathbf{0}` is returned. .. versionchanged:: 0.11.0 Moved into lib.mdamath """ normal = np.cross(vec1, vec2) n = norm(normal) if n == 0.0: return normal # returns [0,0,0] instead of [nan,nan,nan] return normal / n # ... could also use numpy.nan_to_num(normal/norm(normal))
[docs]def angle(a, b): """Returns the angle between two vectors in radians .. versionchanged:: 0.11.0 Moved into lib.mdamath """ x = np.dot(a, b) / (norm(a) * norm(b)) # catch roundoffs that lead to nan otherwise if x > 1.0: return 0.0 elif x < -1.0: return -np.pi return np.arccos(x)
[docs]def stp(vec1, vec2, vec3): r"""Takes the scalar triple product of three vectors. Returns the volume *V* of the parallel epiped spanned by the three vectors .. math:: V = \mathbf{v}_3 \cdot (\mathbf{v}_1 \times \mathbf{v}_2) .. versionchanged:: 0.11.0 Moved into lib.mdamath """ return np.dot(vec3, np.cross(vec1, vec2))
[docs]def dihedral(ab, bc, cd): r"""Returns the dihedral angle in radians between vectors connecting A,B,C,D. The dihedral measures the rotation around bc:: ab A---->B \ bc _\' C---->D cd The dihedral angle is restricted to the range -π <= x <= π. .. versionadded:: 0.8 .. versionchanged:: 0.11.0 Moved into lib.mdamath """ x = angle(normal(ab, bc), normal(bc, cd)) return (x if stp(ab, bc, cd) <= 0.0 else -x)
def _angle(a, b): """Angle between two vectors *a* and *b* in degrees. If one of the lengths is 0 then the angle is returned as 0 (instead of `nan`). """ # This function has different limits than angle? angle = np.arccos(np.dot(a, b) / (norm(a) * norm(b))) if np.isnan(angle): return 0.0 return np.rad2deg(angle)
[docs]def triclinic_box(x, y, z): """Convert the three triclinic box vectors to [A,B,C,alpha,beta,gamma]. Angles are in degrees. * alpha = angle(y,z) * beta = angle(x,z) * gamma = angle(x,y) Note ---- Definition of angles: http://en.wikipedia.org/wiki/Lattice_constant """ A, B, C = [norm(v) for v in (x, y, z)] alpha = _angle(y, z) beta = _angle(x, z) gamma = _angle(x, y) return np.array([A, B, C, alpha, beta, gamma], dtype=np.float32)
[docs]def triclinic_vectors(dimensions): """Convert `[A,B,C,alpha,beta,gamma]` to a triclinic box representation. Original `code by Tsjerk Wassenaar`_ posted on the Gromacs mailinglist. If *dimensions* indicates a non-periodic system (i.e. all lengths 0) then null-vectors are returned. .. _code by Tsjerk Wassenaar: http://www.mail-archive.com/gmx-users@gromacs.org/msg28032.html Parameters ---------- dimensions : [A, B, C, alpha, beta, gamma] list of box lengths and angles (in degrees) such as ``[A,B,C,alpha,beta,gamma]`` Returns ------- numpy.array numpy 3x3 array B, with ``B[0]`` as first box vector, ``B[1]``as second vector, ``B[2]`` as third box vector. Notes ----- The first vector is always pointing along the X-axis, i.e., parallel to (1, 0, 0). .. versionchanged:: 0.7.6 Null-vectors are returned for non-periodic (or missing) unit cell. """ B = np.zeros((3, 3), dtype=np.float32) x, y, z, a, b, c = dimensions[:6] if np.all(dimensions[:3] == 0): return B B[0][0] = x if a == 90. and b == 90. and c == 90.: B[1][1] = y B[2][2] = z else: a = np.deg2rad(a) b = np.deg2rad(b) c = np.deg2rad(c) B[1][0] = y * np.cos(c) B[1][1] = y * np.sin(c) B[2][0] = z * np.cos(b) B[2][1] = z * (np.cos(a) - np.cos(b) * np.cos(c)) / np.sin(c) B[2][2] = np.sqrt(z * z - B[2][0] ** 2 - B[2][1] ** 2) return B
[docs]def box_volume(dimensions): """Return the volume of the unitcell described by *dimensions*. The volume is computed as `det(x1,x2,x2)` where the xi are the triclinic box vectors from :func:`triclinic_vectors`. Parameters ---------- dimensions : [A, B, C, alpha, beta, gamma] list of box lengths and angles (in degrees) such as [A,B,C,alpha,beta,gamma] Returns ------- volume : float """ return np.linalg.det(triclinic_vectors(dimensions))
[docs]def one_to_many_pointers(Ni, Nj, i2j): """Based on a many to one mapping of i to j, create the reverse mapping Arguments --------- Ni : int number of i components Nj : int number of j components i2j : numpy.ndarray relates i to parent js Returns ------- ordered : list an ordered list of i indices [size (i,)] ptrs : list the start and end index for each j [size (Nj, 2)] Example ------- .. code:: python # Residx - the resid of each Atom ordered, ptrs = one_to_many_pointers(Natoms, Nres, Residx) # Returns an array of the atom indices that are in resid 7 atoms = ordered[ptrs[7,0]:ptrs[7,1]] """ ordered = i2j.argsort() sorted_idx = i2j[ordered] borders = np.concatenate([[0], np.where(np.diff(sorted_idx))[0] + 1, [Ni]]) ptrs = np.zeros((Nj, 2), dtype=np.int32) for x, y in zip(borders[:-1], borders[1:]): i = sorted_idx[x] ptrs[i] = x, y return ordered, ptrs