diff --git a/package/AUTHORS b/package/AUTHORS index 887210c0ae..81ada5e07c 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -276,6 +276,7 @@ Chronological list of authors - Kunj Sinha - Ayush Agarwal - Parth Uppal + - Olivier Languin--Cattoën External code ------------- diff --git a/package/CHANGELOG b/package/CHANGELOG index d17748755b..c073e01b97 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -16,7 +16,8 @@ The rules for this file: ------------------------------------------------------------------------------- ??/??/?? IAlibay, orbeckst, marinegor, tylerjereddy, ljwoods2, marinegor, spyke7, talagayev, tanii1125, BradyAJohnston, hejamu, jeremyleung521, - harshitgajjela-droid, kunjsinha, aygarwal, jauy123, Dreamstick9 + harshitgajjela-droid, kunjsinha, aygarwal, jauy123, Dreamstick9, + ollyfutur * 2.11.0 @@ -42,6 +43,8 @@ Fixes DSSP by porting upstream PyDSSP 0.9.1 fix (Issue #4913) Enhancements + * 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 @CRYSIN records (Issue #3341) * Reduces duplication of code in _apply() function (Issue #5247, PR #5294) * Added new top-level `MDAnalysis.fetch` module (PR #4943) diff --git a/package/MDAnalysis/analysis/rms.py b/package/MDAnalysis/analysis/rms.py index f589a597d0..4b76723e7b 100644 --- a/package/MDAnalysis/analysis/rms.py +++ b/package/MDAnalysis/analysis/rms.py @@ -287,17 +287,19 @@ def process_selection(select): Parameters ---------- - select : str or tuple or dict + select : str or tuple or dict or None - `str` -> Any valid string selection - `dict` -> ``{'mobile':sel1, 'reference':sel2}`` - `tuple` -> ``(sel1, sel2)`` + - ``None`` Returns ------- dict selections for 'reference' and 'mobile'. Values are guarenteed to be - iterable (so that one can provide selections to retain order) + iterable (so that one can provide selections to retain order) or + ``None`` if no selection is to be performed. Notes ----- @@ -325,10 +327,16 @@ def process_selection(select): "select dictionary must contain entries for keys " "'mobile' and 'reference'." ) from None + elif select is None: + select = {"reference": None, "mobile": None} else: - raise TypeError("'select' must be either a string, 2-tuple, or dict") - select["mobile"] = asiterable(select["mobile"]) - select["reference"] = asiterable(select["reference"]) + raise TypeError( + "'select' must be either a string, 2-tuple, dict or None" + ) + if select["mobile"] is not None: + select["mobile"] = asiterable(select["mobile"]) + if select["reference"] is not None: + select["reference"] = asiterable(select["reference"]) return select @@ -394,7 +402,7 @@ def __init__( reference : AtomGroup or Universe (optional) Group of reference atoms; if ``None`` then the current frame of `atomgroup` is used. - select : str or dict or tuple (optional) + select : str or dict or tuple or None (optional) The selection to operate on; can be one of: 1. any valid selection string for @@ -405,16 +413,21 @@ def __init__( and *sel2* are valid selection strings that are applied to `atomgroup` and `reference` respectively (the :func:`MDAnalysis.analysis.align.fasta2select` function returns such - a dictionary based on a ClustalW_ or STAMP_ sequence alignment); or + a dictionary based on a ClustalW_ or STAMP_ sequence alignment) or + ``None`` if no selection is to be performed; or 3. a tuple ``(sel1, sel2)`` + 4. ``None`` + When using 2. or 3. with *sel1* and *sel2* then these selection strings are applied to `atomgroup` and `reference` respectively and should generate *groups of equivalent atoms*. *sel1* and *sel2* can each also be a *list of selection strings* to generate a :class:`~MDAnalysis.core.groups.AtomGroup` with defined atom order as - described under :ref:`ordered-selections-label`). + described under :ref:`ordered-selections-label`). When using ``None`` + no selection is performed and all atoms from `atomgroup` or `reference` + are used in their original order. groupselections : list (optional) A list of selections as described for `select`, with the difference @@ -539,8 +552,16 @@ def __init__( self.tol_mass = tol_mass self.ref_frame = ref_frame self.weights_groupselections = weights_groupselections - self.ref_atoms = self.reference.select_atoms(*select["reference"]) - self.mobile_atoms = self.atomgroup.select_atoms(*select["mobile"]) + self.ref_atoms = ( + self.reference.select_atoms(*select["reference"]) + if select["reference"] is not None + else self.reference + ) + self.mobile_atoms = ( + self.atomgroup.select_atoms(*select["mobile"]) + if select["mobile"] is not None + else self.atomgroup + ) if len(self.ref_atoms) != len(self.mobile_atoms): err = ( diff --git a/testsuite/MDAnalysisTests/analysis/test_rms.py b/testsuite/MDAnalysisTests/analysis/test_rms.py index f39baa68f1..9479c96ead 100644 --- a/testsuite/MDAnalysisTests/analysis/test_rms.py +++ b/testsuite/MDAnalysisTests/analysis/test_rms.py @@ -475,6 +475,40 @@ def test_rmsd_attr_warning(self, universe, client_RMSD): with pytest.warns(DeprecationWarning, match=wmsg): assert_equal(RMSD.rmsd, RMSD.results.rmsd) + def test_rmsd_no_selection(self, universe, correct_values, client_RMSD): + reference = MDAnalysis.Universe(PSF, DCD) + ref = reference.select_atoms("name CA") + ag = universe.select_atoms("name CA") + order = np.arange(len(ag)) + order[0] = 2 + order[2] = 0 + + # select=None will not sort the atomgroups + RMSD = MDAnalysis.analysis.rms.RMSD( + ag[order], reference=ref, select=None + ) + RMSD.run(step=49, **client_RMSD) + assert not np.allclose(RMSD.results.rmsd, correct_values) + + RMSD = MDAnalysis.analysis.rms.RMSD( + ag[order], reference=ref[order], select=None + ) + RMSD.run(step=49, **client_RMSD) + assert_almost_equal( + RMSD.results.rmsd, + correct_values, + 4, + err_msg="error: rmsd profile should match " + "between true values and calculated values", + ) + + def test_rmsd_misuse_selec_raises_TypeError(self, universe): + with pytest.raises(TypeError): + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + select=42, + ) + class TestRMSF(object): @pytest.fixture()