Skip to content

Commit 7da5f0a

Browse files
authored
Improve performance of wrap method (#5226)
* Faster wrap (see PR #5226 for details and benchmark data) * Changelog and linting * Add test * Add benchmarks
1 parent beaece1 commit 7da5f0a

File tree

4 files changed

+41
-7
lines changed

4 files changed

+41
-7
lines changed

benchmarks/benchmarks/ag_methods.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ def time_wrap(self, num_atoms):
189189
"""
190190
self.ag.wrap()
191191

192+
def time_wrap_compound(self, num_atoms):
193+
"""Benchmark wrap() operation on
194+
atomgroup with default params.
195+
"""
196+
self.ag.wrap(compound="residues")
192197

193198
class AtomGroupAttrsBench(object):
194199
"""Benchmarks for the various MDAnalysis

package/CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The rules for this file:
1515

1616
-------------------------------------------------------------------------------
1717
??/??/?? IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2, marinegor,
18-
spyke7, talagayev, tanii1125, BradyAJohnston
18+
spyke7, talagayev, tanii1125, BradyAJohnston, hejamu
1919

2020
* 2.11.0
2121

@@ -37,6 +37,7 @@ Fixes
3737
DSSP by porting upstream PyDSSP 0.9.1 fix (Issue #4913)
3838

3939
Enhancements
40+
* Improved performance of `AtomGroup.wrap()` with compounds (PR #5220)
4041
* Adds support for parsing `.tpr` files produced by GROMACS 2026.0
4142
* Enables parallelization for analysis.diffusionmap.DistanceMatrix
4243
(Issue #4679, PR #4745)

package/MDAnalysis/core/groups.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,13 +1899,17 @@ def wrap(self, compound="atoms", center="com", box=None, inplace=True):
18991899
else:
19001900
compound_indices = atoms._get_compound_indices(comp)
19011901

1902-
# apply the shifts:
1902+
# Build mapping from compound index to shift index
19031903
unique_compound_indices = unique_int_1d(compound_indices)
1904-
shift_idx = 0
1905-
for i in unique_compound_indices:
1906-
mask = np.where(compound_indices == i)
1907-
positions[mask] += shifts[shift_idx]
1908-
shift_idx += 1
1904+
index_to_shift = np.empty(
1905+
compound_indices.max() + 1, dtype=int
1906+
)
1907+
index_to_shift[unique_compound_indices] = np.arange(
1908+
len(unique_compound_indices)
1909+
)
1910+
1911+
# Apply shifts
1912+
positions += shifts[index_to_shift[compound_indices]]
19091913

19101914
if inplace:
19111915
atoms.positions = positions

testsuite/MDAnalysisTests/core/test_wrap.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,30 @@ def test_wrap_no_molnums_exception_safety(self, level, compound, center):
444444
group.atoms.positions, ref_wrapped_pos, decimal=self.precision
445445
)
446446

447+
@pytest.mark.parametrize(
448+
"compound",
449+
("segments", "residues", "molecules", "fragments"),
450+
)
451+
@pytest.mark.parametrize("center", ("com", "cog"))
452+
@pytest.mark.parametrize("is_triclinic", (False, True))
453+
def test_wrap_shuffled_index(self, compound, center, is_triclinic):
454+
# get a pristine test universe
455+
u = UnWrapUniverse(is_triclinic=is_triclinic)
456+
# shuffle atom order to make compound indices non-contiguous
457+
rng = np.random.RandomState(1234)
458+
shuffle = rng.permutation(u.atoms.n_atoms)
459+
group = u.atoms[shuffle]
460+
# get expected wrapped coordinates in shuffled order
461+
ref_wrapped_pos = u.wrapped_coords(compound, center)[shuffle]
462+
# wrap the shuffled group:
463+
wrapped_pos = group.wrap(
464+
compound=compound, center=center, inplace=False
465+
)
466+
# check for correct result:
467+
assert_almost_equal(
468+
wrapped_pos, ref_wrapped_pos, decimal=self.precision
469+
)
470+
447471

448472
class TestWrapTRZ(object):
449473
"""Tests the functionality of AtomGroup.wrap() using a TRZ universe."""

0 commit comments

Comments
 (0)