diff --git a/package/CHANGELOG b/package/CHANGELOG index f70078f975d..abaf73f9f2a 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -175,6 +175,9 @@ Enhancements checking if it can be used in parallel analysis. (Issue #2996, PR #2950) Changes + * `analysis.align.AlignTraj` and `analysis.align.AverageStructure` now store + their result attributes using the `analysis.base.Results` class + (Issues #3278 and #3261) * `analysis.hydrogenbonds.hbond_analysis.HydrogenBondAnalyis` now stores `hbonds` data using the `analysis.base.Results class (Issues #3271, #3261) * `analysis.rms.RMSD` and `analysis.rms.RMSF` classes now store `rmsd` and @@ -238,6 +241,12 @@ Changes * Added OpenMM coordinate and topology converters (Issue #2863, PR #2917) Deprecations + * The `analysis.align.AlignTraj.rmsd` attribute is now deprecated in + favour of `analysis.align.AlignTraj.results.rmsd` (Issue #3278, #3261) + * The `universe`, `positions`, and `rmsd` attributes of + `analysis.align.AverageStructure` are now deprecated in favour of + `results.universe`, `results.positions`, and `results.rmsd` + (Issue #3278, #3261) * The `hbonds` attribute of `hydrogenbonds.hbond_analysis.HydrogenBondAnalysis.hbonds` is now deprecated in favour of `results.hbonds` (Issues #3271, #3261) diff --git a/package/MDAnalysis/analysis/align.py b/package/MDAnalysis/analysis/align.py index 1f08bea2d5c..acfcbb37f2c 100644 --- a/package/MDAnalysis/analysis/align.py +++ b/package/MDAnalysis/analysis/align.py @@ -535,7 +535,7 @@ class AlignTraj(AnalysisBase): .. versionchanged:: 1.0.0 ``save()`` has now been removed, as an alternative use ``np.savetxt()`` - on :attr:`rmsd`. + on :attr:`results.rmsd`. """ @@ -589,9 +589,19 @@ def __init__(self, mobile, reference, select='all', filename=None, Atoms of the reference structure to be aligned against mobile_atoms : AtomGroup Atoms inside each trajectory frame to be rmsd_aligned - rmsd : Array + results.rmsd : :class:`numpy.ndarray` Array of the rmsd values of the least rmsd between the mobile_atoms and reference_atoms after superposition and minimimization of rmsd + + .. versionadded:: 2.0.0 + + rmsd : :class:`numpy.ndarray` + Alias to the :attr:`results.rmsd` attribute. + + .. deprecated:: 2.0.0 + Will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.rmsd` instead. + filename : str String reflecting the filename of the file where the aligned positions will be written to upon running RMSD alignment @@ -627,6 +637,10 @@ def __init__(self, mobile, reference, select='all', filename=None, Support for the ``start``, ``stop``, and ``step`` keywords has been removed. These should instead be passed to :meth:`AlignTraj.run`. + .. versionchanged:: 2.0.0 + :attr:`rmsd` results are now stored in a + :class:`MDAnalysis.analysis.base.Results` instance. + """ select = rms.process_selection(select) self.ref_atoms = reference.select_atoms(*select['reference']) @@ -677,17 +691,18 @@ def _prepare(self): self._ref_com = self.ref_atoms.center(self._weights) self._ref_coordinates = self.ref_atoms.positions - self._ref_com # allocate the array for selection atom coords - self.rmsd = np.zeros((self.n_frames,)) + self.results.rmsd = np.zeros((self.n_frames,)) def _single_frame(self): index = self._frame_index mobile_com = self.mobile_atoms.center(self._weights) mobile_coordinates = self.mobile_atoms.positions - mobile_com - mobile_atoms, self.rmsd[index] = _fit_to(mobile_coordinates, - self._ref_coordinates, - self.mobile, - mobile_com, - self._ref_com, self._weights) + mobile_atoms, self.results.rmsd[index] = _fit_to( + mobile_coordinates, + self._ref_coordinates, + self.mobile, + mobile_com, + self._ref_com, self._weights) # write whole aligned input trajectory system self._writer.write(mobile_atoms) @@ -696,6 +711,14 @@ def _conclude(self): if not self._verbose: logging.disable(logging.NOTSET) + @property + def rmsd(self): + wmsg = ("The `rmsd` attribute was deprecated in MDAnalysis 2.0.0 and " + "will be removed in MDAnalysis 3.0.0. Please use " + "`results.rmsd` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.rmsd + class AverageStructure(AnalysisBase): """RMS-align trajectory to a reference structure using a selection, @@ -722,7 +745,7 @@ class AverageStructure(AnalysisBase): # align to the third frame and average structure av = align.AverageStructure(u, ref_frame=3).run() - averaged_universe = av.universe + averaged_universe = av.results.universe """ @@ -775,12 +798,42 @@ def __init__(self, mobile, reference=None, select='all', filename=None, Atoms of the reference structure to be aligned against mobile_atoms : AtomGroup Atoms inside each trajectory frame to be rmsd_aligned - universe : mda.Universe + results.universe : :class:`MDAnalysis.Universe` New Universe with average positions - positions : np.ndarray(dtype=float) + + .. versionadded:: 2.0.0 + + universe : :class:`MDAnalysis.Universe` + Alias to the :attr:`results.universe` attribute. + + .. deprecated:: 2.0.0 + Will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.universe` instead. + + results.positions : np.ndarray(dtype=float) Average positions - rmsd : float + + .. versionadded:: 2.0.0 + + positions : np.ndarray(dtype=float) + Alias to the :attr:`results.positions` attribute. + + .. deprecated:: 2.0.0 + Will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.positions` instead. + + results.rmsd : float Average RMSD per frame + + .. versionadded:: 2.0.0 + + rmsd : float + Alias to the :attr:`results.rmsd` attribute. + + .. deprecated:: 2.0.0 + Will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.rmsd` instead. + filename : str String reflecting the filename of the file where the average structure is written @@ -799,6 +852,9 @@ def __init__(self, mobile, reference=None, select='all', filename=None, .. versionadded:: 1.0.0 + .. versionchanged:: 2.0.0 + :attr:`universe`, :attr:`positions`, and :attr:`rmsd` are now + stored in a :class:`MDAnalysis.analysis.base.Results` instance. """ if in_memory or isinstance(mobile.trajectory, MemoryReader): mobile.transfer_to_memory() @@ -832,9 +888,9 @@ def __init__(self, mobile, reference=None, select='all', filename=None, self.ref_frame = ref_frame self.filename = filename - self.universe = mda.Merge(self.mobile_atoms) + self.results.universe = mda.Merge(self.mobile_atoms) - natoms = len(self.universe.atoms) + natoms = len(self.results.universe.atoms) self.ref_atoms, self.mobile_atoms = get_matching_atoms( self.ref_atoms, self.mobile_atoms, tol_mass=tol_mass, strict=strict, match_atoms=match_atoms) @@ -863,28 +919,53 @@ def _prepare(self): self.reference.universe.trajectory[current_frame] # allocate the array for selection atom coords - self.positions = np.zeros((len(self.mobile_atoms), 3)) - self.rmsd = 0 + self.results.positions = np.zeros((len(self.mobile_atoms), 3)) + self.results.rmsd = 0 def _single_frame(self): mobile_com = self.mobile_atoms.center(self._weights) mobile_coordinates = self.mobile_atoms.positions - mobile_com - self.rmsd += _fit_to(mobile_coordinates, - self._ref_coordinates, - self.mobile, - mobile_com, - self._ref_com, self._weights)[1] - self.positions += self.mobile_atoms.positions + self.results.rmsd += _fit_to(mobile_coordinates, + self._ref_coordinates, + self.mobile, + mobile_com, + self._ref_com, self._weights)[1] + self.results.positions += self.mobile_atoms.positions def _conclude(self): - self.positions /= self.n_frames - self.rmsd /= self.n_frames - self.universe.load_new(self.positions.reshape((1, -1, 3))) - self._writer.write(self.universe.atoms) + self.results.positions /= self.n_frames + self.results.rmsd /= self.n_frames + self.results.universe.load_new( + self.results.positions.reshape((1, -1, 3))) + self._writer.write(self.results.universe.atoms) self._writer.close() if not self._verbose: logging.disable(logging.NOTSET) + @property + def universe(self): + wmsg = ("The `universe` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.universe` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.universe + + @property + def positions(self): + wmsg = ("The `positions` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalyssi 3.0.0. Please use " + "`results.positions` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.positions + + @property + def rmsd(self): + wmsg = ("The `rmsd` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.rmsd` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.rmsd + def sequence_alignment(mobile, reference, match_score=2, mismatch_penalty=-1, gap_penalty=-2, gapextension_penalty=-0.1): diff --git a/testsuite/MDAnalysisTests/analysis/test_align.py b/testsuite/MDAnalysisTests/analysis/test_align.py index 38ad541d848..6edc0ac8fa9 100644 --- a/testsuite/MDAnalysisTests/analysis/test_align.py +++ b/testsuite/MDAnalysisTests/analysis/test_align.py @@ -268,14 +268,23 @@ def test_AlignTraj_step_works(self, universe, reference, tmpdir): # this shouldn't throw an exception align.AlignTraj(universe, reference, filename=outfile).run(step=10) + def test_AlignTraj_deprecated_attribute(self, universe, reference, tmpdir): + reference.trajectory[-1] + outfile = str(tmpdir.join('align_test.dcd')) + x = align.AlignTraj(universe, reference, filename=outfile).run(stop=2) + + wmsg = "The `rmsd` attribute was deprecated in MDAnalysis 2.0.0" + with pytest.warns(DeprecationWarning, match=wmsg): + assert_equal(x.rmsd, x.results.rmsd) + def test_AlignTraj(self, universe, reference, tmpdir): reference.trajectory[-1] outfile = str(tmpdir.join('align_test.dcd')) x = align.AlignTraj(universe, reference, filename=outfile).run() fitted = mda.Universe(PSF, outfile) - assert_almost_equal(x.rmsd[0], 6.9290, decimal=3) - assert_almost_equal(x.rmsd[-1], 5.2797e-07, decimal=3) + assert_almost_equal(x.results.rmsd[0], 6.9290, decimal=3) + assert_almost_equal(x.results.rmsd[-1], 5.2797e-07, decimal=3) # RMSD against the reference frame # calculated on Mac OS X x86 with MDA 0.7.2 r689 @@ -288,8 +297,8 @@ def test_AlignTraj_weighted(self, universe, reference, tmpdir): x = align.AlignTraj(universe, reference, filename=outfile, weights='mass').run() fitted = mda.Universe(PSF, outfile) - assert_almost_equal(x.rmsd[0], 0, decimal=3) - assert_almost_equal(x.rmsd[-1], 6.9033, decimal=3) + assert_almost_equal(x.results.rmsd[0], 0, decimal=3) + assert_almost_equal(x.results.rmsd[-1], 6.9033, decimal=3) self._assert_rmsd(reference, fitted, 0, 0.0, weights=universe.atoms.masses) @@ -308,7 +317,7 @@ def test_AlignTraj_custom_weights(self, universe, reference, tmpdir): x_weights = align.AlignTraj(universe, reference, filename=outfile, weights=weights).run() - assert_array_almost_equal(x.rmsd, x_weights.rmsd) + assert_array_almost_equal(x.results.rmsd, x_weights.results.rmsd) def test_AlignTraj_custom_mass_weights(self, universe, reference, tmpdir): outfile = str(tmpdir.join('align_test.dcd')) @@ -316,8 +325,8 @@ def test_AlignTraj_custom_mass_weights(self, universe, reference, tmpdir): filename=outfile, weights=reference.atoms.masses).run() fitted = mda.Universe(PSF, outfile) - assert_almost_equal(x.rmsd[0], 0, decimal=3) - assert_almost_equal(x.rmsd[-1], 6.9033, decimal=3) + assert_almost_equal(x.results.rmsd[0], 0, decimal=3) + assert_almost_equal(x.results.rmsd[-1], 6.9033, decimal=3) self._assert_rmsd(reference, fitted, 0, 0.0, weights=universe.atoms.masses) @@ -337,8 +346,8 @@ def test_AlignTraj_in_memory(self, universe, reference, tmpdir): x = align.AlignTraj(universe, reference, filename=outfile, in_memory=True).run() assert x.filename is None - assert_almost_equal(x.rmsd[0], 6.9290, decimal=3) - assert_almost_equal(x.rmsd[-1], 5.2797e-07, decimal=3) + assert_almost_equal(x.results.rmsd[0], 6.9290, decimal=3) + assert_almost_equal(x.results.rmsd[-1], 5.2797e-07, decimal=3) # check in memory trajectory self._assert_rmsd(reference, universe, 0, 6.929083044751061) @@ -392,7 +401,7 @@ def _get_aligned_average_positions(ref_files, ref, select="all", **kwargs): prealigner = align.AlignTraj(u, ref, select=select, **kwargs).run() ag = u.select_atoms(select) reference_coordinates = u.trajectory.timeseries(asel=ag).mean(axis=1) - rmsd = sum(prealigner.rmsd/len(u.trajectory)) + rmsd = sum(prealigner.results.rmsd/len(u.trajectory)) return reference_coordinates, rmsd class TestAverageStructure(object): @@ -407,30 +416,51 @@ def universe(self): def reference(self): return mda.Universe(PSF, CRD) + def test_average_structure_deprecated_attrs(self, universe, reference): + # Issue #3278 - remove in MDAnalysis 3.0.0 + avg = align.AverageStructure(universe, reference).run(stop=2) + + wmsg = "The `universe` attribute was deprecated in MDAnalysis 2.0.0" + with pytest.warns(DeprecationWarning, match=wmsg): + assert_equal(avg.universe.atoms.positions, + avg.results.universe.atoms.positions) + + wmsg = "The `positions` attribute was deprecated in MDAnalysis 2.0.0" + with pytest.warns(DeprecationWarning, match=wmsg): + assert_equal(avg.positions, avg.results.positions) + + wmsg = "The `rmsd` attribute was deprecated in MDAnalysis 2.0.0" + with pytest.warns(DeprecationWarning, match=wmsg): + assert avg.rmsd == avg.results.rmsd + def test_average_structure(self, universe, reference): ref, rmsd = _get_aligned_average_positions(self.ref_files, reference) avg = align.AverageStructure(universe, reference).run() - assert_almost_equal(avg.universe.atoms.positions, ref, decimal=4) - assert_almost_equal(avg.rmsd, rmsd) + assert_almost_equal(avg.results.universe.atoms.positions, ref, + decimal=4) + assert_almost_equal(avg.results.rmsd, rmsd) def test_average_structure_mass_weighted(self, universe, reference): ref, rmsd = _get_aligned_average_positions(self.ref_files, reference, weights='mass') avg = align.AverageStructure(universe, reference, weights='mass').run() - assert_almost_equal(avg.universe.atoms.positions, ref, decimal=4) - assert_almost_equal(avg.rmsd, rmsd) + assert_almost_equal(avg.results.universe.atoms.positions, ref, + decimal=4) + assert_almost_equal(avg.results.rmsd, rmsd) def test_average_structure_select(self, universe, reference): select = 'protein and name CA and resid 3-5' ref, rmsd = _get_aligned_average_positions(self.ref_files, reference, select=select) avg = align.AverageStructure(universe, reference, select=select).run() - assert_almost_equal(avg.universe.atoms.positions, ref, decimal=4) - assert_almost_equal(avg.rmsd, rmsd) + assert_almost_equal(avg.results.universe.atoms.positions, ref, + decimal=4) + assert_almost_equal(avg.results.rmsd, rmsd) def test_average_structure_no_ref(self, universe): ref, rmsd = _get_aligned_average_positions(self.ref_files, universe) avg = align.AverageStructure(universe).run() - assert_almost_equal(avg.universe.atoms.positions, ref, decimal=4) - assert_almost_equal(avg.rmsd, rmsd) + assert_almost_equal(avg.results.universe.atoms.positions, ref, + decimal=4) + assert_almost_equal(avg.results.rmsd, rmsd) def test_average_structure_no_msf(self, universe): avg = align.AverageStructure(universe).run() @@ -453,13 +483,15 @@ def test_average_structure_ref_frame(self, universe): universe.trajectory[0] ref, rmsd = _get_aligned_average_positions(self.ref_files, u) avg = align.AverageStructure(universe, ref_frame=ref_frame).run() - assert_almost_equal(avg.universe.atoms.positions, ref, decimal=4) - assert_almost_equal(avg.rmsd, rmsd) + assert_almost_equal(avg.results.universe.atoms.positions, ref, + decimal=4) + assert_almost_equal(avg.results.rmsd, rmsd) def test_average_structure_in_memory(self, universe): avg = align.AverageStructure(universe, in_memory=True).run() reference_coordinates = universe.trajectory.timeseries().mean(axis=1) - assert_almost_equal(avg.universe.atoms.positions, reference_coordinates, decimal=4) + assert_almost_equal(avg.results.universe.atoms.positions, + reference_coordinates, decimal=4) assert avg.filename is None