diff --git a/package/CHANGELOG b/package/CHANGELOG index f40dc5b1316..718bc49b899 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -175,6 +175,10 @@ Enhancements checking if it can be used in parallel analysis. (Issue #2996, PR #2950) Changes + * `analysis.hole2.HoleAnalysis` now stores ``sphpdbs``, ``outfiles`` + and ``profiles`` in the `analysis.base.Results` class (Issues #3261, #3269) + * `helix_analysis.HELANAL` now uses the `analysis.base.Results` class to + store results attributes (Issue #3261, #3267) * `analysis.diffusionmap.DistanceMatrix` class now stores `dist_matrix` using the `analysis.base.Results` class (Issues #3288, #3290) * `analysis.align.AlignTraj` and `analysis.align.AverageStructure` now store @@ -243,6 +247,10 @@ Changes * Added OpenMM coordinate and topology converters (Issue #2863, PR #2917) Deprecations + * The ``sphpdbs``, ``outfiles`` and ``profiles`` attributes of + `analysis.hole2.HoleAnalysis` are now deprecated in favour of + ``results.sphpdbs``, ``results.outfiles`` and + ``results.profiles`` (Issues #3261, #3269) * The `analysis.diffusionmap.DistanceMatrix.dist_matrix` is now deprecated in favour of `analysis.diffusionmap.DistanceMatrix.results.dist_matrix`. It will be removed in 3.0.0 (Issues #3288, #3290) diff --git a/package/MDAnalysis/analysis/hole2/hole.py b/package/MDAnalysis/analysis/hole2/hole.py index 6a6b0a02107..0063c39aea1 100644 --- a/package/MDAnalysis/analysis/hole2/hole.py +++ b/package/MDAnalysis/analysis/hole2/hole.py @@ -90,6 +90,10 @@ The VMD surface created by the class updates the pore for each frame of the trajectory. Use it as normal by loading your trajectory in VMD and sourcing the file in the Tk Console. +You can access the actual profiles generated in the ``results`` attribute:: + + print(ha.results.profiles) + Again, HOLE writes out files for each frame. If you would like to delete these files after the analysis, you can call :meth:`~HoleAnalysis.delete_temporary_files`:: @@ -513,14 +517,52 @@ class HoleAnalysis(AnalysisBase): Files are called `hole.inp`. - Returns - ------- - dict + Attributes + ---------- + results.sphpdbs: numpy.ndarray + Array of sphpdb filenames + + .. versionadded:: 2.0.0 + + results.outfiles: numpy.ndarray + Arrau of output filenames + + .. versionadded:: 2.0.0 + + results.profiles: dict + Profiles generated by HOLE2. A dictionary of :class:`numpy.recarray`\ s, indexed by frame. + .. versionadded:: 2.0.0 + + sphpdbs: numpy.ndarray + Alias of :attr:`results.sphpdbs` + + .. deprecated:: 2.0.0 + This will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.sphpdbs` instead. + + outfiles: numpy.ndarray + Alias of :attr:`results.outfiles` + + .. deprecated:: 2.0.0 + This will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.outfiles` instead. + + profiles: dict + Alias of :attr:`results.profiles` + + .. deprecated:: 2.0.0 + This will be removed in MDAnalysis 3.0.0. Please use + :attr:`results.profiles` instead. .. versionadded:: 1.0 + .. versionchanged:: 2.0.0 + :attr:`sphpdbs`, :attr:`outfiles` and :attr:`profiles ` + are now stored in a :class:`MDAnalysis.analysis.base.Results` + instance. + """ input_file = '{prefix}hole{i:03d}.inp' @@ -552,11 +594,6 @@ class HoleAnalysis(AnalysisBase): _guess_cpoint = False - sphpdbs = None - outfiles = None - frames = None - profiles = None - def __init__(self, universe, select='protein', verbose=False, @@ -680,13 +717,36 @@ def run(self, start=None, stop=None, step=None, verbose=None, return super(HoleAnalysis, self).run(start=start, stop=stop, step=step, verbose=verbose) + @property + def sphpdbs(self): + wmsg = ("The `sphpdbs` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.sphpdbs` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.sphpdbs + + @property + def outfiles(self): + wmsg = ("The `outfiles` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.outfiles` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.outfiles + + @property + def profiles(self): + wmsg = ("The `profiles` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.profiles` instead.") + warnings.warn(wmsg, DeprecationWarning) + return self.results.profiles + def _prepare(self): """Set up containers and generate input file text""" # set up containers - self.sphpdbs = np.zeros(self.n_frames, dtype=object) - self.outfiles = np.zeros(self.n_frames, dtype=object) - self.frames = np.zeros(self.n_frames, dtype=int) - self.profiles = {} + self.results.sphpdbs = np.zeros(self.n_frames, dtype=object) + self.results.outfiles = np.zeros(self.n_frames, dtype=object) + self.results.profiles = {} # generate input file body = set_up_hole_input('', @@ -733,7 +793,6 @@ def _single_frame(self): self.tmp_files.append(sphpdb) else: self.tmp_files.append(sphpdb + '.old') - self.frames[i] = frame # temp pdb logger.info('HOLE analysis frame {}'.format(frame)) @@ -763,7 +822,7 @@ def _single_frame(self): recarrays = collect_hole(outfile=outfile) try: - self.profiles[frame] = recarrays[0] + self.results.profiles[frame] = recarrays[0] except KeyError: msg = 'No profile found in HOLE output. Output level: {}' logger.info(msg.format(self.output_level)) @@ -821,11 +880,12 @@ def create_vmd_surface(self, filename='hole.vmd', dot_density=15, ``filename`` with the pore surfaces. """ - if self.sphpdbs is None or len(self.sphpdbs) == 0: + if not np.any(self.results.get("sphpdbs", [])): raise ValueError('No sphpdb files to read. Try calling run()') frames = [] - for i, sphpdb in zip(self.frames, self.sphpdbs[self.frames]): + for i in self.frames: + sphpdb = self.results.sphpdbs[i] tmp_tri = create_vmd_surface(sphpdb=sphpdb, sph_process=self.exe['sph_process'], sos_triangle=self.exe['sos_triangle'], @@ -870,15 +930,10 @@ def create_vmd_surface(self, filename='hole.vmd', dot_density=15, def min_radius(self): """Return the minimum radius over all profiles as a function of q""" - if not self.profiles: - raise ValueError('No profiles available. Try calling run()') - return np.array([[q, p.radius.min()] for q, p in self.profiles.items()]) - - def min_radius(self): - """Return the minimum radius over all profiles as a function of q""" - if not self.profiles: + profiles = self.results.get("profiles") + if not profiles: raise ValueError('No profiles available. Try calling run()') - return np.array([[q, p.radius.min()] for q, p in self.profiles.items()]) + return np.array([[q, p.radius.min()] for q, p in profiles.items()]) def delete_temporary_files(self): """Delete temporary files""" @@ -888,8 +943,8 @@ def delete_temporary_files(self): except OSError: pass self.tmp_files = [] - self.outfiles = [] - self.sphpdbs = [] + self.results.outfiles = [] + self.results.sphpdbs = [] def __enter__(self): return self @@ -987,17 +1042,17 @@ def plot(self, frames=None, """ - if not self.profiles: + if not self.results.get("profiles"): raise ValueError('No profiles available. Try calling run()') if ax is None: fig, ax = plt.subplots() - fcl = self._process_plot_kwargs(frames=frames, - color=color, cmap=cmap, linestyle=linestyle) + fcl = self._process_plot_kwargs(frames=frames, color=color, + cmap=cmap, linestyle=linestyle) for i, (frame, c, ls) in enumerate(zip(*fcl)): - profile = self.profiles[frame] + profile = self.results.profiles[frame] dy = i*y_shift ax.plot(profile.rxn_coord, profile.radius+dy, color=c, linestyle=ls, zorder=-frame, label=str(frame), @@ -1051,7 +1106,7 @@ def plot3D(self, frames=None, """ - if not self.profiles: + if not self.results.get("profiles"): raise ValueError('No profiles available. Try calling run()') from mpl_toolkits.mplot3d import Axes3D @@ -1065,7 +1120,7 @@ def plot3D(self, frames=None, linestyle=linestyle) for frame, c, ls in zip(*fcl): - profile = self.profiles[frame] + profile = self.results.profiles[frame] if r_max is None: radius = profile.radius rxn_coord = profile.rxn_coord @@ -1104,7 +1159,7 @@ def over_order_parameters(self, order_parameters, frames=None): sorted dictionary of {order_parameter:profile} """ - if not self.profiles: + if not self.results.get("profiles"): raise ValueError('No profiles available. Try calling run()') if isinstance(order_parameters, str): try: @@ -1137,7 +1192,7 @@ def over_order_parameters(self, order_parameters, frames=None): profiles = OrderedDict() for frame in sorted_frames: - profiles[order_parameters[frame]] = self.profiles[frame] + profiles[order_parameters[frame]] = self.results.profiles[frame] return profiles @@ -1220,7 +1275,7 @@ def gather(self, frames=None, flat=False): if frames is None: frames = self.frames frames = util.asiterable(frames) - profiles = [self.profiles[k] for k in frames] + profiles = [self.results.profiles[k] for k in frames] rxncoords = [p.rxn_coord for p in profiles] radii = [p.radius for p in profiles] diff --git a/testsuite/MDAnalysisTests/analysis/test_hole2.py b/testsuite/MDAnalysisTests/analysis/test_hole2.py index c7f9e859e68..d23b30b5d57 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hole2.py +++ b/testsuite/MDAnalysisTests/analysis/test_hole2.py @@ -252,18 +252,26 @@ def frames(self, universe): @pytest.fixture() def profiles(self, hole, frames): - return [hole.profiles[f] for f in frames] + return [hole.results.profiles[f] for f in frames] + @pytest.mark.parametrize("attrname", ["sphpdbs", "outfiles", "profiles"]) + def test_deprecated_warning(self, hole, attrname): + wmsg = (f"The `{attrname}` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + f"Please use `results.{attrname}` instead.") + with pytest.warns(DeprecationWarning, match=wmsg): + value = getattr(hole, attrname) + assert value is hole.results[attrname] class TestHoleAnalysis(BaseTestHole): def test_correct_profile_values(self, hole, frames): - assert_equal(sorted(hole.profiles.keys()), frames, - err_msg="hole.profiles.keys() should contain the frame numbers") + assert_equal(sorted(hole.results.profiles.keys()), frames, + err_msg="hole.results.profiles.keys() should contain the frame numbers") assert_equal(list(hole.frames), frames, err_msg="hole.frames should contain the frame numbers") data = np.transpose([(len(p), p.rxn_coord.mean(), p.radius.min()) - for p in hole.profiles.values()]) + for p in hole.results.profiles.values()]) assert_equal(data[0], [401, 399], err_msg="incorrect profile lengths") assert_almost_equal(data[1], [1.98767, 0.0878], err_msg="wrong mean HOLE rxn_coord") @@ -305,7 +313,7 @@ def test_output_level(self, tmpdir, universe): stop=self.stop, random_seed=self.random_seed) # no profiles - assert len(h.profiles) == 0 + assert len(h.results.profiles) == 0 def test_cpoint_geometry(self, tmpdir, universe): protein = universe.select_atoms('protein') @@ -446,7 +454,7 @@ def order_parameter_keys_values(self, hole): def test_gather(self, hole): gd = hole.gather(flat=False) - for i, p in enumerate(hole.profiles.values()): + for i, p in enumerate(hole.results.profiles.values()): assert_almost_equal(p.rxn_coord, gd['rxn_coord'][i]) assert_almost_equal(p.radius, gd['radius'][i]) assert_almost_equal(p.cen_line_D, gd['cen_line_D'][i]) @@ -454,7 +462,7 @@ def test_gather(self, hole): def test_gather_flat(self, hole): gd = hole.gather(flat=True) i = 0 - for p in hole.profiles.values(): + for p in hole.results.profiles.values(): j = i+len(p.rxn_coord) assert_almost_equal(p.rxn_coord, gd['rxn_coord'][i:j]) assert_almost_equal(p.radius, gd['radius'][i:j]) @@ -464,7 +472,7 @@ def test_gather_flat(self, hole): def test_min_radius(self, hole): rad = hole.min_radius() - for (f1, p), (f2, r) in zip(hole.profiles.items(), rad): + for (f1, p), (f2, r) in zip(hole.results.profiles.items(), rad): assert_equal(f1, f2) assert_almost_equal(min(p.radius), r) @@ -477,7 +485,7 @@ def test_over_order_parameters(self, hole): assert key == rmsd idx = np.argsort(op) - arr = np.array(list(hole.profiles.values()), dtype=object) + arr = np.array(list(hole.results.profiles.values()), dtype=object) for op_prof, arr_prof in zip(profiles.values(), arr[idx]): assert op_prof is arr_prof @@ -493,7 +501,7 @@ def test_over_order_parameters_file(self, hole, tmpdir): assert key == rmsd idx = np.argsort(op) - arr = np.array(list(hole.profiles.values()), dtype=object) + arr = np.array(list(hole.results.profiles.values()), dtype=object) for op_prof, arr_prof in zip(profiles.values(), arr[idx]): assert op_prof is arr_prof @@ -516,7 +524,7 @@ def test_over_order_parameters_frames(self, hole): assert key == rmsd idx = np.argsort(op[:n_frames]) - values = list(hole.profiles.values())[:n_frames] + values = list(hole.results.profiles.values())[:n_frames] arr = np.array(values, dtype=object) for op_prof, arr_prof in zip(profiles.values(), arr[idx]): assert op_prof is arr_prof @@ -532,7 +540,7 @@ def test_bin_radii(self, hole): assert len(radii) == (len(bins)-1) # check first frame profile - first = hole.profiles[0] + first = hole.results.profiles[0] for row in first: coord = row.rxn_coord rad = row.radius @@ -558,7 +566,7 @@ def test_bin_radii_range(self, hole, midpoint): assert len(radii) == (len(bins)-1) # check first frame profile - first = hole.profiles[0] + first = hole.results.profiles[0] for row in first: coord = row.rxn_coord rad = row.radius