-
Notifications
You must be signed in to change notification settings - Fork 663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Dihedral and Ramachandran Analysis #1997
Changes from 14 commits
62a194f
30da783
1f149ad
13ee8a0
abbc3ef
973910c
98c8370
ad4e598
4dabf32
f0d8030
15c2bd9
3b79336
bfc75c9
c5fcfb8
4812dc0
610368e
60b1667
e83bd9a
7561232
193e503
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
import warnings | ||
import six | ||
from six.moves import zip | ||
|
||
import MDAnalysis as mda | ||
from MDAnalysis.analysis.base import AnalysisBase | ||
from MDAnalysis.lib.distances import calc_dihedrals | ||
|
||
|
||
class Ramachandran(AnalysisBase): | ||
"""Calculate phi and psi dihedral angles of specified residues. | ||
|
||
Note | ||
---- | ||
Run the analysis with :meth:`Ramachandran.run()`, which stores the results | ||
in the array :attr:`Ramachandran.angles`. A axes object can be obtained | ||
with :meth: `Ramachandran.run().plot()`. | ||
|
||
If the residue selection is beyond the scope of the protein, then an error | ||
will be raised. If the residue selection includes the first or last residue | ||
then a warning will be raised, and the final array of angles will not | ||
include those residues. | ||
|
||
""" | ||
def __init__(self, atomgroup, **kwargs): | ||
r"""Parameters | ||
---------- | ||
atomgroup : Atomgroup | ||
atoms for residues for which phi and psi are calculated | ||
start : int, optional | ||
starting frame, default None becomes 0. | ||
stop : int, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. aren't we moving those to the run function @orbeckst There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, you're right, of course – I asked @hfmull to start from analysis.rms.RMSD and forgot that it uses soon-to-be changed call signatures (#1463). Do we need to keep init start/stop/step for new additions like this one and deprecate them (#1979) right away?? I would say no (don't teach the user something you want them to forget) but I am open to alternative opinions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still keeping them here makes this behave exactly as any other class. Kind of nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes more sense to not allow them here if we want them in |
||
Frame index to stop analysis. Default: None becomes | ||
n_frames. Iteration stops *before* this frame number, | ||
which means that the trajectory would be read until the end. | ||
step : int, optional | ||
step between frames, default None becomes 1. | ||
|
||
""" | ||
super(Ramachandran, self).__init__(atomgroup.universe.trajectory, **kwargs) | ||
self.atomgroup = atomgroup | ||
residues = self.atomgroup.residues | ||
protein = self.atomgroup.universe.select_atoms("protein").residues | ||
|
||
if not residues.issubset(protein): | ||
raise IndexError("Found atoms outside of protein. Only atoms " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would use a value error here. The |
||
"inside of a 'protein' selection can be used to " | ||
"calculate dihedrals.") | ||
elif not residues.isdisjoint(protein[[0, -1]]): | ||
warnings.warn("Cannot determine phi and psi angles for the first " | ||
"or last residues") | ||
residues = residues.difference(protein[[0, -1]]) | ||
|
||
phi_sel = [res.phi_selection() for res in residues] | ||
psi_sel = [res.psi_selection() for res in residues] | ||
self.ag1 = mda.AtomGroup([atoms[0] for atoms in phi_sel]) | ||
self.ag2 = mda.AtomGroup([atoms[1] for atoms in phi_sel]) | ||
self.ag3 = mda.AtomGroup([atoms[2] for atoms in phi_sel]) | ||
self.ag4 = mda.AtomGroup([atoms[3] for atoms in phi_sel]) | ||
self.ag5 = mda.AtomGroup([atoms[3] for atoms in psi_sel]) | ||
|
||
def _prepare(self): | ||
self.angles = [] | ||
|
||
def _single_frame(self): | ||
phi_angles = calc_dihedrals(self.ag1.positions, self.ag2.positions, | ||
self.ag3.positions, self.ag4.positions, | ||
box=self.ag1.dimensions) | ||
psi_angles = calc_dihedrals(self.ag2.positions, self.ag3.positions, | ||
self.ag4.positions, self.ag5.positions, | ||
box=self.ag1.dimensions) | ||
phi_psi = [(phi, psi) for phi, psi in zip(phi_angles, psi_angles)] | ||
self.angles.append(phi_psi) | ||
|
||
def _conclude(self): | ||
self.angles = np.rad2deg(np.array(self.angles)) | ||
|
||
|
||
def plot(self, ax=None, **kwargs): | ||
"""Plots data into standard ramachandran plot. Each time step in | ||
self.angles is plotted onto the same graph. | ||
|
||
Parameters | ||
---------- | ||
ax : :class:`matplotlib.axes.Axes` | ||
If no `ax` is supplied or set to ``None`` then the plot will | ||
be added to the current active axes. | ||
|
||
Returns | ||
------- | ||
ax : :class:`~matplotlib.axes.Axes` | ||
Axes with the plot, either `ax` or the current axes. | ||
|
||
""" | ||
if ax is None: | ||
ax = plt.gca() | ||
ax.axis([-180,180,-180,180]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do people plot the allowed and marginally allowed regions in "standard" Ramachandran plots? Is there a simple function (something like a spline for a density that gives the correct contours) or a dataset that we could use? It would be nice if we had a helper function to plot them; this method could then use the helper to plot these regions in the background but anyone else could use it, too. (Not a requirement for this PR but something to think about.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
ax.axhline(0, color='k', lw=1) | ||
ax.axvline(0, color='k', lw=1) | ||
ax.set(xticks=range(-180,181,60), yticks=range(-180,181,60), | ||
xlabel=r"$\phi$ (deg)", ylabel=r"$\psi$ (deg)") | ||
a = self.angles.reshape(np.prod(self.angles.shape[:2]), 2) | ||
ax.scatter(a[:,0], a[:,1]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to add |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import numpy as np | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The copyright header should also be added here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pylint will also issue a warning here to add |
||
from numpy.testing import assert_almost_equal | ||
import pytest | ||
|
||
import MDAnalysis as mda | ||
from MDAnalysisTests.datafiles import GRO, XTC, DihedralsArray, GLYDihedralsArray | ||
from MDAnalysis.analysis import dihedrals | ||
|
||
|
||
class TestRamachandran(object): | ||
|
||
@pytest.fixture() | ||
def universe(self): | ||
return mda.Universe(GRO, XTC) | ||
|
||
def test_ramachandran(self, universe): | ||
rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() | ||
test_rama = np.load(DihedralsArray) | ||
|
||
assert_almost_equal(rama.angles, test_rama, 5, | ||
err_msg="error: dihedral angles should " | ||
"match test values") | ||
|
||
def test_ramachandran_single_frame(self, universe): | ||
rama = dihedrals.Ramachandran(universe.select_atoms("protein"), | ||
start=5, stop=6).run() | ||
test_rama = [np.load(DihedralsArray)[5]] | ||
|
||
assert_almost_equal(rama.angles, test_rama, 5, | ||
err_msg="error: dihedral angles should " | ||
"match test values") | ||
|
||
def test_ramachandran_residue_selections(self, universe): | ||
rama = dihedrals.Ramachandran(universe.select_atoms("resname GLY")).run() | ||
test_rama = np.load(GLYDihedralsArray) | ||
|
||
assert_almost_equal(rama.angles, test_rama, 5, | ||
err_msg="error: dihedral angles should " | ||
"match test values") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,7 +155,8 @@ | |
"legacy_DCD_NAMD_coords", # frame 0 read in for SiN_tric_namd.dcd using legacy DCD reader | ||
"legacy_DCD_c36_coords", # frames 1 and 4 read in for tip125_tric_C36.dcd using legacy DCD reader | ||
"GSD", | ||
"GRO_MEMPROT", "XTC_MEMPROT" # YiiP transporter in POPE:POPG lipids with Na+, Cl-, Zn2+ dummy model without water | ||
"GRO_MEMPROT", "XTC_MEMPROT", # YiiP transporter in POPE:POPG lipids with Na+, Cl-, Zn2+ dummy model without water | ||
"DihedralsArray", "GLYDihedralsArray" # phi and psi angles for testing Ramachandran class | ||
] | ||
|
||
from pkg_resources import resource_filename | ||
|
@@ -415,5 +416,8 @@ | |
|
||
GSD = resource_filename(__name__, 'data/example.gsd') | ||
|
||
DihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_dihedrals.npy') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also need to add the to |
||
GLYDihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_GLY_dihedrals.npy') | ||
|
||
# This should be the last line: clean up namespace | ||
del resource_filename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the standard header – copy from the top of rms.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add
from __future__ import absolute_import
as the very first import with any import fromsix.moves
directly below it. Also include therange
import from six.moves