Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow distance routines to accept an NumPy Array or AtomGroup [Core] #3730

Merged
merged 63 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
90d3553
@check_coords can now accept an AtomGroup
hmacdope Jun 21, 2022
932df23
add type hinting
hmacdope Jun 21, 2022
0c7e4de
add tests for atomgroup
hmacdope Jun 21, 2022
c7ff939
add type hinting and versionchanged to self_distance_array
hmacdope Jun 21, 2022
03ed438
add type hints and change docs for distance_array
hmacdope Jun 21, 2022
561d6c0
add noPBC atomgroup test
hmacdope Jun 21, 2022
c3bd362
add more tests for AtomGroup distances
hmacdope Jun 21, 2022
5fe36e8
remove two universes test from atomgroup_matches_numpy
hmacdope Jun 21, 2022
a7c420d
add tests for different boxes
hmacdope Jun 22, 2022
540e74d
add triclinic tests
hmacdope Jun 22, 2022
8a0061c
identify that problem is with OpenMP backend
hmacdope Jun 22, 2022
c9849a3
add note for openmp bug
hmacdope Jun 22, 2022
8497202
add test for @check_coords
hmacdope Jun 22, 2022
af4bd22
add test for mismatched ag sizes
hmacdope Jun 22, 2022
2d92bd3
add type hints to calc_*
hmacdope Jun 22, 2022
0fbef15
add atomgroup tests to calc_bonds
hmacdope Jun 23, 2022
55da6ab
move to atomgroup fixture
hmacdope Jun 23, 2022
74d02f2
add dihedral atomgroup tests
hmacdope Jun 23, 2022
e11ef06
improve docs
hmacdope Jun 23, 2022
3b184d2
add docs to dihedrals
hmacdope Jun 23, 2022
19b6b38
improve docs
hmacdope Jun 23, 2022
a19e23d
improve docs
hmacdope Jun 23, 2022
1a7536e
add allow_atomgroup option to @check_coords
hmacdope Jun 23, 2022
6a909e6
add atomgroup to apply_PBC
hmacdope Jun 23, 2022
bba17c7
apply PBC
hmacdope Jun 23, 2022
1684eb7
refactor some of distances
hmacdope Jun 23, 2022
d2bd296
add tests for unchanged input
hmacdope Jun 23, 2022
57be404
changes to test_util
hmacdope Jun 23, 2022
e14210e
fix classnames
hmacdope Jun 23, 2022
36bc124
changelog
hmacdope Jun 23, 2022
0fc971f
whoops changelog
hmacdope Jun 23, 2022
5e1ec61
fix wrong type hints
hmacdope Jun 23, 2022
ad2cce2
Merge remote-tracking branch 'upstream/develop' into Distances_AtomGr…
hmacdope Jun 23, 2022
7b886a2
parametrize noPBC
hmacdope Jun 23, 2022
a694c56
remove code duplication in DistanceArray with parametrize
hmacdope Jun 24, 2022
abb9d3f
remove extra test
hmacdope Jun 24, 2022
fb24864
clean up apply_PBC
hmacdope Jun 24, 2022
24129c0
PEP8
hmacdope Jun 24, 2022
b41a5aa
PEP8 again
hmacdope Jun 24, 2022
df6e53a
pep8 again
hmacdope Jun 24, 2022
7124a3f
fix final pep8
hmacdope Jun 24, 2022
53f7004
move to NumPy type hints
hmacdope Jun 28, 2022
f9f42e7
move to duck typing over isinstance(AtomGroup)
hmacdope Jun 28, 2022
cbacaa1
clean up formatting in check_coords
hmacdope Jun 28, 2022
4ba0828
add pragma nocover to type checking guard
hmacdope Jun 28, 2022
ecb81f4
space after comma in docstring of check_coords
hmacdope Jun 28, 2022
1416fc9
remove turtle import (how did it get there?)
hmacdope Jun 28, 2022
ec1a1e9
restructure try-except in _check_coords
hmacdope Jun 28, 2022
9142cd4
remove reraise of TypeError on check_coords
hmacdope Jun 29, 2022
7107665
refactor distance test for 2x atomgroups to use fixture
hmacdope Jun 29, 2022
c3e7361
Update testsuite/MDAnalysisTests/lib/test_distances.py
hmacdope Jun 29, 2022
1fa9619
change error to fstring
hmacdope Jun 29, 2022
d0e31a3
add implicit line continuation
hmacdope Jun 29, 2022
085963f
line continuation for unreadable fixture
hmacdope Jun 29, 2022
c3d8100
PEP8
hmacdope Jun 29, 2022
3b61689
PEP8 again
hmacdope Jun 29, 2022
f088887
PEP8 again again
hmacdope Jun 30, 2022
11e04e5
Apply suggestions from IAlibay code review
hmacdope Jul 5, 2022
7d75c9b
close some unclosed lines
hmacdope Jul 6, 2022
939aae4
Merge remote-tracking branch 'upstream/develop' into Distances_AtomGr…
hmacdope Jul 6, 2022
626b45a
roll back to arr: np.ndarray typing
hmacdope Jul 10, 2022
588f753
add typing future warning
hmacdope Jul 11, 2022
4e085b7
Apply suggestions from code review
hmacdope Jul 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Fixes
* add a 0.5 for correct midpoints in hole analysis (Issue #3715)

Enhancements
* Change functions in `lib.distances` to accept AtomGroups as well as NumPy
arrays (CZI Performance track PR #3730)
* Add `norm` parameter to InterRDF, InterRDF_s to normalize as rdf,
number density or do not normalize at all. (Issue #3687)
* Additional logger.info output when per-frame analysis starts (PR #3710)
Expand Down
166 changes: 118 additions & 48 deletions package/MDAnalysis/lib/distances.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@
===================================================================

Fast C-routines to calculate arrays of distances or angles from coordinate
arrays. Many of the functions also exist in parallel versions, which typically
provide higher performance than the serial code.
The boolean attribute `MDAnalysis.lib.distances.USED_OPENMP` can be checked to
see if OpenMP was used in the compilation of MDAnalysis.
arrays. Distance functions can accept a NumPy :class:`np.ndarray` or an
:class:`~MDAnalysis.core.groups.AtomGroup`. Many of the functions also exist
in parallel versions, which typically provide higher performance than the
serial code. The boolean attribute `MDAnalysis.lib.distances.USED_OPENMP` can
be checked to see if OpenMP was used in the compilation of MDAnalysis.

Selection of acceleration ("backend")
-------------------------------------
Expand All @@ -50,6 +51,9 @@
========== ========================= ======================================

.. versionadded:: 0.13.0
.. versionchanged:: 2.3.0
Distance functions can now accept an
:class:`~MDAnalysis.core.groups.AtomGroup` or an :class:`np.ndarray`

Functions
---------
Expand All @@ -70,6 +74,10 @@
import numpy as np
from numpy.lib.utils import deprecate

from typing import Union, Optional, Callable
from typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover
from ..core.groups import AtomGroup
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
from .util import check_coords, check_box
from .mdamath import triclinic_vectors
from ._augment import augment_coordinates, undo_augment
Expand All @@ -91,7 +99,9 @@
pass
del importlib

def _run(funcname, args=None, kwargs=None, backend="serial"):

def _run(funcname: Callable, args: Optional[tuple] = None,
kwargs: Optional[dict] = None, backend: str = "serial") -> Callable:
"""Helper function to select a backend function `funcname`."""
args = args if args is not None else tuple()
kwargs = kwargs if kwargs is not None else dict()
Expand Down Expand Up @@ -129,7 +139,8 @@ def _run(funcname, args=None, kwargs=None, backend="serial"):
from .c_distances_openmp import OPENMP_ENABLED as USED_OPENMP


def _check_result_array(result, shape):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
def _check_result_array(result: np.ndarray, shape: tuple) -> np.ndarray:
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
"""Check if the result array is ok to use.

The `result` array must meet the following requirements:
Expand Down Expand Up @@ -170,10 +181,14 @@ def _check_result_array(result, shape):
return result


# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('reference', 'configuration', reduce_result_if_single=False,
check_lengths_match=False)
def distance_array(reference, configuration, box=None, result=None,
backend="serial"):
check_lengths_match=False, allow_atomgroup=True)
def distance_array(reference: Union[np.ndarray, 'AtomGroup'],
configuration: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
result: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
"""Calculate all possible distances between a reference set and another
configuration.

Expand All @@ -190,12 +205,14 @@ def distance_array(reference, configuration, box=None, result=None,

Parameters
----------
reference : numpy.ndarray
reference :numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Reference coordinate array of shape ``(3,)`` or ``(n, 3)`` (dtype is
arbitrary, will be converted to ``numpy.float32`` internally).
configuration : numpy.ndarray
arbitrary, will be converted to ``numpy.float32`` internally). Also
accepts an :class:`~MDAnalysis.core.groups.AtomGroup`.
configuration : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Configuration coordinate array of shape ``(3,)`` or ``(m, 3)`` (dtype is
arbitrary, will be converted to ``numpy.float32`` internally).
arbitrary, will be converted to ``numpy.float32`` internally). Also
accepts an :class:`~MDAnalysis.core.groups.AtomGroup`.
box : array_like, optional
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand All @@ -221,6 +238,9 @@ def distance_array(reference, configuration, box=None, result=None,
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
Now also accepts single coordinates as input.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
confnum = configuration.shape[0]
refnum = reference.shape[0]
Expand Down Expand Up @@ -251,8 +271,12 @@ def distance_array(reference, configuration, box=None, result=None,
return distances


@check_coords('reference', reduce_result_if_single=False)
def self_distance_array(reference, box=None, result=None, backend="serial"):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('reference', reduce_result_if_single=False, allow_atomgroup=True)
def self_distance_array(reference: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
result: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
"""Calculate all possible distances within a configuration `reference`.

If the optional argument `box` is supplied, the minimum image convention is
Expand All @@ -265,9 +289,10 @@ def self_distance_array(reference, box=None, result=None, backend="serial"):

Parameters
----------
reference : numpy.ndarray
reference : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Reference coordinate array of shape ``(3,)`` or ``(n, 3)`` (dtype is
arbitrary, will be converted to ``numpy.float32`` internally).
arbitrary, will be converted to ``numpy.float32`` internally). Also
accepts an :class:`~MDAnalysis.core.groups.AtomGroup`.
box : array_like, optional
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand Down Expand Up @@ -298,6 +323,9 @@ def self_distance_array(reference, box=None, result=None, backend="serial"):
Added *backend* keyword.
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
refnum = reference.shape[0]
distnum = refnum * (refnum - 1) // 2
Expand Down Expand Up @@ -1241,8 +1269,13 @@ def transform_StoR(coords, box, backend="serial"):
return coords


@check_coords('coords1', 'coords2')
def calc_bonds(coords1, coords2, box=None, result=None, backend="serial"):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('coords1', 'coords2', allow_atomgroup=True)
def calc_bonds(coords1: Union[np.ndarray, 'AtomGroup'],
coords2: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
result: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
"""Calculates the bond lengths between pairs of atom positions from the two
coordinate arrays `coords1` and `coords2`, which must contain the same
number of coordinates. ``coords1[i]`` and ``coords2[i]`` represent the
Expand All @@ -1266,14 +1299,16 @@ def calc_bonds(coords1, coords2, box=None, result=None, backend="serial"):

Parameters
----------
coords1 : numpy.ndarray
coords1 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` for one half of a
single or ``n`` bonds, respectively (dtype is arbitrary, will be
converted to ``numpy.float32`` internally).
coords2 : numpy.ndarray
converted to ``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords2 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` for the other half of
a single or ``n`` bonds, respectively (dtype is arbitrary, will be
converted to ``numpy.float32`` internally).
converted to ``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
box : numpy.ndarray, optional
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand All @@ -1300,6 +1335,9 @@ def calc_bonds(coords1, coords2, box=None, result=None, backend="serial"):
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
Now also accepts single coordinates as input.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
numatom = coords1.shape[0]
bondlengths = _check_result_array(result, (numatom,))
Expand All @@ -1323,9 +1361,14 @@ def calc_bonds(coords1, coords2, box=None, result=None, backend="serial"):
return bondlengths


@check_coords('coords1', 'coords2', 'coords3')
def calc_angles(coords1, coords2, coords3, box=None, result=None,
backend="serial"):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('coords1', 'coords2', 'coords3', allow_atomgroup=True)
def calc_angles(coords1: Union[np.ndarray, 'AtomGroup'],
coords2: Union[np.ndarray, 'AtomGroup'],
coords3: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
result: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
"""Calculates the angles formed between triplets of atom positions from the
three coordinate arrays `coords1`, `coords2`, and `coords3`. All coordinate
arrays must contain the same number of coordinates.
Expand All @@ -1351,18 +1394,21 @@ def calc_angles(coords1, coords2, coords3, box=None, result=None,

Parameters
----------
coords1 : numpy.ndarray
coords1 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Array of shape ``(3,)`` or ``(n, 3)`` containing the coordinates of one
side of a single or ``n`` angles, respectively (dtype is arbitrary, will
be converted to ``numpy.float32`` internally)
coords2 : numpy.ndarray
be converted to ``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords2 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Array of shape ``(3,)`` or ``(n, 3)`` containing the coordinates of the
apices of a single or ``n`` angles, respectively (dtype is arbitrary,
will be converted to ``numpy.float32`` internally)
coords3 : numpy.ndarray
will be converted to ``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords3 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Array of shape ``(3,)`` or ``(n, 3)`` containing the coordinates of the
other side of a single or ``n`` angles, respectively (dtype is
arbitrary, will be converted to ``numpy.float32`` internally)
arbitrary, will be converted to ``numpy.float32`` internally).
Also accepts an :class:`~MDAnalysis.core.groups.AtomGroup`.
box : numpy.ndarray, optional
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand Down Expand Up @@ -1392,6 +1438,9 @@ def calc_angles(coords1, coords2, coords3, box=None, result=None,
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
Now also accepts single coordinates as input.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
numatom = coords1.shape[0]
angles = _check_result_array(result, (numatom,))
Expand All @@ -1415,9 +1464,15 @@ def calc_angles(coords1, coords2, coords3, box=None, result=None,
return angles


@check_coords('coords1', 'coords2', 'coords3', 'coords4')
def calc_dihedrals(coords1, coords2, coords3, coords4, box=None, result=None,
backend="serial"):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('coords1', 'coords2', 'coords3', 'coords4', allow_atomgroup=True)
def calc_dihedrals(coords1: Union[np.ndarray, 'AtomGroup'],
coords2: Union[np.ndarray, 'AtomGroup'],
coords3: Union[np.ndarray, 'AtomGroup'],
coords4: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
result: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
r"""Calculates the dihedral angles formed between quadruplets of positions
from the four coordinate arrays `coords1`, `coords2`, `coords3`, and
`coords4`, which must contain the same number of coordinates.
Expand Down Expand Up @@ -1449,22 +1504,26 @@ def calc_dihedrals(coords1, coords2, coords3, coords4, box=None, result=None,

Parameters
----------
coords1 : numpy.ndarray
coords1 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` containing the 1st
positions in dihedrals (dtype is arbitrary, will be converted to
``numpy.float32`` internally)
coords2 : numpy.ndarray
``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords2 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` containing the 2nd
positions in dihedrals (dtype is arbitrary, will be converted to
``numpy.float32`` internally)
coords3 : numpy.ndarray
``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords3 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` containing the 3rd
positions in dihedrals (dtype is arbitrary, will be converted to
``numpy.float32`` internally)
coords4 : numpy.ndarray
``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
coords4 : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` containing the 4th
positions in dihedrals (dtype is arbitrary, will be converted to
``numpy.float32`` internally)
``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
box : numpy.ndarray, optional
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand Down Expand Up @@ -1497,6 +1556,9 @@ def calc_dihedrals(coords1, coords2, coords3, coords4, box=None, result=None,
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
Now also accepts single coordinates as input.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
numatom = coords1.shape[0]
dihedrals = _check_result_array(result, (numatom,))
Expand All @@ -1520,15 +1582,19 @@ def calc_dihedrals(coords1, coords2, coords3, coords4, box=None, result=None,
return dihedrals


@check_coords('coords')
def apply_PBC(coords, box, backend="serial"):
# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('coords', allow_atomgroup=True)
def apply_PBC(coords: Union[np.ndarray, 'AtomGroup'],
box: Optional[np.ndarray] = None,
backend: str = "serial") -> np.ndarray:
"""Moves coordinates into the primary unit cell.

Parameters
----------
coords : numpy.ndarray
coords : numpy.ndarray or :class:`~MDAnalysis.core.groups.AtomGroup`
Coordinate array of shape ``(3,)`` or ``(n, 3)`` (dtype is arbitrary,
will be converted to ``numpy.float32`` internally).
will be converted to ``numpy.float32`` internally). Also accepts an
:class:`~MDAnalysis.core.groups.AtomGroup`.
box : numpy.ndarray
The unitcell dimensions of the system, which can be orthogonal or
triclinic and must be provided in the same format as returned by
Expand All @@ -1550,6 +1616,9 @@ def apply_PBC(coords, box, backend="serial"):
.. versionchanged:: 0.19.0
Internal dtype conversion of input coordinates to ``numpy.float32``.
Now also accepts (and, likewise, returns) single coordinates.
.. versionchanged:: 2.3.0
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
argument in any position and checks inputs using type hinting.
"""
if len(coords) == 0:
return coords
Expand All @@ -1562,8 +1631,9 @@ def apply_PBC(coords, box, backend="serial"):
return coords


# typing: numpy
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@check_coords('vectors', enforce_copy=False, enforce_dtype=False)
def minimize_vectors(vectors, box):
def minimize_vectors(vectors: np.ndarray, box: np.ndarray) -> np.ndarray:
"""Apply minimum image convention to an array of vectors

This function is required for calculating the correct vectors between two
Expand Down
Loading