Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion package/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,4 @@ Logo

The MDAnalysis 'Atom' logo was designed by Christian Beckstein; it is
Copyright (c) 2011 Christian Beckstein and made available under a
Creative Commons Attribution-NoDerivs 3.0 Unported License.
Creative Commons Attribution-NoDerivs 3.0 Unported License.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep changes minimal – no need to mess with line endings.

5 changes: 4 additions & 1 deletion package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Fixes
DSSP by porting upstream PyDSSP 0.9.1 fix (Issue #4913)

Enhancements
* Improved performance of inverse index mapping in AtomGroup using an optimized
Cython implementation in lib._cutils.inverse_int_index()
(Issue #3387, PR #5252)
* Added `select=None` in `analysis.rms.RMSD` to perform no selection on
the input `atomgroup` and `reference` (Issue #5300, PR #5296)
* MOL2Parser now reads unit cell dimensions from @<TRIPOS>CRYSIN records (Issue #3341)
Expand Down Expand Up @@ -3627,4 +3630,4 @@ Testsuite
licenses

11/12/07 naveen
* prepared for release outside lab
* prepared for release outside lab
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep changes minimal – no need to mess with line endings.

6 changes: 2 additions & 4 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
from ..exceptions import NoDataError
from . import topologyobjects
from ._get_readers import get_writer_for, get_converter_for
from ..lib._cutil import inverse_int_index


def _unpickle(u, ix):
Expand Down Expand Up @@ -912,10 +913,7 @@ def _asunique(self, group, sorted=False, set_mask=False):

indices = unique_int_1d_unsorted(self.ix)
if set_mask:
mask = np.zeros_like(self.ix)
for i, x in enumerate(indices):
values = np.where(self.ix == x)[0]
mask[values] = i
mask = inverse_int_index(self.ix, indices)
self._unique_restore_mask = mask

issorted = int_array_is_sorted(indices)
Expand Down
40 changes: 38 additions & 2 deletions package/MDAnalysis/lib/_cutil.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ from cython.operator cimport dereference as deref

cnp.import_array()

__all__ = ['unique_int_1d', 'make_whole', 'find_fragments',
__all__ = ['unique_int_1d', 'inverse_int_index', 'make_whole', 'find_fragments',
'_sarrus_det_single', '_sarrus_det_multiple']

cdef extern from "calc_distances.h":
Expand Down Expand Up @@ -91,6 +91,42 @@ def unique_int_1d(cnp.intp_t[:] values):

return np.array(result)

@cython.boundscheck(False)
@cython.wraparound(False)
def inverse_int_index(cnp.intp_t[:] values,
cnp.intp_t[:] unique_vals):
"""
Construct inverse index map such that:

unique_vals[mask] == values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mask is not defined — do you need to say "Construct inverse index mask such that ..."? Properly explain what this code does.

Add a simple example in the docs below (look at https://userguide.mdanalysis.org/stable/contributing_code.html#working-with-the-code-documentation for how to structure doc strings using the numpy docstring standard)


Parameters
----------
values : numpy.ndarray
1D array of integers.
unique_vals : numpy.ndarray
1D array of unique integers (unsorted).

Returns
-------
numpy.ndarray
Integer mask mapping values -> index in unique_vals.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a

.. versionadded:: 2.11.0

Make sure that there are TWO blank lines separating this line from the rest.

"""

cdef Py_ssize_t n = values.shape[0]
cdef Py_ssize_t m = unique_vals.shape[0]
cdef Py_ssize_t i

cdef dict lookup = {}
cdef cnp.intp_t[:] mask = np.empty(n, dtype=np.intp)

for i in range(m):
lookup[unique_vals[i]] = i

for i in range(n):
mask[i] = lookup[values[i]]

return np.array(mask)

@cython.boundscheck(False)
def _in2d(cnp.intp_t[:, :] arr1, cnp.intp_t[:, :] arr2):
Expand Down Expand Up @@ -515,4 +551,4 @@ def find_fragments(atoms, bondlist):
# Add fragment to output
frags.append(np.asarray(this_frag))

return frags
return frags
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did black remove the newline? Otherwise keep it as is.

50 changes: 50 additions & 0 deletions testsuite/MDAnalysisTests/lib/test_cutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
unique_int_1d,
find_fragments,
_in2d,
inverse_int_index,
)


Expand Down Expand Up @@ -103,3 +104,52 @@ def test_in2d_VE(arr1, arr2):
ValueError, match=r"Both arrays must be \(n, 2\) arrays"
):
_in2d(arr1, arr2)


def _python_reference_mask(ix, indices):
mask = np.zeros_like(ix)
for i, x in enumerate(indices):
values = np.where(ix == x)[0]
mask[values] = i
return mask


@pytest.mark.parametrize(
"ix,indices",
[
# unsorted and not unique
(
np.array([1, 5, 3, 3, 6], dtype=np.intp),
np.array([1, 5, 3, 6], dtype=np.intp),
),
# sorted and not unique
(
np.array([1, 3, 3, 5, 6], dtype=np.intp),
np.array([1, 3, 5, 6], dtype=np.intp),
),
# unsorted and unique
(
np.array([1, 5, 3, 6], dtype=np.intp),
np.array([1, 5, 3, 6], dtype=np.intp),
),
# sorted and unique
(
np.array([1, 3, 5, 6], dtype=np.intp),
np.array([1, 3, 5, 6], dtype=np.intp),
),
# all elements identical
(
np.array([5, 5, 5], dtype=np.intp),
np.array([5], dtype=np.intp),
),
# single element
(
np.array([7], dtype=np.intp),
np.array([7], dtype=np.intp),
),
],
)
def test_inverse_int_index(ix, indices):
pyref = _python_reference_mask(ix, indices)
cy = inverse_int_index(ix, indices)
assert_equal(pyref, cy)
Loading