diff --git a/package/CHANGELOG b/package/CHANGELOG index 12d2bc53243..e59e797ba41 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -94,6 +94,9 @@ Changes * adding element attribute to TXYZParser if all atom names are valid element symbols (PR #3826) Deprecations + * Direct indexing of results in analysis.nucleicacids' NucPairDist + and WatsonCrickDist classes is deprecated and will be removed in 2.5.0. + Please use `results.pair_distances` instead (Issue #3744) * Deprecated analysis.align.sequence_alignment() for removal in 3.0 (#3950) * Add deprecation warning for `timestep` copying in DCDReader (Issue #3889, PR #3888) diff --git a/package/MDAnalysis/analysis/nucleicacids.py b/package/MDAnalysis/analysis/nucleicacids.py index acb011ddec1..cf9795398c0 100644 --- a/package/MDAnalysis/analysis/nucleicacids.py +++ b/package/MDAnalysis/analysis/nucleicacids.py @@ -45,7 +45,7 @@ Denning2012 Distances -_________ +--------- .. autoclass:: NucPairDist :members: @@ -60,6 +60,7 @@ """ from typing import List, Dict +import warnings import numpy as np @@ -69,6 +70,30 @@ from MDAnalysis.core.groups import Residue +# Remove in 2.5.0 +class DeprecatedResults(Results): + def __getitem__(self, key): + if key in self.data: + if key == "times": + wmsg = ("The `times` results attribute is deprecated and will " + "be removed in MDAnalysis 2.5.0.") + warnings.warn(wmsg, DeprecationWarning) + return self.data[key] + if hasattr(self.__class__, "__missing__"): + return self.__class__.__missing__(self, key) + if isinstance(key, int) and key >= 0: + try: + item = self['pair_distances'][:, key] + except IndexError: + raise KeyError(key) + else: + wmsg = ("Accessing results via selection indices is " + "deprecated and will be removed in MDAnalysis 2.5.0") + warnings.warn(wmsg, DeprecationWarning) + return item + raise KeyError(key) + + class NucPairDist(AnalysisBase): r"""Atom Pair distance calculation base class. @@ -79,30 +104,56 @@ class NucPairDist(AnalysisBase): :class:`~MDAnalysis.core.groups.AtomGroup`. Parameters - __________ + ---------- selection1: List[AtomGroup] - list of :class:`~MDAnalysis.core.groups.AtomGroup` containing an atom + List of :class:`~MDAnalysis.core.groups.AtomGroup` containing an atom of each nucleic acid being analyzed. - selection1: List[AtomGroup] - list of :class:`~MDAnalysis.core.groups.AtomGroup` containing an atom + selection2: List[AtomGroup] + List of :class:`~MDAnalysis.core.groups.AtomGroup` containing an atom of each nucleic acid being analyzed. kwargs: dict - arguments for :class:`~MDAnalysis.analysis.base.AnalysisBase` + Arguments for :class:`~MDAnalysis.analysis.base.AnalysisBase` Attributes - __________ - results: numpy.ndarray - first index is selection second index is time - results.times: numpy.ndarray - times used in analysis + ---------- + times: numpy.ndarray + Simulation times for analysis. + results: numpy.ndarray + Array of pair distances. First index is selection, second index is time. + + .. deprecated:: 2.4.0 + Will be removed in MDAnalysis 2.5.0. Please use + :attr:`results.pair_distances` instead. + + results.times: numpy.ndarray + Simulation times used in analysis + + .. deprecated:: 2.4.0 + Will be removed in MDAnalysis 2.5.0. Please use + :attr:`times` instead. + + results.pair_distances: numpy.ndarray + 2D array of pair distances. First dimension is simulation time, second + dimension contains the pair distances for each each entry pair in + selection1 and selection2. + + .. versionadded:: 2.4.0 + Raises - ______ + ------ ValueError - if the selections given are not the same length + If the selections given are not the same length - """ + + .. deprecated:: 2.4.0 + Accessing results by passing selection indices to :attr:`results` is + now deprecated and will be removed in MDAnalysis version 2.5.0. Please + use :attr:`results.pair_distances` instead. + The :attr:`results.times` is deprecated and will be removed in version + 2.5.0. Please use the class attribute :attr:`times` instead. + """ _s1: mda.AtomGroup _s2: mda.AtomGroup @@ -127,23 +178,19 @@ def __init__(self, selection1: List[mda.AtomGroup], self._s1 += selection1[i] self._s2 += selection2[i] - self.results = Results() + self.results = DeprecatedResults() def _prepare(self) -> None: - self._res_dict = {k: [] for k in range(self._n_sel)} - self._times = [] + self._res_array: np.ndarray = np.zeros([self.n_frames, self._n_sel]) def _single_frame(self) -> None: dist: np.ndarray = calc_bonds(self._s1.positions, self._s2.positions) - - for i in range(self._n_sel): - self._res_dict[i].append(dist[i]) - self._times.append(self._ts.time) + self._res_array[self._frame_index, :] = dist def _conclude(self) -> None: - self.results['times'] = np.array(self._times) - for i in range(self._n_sel): - self.results[i] = np.array(self._res_dict[i]) + # Remove 2.5.0 + self.results['times'] = np.array(self.times) + self.results['pair_distances'] = self._res_array class WatsonCrickDist(NucPairDist): @@ -154,49 +201,69 @@ class WatsonCrickDist(NucPairDist): their index in the lists given as arguments. Parameters - __________ + ---------- strand1: List[Residue] First list of bases strand2: List[Residue] Second list of bases n1_name: str (optional) - Name of Nitrogen 1 of nucleic acids - by default assigned to N1 + Name of Nitrogen 1 of nucleic acids, by default assigned to N1 n3_name: str (optional) - Name of Nitrogen 3 of nucleic acids - by default assigned to N3 + Name of Nitrogen 3 of nucleic acids, by default assigned to N3 g_name: str (optional) - Name of Guanine in topology - by default assigned to G + Name of Guanine in topology, by default assigned to G a_name: str (optional) - Name of Adenine in topology - by default assigned to G + Name of Adenine in topology, by default assigned to A u_name: str (optional) - Name of Uracil in topology - by default assigned to U + Name of Uracil in topology, by default assigned to U t_name: str (optional) - Name of Thymine in topology - by default assigned to T + Name of Thymine in topology, by default assigned to T c_name: str (optional) - Name of Cytosine in topology - by default assigned to C + Name of Cytosine in topology, by default assigned to C **kwargs: dict arguments for :class:`~MDAnalysis.analysis.base.AnalysisBase` Attributes - __________ - results: numpy.ndarray - first index is selection second index is time - results.times: numpy.ndarray - times used in analysis + ---------- + times: numpy.ndarray + Simulation times for analysis. + results: numpy.ndarray + Array of Watson-Crick basepair distances. First index is selection, + second index is time. + + .. deprecated:: 2.4.0 + Will be removed in MDAnalysis 2.5.0. Please use + :attr:`results.pair_distances` instead. + + results.times: numpy.ndarray + Simulation times used in analysis + + .. deprecated:: 2.4.0 + Will be removed in MDAnalysis 2.5.0. Please use + :attr:`times` instead. + + results.pair_distances: numpy.ndarray + 2D array of Watson-Crick basepair distances. First dimension is + simulation time, second dimension contains the pair distances for + each each entry pair in strand1 and strand2. + + .. versionadded:: 2.4.0 + Raises - ______ + ------ ValueError - if the residues given are not amino acids + If the residues given are not amino acids ValueError - if the selections given are not the same length + If the selections given are not the same length + + .. deprecated:: 2.4.0 + Accessing results by passing strand indices to :attr:`results` is + now deprecated and will be removed in MDAnalysis version 2.5.0. Please + use :attr:`results.pair_distances` instead. + The :attr:`results.times` is deprecated and will be removed in version + 2.5.0. Please use the class attribute :attr:`times` instead. """ def __init__(self, strand1: List[Residue], strand2: List[Residue], diff --git a/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py b/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py index 2d22b4a8800..cf98ae9f806 100644 --- a/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py +++ b/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py @@ -33,13 +33,44 @@ def u(): return mda.Universe(RNA_PSF, RNA_PDB) -def test_wc_dist(u): +@pytest.fixture(scope='module') +def wc_rna(u): strand: mda.AtomGroup = u.select_atoms("segid RNAA") strand1 = [strand.residues[0], strand.residues[21]] strand2 = [strand.residues[1], strand.residues[22]] WC = WatsonCrickDist(strand1, strand2) WC.run() + return WC + + +def test_wc_dist(wc_rna): + assert_allclose(wc_rna.results.pair_distances[0, 0], 4.3874702, atol=1e-3) + assert_allclose(wc_rna.results.pair_distances[0, 1], 4.1716404, atol=1e-3) + + +def test_wc_dist_sel_indices_deprecated(wc_rna): + wmsg = ("Accessing results via selection indices is " + "deprecated and will be removed in MDAnalysis 2.5.0") + + with pytest.deprecated_call(match=wmsg): + for i in range(wc_rna._n_sel): + assert_allclose( + wc_rna.results.pair_distances[:, i], + wc_rna.results[i][0] + ) + + +def test_wc_dist_times_deprecated(wc_rna): + wmsg = ("The `times` results attribute is deprecated and will " + "be removed in MDAnalysis 2.5.0.") + + with pytest.deprecated_call(match=wmsg): + assert_allclose(wc_rna.times, wc_rna.results.times) + + +@pytest.mark.parametrize('key', [2, 'parsnips', 'time', -1]) +def test_wc_dis_results_keyerrs(wc_rna, key): - assert_allclose(WC.results[0][0], 4.3874702, atol=1e-3) - assert_allclose(WC.results[1][0], 4.1716404, atol=1e-3) + with pytest.raises(KeyError, match=f"{key}"): + wc_rna.results[key]