Skip to content

Commit 550e3ac

Browse files
Updated RDF package to enable backend selection (#5038)
* Updated RDF package to enable backend selection * removed some whitespace to keep flake8 happy * black made some changes to distances.py * Added check for nsgrid functions that don't use backend * added tests for RDF backend * fixed mypy errors * updated AUTHORS and CHANGELOG for PR 5038 * moved distopia check function to common location * updated CHANGELOG * fix linting issue * fixed line width, and removed duplicated entry --------- Co-authored-by: Oliver Beckstein <orbeckst@gmail.com>
1 parent 75b5f72 commit 550e3ac

File tree

8 files changed

+178
-49
lines changed

8 files changed

+178
-49
lines changed

package/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Chronological list of authors
257257
- Debasish Mohanty
258258
- Abdulrahman Elbanna
259259
- Tulga-Erdene Sodjargal
260+
- Gareth Elliott
260261

261262

262263
External code

package/CHANGELOG

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

1616
-------------------------------------------------------------------------------
1717
??/??/?? IAlibay, orbeckst, BHM-Bob, TRY-ER, Abdulrahman-PROG, pbuslaev,
18-
yuxuanzhuang, yuyuan871111, tanishy7777, tulga-rdn
18+
yuxuanzhuang, yuyuan871111, tanishy7777, tulga-rdn, Gareth-elliott
1919

2020

2121
* 2.10.0
@@ -48,11 +48,18 @@ Enhancements
4848
(Issue #4948 #2874, PR #4965)
4949
* Enables parallelization for analysis.lineardensity.LinearDensity
5050
(Issue #4678, PR #5007)
51-
* Remove `default` channel from RTD conda env. (Issue # 5036, PR # 5037)
51+
* Enabled selection of backend for distance calculations in RDF classes
52+
(PR #5038)
53+
* Modified functions in lib.distances to add the backend keyword,
54+
so that it gets passed through from the calling functions and classes
55+
(PR #5038)
56+
* Moved distopia checking function to common import location in
57+
MDAnalysisTest.util (PR #5038)
5258

5359
Changes
5460
* Removed undocumented and unused attribute
5561
`analysis.lineardensity.LinearDensity.totalmass` (PR #5007)
62+
* Remove `default` channel from RTD conda env. (Issue # 5036, PR # 5037)
5663

5764
Deprecations
5865

package/MDAnalysis/analysis/rdf.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ class InterRDF(AnalysisBase):
131131
spatially correlated due to direct bonded connections.
132132
verbose : bool
133133
Show detailed progress of the calculation if set to `True`
134+
backend : {'serial', 'OpenMP', 'distopia'}, optional
135+
Keyword selecting the type of acceleration of the distance calculations.
136+
Default is 'serial'.
137+
138+
.. versionadded:: 2.10.0
134139
135140
Attributes
136141
----------
@@ -227,6 +232,7 @@ def __init__(
227232
norm="rdf",
228233
exclusion_block=None,
229234
exclude_same=None,
235+
backend="serial",
230236
**kwargs,
231237
):
232238
super(InterRDF, self).__init__(g1.universe.trajectory, **kwargs)
@@ -262,6 +268,8 @@ def __init__(
262268
"Use 'rdf', 'density' or 'none'."
263269
)
264270

271+
self.backend = backend
272+
265273
def _prepare(self):
266274
# Empty histogram to store the RDF
267275
count, edges = np.histogram([-1], **self.rdf_settings)
@@ -283,6 +291,7 @@ def _single_frame(self):
283291
self.g2.positions,
284292
self._maxrange,
285293
box=self._ts.dimensions,
294+
backend=self.backend,
286295
)
287296
# Maybe exclude same molecule distances
288297
if self._exclusion_block is not None:
@@ -424,6 +433,12 @@ class InterRDF_s(AnalysisBase):
424433
.. deprecated:: 2.3.0
425434
Instead of `density=True` use `norm='density'`
426435
436+
backend : {'serial', 'OpenMP', 'distopia'}, optional
437+
Keyword selecting the type of acceleration of the distance calculations.
438+
Default is 'serial'.
439+
440+
.. versionadded:: 2.10.0
441+
427442
Attributes
428443
----------
429444
results.bins : numpy.ndarray
@@ -571,6 +586,7 @@ def __init__(
571586
range=(0.0, 15.0),
572587
norm="rdf",
573588
density=False,
589+
backend="serial",
574590
**kwargs,
575591
):
576592
super(InterRDF_s, self).__init__(
@@ -603,6 +619,8 @@ def __init__(
603619
)
604620
self.norm = "density"
605621

622+
self.backend = backend
623+
606624
def _prepare(self):
607625
count, edges = np.histogram([-1], **self.rdf_settings)
608626
self.results.count = [
@@ -624,6 +642,7 @@ def _single_frame(self):
624642
ag2.positions,
625643
self._maxrange,
626644
box=self._ts.dimensions,
645+
backend=self.backend,
627646
)
628647

629648
for j, (idx1, idx2) in enumerate(pairs):

package/MDAnalysis/lib/distances.py

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ def capped_distance(
519519
box: Optional[npt.NDArray] = None,
520520
method: Optional[str] = None,
521521
return_distances: Optional[bool] = True,
522+
backend: Optional[str] = "serial",
522523
):
523524
"""Calculates pairs of indices corresponding to entries in the `reference`
524525
and `configuration` arrays which are separated by a distance lying within
@@ -557,6 +558,8 @@ def capped_distance(
557558
method.
558559
return_distances : bool, optional
559560
If set to ``True``, distances will also be returned.
561+
backend : {'serial', 'OpenMP', 'distopia'}, optional
562+
Keyword selecting the type of acceleration.
560563
561564
Returns
562565
-------
@@ -599,6 +602,9 @@ def capped_distance(
599602
.. versionchanged:: 2.3.0
600603
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
601604
argument in any position and checks inputs using type hinting.
605+
.. versionchanged:: 2.10.0
606+
Added the "backend" argument to select the type of acceleration of
607+
the distance calculations.
602608
"""
603609
if box is not None:
604610
box = np.asarray(box, dtype=np.float32)
@@ -622,14 +628,26 @@ def capped_distance(
622628
box=box,
623629
method=method,
624630
)
625-
return function(
626-
reference,
627-
configuration,
628-
max_cutoff,
629-
min_cutoff=min_cutoff,
630-
box=box,
631-
return_distances=return_distances,
632-
)
631+
632+
if function.__name__ == "_nsgrid_capped":
633+
return function(
634+
reference,
635+
configuration,
636+
max_cutoff,
637+
min_cutoff=min_cutoff,
638+
box=box,
639+
return_distances=return_distances,
640+
)
641+
else:
642+
return function(
643+
reference,
644+
configuration,
645+
max_cutoff,
646+
min_cutoff=min_cutoff,
647+
box=box,
648+
return_distances=return_distances,
649+
backend=backend,
650+
)
633651

634652

635653
def _determine_method(
@@ -727,6 +745,7 @@ def _bruteforce_capped(
727745
min_cutoff: Optional[float] = None,
728746
box: Optional[npt.NDArray] = None,
729747
return_distances: Optional[bool] = True,
748+
backend: Optional[str] = "serial",
730749
):
731750
"""Capped distance evaluations using a brute force method.
732751
@@ -764,6 +783,8 @@ def _bruteforce_capped(
764783
``[lx, ly, lz, alpha, beta, gamma]``.
765784
return_distances : bool, optional
766785
If set to ``True``, distances will also be returned.
786+
backend : {'serial', 'OpenMP', 'distopia'}, optional
787+
Keyword selecting the type of acceleration.
767788
768789
Returns
769790
-------
@@ -784,13 +805,18 @@ def _bruteforce_capped(
784805
.. versionchanged:: 2.3.0
785806
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
786807
argument in any position and checks inputs using type hinting.
808+
.. versionchanged:: 2.10.0
809+
Added the "backend" argument to select the type of acceleration of
810+
the distance calculations.
787811
"""
788812
# Default return values (will be overwritten only if pairs are found):
789813
pairs = np.empty((0, 2), dtype=np.intp)
790814
distances = np.empty((0,), dtype=np.float64)
791815

792816
if len(reference) > 0 and len(configuration) > 0:
793-
_distances = distance_array(reference, configuration, box=box)
817+
_distances = distance_array(
818+
reference, configuration, box=box, backend=backend
819+
)
794820
if min_cutoff is not None:
795821
mask = np.where(
796822
(_distances <= max_cutoff) & (_distances > min_cutoff)
@@ -823,6 +849,7 @@ def _pkdtree_capped(
823849
min_cutoff: Optional[float] = None,
824850
box: Optional[npt.NDArray] = None,
825851
return_distances: Optional[bool] = True,
852+
backend: Optional[str] = "serial",
826853
):
827854
"""Capped distance evaluations using a KDtree method.
828855
@@ -860,6 +887,8 @@ def _pkdtree_capped(
860887
``[lx, ly, lz, alpha, beta, gamma]``.
861888
return_distances : bool, optional
862889
If set to ``True``, distances will also be returned.
890+
backend : {'serial', 'OpenMP', 'distopia'}, optional
891+
Keyword selecting the type of acceleration.
863892
864893
Returns
865894
-------
@@ -880,6 +909,9 @@ def _pkdtree_capped(
880909
.. versionchanged:: 2.3.0
881910
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
882911
argument in any position and checks inputs using type hinting.
912+
.. versionchanged:: 2.10.0
913+
Added the "backend" argument to select the type of acceleration of
914+
the distance calculations.
883915
"""
884916
from .pkdtree import (
885917
PeriodicKDTree,
@@ -899,7 +931,10 @@ def _pkdtree_capped(
899931
if return_distances or (min_cutoff is not None):
900932
refA, refB = pairs[:, 0], pairs[:, 1]
901933
distances = calc_bonds(
902-
reference[refA], configuration[refB], box=box
934+
reference[refA],
935+
configuration[refB],
936+
box=box,
937+
backend=backend,
903938
)
904939
if min_cutoff is not None:
905940
mask = np.where(distances > min_cutoff)
@@ -1044,6 +1079,7 @@ def self_capped_distance(
10441079
box: Optional[npt.NDArray] = None,
10451080
method: Optional[str] = None,
10461081
return_distances: Optional[bool] = True,
1082+
backend: Optional[str] = "serial",
10471083
):
10481084
"""Calculates pairs of indices corresponding to entries in the `reference`
10491085
array which are separated by a distance lying within the specified
@@ -1078,6 +1114,8 @@ def self_capped_distance(
10781114
method.
10791115
return_distances : bool, optional
10801116
If set to ``True``, distances will also be returned.
1117+
backend : {'serial', 'OpenMP', 'distopia'}, optional
1118+
Keyword selecting the type of acceleration.
10811119
10821120
Returns
10831121
-------
@@ -1126,6 +1164,9 @@ def self_capped_distance(
11261164
.. versionchanged:: 2.3.0
11271165
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
11281166
argument in any position and checks inputs using type hinting.
1167+
.. versionchanged:: 2.10.0
1168+
Added the "backend" argument to select the type of acceleration of
1169+
the distance calculations.
11291170
"""
11301171
if box is not None:
11311172
box = np.asarray(box, dtype=np.float32)
@@ -1146,13 +1187,24 @@ def self_capped_distance(
11461187
box=box,
11471188
method=method,
11481189
)
1149-
return function(
1150-
reference,
1151-
max_cutoff,
1152-
min_cutoff=min_cutoff,
1153-
box=box,
1154-
return_distances=return_distances,
1155-
)
1190+
1191+
if function.__name__ == "_nsgrid_capped_self":
1192+
return function(
1193+
reference,
1194+
max_cutoff,
1195+
min_cutoff=min_cutoff,
1196+
box=box,
1197+
return_distances=return_distances,
1198+
)
1199+
else:
1200+
return function(
1201+
reference,
1202+
max_cutoff,
1203+
min_cutoff=min_cutoff,
1204+
box=box,
1205+
return_distances=return_distances,
1206+
backend=backend,
1207+
)
11561208

11571209

11581210
def _determine_method_self(
@@ -1236,6 +1288,7 @@ def _bruteforce_capped_self(
12361288
min_cutoff: Optional[float] = None,
12371289
box: Optional[npt.NDArray] = None,
12381290
return_distances: Optional[bool] = True,
1291+
backend: Optional[str] = "serial",
12391292
):
12401293
"""Capped distance evaluations using a brute force method.
12411294
@@ -1266,6 +1319,8 @@ def _bruteforce_capped_self(
12661319
``[lx, ly, lz, alpha, beta, gamma]``.
12671320
return_distances : bool, optional
12681321
If set to ``True``, distances will also be returned.
1322+
backend : {'serial', 'OpenMP', 'distopia'}, optional
1323+
Keyword selecting the type of acceleration.
12691324
12701325
Returns
12711326
-------
@@ -1287,6 +1342,9 @@ def _bruteforce_capped_self(
12871342
.. versionchanged:: 2.3.0
12881343
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
12891344
argument in any position and checks inputs using type hinting.
1345+
.. versionchanged:: 2.10.0
1346+
Added the "backend" argument to select the type of acceleration of
1347+
the distance calculations.
12901348
"""
12911349
# Default return values (will be overwritten only if pairs are found):
12921350
pairs = np.empty((0, 2), dtype=np.intp)
@@ -1296,7 +1354,7 @@ def _bruteforce_capped_self(
12961354
# We're searching within a single coordinate set, so we need at least two
12971355
# coordinates to find distances between them.
12981356
if N > 1:
1299-
distvec = self_distance_array(reference, box=box)
1357+
distvec = self_distance_array(reference, box=box, backend=backend)
13001358
dist = np.full((N, N), np.finfo(np.float64).max, dtype=np.float64)
13011359
dist[np.triu_indices(N, 1)] = distvec
13021360

@@ -1325,6 +1383,7 @@ def _pkdtree_capped_self(
13251383
min_cutoff: Optional[float] = None,
13261384
box: Optional[npt.NDArray] = None,
13271385
return_distances: Optional[bool] = True,
1386+
backend: Optional[str] = "serial",
13281387
):
13291388
"""Capped distance evaluations using a KDtree method.
13301389
@@ -1355,6 +1414,8 @@ def _pkdtree_capped_self(
13551414
``[lx, ly, lz, alpha, beta, gamma]``.
13561415
return_distances : bool, optional
13571416
If set to ``True``, distances will also be returned.
1417+
backend : {'serial', 'OpenMP', 'distopia'}, optional
1418+
Keyword selecting the type of acceleration.
13581419
13591420
Returns
13601421
-------
@@ -1376,6 +1437,9 @@ def _pkdtree_capped_self(
13761437
.. versionchanged:: 2.3.0
13771438
Can now accept an :class:`~MDAnalysis.core.groups.AtomGroup` as an
13781439
argument in any position and checks inputs using type hinting.
1440+
.. versionchanged:: 2.10.0
1441+
Added the "backend" argument to select the type of acceleration of
1442+
the distance calculations.
13791443
"""
13801444
from .pkdtree import (
13811445
PeriodicKDTree,
@@ -1397,7 +1461,7 @@ def _pkdtree_capped_self(
13971461
if return_distances or (min_cutoff is not None):
13981462
refA, refB = pairs[:, 0], pairs[:, 1]
13991463
distances = calc_bonds(
1400-
reference[refA], reference[refB], box=box
1464+
reference[refA], reference[refB], box=box, backend=backend
14011465
)
14021466
if min_cutoff is not None:
14031467
idx = distances > min_cutoff

testsuite/MDAnalysisTests/analysis/test_rdf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import MDAnalysis as mda
2626
from MDAnalysis.analysis.rdf import InterRDF
27+
from MDAnalysisTests.util import distopia_conditional_backend
2728

2829
from MDAnalysisTests.datafiles import two_water_gro
2930

@@ -152,3 +153,10 @@ def test_unknown_norm(sels):
152153
s1, s2 = sels
153154
with pytest.raises(ValueError, match="invalid norm"):
154155
InterRDF(s1, s2, sels, norm="foo")
156+
157+
158+
@pytest.mark.parametrize("backend", distopia_conditional_backend())
159+
def test_norm(sels, backend):
160+
s1, s2 = sels
161+
rdf = InterRDF(s1, s2, norm="none", backend=backend).run()
162+
assert_allclose(max(rdf.results.rdf), 4)

0 commit comments

Comments
 (0)