From 62a194f2895bca7b66f8198bbc001ab6df820ad0 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Tue, 10 Jul 2018 14:41:39 -0700 Subject: [PATCH 01/20] First commit of dihedrals.py --- package/MDAnalysis/analysis/dihedrals.py | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 package/MDAnalysis/analysis/dihedrals.py diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py new file mode 100644 index 00000000000..612d55ec1bf --- /dev/null +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -0,0 +1,60 @@ +def dihedral_calc(atomgroups): + """Calculates phi and psi angles for a list of AtomGroups over trajectory. + + Parameters + ---------- + atomgroups : list of AtomGroups + must be a list of one or more AtomGroups containing 5 atoms in the + correct order (i.e. C-N-CA-C-N) + + Returns + ------- + angles : numpy.ndarray + An array of time steps which contain (phi,psi) for all AtomGroups. + """ + + dihedrals = [atomgroup.dihedral for atomgroup in atomgroups] + angles = np.array([dih.value() for dih in dihedrals]) + + return angles + + +class Ramachandran(AnalysisBase): + """ + """ + def __init__(self, atomgroup, **kwargs): + r""" + """ + super(Ramachandran, self).__init__(atomgroup.universe.trajectory, **kwargs) + self.atomgroup = atomgroup + + def run(self, start=None, stop=None, step=None, verbose=None, quiet=None): + """Perform the analysis.""" + + if any([el is not None for el in (start, stop, step, quiet)]): + warnings.warn("run arguments are deprecated. Please pass them at " + "class construction. These options will be removed in 0.17.0", + category=DeprecationWarning) + verbose = _set_verbose(verbose, quiet, default=False) + # regenerate class with correct args + super(Ramachandran, self).__init__(self.atomgroup.universe.trajectory, + start=start, stop=stop, step=step, + verbose=verbose) + return super(RMSF, self).run() + + def _prepare(self): + self.protein = self.atomgroup.universe.atoms.select_atoms("protein") + self.resids = self.atomgroup.residues.resids + self.phi_atoms = [self.protein.residues[resid-1].phi_selection() + for resid in self.resids + if 1 < resid < len(self.protein.residues)] + self.psi_atoms = [self.protein.rseidues[resid-1].psi_selection() + for resid in self.resids + if 1 < resid < len(self.protein.residues)] + + def _single_frame(self): + self.phi_angles = [dihedral_calc(atoms) for atoms in self.phi_atoms] + self.psi_angles = [dihedral_calc(atoms) for atoms in self.psi_atoms] + + def _conclude(self): + self.angles = np.array(self.phi_angles,self.psi_angles) From 30da78311e2ac87d6d83852d8127f5ff5605dada Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Tue, 10 Jul 2018 14:50:23 -0700 Subject: [PATCH 02/20] added imports to dihedrals.py --- package/MDAnalysis/analysis/dihedrals.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 612d55ec1bf..2a52112fad0 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,3 +1,17 @@ +from six.moves import zip +from six import string_types +import numpy as np +import logging +import warnings + + +import MDAnalysis.lib.qcprot as qcp +from MDAnalysis.analysis.base import AnalysisBase +from MDAnalysis.exceptions import SelectionError, NoDataError +from MDAnalysis.lib.log import ProgressMeter, _set_verbose +from MDAnalysis.lib.util import asiterable, iterable, get_weights + + def dihedral_calc(atomgroups): """Calculates phi and psi angles for a list of AtomGroups over trajectory. From 1f149adffa70538be6a4297c8f149670117273b5 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Tue, 10 Jul 2018 16:28:28 -0700 Subject: [PATCH 03/20] fixed calculation errors and added plot function --- package/MDAnalysis/analysis/dihedrals.py | 29 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 2a52112fad0..db15a1d8802 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -12,6 +12,8 @@ from MDAnalysis.lib.util import asiterable, iterable, get_weights +import numpy as np + def dihedral_calc(atomgroups): """Calculates phi and psi angles for a list of AtomGroups over trajectory. @@ -28,7 +30,7 @@ def dihedral_calc(atomgroups): """ dihedrals = [atomgroup.dihedral for atomgroup in atomgroups] - angles = np.array([dih.value() for dih in dihedrals]) + angles = [dih.value() for dih in dihedrals] return angles @@ -54,7 +56,7 @@ def run(self, start=None, stop=None, step=None, verbose=None, quiet=None): super(Ramachandran, self).__init__(self.atomgroup.universe.trajectory, start=start, stop=stop, step=step, verbose=verbose) - return super(RMSF, self).run() + return super(Ramachandran, self).run() def _prepare(self): self.protein = self.atomgroup.universe.atoms.select_atoms("protein") @@ -62,13 +64,28 @@ def _prepare(self): self.phi_atoms = [self.protein.residues[resid-1].phi_selection() for resid in self.resids if 1 < resid < len(self.protein.residues)] - self.psi_atoms = [self.protein.rseidues[resid-1].psi_selection() + self.psi_atoms = [self.protein.residues[resid-1].psi_selection() for resid in self.resids if 1 < resid < len(self.protein.residues)] + self.angles = [] def _single_frame(self): - self.phi_angles = [dihedral_calc(atoms) for atoms in self.phi_atoms] - self.psi_angles = [dihedral_calc(atoms) for atoms in self.psi_atoms] + self.phi_angles = dihedral_calc(self.phi_atoms) + self.psi_angles = dihedral_calc(self.psi_atoms) + self.angles.append((self.phi_angles,self.psi_angles)) def _conclude(self): - self.angles = np.array(self.phi_angles,self.psi_angles) + self.angles = np.array(self.angles) + + def plot(self): + fig = plt.figure(figsize=(10,10)) + ax1 = plt.subplot(111) + ax1.axis([-180,180,-180,180]) + ax1.axhline(0, color='k', lw=1) + ax1.axvline(0, color='k', lw=1) + plt.xticks(np.arange(-180,181,60)) + plt.yticks(np.arange(-180,181,60)) + plt.xlabel(r"$\phi$ (deg)") + plt.ylabel(r"$\psi$ (deg)") + for angles in self.angles: + ax1.plot(angles[0],angles[1],'ks') From 13ee8a0fa9eeea4f1ab30d3deec37c70607f38e0 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Wed, 11 Jul 2018 09:30:29 -0700 Subject: [PATCH 04/20] Added docstring to dihedrals function --- package/MDAnalysis/analysis/dihedrals.py | 41 +++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index db15a1d8802..d773bb659ca 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,18 +1,7 @@ -from six.moves import zip -from six import string_types -import numpy as np -import logging -import warnings - - -import MDAnalysis.lib.qcprot as qcp from MDAnalysis.analysis.base import AnalysisBase -from MDAnalysis.exceptions import SelectionError, NoDataError -from MDAnalysis.lib.log import ProgressMeter, _set_verbose -from MDAnalysis.lib.util import asiterable, iterable, get_weights - - import numpy as np +import matplotlib.pyplot as plt + def dihedral_calc(atomgroups): """Calculates phi and psi angles for a list of AtomGroups over trajectory. @@ -36,10 +25,32 @@ def dihedral_calc(atomgroups): 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 basic plot can be obtained + with :meth: `Ramachandran.run().plot()`. + """ def __init__(self, atomgroup, **kwargs): - r""" + 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) + 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. + verbose : bool (optional) + Show detailed progress of the calculation if set to ``True``; the + default is ``False``. + """ super(Ramachandran, self).__init__(atomgroup.universe.trajectory, **kwargs) self.atomgroup = atomgroup From abbc3efb3bcf95125ef8c0d566ae67e8cb388272 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Fri, 13 Jul 2018 14:33:40 -0700 Subject: [PATCH 05/20] updated plot function, simplified preparation --- package/MDAnalysis/analysis/dihedrals.py | 87 ++++++++++++------------ 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index d773bb659ca..3b8a24f1c42 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,7 +1,7 @@ -from MDAnalysis.analysis.base import AnalysisBase import numpy as np import matplotlib.pyplot as plt +from MDAnalysis.analysis.base import AnalysisBase def dihedral_calc(atomgroups): """Calculates phi and psi angles for a list of AtomGroups over trajectory. @@ -30,8 +30,8 @@ class Ramachandran(AnalysisBase): Note ---- Run the analysis with :meth:`Ramachandran.run()`, which stores the results - in the array :attr:`Ramachandran.angles`. A basic plot can be obtained - with :meth: `Ramachandran.run().plot()`. + in the array :attr:`Ramachandran.angles`. A axes object can be obtained + with :meth: `Ramachandran.run().plot()`. """ def __init__(self, atomgroup, **kwargs): @@ -39,45 +39,23 @@ def __init__(self, atomgroup, **kwargs): ---------- atomgroup : Atomgroup atoms for residues for which phi and psi are calculated - start : int (optional) + start : int, optional starting frame, default None becomes 0. - stop : int (optional) + stop : int, optional 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 : int, optional step between frames, default None becomes 1. - verbose : bool (optional) - Show detailed progress of the calculation if set to ``True``; the - default is ``False``. """ super(Ramachandran, self).__init__(atomgroup.universe.trajectory, **kwargs) self.atomgroup = atomgroup - def run(self, start=None, stop=None, step=None, verbose=None, quiet=None): - """Perform the analysis.""" - - if any([el is not None for el in (start, stop, step, quiet)]): - warnings.warn("run arguments are deprecated. Please pass them at " - "class construction. These options will be removed in 0.17.0", - category=DeprecationWarning) - verbose = _set_verbose(verbose, quiet, default=False) - # regenerate class with correct args - super(Ramachandran, self).__init__(self.atomgroup.universe.trajectory, - start=start, stop=stop, step=step, - verbose=verbose) - return super(Ramachandran, self).run() - def _prepare(self): - self.protein = self.atomgroup.universe.atoms.select_atoms("protein") - self.resids = self.atomgroup.residues.resids - self.phi_atoms = [self.protein.residues[resid-1].phi_selection() - for resid in self.resids - if 1 < resid < len(self.protein.residues)] - self.psi_atoms = [self.protein.residues[resid-1].psi_selection() - for resid in self.resids - if 1 < resid < len(self.protein.residues)] + self.residues = self.atomgroup.residues + self.phi_atoms = [residue.phi_selection() for residue in self.residues] + self.psi_atoms = [residue.psi_selection() for residue in self.residues] self.angles = [] def _single_frame(self): @@ -88,15 +66,40 @@ def _single_frame(self): def _conclude(self): self.angles = np.array(self.angles) - def plot(self): - fig = plt.figure(figsize=(10,10)) - ax1 = plt.subplot(111) - ax1.axis([-180,180,-180,180]) - ax1.axhline(0, color='k', lw=1) - ax1.axvline(0, color='k', lw=1) - plt.xticks(np.arange(-180,181,60)) - plt.yticks(np.arange(-180,181,60)) - plt.xlabel(r"$\phi$ (deg)") - plt.ylabel(r"$\psi$ (deg)") + def plot(self, ax=None, color='k', marker='s', title=None): + """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. + color : string, optional + Color used for markers in the plot; the default color is 'black'. + marker : string, optional + Marker used in plot; the default marker is 'square'. + title : string, optional + Title of axes object; the default `None` leaves plot without a + title. + + 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]) + ax.axhline(0, color='k', lw=1) + ax.axvline(0, color='k', lw=1) + ax.set_xticks(range(-180,181,60)) + ax.set_yticks(range(-180,181,60)) + ax.set_xlabel(r"$\phi$ (deg)") + ax.set_ylabel(r"$\psi$ (deg)") + if title is not None: + ax.set_title(title) for angles in self.angles: - ax1.plot(angles[0],angles[1],'ks') + ax.plot(angles[0],angles[1], color=color, + marker=marker, linestyle='') From 973910c9caa3d776b7e7d8439d96ee68de5102f7 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Fri, 13 Jul 2018 16:57:35 -0700 Subject: [PATCH 06/20] Added IndexErrors to function --- package/MDAnalysis/analysis/dihedrals.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 3b8a24f1c42..d9551b8a315 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -54,8 +54,16 @@ def __init__(self, atomgroup, **kwargs): def _prepare(self): self.residues = self.atomgroup.residues - self.phi_atoms = [residue.phi_selection() for residue in self.residues] - self.psi_atoms = [residue.psi_selection() for residue in self.residues] + res_min = np.min(self.atomgroup.universe.select_atoms("protein").resids) + res_max = np.max(self.atomgroup.universe.select_atoms("protein").resids) + if any([(resid < res_min) or (resid > res_max) for resid in self.residues.resids]): + raise IndexError("Selection outside of protein") + elif any([resid == (res_min or res_max) for resid in self.residues.resids]): + raise IndexError("Cannot determine phi and psi angles for the first or last residues") + else: + self.phi_atoms = [residue.phi_selection() for residue in self.residues] + self.psi_atoms = [residue.psi_selection() for residue in self.residues] + self.angles = [] def _single_frame(self): From 98c83709c638a57b7e9290f692364b4f1ac931cf Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Tue, 17 Jul 2018 14:02:46 -0700 Subject: [PATCH 07/20] Updated residue selection of function, added test files --- package/MDAnalysis/analysis/dihedrals.py | 21 ++++---- .../analysis/test_dihedrals.py | 51 ++++++++++++++++++ .../data/adk_oplsaa_GLY_dihedrals.npy | Bin 0 -> 3168 bytes .../data/adk_oplsaa_dihedrals.npy | Bin 0 -> 34048 bytes testsuite/MDAnalysisTests/datafiles.py | 3 ++ 5 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 testsuite/MDAnalysisTests/analysis/test_dihedrals.py create mode 100644 testsuite/MDAnalysisTests/data/adk_oplsaa_GLY_dihedrals.npy create mode 100644 testsuite/MDAnalysisTests/data/adk_oplsaa_dihedrals.npy diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index d9551b8a315..c60ac077bcf 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,5 +1,6 @@ import numpy as np import matplotlib.pyplot as plt +import warnings from MDAnalysis.analysis.base import AnalysisBase @@ -54,15 +55,17 @@ def __init__(self, atomgroup, **kwargs): def _prepare(self): self.residues = self.atomgroup.residues - res_min = np.min(self.atomgroup.universe.select_atoms("protein").resids) - res_max = np.max(self.atomgroup.universe.select_atoms("protein").resids) - if any([(resid < res_min) or (resid > res_max) for resid in self.residues.resids]): - raise IndexError("Selection outside of protein") - elif any([resid == (res_min or res_max) for resid in self.residues.resids]): - raise IndexError("Cannot determine phi and psi angles for the first or last residues") - else: - self.phi_atoms = [residue.phi_selection() for residue in self.residues] - self.psi_atoms = [residue.psi_selection() for residue in self.residues] + res_min = np.min(self.atomgroup.universe.select_atoms("protein").residues) + res_max = np.max(self.atomgroup.universe.select_atoms("protein").residues) + if any([residue > res_max for residue in self.residues]): + raise IndexError("Selection exceeds protein length") + elif any([residue == (res_min or res_max) for residue in self.residues]): + warnings.warn("Cannot determine phi and psi angles for the first or last residues") + + self.phi_atoms = [residue.phi_selection() for residue in self.residues + if res_min < residue < res_max] + self.psi_atoms = [residue.psi_selection() for residue in self.residues + if res_min < residue < res_max] self.angles = [] diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py new file mode 100644 index 00000000000..a16201904ef --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -0,0 +1,51 @@ +import numpy as np +from numpy.testing import assert_array_almost_equal +import pytest +import os + +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_array_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_array_almost_equal(rama.angles, test_rama, 5, + err_msg="error: dihedral angles should match test values") + + def test_ramachandran_identical_frames(self, universe, tmpdir): + + outfile = os.path.join(str(tmpdir), 'dihedrals.xtc') + + # write a dummy trajectory of all the same frame + with mda.Writer(outfile, universe.atoms.n_atoms) as W: + for _ in range(universe.trajectory.n_frames): + W.write(universe) + + universe = mda.Universe(GRO, outfile) + rama = dihedrals.Ramachandran(universe.select_atoms('protein')).run() + test_rama = [np.load(DihedralsArray)[0] for ts in universe.trajectory] + + assert_array_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_array_almost_equal(rama.angles, test_rama, 5, + err_msg="error: dihedral angles should match test values") diff --git a/testsuite/MDAnalysisTests/data/adk_oplsaa_GLY_dihedrals.npy b/testsuite/MDAnalysisTests/data/adk_oplsaa_GLY_dihedrals.npy new file mode 100644 index 0000000000000000000000000000000000000000..1ce2e9e4e47c7a16361dcb005fa48752cf998529 GIT binary patch literal 3168 zcmbVN=_Aw&8_kkb6JATQ52=yJmNl7&bSb-O%*^jMgRvz`)=-MB8*P%PRNQ21p)3`b z&^)Csp(t`&tw|+oD+fw zJ`D2zj9pIhp!D5rLI9V5BZ{k{^^`b>OWcA*=^miXKeE6mamI6w@`4XuOqkZ6zsA-i zP@UtSFnO5`e}uc+FBlWx`b?GfT%QH2GBhfi6uD3k^w>mkoJ9MZ+2vd6cyM;SYOZPx z2|uEDi4}Hnz>Kgqa!_)|@cS}LXNq}nP-Xad+yPH?-5Td6c+LmSIDTsA8cEEyVw+9m z(II8Q!f8W?K0K+}O%*iSKqtjwW@GPGJgT<$_r82hxNd#Elwx9pTNWQ3I@V(Yq2?8Z z#T9;N^2YagW4IWEeKK4jo~{RpZ9;PeZAOq*M2$$?tBKVg|Be4r$WCT5^&VO!87#n+<^Jkt&_yOhHwTz7SQZS!#_v^qXClL2IPp{Z#ihr_{m3w@q1ieWQ%wpqh zFto4u58m{8cs{6W?4fUoVi9|4hyn{pnABIjzt10*e{r0i(^1D;!c#}2XEp%i{w|%D z$?H%pZBVvvnFXvpY+I|x5JAt))Z(YL8-S9w-E0fR8mBZOf1bD}3GXtKe;7w8gHvK& zL|&^WluZT&#m@6!PU1<#7M2&Z*A~^+N$)~}8CLV5f&o*`7v*dBuSLgUz{%1?H3;Fxl@0S&z2D3RVK*iXWcg2r{ZYy$LS9Ow^hxp*dISfAI$f>&Sc z?;Nb>0v_8hBGt?S$%bUcjVDa>dfTkl23+`jrKYa%5*McmBxlPLePJ@{jg;>TbG$H` zOMaW#0vlXTQB>`$G23D0pFjR`h5QQxPP$H}C~7$n(v37w-rpd)j7mq>lDU!|HFGFC zIL8bu^1~R~tB@t)W_Yne%wqbQ90aFbRLtU2;iKBWA9lynP&k#^=qJ8NKPk1=6%*zJ zj?F`_g*1)uz=y@G`bTmQFCte==h%SC&Rw0~H9X;J(~giLJ_#S~2JX6Oa3TJC#7oPc zY~1UHbrmfHxa&^Q9VSRzyz|h**PVm|Ma6x4vRF7CTGLyzjf86=r>kXaykWZ5g3=#J z!iVcNo0|V2VVLB#s=D*=g$VvM|H_4P8Hu#ML<0DVm0s=HY)A+t7qr8es7W{O`r^ie z%6FF8UfbDdMf3V|tjY)KcMB;Rkiw{8x>{-?!UitY8px(!*@}njI?N@T46sEkY}wB* zt~j16;WQk)6u%u_|MZYB9fy46RZ~Axun?SFT-UWs=JjI(c;+dfyjo4gN9kj+ zP3lYVig$hYc_S^{*Veo0PK5$gc1Aqk(=81NHup|N`)^0u{JOk`0%>%ZIhDJ<)e?S= zTx@eCJE&-@ok4lDp}0|;?N=0)ie&qF$ =bL6qRq;5J^at9o10Gj zkcDFt5e91{0+4paLfjdN43{q?-{VhgjRgnUzvPZ4o{z zqeul`ZgjW$Tx5=!7fWuY$F2vr`y+wkZ#8jDLY6W*>ji3t&DtMDxDd?>9qEqX!V#N- zhO5s!&?&|4R&^f>j1=-pH;d;Z5YQ1c4YX%l? zpbZ<(5|D8EZNi=$5?$LHoGFe0K-FJu)GMQgN*Nl^lg1=|w55Ws;nxBKU2XGmyme)#eIuf9#lpEW0fIlz{q$c$!j@VAiD&Mhn+ zWII}27Lj9OZhX&*(tZN&xEQFfOz;NLBg}4EyC+<}rCqS`3jq%Wxw(PbEHsH>XD=xt zq2*lp&gd3zU`13f4o<9Ad&RP9)wv{!6BK0O=r`EER5{;%iwe#9O!E-fx*y6PaYT}#|F zTpfS!b12<()E7q=u4iXF+KDR~M>TeIn!LD>L@OIAVM+s|;_}j2TzmkF?m7(&~L=#BWo$2tV@PSm;(3I!$z(w)Rfy#e9z~1U0 z`|iwk-0*E1d%A=TW35gL?>b2=JFR7_ok+q88Kw)z#{>5e$vIwUJ%EKj+jHM|g4CA5 zPWxmQSodsH?|e(b<|}zcAqTiPGHaK$td<3vQ#r-sQe4vM?2k#D^l=YowV}K(k{dWUjP(FCq8?p$tw}kvRmftd~rNaV^#Om5Pd*)yh-Su zA~c47e0(Cw477B)$Ll<4Fk2!XHF{eUA2c|P)cLG~_XiDq$_m}EcfNG-eH9%}Dz0($ z^i{;5?gWQ^(gz5GHRIRyco1mPDP&jd3Dw(ll5ZmuTmAF9hBOJV*^$@LTjqr=%l;Vm z*W|&DPrDwUFmu5JR%=H5OMmT`#MY2A>_^o2`L zn@2yX%m^0#&f3?{vIHaBr7Rg8V+=NTb2D8ckC&{LeQBmUU|5=?3u`$8+%xp7-_)dL(<69x7&DQ(7X1;p%PUF-1NoH+t4f_*ZW3QHYaO zSza*ss#2G}iiPFV*C*|SS&($IvNk}Ji>k5BjVmPy5WHF?A)C&`d8djmB97Uvg3>g={&nU#vA+O@V}|^oHH$ zrA!P`r@I+Ma=|J}MLJS~hqYI2IzQ60fWVb1&VR@2DF>)v}qAc|9t=cD{S! zhO#COl(z+ab+kj07xkSiWP(uekK-BdjZiwc{OXLf0tzd5s#faqF~3f0E9)i|+g8dy zzG%D|UZn7u{Pz;DdrTqG#!(zh9=7{UR<42vG0C=Ti`3Du+SSZOdlTx?EhL##3Q%-T KnjGG(5B~*55Vw5* literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/data/adk_oplsaa_dihedrals.npy b/testsuite/MDAnalysisTests/data/adk_oplsaa_dihedrals.npy new file mode 100644 index 0000000000000000000000000000000000000000..0f579dc86d216e93dbc94b4354dc47d268c059f0 GIT binary patch literal 34048 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 VxS#lr*lJQE1O1Bzw%I;9=zo5HXtDqR literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index b616f8e4870..1e053f06f9b 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -415,5 +415,8 @@ GSD = resource_filename(__name__, 'data/example.gsd') +DihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_dihedrals.npy') +GLYDihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_GLY_dihedrals.npy') + # This should be the last line: clean up namespace del resource_filename From ad4e598f1500464fe69b049663344931f25b4102 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Wed, 18 Jul 2018 10:37:10 -0700 Subject: [PATCH 08/20] Updated docstring of dihedrals.py --- package/MDAnalysis/analysis/dihedrals.py | 7 +++++- .../analysis/test_dihedrals.py | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index c60ac077bcf..63faa999196 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -34,6 +34,11 @@ class Ramachandran(AnalysisBase): 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 @@ -57,7 +62,7 @@ def _prepare(self): self.residues = self.atomgroup.residues res_min = np.min(self.atomgroup.universe.select_atoms("protein").residues) res_max = np.max(self.atomgroup.universe.select_atoms("protein").residues) - if any([residue > res_max for residue in self.residues]): + if any([(residue < res_min or residue > res_max) for residue in self.residues]): raise IndexError("Selection exceeds protein length") elif any([residue == (res_min or res_max) for residue in self.residues]): warnings.warn("Cannot determine phi and psi angles for the first or last residues") diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py index a16201904ef..0f2c547eb0f 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -7,29 +7,33 @@ 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) + return mda.Universe(GRO, XTC) def test_ramachandran(self, universe): rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() test_rama = np.load(DihedralsArray) assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should match test values") + 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() + rama = dihedrals.Ramachandran(universe.select_atoms("protein"), + start=5, stop=6).run() test_rama = [np.load(DihedralsArray)[5]] assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should match test values") + err_msg="error: dihedral angles should " + "match test values") def test_ramachandran_identical_frames(self, universe, tmpdir): - outfile = os.path.join(str(tmpdir), 'dihedrals.xtc') + outfile = os.path.join(str(tmpdir), "dihedrals.xtc") # write a dummy trajectory of all the same frame with mda.Writer(outfile, universe.atoms.n_atoms) as W: @@ -37,15 +41,17 @@ def test_ramachandran_identical_frames(self, universe, tmpdir): W.write(universe) universe = mda.Universe(GRO, outfile) - rama = dihedrals.Ramachandran(universe.select_atoms('protein')).run() + rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() test_rama = [np.load(DihedralsArray)[0] for ts in universe.trajectory] assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should match test values") + 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() + rama = dihedrals.Ramachandran(universe.select_atoms("resname GLY")).run() test_rama = np.load(GLYDihedralsArray) assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should match test values") + err_msg="error: dihedral angles should " + "match test values") From 4dabf32c9861735c9aca7f0657498cdc5abd3568 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 16:14:11 -0700 Subject: [PATCH 09/20] Switched to distances.calc_dihedrals and simplified plot function --- package/MDAnalysis/analysis/dihedrals.py | 88 +++++++++--------------- 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 63faa999196..4f3114bd7e3 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -3,26 +3,7 @@ import warnings from MDAnalysis.analysis.base import AnalysisBase - -def dihedral_calc(atomgroups): - """Calculates phi and psi angles for a list of AtomGroups over trajectory. - - Parameters - ---------- - atomgroups : list of AtomGroups - must be a list of one or more AtomGroups containing 5 atoms in the - correct order (i.e. C-N-CA-C-N) - - Returns - ------- - angles : numpy.ndarray - An array of time steps which contain (phi,psi) for all AtomGroups. - """ - - dihedrals = [atomgroup.dihedral for atomgroup in atomgroups] - angles = [dih.value() for dih in dihedrals] - - return angles +from MDAnalysis.lib.distances import calc_dihedrals class Ramachandran(AnalysisBase): @@ -57,32 +38,43 @@ def __init__(self, atomgroup, **kwargs): """ 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 " + "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.residues = self.atomgroup.residues - res_min = np.min(self.atomgroup.universe.select_atoms("protein").residues) - res_max = np.max(self.atomgroup.universe.select_atoms("protein").residues) - if any([(residue < res_min or residue > res_max) for residue in self.residues]): - raise IndexError("Selection exceeds protein length") - elif any([residue == (res_min or res_max) for residue in self.residues]): - warnings.warn("Cannot determine phi and psi angles for the first or last residues") - - self.phi_atoms = [residue.phi_selection() for residue in self.residues - if res_min < residue < res_max] - self.psi_atoms = [residue.psi_selection() for residue in self.residues - if res_min < residue < res_max] - self.angles = [] def _single_frame(self): - self.phi_angles = dihedral_calc(self.phi_atoms) - self.psi_angles = dihedral_calc(self.psi_atoms) - self.angles.append((self.phi_angles,self.psi_angles)) + 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 = zip(phi_angles, psi_angles) + self.angles.append(phi_psi) def _conclude(self): - self.angles = np.array(self.angles) + self.angles = np.rad2deg(np.array(self.angles)) - def plot(self, ax=None, color='k', marker='s', title=None): + def plot(self, ax=None, **kwargs): """Plots data into standard ramachandran plot. Each time step in self.angles is plotted onto the same graph. @@ -91,13 +83,6 @@ def plot(self, ax=None, color='k', marker='s', title=None): 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. - color : string, optional - Color used for markers in the plot; the default color is 'black'. - marker : string, optional - Marker used in plot; the default marker is 'square'. - title : string, optional - Title of axes object; the default `None` leaves plot without a - title. Returns ------- @@ -110,12 +95,7 @@ def plot(self, ax=None, color='k', marker='s', title=None): ax.axis([-180,180,-180,180]) ax.axhline(0, color='k', lw=1) ax.axvline(0, color='k', lw=1) - ax.set_xticks(range(-180,181,60)) - ax.set_yticks(range(-180,181,60)) - ax.set_xlabel(r"$\phi$ (deg)") - ax.set_ylabel(r"$\psi$ (deg)") - if title is not None: - ax.set_title(title) - for angles in self.angles: - ax.plot(angles[0],angles[1], color=color, - marker=marker, linestyle='') + 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]) From f0d8030f0100a0c7c9d7e360e75fa08de161d554 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 16:19:56 -0700 Subject: [PATCH 10/20] Fixed imports --- package/MDAnalysis/analysis/dihedrals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 4f3114bd7e3..9f8f029e374 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt import warnings +import MDAnalysis as mda from MDAnalysis.analysis.base import AnalysisBase from MDAnalysis.lib.distances import calc_dihedrals From 15c2bd95d2bc5bf9114587b209948cbc1e442101 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 16:49:40 -0700 Subject: [PATCH 11/20] Added six to imports --- package/MDAnalysis/analysis/dihedrals.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 9f8f029e374..262094b0f26 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,6 +1,8 @@ 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 @@ -75,6 +77,7 @@ def _single_frame(self): 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. From 3b79336125117c2a397008d321da48a039220757 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 17:20:18 -0700 Subject: [PATCH 12/20] Fixed zip function --- package/MDAnalysis/analysis/dihedrals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 262094b0f26..eeab3cdc7fd 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,7 +1,7 @@ import numpy as np import matplotlib.pyplot as plt import warnings -import six +import six from six.moves import zip import MDAnalysis as mda @@ -71,7 +71,7 @@ def _single_frame(self): psi_angles = calc_dihedrals(self.ag2.positions, self.ag3.positions, self.ag4.positions, self.ag5.positions, box=self.ag1.dimensions) - phi_psi = zip(phi_angles, psi_angles) + phi_psi = [(phi, psi) for phi, psi in zip(phi_angles, psi_angles)] self.angles.append(phi_psi) def _conclude(self): From bfc75c9d6d8341e4951c14ec53aedb42809bdacd Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 17:21:17 -0700 Subject: [PATCH 13/20] Updated test references and added them to __all__ --- .../data/adk_oplsaa_GLY_dihedrals.npy | Bin 3168 -> 3168 bytes .../data/adk_oplsaa_dihedrals.npy | Bin 34048 -> 34048 bytes testsuite/MDAnalysisTests/datafiles.py | 3 ++- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/data/adk_oplsaa_GLY_dihedrals.npy b/testsuite/MDAnalysisTests/data/adk_oplsaa_GLY_dihedrals.npy index 1ce2e9e4e47c7a16361dcb005fa48752cf998529..7f034dc41d08f75426e912f08c4a74a11d76a20f 100644 GIT binary patch literal 3168 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 =bL6qRq;5J^at9o10Gj zkcDFt5e91{0+4paLfjdN43{q?-{VhgjRgnUzvPZ4o{z zqeul`ZgjW$Tx5=!7fWuY$F2vr`y+wkZ#8jDLY6W*>ji3t&DtMDxDd?>9qEqX!V#N- zhO5s!&?&|4R&^f>j1=-pH;d;Z5YQ1c4YX%l? zpbZ<(5|D8EZNi=$5?$LHoGFe0K-FJu)GMQgN*Nl^lg1=|w55Ws;nxBKU2XGmyme)#eIuf9#lpEW0fIlz{q$c$!j@VAiD&Mhn+ zWII}27Lj9OZhX&*(tZN&xEQFfOz;NLBg}4EyC+<}rCqS`3jq%Wxw(PbEHsH>XD=xt zq2*lp&gd3zU`13f4o<9Ad&RP9)wv{!6BK0O=r`EER5{;%iwe#9O!E-fx*y6PaYT}#|F zTpfS!b12<()E7q=u4iXF+KDR~M>TeIn!LD>L@OIAVM+s|;_}j2TzmkF?m7(&~L=#BWo$2tV@PSm;(3I!$z(w)Rfy#e9z~1U0 z`|iwk-0*E1d%A=TW35gL?>b2=JFR7_ok+q88Kw)z#{>5e$vIwUJ%EKj+jHM|g4CA5 zPWxmQSodsH?|e(b<|}zcAqTiPGHaK$td<3vQ#r-sQe4vM?2k#D^l=YowV}K(k{dWUjP(FCq8?p$tw}kvRmftd~rNaV^#Om5Pd*)yh-Su zA~c47e0(Cw477B)$Ll<4Fk2!XHF{eUA2c|P)cLG~_XiDq$_m}EcfNG-eH9%}Dz0($ z^i{;5?gWQ^(gz5GHRIRyco1mPDP&jd3Dw(ll5ZmuTmAF9hBOJV*^$@LTjqr=%l;Vm z*W|&DPrDwUFmu5JR%=H5OMmT`#MY2A>_^o2`L zn@2yX%m^0#&f3?{vIHaBr7Rg8V+=NTb2D8ckC&{LeQBmUU|5=?3u`$8+%xp7-_)dL(<69x7&DQ(7X1;p%PUF-1NoH+t4f_*ZW3QHYaO zSza*ss#2G}iiPFV*C*|SS&($IvNk}Ji>k5BjVmPy5WHF?A)C&`d8djmB97Uvg3>g={&nU#vA+O@V}|^oHH$ zrA!P`r@I+Ma=|J}MLJS~hqYI2IzQ60fWVb1&VR@2DF>)v}qAc|9t=cD{S! zhO#COl(z+ab+kj07xkSiWP(uekK-BdjZiwc{OXLf0tzd5s#faqF~3f0E9)i|+g8dy zzG%D|UZn7u{Pz;DdrTqG#!(zh9=7{UR<42vG0C=Ti`3Du+SSZOdlTx?EhL##3Q%-T KnjGG(5B~*55Vw5* diff --git a/testsuite/MDAnalysisTests/data/adk_oplsaa_dihedrals.npy b/testsuite/MDAnalysisTests/data/adk_oplsaa_dihedrals.npy index 0f579dc86d216e93dbc94b4354dc47d268c059f0..0ee56e9ebc0f5b7573f57418f5a672630c774a8c 100644 GIT binary patch literal 34048 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 VxS#lr*lJQE1O1Bzw%I;9=zo5HXtDqR diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 1e053f06f9b..3109f100527 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -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 From c5fcfb8d80470aa147b662a2b52400029f9f2da5 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Thu, 19 Jul 2018 17:27:49 -0700 Subject: [PATCH 14/20] Removed identical frames test, switched to assert_almost_equal --- .../analysis/test_dihedrals.py | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py index 0f2c547eb0f..152e3342cac 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -1,7 +1,6 @@ import numpy as np -from numpy.testing import assert_array_almost_equal +from numpy.testing import assert_almost_equal import pytest -import os import MDAnalysis as mda from MDAnalysisTests.datafiles import GRO, XTC, DihedralsArray, GLYDihedralsArray @@ -18,40 +17,23 @@ def test_ramachandran(self, universe): rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() test_rama = np.load(DihedralsArray) - assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should " - "match test values") + 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_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should " - "match test values") - - def test_ramachandran_identical_frames(self, universe, tmpdir): - - outfile = os.path.join(str(tmpdir), "dihedrals.xtc") - - # write a dummy trajectory of all the same frame - with mda.Writer(outfile, universe.atoms.n_atoms) as W: - for _ in range(universe.trajectory.n_frames): - W.write(universe) - - universe = mda.Universe(GRO, outfile) - rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() - test_rama = [np.load(DihedralsArray)[0] for ts in universe.trajectory] - - assert_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should " - "match test values") + 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_array_almost_equal(rama.angles, test_rama, 5, - err_msg="error: dihedral angles should " - "match test values") + assert_almost_equal(rama.angles, test_rama, 5, + err_msg="error: dihedral angles should " + "match test values") From 4812dc013b0f7002b034f321b26c7b5ac8cea85e Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Fri, 20 Jul 2018 12:46:35 -0700 Subject: [PATCH 15/20] Updated docstrings of dihedrals.py, added files to docs --- package/AUTHORS | 4 +- package/MDAnalysis/analysis/dihedrals.py | 135 +++++++++++++++--- .../analysis/dihedrals.rst | 1 + .../documentation_pages/analysis_modules.rst | 5 +- 4 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 package/doc/sphinx/source/documentation_pages/analysis/dihedrals.rst diff --git a/package/AUTHORS b/package/AUTHORS index 4870f02ac30..1015e5bce8e 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -103,7 +103,8 @@ Chronological list of authors - Ayush Suhane - Davide Cruz - Shujie Fan - + - Henry Mull + External code ------------- @@ -152,4 +153,3 @@ Logo The MDAnalysis 'Atom' logo was designed by Christian Beckstein; it is Copyright (c) 2011 Christian Beckstein and made available under a Creative Commons Attribution-NoDerivs 3.0 Unported License. - diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index eeab3cdc7fd..b6fe75982e5 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -1,3 +1,97 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# +""" +Dihedral and Ramachandran analysis --- :mod:`MDAnalysis.analysis.dihedrals` +=========================================================================== + +:Author: Henry Mull +:Year: 2018 +:Copyright: GNU Public License v2 + +.. versionadded:: 0.18.1 + +This module calculates the dihedral angles phi and psi in degrees for a given +list of residues (or atoms corresponding to the residues). This can be done for +selected frames or whole trajectories. + +A list of time steps that contain phi and psi angles for each residue is +generated, and a basic Ramachandran plot can be generated using the attribute +:meth:`Ramachandran.plot`. This plot is best used as a reference, but it also +allows for user customization. + + +See Also +-------- +:mod:`MDAnalysis.lib.distances.calc_diherals` + + +Example application +------------------- +This example will show how to calculate the phi and psi angles of adenylate +kinase and generate a basic Ramachandran plot. The trajectory is included with +the test data files:: + + import MDAnalysis as mda + from MDAnalysisTests.datafiles import GRO, XTC + u = mda.Universe(GRO, XTC) + r = u.select_atoms("protein") # selection of residues + + from MDAnalysis.analysis.dihedrals import Ramachandran + R = Ramachandran(r) + R.run() + + import matplotlib.pyplot as plt + fig = plt.figure(figsize(5,5)) + ax = fig.add_subplot(111) + ax.set_title("Ramachandran Plot (AdK)") + R.plot(ax=ax, color='k', marker='s') + +Alternatively, if you wanted to plot the data yourself, the angles themselves +can be accessed using :meth:`Ramachandran.angles`:: + + fig = plt.figure(figsize(5, 5)) + ax = fig.add_subplot(111) + ax.axis([-180,180,-180,180]) + 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)") + for ts in R.angles: + ax.scatter(ts[:,0], ts[:,1], color='k', marker='s') + +Analysis Class +-------------- + +.. autoclass:: Ramachandran + :members: + :inherited-members: + + .. attribute:: angles + + Contains the time series of the phi and psi angles for each residue as + an N×3 :class:`numpy.ndarray` array with content + ``[[phi, psi], [...], ...]``. + +""" import numpy as np import matplotlib.pyplot as plt import warnings @@ -10,33 +104,34 @@ class Ramachandran(AnalysisBase): - """Calculate phi and psi dihedral angles of specified residues. + r"""Calculate phi and psi dihedral angles of specified residues. + + Phi and psi angles wil be calculated for each residue in `atomgroup` for + each time step in the trajectory. A ReisdueGroup is generated from + `atomgroup` which is compared to the protein to determine if it is a + legitimate selection. 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. + then a warning will be raised and they will be removed from the list of + residues, but the analysis will still run. + + Run the analysis with :meth:`Ramachandran.run()`, whcih stores the results in the + array :attr:`Ramachandran.angles` """ def __init__(self, atomgroup, **kwargs): - r"""Parameters + """Parameters ---------- - atomgroup : Atomgroup + atomgroup : AtomGroup or ResidueGroup atoms for residues for which phi and psi are calculated - start : int, optional - starting frame, default None becomes 0. - stop : int, optional - 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. + + Raises + ------ + ValueError + If the selection of residues is not contained within the protein """ super(Ramachandran, self).__init__(atomgroup.universe.trajectory, **kwargs) @@ -45,7 +140,7 @@ def __init__(self, atomgroup, **kwargs): protein = self.atomgroup.universe.select_atoms("protein").residues if not residues.issubset(protein): - raise IndexError("Found atoms outside of protein. Only atoms " + raise ValueError("Found atoms outside of protein. Only atoms " "inside of a 'protein' selection can be used to " "calculate dihedrals.") elif not residues.isdisjoint(protein[[0, -1]]): @@ -90,7 +185,7 @@ def plot(self, ax=None, **kwargs): Returns ------- - ax : :class:`~matplotlib.axes.Axes` + ax : :class:`matplotlib.axes.Axes` Axes with the plot, either `ax` or the current axes. """ @@ -102,4 +197,4 @@ def plot(self, ax=None, **kwargs): 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]) + ax.scatter(a[:,0], a[:,1], **kwargs) diff --git a/package/doc/sphinx/source/documentation_pages/analysis/dihedrals.rst b/package/doc/sphinx/source/documentation_pages/analysis/dihedrals.rst new file mode 100644 index 00000000000..eeb1a2ec4b8 --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/analysis/dihedrals.rst @@ -0,0 +1 @@ +.. automodule:: MDAnalysis.analysis.dihedrals diff --git a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst index ea9e78ef134..8df5c8fc8aa 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst @@ -59,7 +59,7 @@ Distances and contacts analysis/rms analysis/psa analysis/encore - + Hydrogen bonding ================ @@ -67,7 +67,7 @@ Hydrogen bonding :maxdepth: 1 analysis/hbond_analysis - analysis/hbond_autocorrel + analysis/hbond_autocorrel analysis/wbridge_analysis Membranes and membrane proteins @@ -105,6 +105,7 @@ Structure analysis/gnm analysis/helanal analysis/rdf + analysis/dihedrals Volumetric analysis From 610368e3509ef1adb648fe567967011d0aa0aa09 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Fri, 20 Jul 2018 12:50:27 -0700 Subject: [PATCH 16/20] Updated CHANGELOG --- package/CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 3582194490c..5e3d8675de9 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -14,7 +14,7 @@ The rules for this file: ------------------------------------------------------------------------------ ??/??/18 tylerjereddy, richardjgowers, palnabarun, orbeckst, kain88-de, zemanj, - VOD555, davidercruz, jbarnoud, ayushsuhane + VOD555, davidercruz, jbarnoud, ayushsuhane, hfmull * 0.18.1 @@ -48,6 +48,7 @@ Enhancements generated with gromacs -noappend (PR #1728) * MDAnalysis.lib.mdamath now supports triclinic boxes and rewrote in Cython (PR #1965) * AtomGroup.write can write a trajectory of selected frames (Issue #1037) + * Added dihedrals.py with Ramachandran class to analysis module Fixes * rewind in the SingleFrameReader now reads the frame from the file (Issue #1929) From 60b1667205de120c53e5bae116113a07ec6db1ab Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Fri, 20 Jul 2018 15:06:18 -0700 Subject: [PATCH 17/20] Fixed plotting in dihedrals.py, added more tests to test_dihedrals.py --- package/MDAnalysis/analysis/dihedrals.py | 12 ++--- .../analysis/test_dihedrals.py | 48 ++++++++++++++++--- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index b6fe75982e5..36c06f8b06d 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -56,20 +56,17 @@ r = u.select_atoms("protein") # selection of residues from MDAnalysis.analysis.dihedrals import Ramachandran - R = Ramachandran(r) - R.run() + R = Ramachandran(r).run() import matplotlib.pyplot as plt - fig = plt.figure(figsize(5,5)) - ax = fig.add_subplot(111) + fig, ax = plt.subplots(figsize=plt.figaspect(1)) ax.set_title("Ramachandran Plot (AdK)") R.plot(ax=ax, color='k', marker='s') Alternatively, if you wanted to plot the data yourself, the angles themselves can be accessed using :meth:`Ramachandran.angles`:: - fig = plt.figure(figsize(5, 5)) - ax = fig.add_subplot(111) + fig, ax = plt.subplots(figsize=plt.figaspect(1)) ax.axis([-180,180,-180,180]) ax.axhline(0, color='k', lw=1) ax.axvline(0, color='k', lw=1) @@ -88,7 +85,7 @@ .. attribute:: angles Contains the time series of the phi and psi angles for each residue as - an N×3 :class:`numpy.ndarray` array with content + an N×2 :class:`numpy.ndarray` array with content ``[[phi, psi], [...], ...]``. """ @@ -198,3 +195,4 @@ def plot(self, ax=None, **kwargs): 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], **kwargs) + return ax diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py index 152e3342cac..8de1289abb4 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -1,10 +1,33 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# import numpy as np from numpy.testing import assert_almost_equal +import matplotlib import pytest import MDAnalysis as mda -from MDAnalysisTests.datafiles import GRO, XTC, DihedralsArray, GLYDihedralsArray -from MDAnalysis.analysis import dihedrals +from MDAnalysisTests.datafiles import (GRO, XTC, DihedralsArray, + GLYDihedralsArray) +from MDAnalysis.analysis.dihedrals import Ramachandran class TestRamachandran(object): @@ -14,7 +37,7 @@ def universe(self): return mda.Universe(GRO, XTC) def test_ramachandran(self, universe): - rama = dihedrals.Ramachandran(universe.select_atoms("protein")).run() + rama = Ramachandran(universe.select_atoms("protein")).run() test_rama = np.load(DihedralsArray) assert_almost_equal(rama.angles, test_rama, 5, @@ -22,8 +45,8 @@ def test_ramachandran(self, universe): "match test values") def test_ramachandran_single_frame(self, universe): - rama = dihedrals.Ramachandran(universe.select_atoms("protein"), - start=5, stop=6).run() + rama = Ramachandran(universe.select_atoms("protein"), + start=5, stop=6).run() test_rama = [np.load(DihedralsArray)[5]] assert_almost_equal(rama.angles, test_rama, 5, @@ -31,9 +54,22 @@ def test_ramachandran_single_frame(self, universe): "match test values") def test_ramachandran_residue_selections(self, universe): - rama = dihedrals.Ramachandran(universe.select_atoms("resname GLY")).run() + rama = 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") + + def test_outside_protein_length(self, universe): + with pytest.raises(ValueError): + rama = Ramachandran(universe.select_atoms("resid 220")).run() + + def test_protein_ends(self, universe): + with pytest.warns(UserWarning): + rama = Ramachandran(universe.select_atoms("protein")).run() + + def test_plot(self, universe): + ax = Ramachandran(universe.select_atoms("resid 5-10")).run().plot() + assert isinstance(ax, matplotlib.axes.Axes), \ + "Ramachandran.plot() did not return and Axes instance" From e83bd9aade3b4392d51ba068d3ceef79725f65ce Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Mon, 23 Jul 2018 11:54:20 -0700 Subject: [PATCH 18/20] Fixed imports and docstring --- package/MDAnalysis/analysis/dihedrals.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 36c06f8b06d..30d344251f6 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -41,7 +41,8 @@ See Also -------- -:mod:`MDAnalysis.lib.distances.calc_diherals` +:mod:`MDAnalysis.lib.distances` + contains functions to calculate dihedral angles from atom positions Example application @@ -84,16 +85,18 @@ .. attribute:: angles - Contains the time series of the phi and psi angles for each residue as - an N×2 :class:`numpy.ndarray` array with content - ``[[phi, psi], [...], ...]``. + Contains the time steps of the phi and psi angles for each residue as + an n_frames×n_residuesx2 :class:`numpy.ndarray` array with content + ``[[[phi, psi], [residue 2], ...], [time step 2], ...]``. """ +from __future__ import absolute_import +from six.moves import zip, range + 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 756123291154d781c452babcf948c45b002e690d Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Mon, 23 Jul 2018 13:58:20 -0700 Subject: [PATCH 19/20] Fixed docstring --- package/MDAnalysis/analysis/dihedrals.py | 33 +++++++++++------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index 30d344251f6..09f0c7dce92 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -34,22 +34,22 @@ selected frames or whole trajectories. A list of time steps that contain phi and psi angles for each residue is -generated, and a basic Ramachandran plot can be generated using the attribute -:meth:`Ramachandran.plot`. This plot is best used as a reference, but it also +generated, and a basic Ramachandran plot can be generated using the method +:meth:`Ramachandran.plot()`. This plot is best used as a reference, but it also allows for user customization. See Also -------- -:mod:`MDAnalysis.lib.distances` - contains functions to calculate dihedral angles from atom positions +:func:`MDAnalysis.lib.distances.calc_dihedrals()` + function to calculate dihedral angles from atom positions Example application ------------------- This example will show how to calculate the phi and psi angles of adenylate -kinase and generate a basic Ramachandran plot. The trajectory is included with -the test data files:: +kinase (AdK) and generate a basic Ramachandran plot. The trajectory is included +within the test data files:: import MDAnalysis as mda from MDAnalysisTests.datafiles import GRO, XTC @@ -65,7 +65,7 @@ R.plot(ax=ax, color='k', marker='s') Alternatively, if you wanted to plot the data yourself, the angles themselves -can be accessed using :meth:`Ramachandran.angles`:: +can be accessed using :attr:`Ramachandran.angles`:: fig, ax = plt.subplots(figsize=plt.figaspect(1)) ax.axis([-180,180,-180,180]) @@ -86,7 +86,7 @@ .. attribute:: angles Contains the time steps of the phi and psi angles for each residue as - an n_frames×n_residuesx2 :class:`numpy.ndarray` array with content + an n_frames×n_residues×2 :class:`numpy.ndarray` with content ``[[[phi, psi], [residue 2], ...], [time step 2], ...]``. """ @@ -104,23 +104,20 @@ class Ramachandran(AnalysisBase): - r"""Calculate phi and psi dihedral angles of specified residues. + """Calculate phi and psi dihedral angles of selected residues. - Phi and psi angles wil be calculated for each residue in `atomgroup` for - each time step in the trajectory. A ReisdueGroup is generated from - `atomgroup` which is compared to the protein to determine if it is a - legitimate selection. + Phi and psi angles will be calculated for each residue corresponding to + `atomgroup` for each time step in the trajectory. A :class:`ResidueGroup` + is generated from `atomgroup` which is compared to the protein to determine + if it is a legitimate selection. Note ---- 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 + will be raised. If the residue selection includes the first or last residue, then a warning will be raised and they will be removed from the list of residues, but the analysis will still run. - Run the analysis with :meth:`Ramachandran.run()`, whcih stores the results in the - array :attr:`Ramachandran.angles` - """ def __init__(self, atomgroup, **kwargs): """Parameters @@ -175,7 +172,7 @@ def _conclude(self): def plot(self, ax=None, **kwargs): """Plots data into standard ramachandran plot. Each time step in - self.angles is plotted onto the same graph. + :attr:`Ramachandran.angles` is plotted onto the same graph. Parameters ---------- From 193e503fae739130dcd59a0bedc1972a3b4e1345 Mon Sep 17 00:00:00 2001 From: Henry Mull Date: Tue, 24 Jul 2018 11:40:04 -0700 Subject: [PATCH 20/20] Fixed imports of test_dihedral.py --- testsuite/MDAnalysisTests/analysis/test_dihedrals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py index 8de1289abb4..07e4b89e1d7 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -19,6 +19,8 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +from __future__ import absolute_import + import numpy as np from numpy.testing import assert_almost_equal import matplotlib