Skip to content

Commit e89e29e

Browse files
authored
Merge branch 'develop' into fix/replace-numpy-asserts
2 parents e1ee51d + bbcef1b commit e89e29e

File tree

7 files changed

+115
-78
lines changed

7 files changed

+115
-78
lines changed

package/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ Chronological list of authors
264264
- Ch Zhang
265265
- Raúl Lois-Cuns
266266
- Kushagar Garg
267+
- Pranay Pelapkar
267268
- Shreejan Dolai
268269

269270
External code

package/CHANGELOG

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,24 @@ The rules for this file:
1414

1515

1616
-------------------------------------------------------------------------------
17-
??/??/?? IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2
17+
??/??/?? IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2, marinegor,
18+
spyke7, talagayev
1819

1920
* 2.11.0
2021

2122
Fixes
23+
* Fixes the verbose=False in EinsteinMSD, and only shows progress bar when
24+
verbose=True (Issue #5144, PR #5153)
2225
* Fix incorrect assignment of topology_format to format (and vice versa)
2326
when a parsing class is provided to either (Issue #5147, PR #5148)
2427
* Fix incorrect TPR file parsing for GROMACS topologies produced prior
2528
to version 5.1.0 (Issue #5145, PR #5146)
2629
* Fixes incorrect assignment of secondary structure to proline residues in
2730
DSSP by porting upstream PyDSSP 0.9.1 fix (Issue #4913)
2831

29-
Changes
30-
31-
Deprecations
32-
33-
34-
11/24/25 IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2, marinegor, spyke7
35-
36-
* 2.11.0
37-
38-
Fixes
39-
* Fixes the verbose=False in EinsteinMSD, and only shows progress bar when
40-
verbose=True (Issue #5144, PR #5153)
32+
Enhancements
33+
* Enables parallelization for analysis.diffusionmap.DistanceMatrix
34+
(Issue #4679, PR #4745)
4135

4236
Changes
4337
* The msd.py inside analysis is changed, and ProgressBar is implemented inside

package/MDAnalysis/analysis/diffusionmap.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143
from MDAnalysis.core.universe import Universe
144144
from MDAnalysis.core.groups import AtomGroup, UpdatingAtomGroup
145145
from .rms import rmsd
146-
from .base import AnalysisBase
146+
from .base import AnalysisBase, ResultsGroup
147147

148148
logger = logging.getLogger("MDAnalysis.analysis.diffusionmap")
149149

@@ -234,8 +234,22 @@ class DistanceMatrix(AnalysisBase):
234234
.. versionchanged:: 2.8.0
235235
:class:`DistanceMatrix` is now correctly works with `frames=...`
236236
parameter (#4432) by iterating over `self._sliced_trajectory`
237+
.. versionchanged:: 2.11.0
238+
Enabled **parallel execution** with the ``multiprocessing`` and ``dask``
239+
backends; use the new method :meth:`get_supported_backends` to see all
240+
supported backends.
237241
"""
238242

243+
_analysis_algorithm_is_parallelizable = True
244+
245+
@classmethod
246+
def get_supported_backends(cls):
247+
return (
248+
"serial",
249+
"multiprocessing",
250+
"dask",
251+
)
252+
239253
def __init__(
240254
self,
241255
universe,
@@ -265,27 +279,16 @@ def __init__(
265279
self._calculated = False
266280

267281
def _prepare(self):
268-
self.results.dist_matrix = np.zeros((self.n_frames, self.n_frames))
282+
# Perpare for parallelization workers
283+
n_atoms = self.atoms.n_atoms
284+
n_dim = self.atoms.positions.shape[1]
285+
self.results._positions = np.zeros(
286+
(self.n_frames, n_atoms, n_dim), dtype=np.float64
287+
)
269288

270289
def _single_frame(self):
271-
iframe = self._frame_index
272-
i_ref = self.atoms.positions
273-
# diagonal entries need not be calculated due to metric(x,x) == 0 in
274-
# theory, _ts not updated properly. Possible savings by setting a
275-
# cutoff for significant decimal places to sparsify matrix
276-
for j, ts in enumerate(self._sliced_trajectory[iframe:]):
277-
self._ts = ts
278-
j_ref = self.atoms.positions
279-
dist = self._metric(i_ref, j_ref, weights=self._weights)
280-
self.results.dist_matrix[
281-
self._frame_index, j + self._frame_index
282-
] = (dist if dist > self._cutoff else 0)
283-
self.results.dist_matrix[
284-
j + self._frame_index, self._frame_index
285-
] = self.results.dist_matrix[
286-
self._frame_index, j + self._frame_index
287-
]
288-
self._ts = self._sliced_trajectory[iframe]
290+
# Store current frame positions
291+
self.results._positions[self._frame_index] = self.atoms.positions
289292

290293
@property
291294
def dist_matrix(self):
@@ -298,8 +301,38 @@ def dist_matrix(self):
298301
return self.results.dist_matrix
299302

300303
def _conclude(self):
304+
# Build the full pairwise distance matrix from stored positions
305+
# Calculate and store results
306+
pos = np.asarray(
307+
self.results._positions, dtype=np.float64
308+
) # (n_frames, n_atoms, n_dim)
309+
n = pos.shape[0]
310+
311+
D = np.zeros((n, n), dtype=np.float64)
312+
313+
metric = self._metric
314+
cutoff = self._cutoff
315+
weights = self._weights
316+
317+
for i in range(n):
318+
pi = pos[i]
319+
for j in range(i + 1, n):
320+
pj = pos[j]
321+
d = metric(pi, pj, weights=weights)
322+
if d > cutoff:
323+
D[i, j] = d
324+
D[j, i] = d
325+
326+
self.results.dist_matrix = D
301327
self._calculated = True
302328

329+
def _get_aggregator(self):
330+
return ResultsGroup(
331+
lookup={
332+
"_positions": ResultsGroup.ndarray_vstack, # Get positions
333+
}
334+
)
335+
303336

304337
class DiffusionMap(object):
305338
"""Non-linear dimension reduction method

testsuite/MDAnalysisTests/analysis/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from MDAnalysis.analysis.dihedrals import Dihedral, Ramachandran, Janin
1111
from MDAnalysis.analysis.bat import BAT
1212
from MDAnalysis.analysis.gnm import GNMAnalysis
13+
from MDAnalysis.analysis.diffusionmap import DistanceMatrix
1314
from MDAnalysis.analysis.dssp.dssp import DSSP
1415
from MDAnalysis.analysis.hydrogenbonds.hbond_analysis import (
1516
HydrogenBondAnalysis,
@@ -208,3 +209,11 @@ def client_InterRDF(request):
208209
@pytest.fixture(scope="module", params=params_for_cls(InterRDF_s))
209210
def client_InterRDF_s(request):
210211
return request.param
212+
213+
214+
# MDAnalysis.analysis.diffusionmap
215+
216+
217+
@pytest.fixture(scope="module", params=params_for_cls(DistanceMatrix))
218+
def client_DistanceMatrix(request):
219+
return request.param

testsuite/MDAnalysisTests/analysis/test_density.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import warnings
2828
from unittest.mock import Mock, patch
2929

30-
from numpy.testing import assert_equal, assert_almost_equal
30+
from numpy.testing import assert_equal, assert_allclose
3131

3232
import gridData.OpenDX
3333

@@ -77,30 +77,26 @@ def test_shape(self, D):
7777

7878
def test_edges(self, bins, D):
7979
for dim, (edges, fixture) in enumerate(zip(D.edges, bins)):
80-
assert_almost_equal(
81-
edges, fixture, err_msg="edges[{0}] mismatch".format(dim)
82-
)
80+
assert_allclose(edges, fixture, err_msg=f"edges[{dim}] mismatch")
8381

8482
def test_midpoints(self, bins, D):
8583
midpoints = [0.5 * (b[:-1] + b[1:]) for b in bins]
8684
for dim, (mp, fixture) in enumerate(zip(D.midpoints, midpoints)):
87-
assert_almost_equal(
88-
mp, fixture, err_msg="midpoints[{0}] mismatch".format(dim)
89-
)
85+
assert_allclose(mp, fixture, err_msg=f"midpoints[{dim}] mismatch")
9086

9187
def test_delta(self, D):
9288
deltas = np.array([self.Lmax]) / np.array(self.nbins)
93-
assert_almost_equal(D.delta, deltas)
89+
assert_allclose(D.delta, deltas)
9490

9591
def test_grid(self, D):
9692
dV = D.delta.prod() # orthorhombic grids only!
9793
# counts = (rho[0] * dV[0] + rho[1] * dV[1] ...) = sum_i rho[i] * dV
98-
assert_almost_equal(D.grid.sum() * dV, self.counts)
94+
assert D.grid.sum() * dV == pytest.approx(self.counts)
9995

10096
def test_origin(self, bins, D):
10197
midpoints = [0.5 * (b[:-1] + b[1:]) for b in bins]
10298
origin = [m[0] for m in midpoints]
103-
assert_almost_equal(D.origin, origin)
99+
assert_allclose(D.origin, origin)
104100

105101
def test_check_set_unit_keyerror(self, D):
106102
units = {"weight": "A"}
@@ -146,28 +142,28 @@ def test_check_convert_density_units_same_density_units(self, D):
146142
D_orig = copy.deepcopy(D)
147143
D.convert_density(unit)
148144
assert D.units["density"] == D_orig.units["density"] == unit
149-
assert_almost_equal(D.grid, D_orig.grid)
145+
assert_allclose(D.grid, D_orig.grid)
150146

151147
def test_check_convert_density_units_density(self, D):
152148
unit = "nm^{-3}"
153149
D_orig = copy.deepcopy(D)
154150
D.convert_density(unit)
155151
assert D.units["density"] == "nm^{-3}"
156-
assert_almost_equal(D.grid, 10**3 * D_orig.grid)
152+
assert_allclose(D.grid, 10**3 * D_orig.grid)
157153

158154
def test_convert_length_same_length_units(self, D):
159155
unit = "A"
160156
D_orig = copy.deepcopy(D)
161157
D.convert_length(unit)
162158
assert D.units["length"] == D_orig.units["length"] == unit
163-
assert_almost_equal(D.grid, D_orig.grid)
159+
assert_allclose(D.grid, D_orig.grid)
164160

165161
def test_convert_length_other_length_units(self, D):
166162
unit = "nm"
167163
D_orig = copy.deepcopy(D)
168164
D.convert_length(unit)
169165
assert D.units["length"] == unit
170-
assert_almost_equal(D.grid, D_orig.grid)
166+
assert_allclose(D.grid, D_orig.grid)
171167

172168
def test_repr(self, D, D1):
173169
assert str(D) == "<Density density with (3, 4, 5) bins>"
@@ -178,14 +174,14 @@ def test_check_convert_length_edges(self, D):
178174
unit = "nm"
179175
D.convert_length(unit)
180176
for prev_edge, conv_edge in zip(D1.edges, D.edges):
181-
assert_almost_equal(prev_edge, 10 * conv_edge)
177+
assert_allclose(prev_edge, 10 * conv_edge)
182178

183179
def test_check_convert_density_edges(self, D):
184180
unit = "nm^{-3}"
185181
D_orig = copy.deepcopy(D)
186182
D.convert_density(unit)
187183
for new_den, orig_den in zip(D.edges, D_orig.edges):
188-
assert_almost_equal(new_den, orig_den)
184+
assert_allclose(new_den, orig_den)
189185

190186
@pytest.mark.parametrize("dxtype", ("float", "double", "int", "byte"))
191187
def test_export_types(self, D, dxtype, tmpdir, outfile="density.dx"):
@@ -261,18 +257,17 @@ def check_DensityAnalysis(
261257
D = density.DensityAnalysis(ag, delta=self.delta, **kwargs).run(
262258
**runargs, **client_DensityAnalysis
263259
)
264-
assert_almost_equal(
265-
D.results.density.grid.mean(),
266-
ref_meandensity,
267-
err_msg="mean density does not match",
268-
)
260+
assert D.results.density.grid.mean() == pytest.approx(
261+
ref_meandensity
262+
), "mean density does not match"
269263
D.results.density.export(self.outfile)
270264

271265
D2 = density.Density(self.outfile)
272-
assert_almost_equal(
266+
assert_allclose(
273267
D.results.density.grid,
274268
D2.grid,
275-
decimal=self.precision,
269+
rtol=0,
270+
atol=10 ** (-self.precision),
276271
err_msg="DX export failed: different grid sizes",
277272
)
278273

testsuite/MDAnalysisTests/analysis/test_diffusionmap.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ def test_eg(dist, dmap):
5252
# makes no sense to test values here, no physical meaning
5353

5454

55-
def test_dist_weights(u):
55+
def test_dist_weights(u, client_DistanceMatrix):
5656
backbone = u.select_atoms("backbone")
5757
weights_atoms = np.ones(len(backbone.atoms))
5858
dist = diffusionmap.DistanceMatrix(
5959
u, select="backbone", weights=weights_atoms
6060
)
61-
dist.run(step=3)
61+
dist.run(**client_DistanceMatrix, step=3)
6262
dmap = diffusionmap.DiffusionMap(dist)
6363
dmap.run()
6464
assert_array_almost_equal(dmap.eigenvalues, [1, 1, 1, 1], 4)
@@ -76,11 +76,11 @@ def test_dist_weights(u):
7676
)
7777

7878

79-
def test_dist_weights_frames(u):
79+
def test_dist_weights_frames(u, client_DistanceMatrix):
8080
backbone = u.select_atoms("backbone")
8181
weights_atoms = np.ones(len(backbone.atoms))
8282
dist = diffusionmap.DistanceMatrix(
83-
u, select="backbone", weights=weights_atoms
83+
u, **client_DistanceMatrix, select="backbone", weights=weights_atoms
8484
)
8585
frames = np.arange(len(u.trajectory))
8686
dist.run(frames=frames[::3])
@@ -101,19 +101,25 @@ def test_dist_weights_frames(u):
101101
)
102102

103103

104-
def test_distvalues_ag_universe(u):
105-
dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run()
104+
def test_distvalues_ag_universe(u, client_DistanceMatrix):
105+
dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run(
106+
**client_DistanceMatrix
107+
)
106108
ag = u.select_atoms("backbone")
107-
dist_ag = diffusionmap.DistanceMatrix(ag).run()
109+
dist_ag = diffusionmap.DistanceMatrix(ag).run(**client_DistanceMatrix)
108110
assert_allclose(
109111
dist_universe.results.dist_matrix, dist_ag.results.dist_matrix
110112
)
111113

112114

113-
def test_distvalues_ag_select(u):
114-
dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run()
115+
def test_distvalues_ag_select(u, client_DistanceMatrix):
116+
dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run(
117+
**client_DistanceMatrix
118+
)
115119
ag = u.select_atoms("protein")
116-
dist_ag = diffusionmap.DistanceMatrix(ag, select="backbone").run()
120+
dist_ag = diffusionmap.DistanceMatrix(ag, select="backbone").run(
121+
**client_DistanceMatrix
122+
)
117123
assert_allclose(
118124
dist_universe.results.dist_matrix, dist_ag.results.dist_matrix
119125
)
@@ -156,8 +162,10 @@ def test_not_universe_atomgroup_error(u):
156162
diffusionmap.DiffusionMap(trj_only)
157163

158164

159-
def test_DistanceMatrix_attr_warning(u):
160-
dist = diffusionmap.DistanceMatrix(u, select="backbone").run(step=3)
165+
def test_DistanceMatrix_attr_warning(u, client_DistanceMatrix):
166+
dist = diffusionmap.DistanceMatrix(u, select="backbone").run(
167+
**client_DistanceMatrix, step=3
168+
)
161169
wmsg = f"The `dist_matrix` attribute was deprecated in MDAnalysis 2.0.0"
162170
with pytest.warns(DeprecationWarning, match=wmsg):
163171
assert getattr(dist, "dist_matrix") is dist.results.dist_matrix

0 commit comments

Comments
 (0)