Skip to content

make AnalysisBase-derived classes use .results #3261

@orbeckst

Description

@orbeckst

Is your feature request related to a problem?

With PR #3233 merged, we established a new API for AnalysisBase-derived analysis classes: results should be stored in the .results attribute, which is a dict that makes keys available as attributes. Any classes that have not been using results should start using it in a backwards compatible manner.

Describe the solution you'd like

Add .results to all Analysis-classes in such a way that access to the old documented attributes still works for the full 2.x life cycle.

Classes

The following classes should be upgraded (roughly in order of importance) --- raise individual issues and add them here; add more classes to be upgraded as necessary

Approach

Generally follow the approach taken for DensityAnalysis in PR #3263

Code

Assume that the old code in a class ExampleAnalysis(AnalysisBase) stored a result in attribute foo.

  • change to using .results:
    • You do not need to set up self.results = Results() because AnalysisBase does it.
    • You must change any attribute assignments to be in self.results instead of self. For example, self.foo = data becomes self.results.foo = data.
    • Note that Results is really a dict where keys can be accessed as attributes. This can be useful when key access is more convenient than attribute access.
    • Results may be nested if necessary.
  • Required changes to keep access to foo backward compatible + deprecation warning:
import warnings


class ExampleAnalysis(AnalysisBase):
    ...
    @property
    def foo(self):
        wmsg = ("The `foo` attribute was deprecated in MDAnalysis 2.0.0 "
                "and will be removed in MDAnalysis 3.0.0. Please use "
                "`results.foo` instead")
        warnings.warn(wmsg, DeprecationWarning)
        return self.results.foo

Docs

Change all examples in the docs to use the new .results. Pay particular attention to any ``.. attribute::` section and examples. Add a deprecation note in docs directly to the attribute:

.. attribute:: foo

   The results of the calculation are stored here.

   .. deprecated:: 2.0.0 
       This attribute will be removed in 3.0.0. Use :attr:`results.foo` instead.

Add docs for the results attribute with a versionchanged

.. attribute:: results.foo

   The results of the calculation are stored here.

   .. versionadded:: 2.0.0 

Add a version changed at the bottom of the class docs

.. versionchanged:: 2.0.0
       Results are now stored in a :class:`MDAnalysis.analysis.base.Results` instance.

If necessary, raise issues for the User Guide if you already know that it needs updating, too.

CHANGELOG

Changes
  * `ExampleAnalysis` now uses the `results.foo` attribute for storing
    data. The `ExampleAnalysis.foo` attribute is now deprecated
    (Issue #xxxx)

Deprecations
  * The `foo` attribute of `analysis.example.ExampleAnalysis.foo` is now
    deprecated in favour of `results.foo`. It will be removed in 3.0.0
    (Issue #xxxx)

Describe alternatives you've considered

Do nothing

Do nothing right now and update as we go along. The downside is that we then miss the opportunity to educate users right from 2.0.0 onwards to use the new API.

Other ways to still make the attributes available

For results that can be stored by reference, it will be sufficient to just assign to self.results.NAME in addition to self.NAME:

class ExampleAnalysis(base.AnalysisBase):
   ...

   def _conclude(self):
         # old
         # self.data = np.array(self._data)
         # new (remove self.data in 3.0.0)
         self.results.data = self.data = np.array(self._data)

This simple approach has the disadvantage that we eventually have to manually add deprecation warnings that the old attributes will be removed.

If necessary, one could use properties and also add the deprecation warning:

from MDAnalysis.lib.util import deprecate


class ExampleAnalysis(AnalysisBase):

   def _conclude(self):
        self.results.data = ...

   @property
   @deprecate(old_name="ExampleAnalysis.data", new_name="ExampleAnalysis.results.data",
            release="2.0.0", remove="3.0.0")
   def data(self):
        return self.results.data
   
   # for full compatibility there should be a setter but I am not sure if really needed
   @data.setter
   def data(self, value):
         self.results.data = value

Additional context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions