diff --git a/package/CHANGELOG b/package/CHANGELOG index 3b1161ebbdc..79a6841545f 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -18,7 +18,7 @@ The rules for this file: calcraven,xiki-tempula, mieczyslaw, manuel.nuno.melo, PicoCentauri, hanatok, rmeli, aditya-kamath, tirkarthi, LeonardoBarneschi, hejamu, biogen98, orioncohen, z3y50n, hp115, ojeda-e, thadanipaarth, HenryKobin, - 1ut, sulays + 1ut, sulays, PicoCentauri * 2.0.0 @@ -103,6 +103,7 @@ Fixes * Fix syntax warning over comparison of literals using is (Issue #3066) Enhancements + * Switch GNMAnalysis to AnalysisBase (Issue #3243) * Adds python 3.9 support (Issue #2974, PR #3027, #3245) * Added an MDAnalysis shields.io badge to the README (Issue #3227, PR #3229) * Added sort method to the atomgroup (Issue #2976, PR #3188) diff --git a/package/MDAnalysis/analysis/gnm.py b/package/MDAnalysis/analysis/gnm.py index 65c5356f73c..d4fd2700f34 100644 --- a/package/MDAnalysis/analysis/gnm.py +++ b/package/MDAnalysis/analysis/gnm.py @@ -86,11 +86,13 @@ """ import itertools +import logging +import warnings import numpy as np -import warnings -import logging +from .base import AnalysisBase + logger = logging.getLogger('MDAnalysis.analysis.GNM') @@ -189,7 +191,7 @@ def order_list(w): return list_map -class GNMAnalysis(object): +class GNMAnalysis(AnalysisBase): """Basic tool for GNM analysis. Each frame is treated as a novel structure and the GNM @@ -217,6 +219,13 @@ class GNMAnalysis(object): `Bonus_groups` is contained in `selection` as this could lead to double counting. No checks are applied. Default is ``None``. + Attributes + ---------- + results : list + GNM results per frame: + results = [(time,eigenvalues[1],eigenvectors[1]), + (time,eigenvalues[1],eigenvectors[1]), ...] + See Also -------- :class:`closeContactGNMAnalysis` @@ -228,6 +237,8 @@ class GNMAnalysis(object): .. versionchanged:: 1.0.0 Changed `selection` keyword to `select` + .. versionchanged:: 2.0.0 + Use :class:`~MDAnalysis.analysis.AnalysisBase` as parent class. """ def __init__(self, @@ -236,6 +247,7 @@ def __init__(self, cutoff=7.0, ReportVector=None, Bonus_groups=None): + super(GNMAnalysis, self).__init__(universe.trajectory) self.u = universe self.select = select self.cutoff = cutoff @@ -306,49 +318,31 @@ def generate_kirchoff(self): return matrix - def run(self, start=None, stop=None, step=None): - """Analyze trajectory and produce timeseries. - - Parameters - ---------- - start : int (optional) - stop : int (optional) - step : int (optional) - - Returns - ------- - results : list - GNM results per frame:: - - results = [(time,eigenvalues[1],eigenvectors[1]),(time,eigenvalues[1],eigenvectors[1])... ] - - .. versionchanged:: 0.16.0 - use start, stop, step instead of skip - """ - logger.info("GNM analysis: starting") - + def _prepare(self): self.timeseries = [] - self._timesteps = [] - - for ts in self.u.trajectory[start:stop:step]: - self._timesteps.append(ts.time) - - matrix = self.generate_kirchoff() - try: - u, w, v = np.linalg.svd(matrix) - except np.linalg.LinAlgError: - print("\nFrame skip at", ts.time, - "(SVD failed to converge). Cutoff", self.cutoff) - continue - #Save the results somewhere useful in some useful format. Usefully. - self._generate_output( - w, - v, - self.results, - ts.time, - matrix, - ReportVector=self.ReportVector, - counter=ts.frame) + + def _single_frame(self): + matrix = self.generate_kirchoff() + try: + _, w, v = np.linalg.svd(matrix) + except np.linalg.LinAlgError: + msg = f"SVD with cutoff {self.cutoff} failed to converge. " + msg += f"Skip frame at {self._ts.time}." + warnings.warn(msg) + logger.warning(msg) + return + # Save the results somewhere useful in some useful format. Usefully. + self._generate_output( + w, + v, + self.results, + self._ts.time, + matrix, + ReportVector=self.ReportVector, + counter=self._ts.frame) + + def _conclude(self): + self._timesteps = self.times class closeContactGNMAnalysis(GNMAnalysis): @@ -394,6 +388,9 @@ class closeContactGNMAnalysis(GNMAnalysis): .. versionchanged:: 1.0.0 MassWeight option (see above deprecation entry). Changed `selection` keyword to `select` + + .. versionchanged:: 2.0.0 + Use :class:`~MDAnalysis.analysis.AnalysisBase` as parent class. """ def __init__(self, @@ -402,18 +399,13 @@ def __init__(self, cutoff=4.5, ReportVector=None, weights="size"): - self.u = universe - self.select = select - self.cutoff = cutoff - self.results = [] # final result - self._timesteps = None # time for each frame - self.ReportVector = ReportVector - self.ca = self.u.select_atoms(self.select) - + super(closeContactGNMAnalysis, self).__init__(universe, + select, + cutoff, + ReportVector) self.weights = weights def generate_kirchoff(self): - natoms = self.ca.n_atoms nresidues = self.ca.n_residues positions = self.ca.positions residue_index_map = [ diff --git a/testsuite/MDAnalysisTests/analysis/test_gnm.py b/testsuite/MDAnalysisTests/analysis/test_gnm.py index 9587a664d74..0bb25e1b4c7 100644 --- a/testsuite/MDAnalysisTests/analysis/test_gnm.py +++ b/testsuite/MDAnalysisTests/analysis/test_gnm.py @@ -21,6 +21,7 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # import os +from unittest.mock import patch import MDAnalysis as mda import MDAnalysis.analysis.gnm @@ -80,6 +81,15 @@ def test_generate_kirchoff(universe): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +def test_gnm_SVD_fail(universe): + with patch.object(np.linalg, "svd") as np_load_mock: + np_load_mock.side_effect = np.linalg.LinAlgError + msg = "SVD with cutoff 7.0 failed to converge. " + msg += "Skip frame at 0.0." + with pytest.warns(UserWarning, match=msg): + mda.analysis.gnm.GNMAnalysis(universe).run(stop=1) + + def test_closeContactGNMAnalysis(universe): gnm = mda.analysis.gnm.closeContactGNMAnalysis(universe, weights="size") gnm.run(stop=2) @@ -127,4 +137,4 @@ def test_closeContactGNMAnalysis_weights_None(universe): 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -43.0, -3.0]) \ No newline at end of file + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -43.0, -3.0])