Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ The rules for this file:

* 0.15.1

Enhancements

* All classes derived from 'AnalysisBase' now display a ProgressMeter
with 'quiet=False'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be when quiet=True?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. You are thinking of verbose=True which is the normal way people turn on logging and output. For some reason MDAnalysis has decided in the past to rather go with the word quiet and there False would say the function can print and log stuff for me,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right yeah, it's backwards to what I expect, ok cool

* The 'run' method from all 'AnalysisBase' derived classes return the
class itself.

Fixes
* GROWriter resids now truncated properly (Issue #886)
* reading/writing lambda value in trr files (Issue #859)
Expand Down
17 changes: 7 additions & 10 deletions package/MDAnalysis/analysis/align.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- http://www.MDAnalysis.org
# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein
# and contributors (see AUTHORS for the full list)
# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver
# Beckstein and contributors (see AUTHORS for the full list)
#
# Released under the GNU Public Licence, v2 or any higher version
#
Expand Down Expand Up @@ -472,8 +472,7 @@ class AlignTraj(AnalysisBase):

def __init__(self, mobile, reference, select='all', filename=None,
prefix='rmsfit_', mass_weighted=False, tol_mass=0.1,
strict=False, force=True, quiet=False, start=None, stop=None,
step=None, **kwargs):
strict=False, force=True, **kwargs):
"""Initialization

Parameters
Expand Down Expand Up @@ -516,18 +515,16 @@ def __init__(self, mobile, reference, select='all', filename=None,
exception

"""
self._quiet = quiet
super(AlignTraj, self).__init__(mobile.trajectory, **kwargs)
if self._quiet:
logging.disable(logging.WARN)

traj = mobile.trajectory
select = rms.process_selection(select)
self.ref_atoms = reference.select_atoms(*select['reference'])
self.mobile_atoms = mobile.select_atoms(*select['mobile'])
kwargs.setdefault('remarks', 'RMS fitted trajectory to reference')

if filename is None:
path, fn = os.path.split(traj.filename)
path, fn = os.path.split(self._trajectory.filename)
filename = os.path.join(path, prefix + fn)
logger.info('filename of rms_align with no filename given'
': {0}'.format(filename))
Expand All @@ -540,7 +537,8 @@ def __init__(self, mobile, reference, select='all', filename=None,

natoms = self.mobile_atoms.n_atoms
self.ref_atoms, self.mobile_atoms = get_matching_atoms(
self.ref_atoms, self.mobile_atoms, tol_mass=tol_mass, strict=strict)
self.ref_atoms, self.mobile_atoms, tol_mass=tol_mass,
strict=strict)

self._writer = mda.Writer(self.filename, natoms)

Expand All @@ -551,7 +549,6 @@ def __init__(self, mobile, reference, select='all', filename=None,
self._weights = None

logger.info("RMS-fitting on {0:d} atoms.".format(len(self.ref_atoms)))
self._setup_frames(traj, start, stop, step)

def _prepare(self):
# reference centre of mass system
Expand Down
125 changes: 91 additions & 34 deletions package/MDAnalysis/analysis/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- http://www.MDAnalysis.org
# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver Beckstein
# and contributors (see AUTHORS for the full list)
# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver
# Beckstein and contributors (see AUTHORS for the full list)
#
# Released under the GNU Public Licence, v2 or any higher version
#
Expand All @@ -24,61 +24,116 @@
"""
from six.moves import range

import numpy as np
import logging
from MDAnalysis.lib.log import ProgressMeter

logger = logging.getLogger(__name__)


class AnalysisBase(object):
"""Base class for defining multi frame analysis

The analysis base class is designed as a template for creating
multiframe analysis.

The class implements the following methods:

_setup_frames(trajectory, start=None, stop=None, step=None)
Pass a Reader object and define the desired iteration pattern
through the trajectory

run
The user facing run method. Calls the analysis methods
defined below

Your analysis can implement the following methods, which are
called from run:

_prepare
Called before iteration on the trajectory has begun.
Data structures can be set up at this time, however most
error checking should be done in the __init__

_single_frame
Called after the trajectory is moved onto each new frame.

_conclude
Called once iteration on the trajectory is finished.
Apply normalisation and averaging to results here.
"""Base class for defining multi frame analysis, it is designed as a
template for creating multiframe analysis. This class will automatically
take care of setting up the trajectory reader for iterating and offers to
show a progress meter.

To define a new Analysis, `AnalysisBase` needs to be subclassed
`_single_frame` must be defined. It is also possible to define
`_prepare` and `_conclude` for pre and post processing. See the example
below.

.. code-block:: python

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code block is now the definition of the AnalysisBase API. There's a danger that this will be overlooked. Furthermore, one cannot easily link to it.

There should be some text at the module level similar to the one that used to be here and formally describe the API.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can we easily link to this? I thought it would be enough to make an See Also mention in the other classes docs. I can make some more comments about the individual functions that can be overwritten in a child class. would that be better for you?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On 12 Jul, 2016, at 02:49, Max Linke notifications@github.com wrote:

how can we easily link to this? I thought it would be enough to make an See Also mention in the other classes docs.

I suppose that works. Just remark somewhere above that this piece of code is the definition of what the Analysis API looks like at the moment.

I was thinking about a document similar to the Trajectory API but to be honest, this PR is not really the place for that.

I can make some more comments about the individual functions that can be overwritten in a child class. would that be better for you?

Anything that helps developers :-) – but I’d say we leave all of this for another PR, say, “Writing Analysis classes docs” (or have we already got something like that?).

class NewAnalysis(AnalysisBase):
def __init__(self, atomgroup, parameter, **kwargs):
super(NewAnalysis, self).__init__(atomgroup.universe.trajectory,
**kwargs)
self._parameter = parameter
self._ag = atomgroup

def _prepare(self):
# OPTIONAL
# Called before iteration on the trajectory has begun.
# Data structures can be set up at this time
self.result = []

def _single_frame(self):
# REQUIRED
# Called after the trajectory is moved onto each new frame.
# store result of `some_function` for a single frame
self.result.append(some_function(self._ag, self._parameter))

def _conclude(self):
# OPTIONAL
# Called once iteration on the trajectory is finished.
# Apply normalisation and averaging to results here.
self.result = np.asarray(self.result) / np.sum(self.result)

Afterwards the new analysis can be run like this.

.. code-block:: python
na = NewAnalysis(u.select_atoms('name CA'), 35).run()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this.

print(na.result)

"""
def __init__(self, trajectory, start=None,
stop=None, step=None, quiet=True):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for quiet=True vs verbose=False. We could change it: deprecate quiet and override with verbose.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we can do that. LIkely something we should do before 1.0.0. Can you open a bug for this. The deprecation is easy to do just tedious to catch all the instances where we use quiet.

"""
Parameters
----------
trajectory : mda.Reader
A trajectory Reader
start : int, optional
start frame of analysis
stop : int, optional
stop frame of analysis
step : int, optional
number of frames to skip between each analysed frame
quiet : bool, optional
Turn off verbosity
"""
self._quiet = quiet
self._setup_frames(trajectory, start, stop, step)

def _setup_frames(self, trajectory, start=None,
stop=None, step=None):
"""
Pass a Reader object and define the desired iteration pattern
through the trajectory

Parameters
----------
trajectory : mda.Reader
A trajectory Reader
start : int, optional
start frame of analysis
stop : int, optional
stop frame of analysis
step : int, optional
number of frames to skip between each analysed frame
"""
self._trajectory = trajectory
start, stop, step = trajectory.check_slice_indices(
start, stop, step)
self.start = start
self.stop = stop
self.step = step
self.n_frames = len(range(start, stop, step))
interval = int(self.n_frames // 100)
if interval == 0:
interval = 1

# ensure _quiet is set when __init__ wasn't called, this is to not
# break pre 0.16.0 API usage of AnalysisBase
if not hasattr(self, '_quiet'):
self._quiet = True
self._pm = ProgressMeter(self.n_frames if self.n_frames else 1,
interval=interval, quiet=self._quiet)

def _single_frame(self):
"""Calculate data from a single frame of trajectory

Don't worry about normalising, just deal with a single frame.
"""
pass
raise NotImplementedError("Only implemented in child classes")

def _prepare(self):
"""Set things up before the analysis loop begins"""
Expand All @@ -91,7 +146,7 @@ def _conclude(self):
"""
pass

def run(self, **kwargs):
def run(self):
"""Perform the calculation"""
logger.info("Starting preparation")
self._prepare()
Expand All @@ -101,5 +156,7 @@ def run(self, **kwargs):
self._ts = ts
# logger.info("--> Doing frame {} of {}".format(i+1, self.n_frames))
self._single_frame()
self._pm.echo(self._frame_index)
logger.info("Finishing up")
self._conclude()
return self
9 changes: 4 additions & 5 deletions package/MDAnalysis/analysis/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ class Contacts(AnalysisBase):

"""
def __init__(self, u, selection, refgroup, method="hard_cut", radius=4.5,
kwargs=None, start=None, stop=None, step=None,):
kwargs=None, **basekwargs):
"""Initialization

Parameters
Expand Down Expand Up @@ -404,6 +404,9 @@ def __init__(self, u, selection, refgroup, method="hard_cut", radius=4.5,
Step between frames to analyse, Default: 1

"""
self.u = u
super(Contacts, self).__init__(self.u.trajectory, **basekwargs)

if method == 'hard_cut':
self.fraction_contacts = hard_cut_q
elif method == 'soft_cut':
Expand All @@ -413,10 +416,6 @@ def __init__(self, u, selection, refgroup, method="hard_cut", radius=4.5,
raise ValueError("method has to be callable")
self.fraction_contacts = method

# setup boilerplate
self.u = u
self._setup_frames(self.u.trajectory, start, stop, step)

self.selection = selection
self.grA = u.select_atoms(selection[0])
self.grB = u.select_atoms(selection[1])
Expand Down
Loading