From 4eab969e90205d318eb93ff7e602acd4fc91e25f Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Thu, 15 Jun 2017 17:18:13 -0700 Subject: [PATCH 1/4] make scipy and matplotlib full dependencies (#1159) --- .travis.yml | 6 +++--- package/CHANGELOG | 3 +++ package/setup.py | 9 +++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 705eda558f5..714201306a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,10 +31,10 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST} ${PYTEST_FLAGS}; python ./testsuite/MDAnalysisTests/mda_nosetests ${NOSE_TEST_LIST} ${NOSE_FLAGS}" - SETUP_CMD="" - BUILD_CMD="pip install -v package/ && pip install testsuite/" - - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer pytest=3.1.2 pytest-cov=2.5.1" - - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy seaborn coveralls clustalw=2.1 pytest=3.1.2 pytest-cov=2.5.1" + - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib scipy griddataformats pytest=3.1.2 pytest-cov=2.5.1" + - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy griddataformats seaborn coveralls clustalw=2.1 pytest=3.1.2 pytest-cov=2.5.1" # Install griddataformats from PIP so that scipy is only installed in the full build (#1147) - - PIP_DEPENDENCIES='griddataformats' + - PIP_DEPENDENCIES='' - CONDA_CHANNELS='biobuilds conda-forge' - CONDA_CHANNEL_PRIORITY=True - NUMPY_VERSION=stable diff --git a/package/CHANGELOG b/package/CHANGELOG index f1aba435b37..6859b765b25 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -29,6 +29,9 @@ Fixes * Fixed dtype of numpy arrays to accomodate 32 bit architectures (Issue #1362) * Groups are hashable on python 3 (Issue #1397) +Changes + * scipy and matplotlib are now required dependencies (Issue #1159) + 06/03/17 utkbansal, kain88-de, xiki-tempula, kaplajon, wouterboomsma, richardjgowers, Shtkddud123, QuantumEntangledAndy, orbeckst, diff --git a/package/setup.py b/package/setup.py index 5aecb4889c5..04b7fb23e1e 100755 --- a/package/setup.py +++ b/package/setup.py @@ -494,11 +494,12 @@ def dynamic_author_list(): classifiers=CLASSIFIERS, cmdclass=cmdclass, requires=['numpy (>=1.10.4)', 'biopython', 'mmtf (>=1.0.0)', - 'networkx (>=1.0)', 'GridDataFormats (>=0.3.2)', 'joblib'], + 'networkx (>=1.0)', 'GridDataFormats (>=0.3.2)', 'joblib', + 'scipy', 'matplotlib (>=1.5.1)'], # all standard requirements are available through PyPi and # typically can be installed without difficulties through setuptools setup_requires=[ - 'numpy>=1.9.3', + 'numpy>=1.10.4', ], install_requires=[ 'numpy>=1.10.4', @@ -508,6 +509,8 @@ def dynamic_author_list(): 'six>=1.4.0', 'mmtf-python>=1.0.0', 'joblib', + 'scipy', + 'matplotlib>=1.5.1', ], # extras can be difficult to install through setuptools and/or # you might prefer to use the version available through your @@ -516,8 +519,6 @@ def dynamic_author_list(): 'AMBER': ['netCDF4>=1.0'], # for AMBER netcdf, also needs HDF5 # and netcdf-4 'analysis': [ - 'matplotlib>=1.5.1', - 'scipy', 'seaborn', # for annotated heat map and nearest neighbor # plotting in PSA 'sklearn', # For clustering and dimensionality reduction From a56ec2a1425e01c59c1b2138df3d2a390efa416b Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Thu, 15 Jun 2017 19:37:48 -0700 Subject: [PATCH 2/4] scipy and matplotlib are imported at top in analysis - updated all modules - removed any code that guards against scipy or matplotlib import - conforms to style guide https://github.com/MDAnalysis/mdanalysis/wiki/Style-Guide#module-imports-in-mdanalysisanalysis - fixes #1159 - fixes #1361 --- package/MDAnalysis/analysis/distances.py | 23 +---- .../MDAnalysis/analysis/encore/similarity.py | 36 ++----- .../analysis/hbonds/hbond_autocorrel.py | 25 ++--- package/MDAnalysis/analysis/hole.py | 16 ++- package/MDAnalysis/analysis/legacy/x3dna.py | 5 +- package/MDAnalysis/analysis/pca.py | 7 +- package/MDAnalysis/analysis/polymer.py | 7 +- package/MDAnalysis/analysis/psa.py | 99 ++++++++++++++----- 8 files changed, 114 insertions(+), 104 deletions(-) diff --git a/package/MDAnalysis/analysis/distances.py b/package/MDAnalysis/analysis/distances.py index ae4ad699141..d524a993625 100644 --- a/package/MDAnalysis/analysis/distances.py +++ b/package/MDAnalysis/analysis/distances.py @@ -42,6 +42,7 @@ 'contact_matrix', 'dist', 'between'] import numpy as np +import scipy.sparse from MDAnalysis.lib.distances import distance_array, self_distance_array from MDAnalysis.lib.c_distances import contact_matrix_no_pbc, contact_matrix_pbc @@ -51,15 +52,6 @@ import logging logger = logging.getLogger("MDAnalysis.analysis.distances") -try: - from scipy import sparse -except ImportError: - sparse = None - msg = "scipy.sparse could not be imported: some functionality will " \ - "not be available in contact_matrix()" - warnings.warn(msg, category=ImportWarning) - logger.warn(msg) - del msg def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): '''Calculates a matrix of contacts. @@ -93,12 +85,6 @@ def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): The contact matrix is returned in a format determined by the `returntype` keyword. - - Note - ---- - :mod:`scipy.sparse` is require for using *sparse* matrices; if it cannot - be imported then an `ImportError` is raised. - See Also -------- :mod:`MDAnalysis.analysis.contacts` for native contact analysis @@ -112,14 +98,9 @@ def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): adj = (distance_array(coord, coord, box=box) < cutoff) return adj elif returntype == "sparse": - if sparse is None: - # hack: if we are running with minimal dependencies then scipy was - # not imported and we have to bail here (see scipy import at top) - raise ImportError("For sparse matrix functionality you need to " - "import scipy.") # Initialize square List of Lists matrix of dimensions equal to number # of coordinates passed - sparse_contacts = sparse.lil_matrix((len(coord), len(coord)), dtype='bool') + sparse_contacts = scipy.sparse.lil_matrix((len(coord), len(coord)), dtype='bool') if box is not None: # with PBC contact_matrix_pbc(coord, sparse_contacts, box, cutoff) diff --git a/package/MDAnalysis/analysis/encore/similarity.py b/package/MDAnalysis/analysis/encore/similarity.py index bd7e9e5e0ea..a56b3c0c1e5 100644 --- a/package/MDAnalysis/analysis/encore/similarity.py +++ b/package/MDAnalysis/analysis/encore/similarity.py @@ -172,21 +172,13 @@ from __future__ import print_function, division, absolute_import from six.moves import range, zip -import MDAnalysis as mda -import numpy as np import warnings import logging -try: - from scipy.stats import gaussian_kde -except ImportError: - gaussian_kde = None - msg = "scipy.stats.gaussian_kde could not be imported. " \ - "Dimensionality reduction ensemble comparisons will not " \ - "be available." - warnings.warn(msg, - category=ImportWarning) - logging.warn(msg) - del msg + +import numpy as np +import scipy.stats + +import MDAnalysis as mda from ...coordinates.memory import MemoryReader from .confdistmatrix import get_distance_matrix @@ -460,18 +452,11 @@ def gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, embedded_ensembles = [] resamples = [] - if gaussian_kde is None: - # hack: if we are running with minimal dependencies then scipy was - # not imported and we have to bail here (see scipy import at top) - raise ImportError("For Kernel Density Estimation functionality you" - "need to import scipy") - for i in range(1, nensembles + 1): this_embedded = embedded_space.transpose()[ np.where(np.array(ensemble_assignment) == i)].transpose() embedded_ensembles.append(this_embedded) - kdes.append(gaussian_kde( - this_embedded)) + kdes.append(scipy.stats.gaussian_kde(this_embedded)) # # Set number of samples # if not nsamples: @@ -623,12 +608,6 @@ def cumulative_gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, """ - if gaussian_kde is None: - # hack: if we are running with minimal dependencies then scipy was - # not imported and we have to bail here (see scipy import at top) - raise ImportError("For Kernel Density Estimation functionality you" - "need to import scipy") - kdes = [] embedded_ensembles = [] resamples = [] @@ -639,8 +618,7 @@ def cumulative_gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, np.logical_and(ensemble_assignment >= ens_id_min, ensemble_assignment <= i))].transpose() embedded_ensembles.append(this_embedded) - kdes.append( - gaussian_kde(this_embedded)) + kdes.append(scipy.stats.gaussian_kde(this_embedded)) # Resample according to probability distributions for this_kde in kdes: diff --git a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py index a0bf6bb5451..bf81e3529c5 100644 --- a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py +++ b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py @@ -155,6 +155,8 @@ from __future__ import division, absolute_import from six.moves import zip import numpy as np +import scipy.optimize + import warnings from MDAnalysis.lib.log import ProgressMeter @@ -162,7 +164,7 @@ class HydrogenBondAutoCorrel(object): - """Perform a time autocorrelation of the hydrogen bonds in the system. + """Perform a time autocorrelation of the hydrogen bonds in the system. Parameters ---------- @@ -421,8 +423,9 @@ def solve(self, p_guess=None): Initial guess for the leastsq fit, must match the shape of the expected coefficients - Continuous defition results are fitted to a double exponential, - intermittent definition are fit to a triple exponential. + Continuous defition results are fitted to a double exponential with + :func:`scipy.optimize.leastsq`, intermittent definition are fit to a + triple exponential. The results of this fitting procedure are saved into the *fit*, *tau* and *estimate* keywords in the solution dict. @@ -434,14 +437,14 @@ def solve(self, p_guess=None): - *estimate* contains the estimate provided by the fit of the time autocorrelation function - In addition, the output of the leastsq function is saved into the - solution dict + In addition, the output of the :func:`~scipy.optimize.leastsq` function + is saved into the solution dict - *infodict* - *mesg* - *ier* + """ - from scipy.optimize import leastsq if self.solution['results'] is None: raise ValueError( @@ -498,9 +501,8 @@ def triple(x, A1, A2, tau1, tau2, tau3): if p_guess is None: p_guess = (0.5, 10 * self.sample_time, self.sample_time) - p, cov, infodict, mesg, ier = leastsq(err, p_guess, - args=(time, results), - full_output=True) + p, cov, infodict, mesg, ier = scipy.optimize.leastsq( + err, p_guess, args=(time, results), full_output=True) self.solution['fit'] = p A1, tau1, tau2 = p A2 = 1 - A1 @@ -512,9 +514,8 @@ def triple(x, A1, A2, tau1, tau2, tau3): p_guess = (0.33, 0.33, 10 * self.sample_time, self.sample_time, 0.1 * self.sample_time) - p, cov, infodict, mesg, ier = leastsq(err, p_guess, - args=(time, results), - full_output=True) + p, cov, infodict, mesg, ier = scipy.optimize.leastsq( + err, p_guess, args=(time, results), full_output=True) self.solution['fit'] = p A1, A2, tau1, tau2, tau3 = p A3 = 1 - A1 - A2 diff --git a/package/MDAnalysis/analysis/hole.py b/package/MDAnalysis/analysis/hole.py index e43316a868c..7088e5f8a7c 100644 --- a/package/MDAnalysis/analysis/hole.py +++ b/package/MDAnalysis/analysis/hole.py @@ -245,7 +245,6 @@ from six.moves import zip, cPickle import six -import numpy as np import glob import os import errno @@ -258,6 +257,10 @@ import logging from itertools import cycle +import numpy as np +import matplotlib +import matplotlib.pyplot as plt + from MDAnalysis import Universe from MDAnalysis.exceptions import ApplicationError from MDAnalysis.lib.util import which, realpath, asiterable @@ -370,8 +373,6 @@ def save(self, filename="hole.pickle"): cPickle.dump(self.profiles, open(filename, "wb"), cPickle.HIGHEST_PROTOCOL) def _process_plot_kwargs(self, kwargs): - import matplotlib.colors - kw = {} frames = kwargs.pop('frames', None) if frames is None: @@ -448,9 +449,6 @@ def plot(self, **kwargs): Returns ``ax``. """ - - import matplotlib.pyplot as plt - kw, kwargs = self._process_plot_kwargs(kwargs) ax = kwargs.pop('ax', None) @@ -517,8 +515,7 @@ def plot3D(self, **kwargs): Returns ``ax``. """ - - import matplotlib.pyplot as plt + # installed with matplotlib; imported here to enable 3D axes from mpl_toolkits.mplot3d import Axes3D kw, kwargs = self._process_plot_kwargs(kwargs) @@ -540,8 +537,7 @@ def plot3D(self, **kwargs): rxncoord = profile.rxncoord else: # does not seem to work with masked arrays but with nan hack! - # http://stackoverflow.com/questions/4913306/python-matplotlib-mplot3d-how-do-i-set-a-maximum-value - # -for-the-z-axis + # http://stackoverflow.com/questions/4913306/python-matplotlib-mplot3d-how-do-i-set-a-maximum-value-for-the-z-axis #radius = np.ma.masked_greater(profile.radius, rmax) #rxncoord = np.ma.array(profile.rxncoord, mask=radius.mask) rxncoord = profile.rxncoord diff --git a/package/MDAnalysis/analysis/legacy/x3dna.py b/package/MDAnalysis/analysis/legacy/x3dna.py index f960cd1fc5d..99cb4a8fc65 100644 --- a/package/MDAnalysis/analysis/legacy/x3dna.py +++ b/package/MDAnalysis/analysis/legacy/x3dna.py @@ -132,13 +132,15 @@ import errno import shutil import warnings -import numpy as np import os.path import subprocess import tempfile import textwrap from collections import OrderedDict +import numpy as np +import matplotlib.pyplot as plt + from MDAnalysis import ApplicationError from MDAnalysis.lib.util import which, realpath, asiterable @@ -413,7 +415,6 @@ def plot(self, **kwargs): Provide `ax` to have all plots plotted in the same axes. """ - import matplotlib.pyplot as plt na_avg, na_std = self.mean_std() for k in range(len(na_avg[0])): diff --git a/package/MDAnalysis/analysis/pca.py b/package/MDAnalysis/analysis/pca.py index 18d729ecc08..b9920a27ed3 100644 --- a/package/MDAnalysis/analysis/pca.py +++ b/package/MDAnalysis/analysis/pca.py @@ -106,6 +106,7 @@ import warnings import numpy as np +import scipy.integrate from MDAnalysis import Universe from MDAnalysis.analysis.align import _fit_to @@ -357,9 +358,9 @@ def cosine_content(pca_space, i): .. [BerkHess1] Berk Hess. Convergence of sampling in protein simulations. Phys. Rev. E 65, 031910 (2002). """ - from scipy.integrate import simps + t = np.arange(len(pca_space)) T = len(pca_space) cos = np.cos(np.pi * t * (i + 1) / T) - return ((2.0 / T) * (simps(cos*pca_space[:, i])) ** 2 / - simps(pca_space[:, i] ** 2)) + return ((2.0 / T) * (scipy.integrate.simps(cos*pca_space[:, i])) ** 2 / + scipy.integrate.simps(pca_space[:, i] ** 2)) diff --git a/package/MDAnalysis/analysis/polymer.py b/package/MDAnalysis/analysis/polymer.py index 645055e1878..b7babb204ab 100644 --- a/package/MDAnalysis/analysis/polymer.py +++ b/package/MDAnalysis/analysis/polymer.py @@ -36,6 +36,8 @@ from six.moves import range import numpy as np +import scipy.optimize + import logging from .. import NoDataError @@ -165,13 +167,10 @@ def fit_exponential_decay(x, y): ----- This function assumes that data starts at 1.0 and decays to 0.0 - Requires scipy """ - from scipy.optimize import curve_fit - def expfunc(x, a): return np.exp(-x/a) - a = curve_fit(expfunc, x, y)[0][0] + a = scipy.optimize.curve_fit(expfunc, x, y)[0][0] return a diff --git a/package/MDAnalysis/analysis/psa.py b/package/MDAnalysis/analysis/psa.py index 6c002241a40..f7178d678c1 100644 --- a/package/MDAnalysis/analysis/psa.py +++ b/package/MDAnalysis/analysis/psa.py @@ -216,7 +216,11 @@ from six.moves import range, cPickle import numpy as np -import warnings,numbers +from scipy import spatial, cluster +import matplotlib + +import warnings +import numbers import MDAnalysis import MDAnalysis.analysis.align @@ -396,13 +400,14 @@ def hausdorff(P, Q): Notes ----- - - The Hausdorff distance is calculated in a brute force manner from the - distance matrix without further optimizations, essentially following - [Huttenlocher1993]_. - - :func:`scipy.spatial.distance.directed_hausdorff` is an optimized - implementation of the early break algorithm of [Taha2015]_; note that - one still has to calculate the *symmetric* Hausdorff distance as - `max(directed_hausdorff(P, Q)[0], directed_hausdorff(Q, P)[0])`. + The Hausdorff distance is calculated in a brute force manner from the + distance matrix without further optimizations, essentially following + [Huttenlocher1993]_. + + :func:`scipy.spatial.distance.directed_hausdorff` is an optimized + implementation of the early break algorithm of [Taha2015]_; note that one + still has to calculate the *symmetric* Hausdorff distance as + `max(directed_hausdorff(P, Q)[0], directed_hausdorff(Q, P)[0])`. References ---------- @@ -415,6 +420,10 @@ def hausdorff(P, Q): calculating the exact Hausdorff distance. IEEE Transactions On Pattern Analysis And Machine Intelligence, 37:2153-63, 2015. + SeeAlso + ------- + scipy.spatial.distance.directed_hausdorff + """ N, axis = get_coord_axes(P) d = get_msd_matrix(P, Q, axis=axis) @@ -1650,7 +1659,7 @@ def plot(self, filename=None, linkage='ward', count_sort=False, If `filename` is supplied then the figure is also written to file (the suffix determines the file type, e.g. pdf, png, eps, ...). All other - keyword arguments are passed on to :func:`matplotlib.pyplot.imshow`. + keyword arguments are passed on to :func:`matplotlib.pyplot.matshow`. Parameters @@ -1669,6 +1678,15 @@ def plot(self, filename=None, linkage='ward', count_sort=False, set the font size for colorbar labels; font size for path labels on dendrogram default to 3 points smaller [``12``] + Returns + ------- + Z + `Z` from :meth:`cluster` + dgram + `dgram` from :meth:`cluster` + dist_matrix_clus + clustered distance matrix (reordered) + """ from matplotlib.pyplot import figure, colorbar, cm, savefig, clf @@ -1770,6 +1788,23 @@ def plot_annotated_heatmap(self, filename=None, linkage='ward', \ annot_size : float font size of annotation labels on heat map [``6.5``] + Returns + ------- + Z + `Z` from :meth:`cluster` + dgram + `dgram` from :meth:`cluster` + dist_matrix_clus + clustered distance matrix (reordered) + + + Note + ---- + This function requires the seaborn_ package, which can be installed + with `pip install seaborn` or `conda install seaborn`. + + .. _seaborn: https://seaborn.pydata.org/ + """ from matplotlib.pyplot import figure, colorbar, cm, savefig, clf @@ -1870,6 +1905,17 @@ def plot_nearest_neighbors(self, filename=None, idx=0, \ set the font size for colorbar labels; font size for path labels on dendrogram default to 3 points smaller [``12``] + Returns + ------- + ax : axes + + Note + ---- + This function requires the seaborn_ package, which can be installed + with `pip install seaborn` or `conda install seaborn`. + + .. _seaborn: https://seaborn.pydata.org/ + """ from matplotlib.pyplot import figure, savefig, tight_layout, clf, show try: @@ -1927,7 +1973,8 @@ def plot_nearest_neighbors(self, filename=None, idx=0, \ head = self.targetdir + self.datadirs['plots'] outfile = os.path.join(head, filename) savefig(outfile, dpi=300, bbox_inches='tight') - show() + + return ax def cluster(self, distArray, method='ward', count_sort=False, \ @@ -1955,22 +2002,28 @@ def cluster(self, distArray, method='ward', count_sort=False, \ Returns ------- - list + Z + output from :func:`scipy.cluster.hierarchy.linkage`; list of indices representing the row-wise order of the objects after clustering + dgram + output from :func:`scipy.cluster.hierarchy.dendrogram` """ - import matplotlib - from scipy.cluster.hierarchy import linkage, dendrogram - + # perhaps there is a better way to manipulate the plot... or perhaps it + # is not even necessary? In any case, the try/finally makes sure that + # we are not permanently changing the user's global state + orig_linewidth = matplotlib.rcParams['lines.linewidth'] matplotlib.rcParams['lines.linewidth'] = 0.5 - - Z = linkage(distArray, method=method) - dgram = dendrogram(Z, no_labels=no_labels, orientation='left', \ - count_sort=count_sort, distance_sort=distance_sort, \ - no_plot=no_plot, color_threshold=color_threshold) + try: + Z = cluster.hierarchy.linkage(distArray, method=method) + dgram = cluster.hierarchy.dendrogram( + Z, no_labels=no_labels, orientation='left', + count_sort=count_sort, distance_sort=distance_sort, + no_plot=no_plot, color_threshold=color_threshold) + finally: + matplotlib.rcParams['lines.linewidth'] = orig_linewidth return Z, dgram - def _get_plot_obj_locs(self): """Find and return coordinates for dendrogram, heat map, and colorbar. @@ -2005,7 +2058,8 @@ def get_num_atoms(self): Returns ------- - the number of atoms + int + the number of atoms Note ---- @@ -2077,8 +2131,7 @@ def get_pairwise_distances(self, vectorform=False): err_str = "No distance data; do 'PSAnalysis.run(store=True)' first." raise ValueError(err_str) if vectorform: - from scipy.spatial.distance import squareform - return squareform(self.D) + return spatial.distance.squareform(self.D) else: return self.D From 677924098bd919d1a52142ebc7c3b6b822e9e5d6 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Thu, 15 Jun 2017 20:44:17 -0700 Subject: [PATCH 3/4] removed conditional skipping of tests when scipy or matplotlib are missing --- .../MDAnalysisTests/analysis/test_density.py | 7 -- .../analysis/test_distances.py | 67 ++----------------- .../MDAnalysisTests/analysis/test_encore.py | 21 +----- .../MDAnalysisTests/analysis/test_hole.py | 9 +-- .../analysis/test_hydrogenbondautocorrel.py | 3 - .../MDAnalysisTests/analysis/test_leaflet.py | 2 - .../MDAnalysisTests/analysis/test_pca.py | 2 - .../analysis/test_persistencelength.py | 14 +--- .../MDAnalysisTests/analysis/test_psa.py | 15 ++--- 9 files changed, 17 insertions(+), 123 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_density.py b/testsuite/MDAnalysisTests/analysis/test_density.py index 75502b71380..adc51445244 100644 --- a/testsuite/MDAnalysisTests/analysis/test_density.py +++ b/testsuite/MDAnalysisTests/analysis/test_density.py @@ -30,9 +30,6 @@ assert_raises) import MDAnalysis as mda -# imported inside a skipif-protected method so that it can -# be tested in the absence of scipy -## import MDAnalysis.analysis.density from MDAnalysisTests.datafiles import TPR, XTC, GRO from MDAnalysisTests import module_not_found, tempdir @@ -45,8 +42,6 @@ class TestDensity(TestCase): counts = 100 Lmax = 10. - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def setUp(self): import MDAnalysis.analysis.density @@ -123,8 +118,6 @@ class Test_density_from_Universe(TestCase): cutoffs = {'notwithin': 4.0, } precision = 5 - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def setUp(self): self.outfile = 'density.dx' self.universe = mda.Universe(self.topology, self.trajectory) diff --git a/testsuite/MDAnalysisTests/analysis/test_distances.py b/testsuite/MDAnalysisTests/analysis/test_distances.py index a217f6142d8..91562f00f6f 100644 --- a/testsuite/MDAnalysisTests/analysis/test_distances.py +++ b/testsuite/MDAnalysisTests/analysis/test_distances.py @@ -21,22 +21,25 @@ # from __future__ import print_function, absolute_import +import scipy +import scipy.spatial + import MDAnalysis from MDAnalysisTests import module_not_found from MDAnalysisTests.datafiles import GRO from MDAnalysisTests.util import block_import +import MDAnalysis.analysis.distances + from numpy.testing import TestCase, assert_equal, dec import numpy as np + import warnings -from mock import Mock, patch import sys class TestContactMatrix(TestCase): - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def setUp(self): import MDAnalysis.analysis.distances self.coord = np.array([[1, 1, 1], @@ -87,17 +90,7 @@ def test_box_sparse(self): assert_equal(contacts.toarray(), self.res_pbc) class TestDist(TestCase): - '''Tests for MDAnalysis.analysis.distances.dist(). - Imports do not happen at the top level of the module - because of the scipy dependency.''' - - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") - def setUp(self): - import MDAnalysis.analysis.distances - import scipy - import scipy.spatial self.u = MDAnalysis.Universe(GRO) self.ag = self.u.atoms[:20] self.u2 = MDAnalysis.Universe(GRO) @@ -142,17 +135,7 @@ def test_mismatch_exception(self): MDAnalysis.analysis.distances.dist(self.ag[:19], self.ag2) class TestBetween(TestCase): - '''Tests for MDAnalysis.analysis.distances.between(). - Imports do not happen at the top level of the module - because of the scipy dependency.''' - - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") - def setUp(self): - import MDAnalysis.analysis.distances - import scipy - import scipy.spatial self.u = MDAnalysis.Universe(GRO) self.ag = self.u.atoms[:10] self.ag2 = self.u.atoms[12:33] @@ -190,41 +173,3 @@ def test_between_simple_case_indices_only(self): self.ag2, self.distance).indices) assert_equal(actual, self.expected) - -class TestImportWarnings(TestCase): - # see unit testing for warnings: - # http://stackoverflow.com/a/3892301 - - def setUp(self): - sys.modules.pop('MDAnalysis.analysis.distances', None) - - @block_import('scipy') - def test_warning_raised_no_scipy_module_level(self): - # an appropriate warning rather than an exception should be - # raised if scipy is absent when importing - # MDAnalysis.analysis.distances - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - import MDAnalysis.analysis.distances - assert issubclass(w[-1].category, ImportWarning) - - def test_silent_success_scipy_present_module_level(self): - # if scipy is present no module level ImportWarning should be - # raised when importing MDAnalysis.analysis.distances - mock = Mock() # mock presence of scipy - with patch.dict('sys.modules', {'scipy':mock}): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - import MDAnalysis.analysis.distances - assert w == [] - - @block_import('scipy') - def test_import_error_contact_matrix_no_scipy(self): - # contact_matrix should raise an ImportError if returntype is - # "sparse" and scipy is not available - with self.assertRaises(ImportError): - np.random.seed(321) - points = np.random.random_sample((10, 3)) - import MDAnalysis.analysis.distances - MDAnalysis.analysis.distances.contact_matrix(points, - returntype="sparse") diff --git a/testsuite/MDAnalysisTests/analysis/test_encore.py b/testsuite/MDAnalysisTests/analysis/test_encore.py index 884545cf94d..78bdfae9e27 100644 --- a/testsuite/MDAnalysisTests/analysis/test_encore.py +++ b/testsuite/MDAnalysisTests/analysis/test_encore.py @@ -128,7 +128,7 @@ def test_triangular_matrix(): multiplied_triangular_matrix_2 = triangular_matrix_2 * scalar assert_equal(multiplied_triangular_matrix_2[0,1], expected_value * scalar, - err_msg="Error in TriangularMatrix: multiplication by scalar gave\ + err_msg="Error in TriangularMatrix: multiplication by scalar gave\ inconsistent results") triangular_matrix_2 *= scalar @@ -299,8 +299,6 @@ def test_ces(self): assert_almost_equal(result_value, expected_value, decimal=2, err_msg="Unexpected value for Cluster Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, expected_value)) - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_dres_to_self(self): results, details = encore.dres([self.ens1, self.ens1]) result_value = results[0,1] @@ -308,8 +306,6 @@ def test_dres_to_self(self): assert_almost_equal(result_value, expected_value, decimal=2, err_msg="Dim. Reduction Ensemble Similarity to itself not zero: {0:f}".format(result_value)) - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_dres(self): results, details = encore.dres([self.ens1, self.ens2], selection="name CA and resnum 1-10") result_value = results[0,1] @@ -317,8 +313,6 @@ def test_dres(self): self.assertLess(result_value, upper_bound, msg="Unexpected value for Dim. reduction Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, upper_bound)) - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_dres_without_superimposition(self): distance_matrix = encore.get_distance_matrix( encore.merge_universes([self.ens1, self.ens2]), @@ -338,8 +332,6 @@ def test_ces_convergence(self): assert_almost_equal(ev, results[i], decimal=2, err_msg="Unexpected value for Clustering Ensemble similarity in convergence estimation") - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_dres_convergence(self): expected_values = [ 0.3, 0.] results = encore.dres_convergence(self.ens1, 10) @@ -399,8 +391,6 @@ def test_ces_error_estimation_ensemble_bootstrap(self): err_msg="Unexpected standard daviation for bootstrapped samples in Clustering Ensemble similarity") @dec.slow - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_dres_error_estimation(self): average_upper_bound = 0.3 stdev_upper_bound = 0.2 @@ -847,18 +837,9 @@ def _check_sklearn_import_warns(self, package): warnings.simplefilter('always') assert_warns(ImportWarning, importlib.import_module, package) - @block_import('scipy') - def _check_scipy_import_warns(self, package): - warnings.simplefilter('always') - assert_warns(ImportWarning, importlib.import_module, package) - def test_import_warnings(self): for pkg in ( 'MDAnalysis.analysis.encore.dimensionality_reduction.DimensionalityReductionMethod', 'MDAnalysis.analysis.encore.clustering.ClusteringMethod', ): yield self._check_sklearn_import_warns, pkg - for pkg in ( - 'MDAnalysis.analysis.encore.similarity', - ): - yield self._check_scipy_import_warns, pkg diff --git a/testsuite/MDAnalysisTests/analysis/test_hole.py b/testsuite/MDAnalysisTests/analysis/test_hole.py index e001b265d4f..22d891daca3 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hole.py +++ b/testsuite/MDAnalysisTests/analysis/test_hole.py @@ -32,6 +32,9 @@ assert_array_equal, assert_array_almost_equal, assert_) import numpy as np +import matplotlib +import mpl_toolkits.mplot3d + import nose from nose.plugins.attrib import attr @@ -150,27 +153,21 @@ def test_min_radius(self): @attr('slow') @dec.skipif(executable_not_found("hole"), msg="Test skipped because HOLE not found") - @dec.skipif(module_not_found("matplotlib")) def test_plot(self): - import matplotlib.axes ax = self.H.plot(label=True) assert_(isinstance(ax, matplotlib.axes.Axes), msg="H.plot() did not produce an Axes instance") @attr('slow') @dec.skipif(executable_not_found("hole"), msg="Test skipped because HOLE not found") - @dec.skipif(module_not_found("matplotlib")) def test_plot3D(self): - import mpl_toolkits.mplot3d ax = self.H.plot3D() assert_(isinstance(ax, mpl_toolkits.mplot3d.Axes3D), msg="H.plot3D() did not produce an Axes3D instance") @attr('slow') @dec.skipif(executable_not_found("hole"), msg="Test skipped because HOLE not found") - @dec.skipif(module_not_found("matplotlib")) def test_plot3D_rmax(self): - import mpl_toolkits.mplot3d ax = self.H.plot3D(rmax=2.5) assert_(isinstance(ax, mpl_toolkits.mplot3d.Axes3D), msg="H.plot3D(rmax=float) did not produce an Axes3D instance") diff --git a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py index 59ca9509e6a..49d3b80c040 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py +++ b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py @@ -142,7 +142,6 @@ def test_intermittent_excl(self): # For `solve` the test trajectories aren't long enough # So spoof the results and check that solver finds solution - @dec.skipif(module_not_found('scipy')) def test_solve_continuous(self): hbond = HBAC(self.u, hydrogens=self.H, @@ -168,7 +167,6 @@ def actual_function_cont(t): np.array([0.75, 0.5, 0.1]), ) - @dec.skipif(module_not_found('scipy')) def test_solve_intermittent(self): hbond = HBAC(self.u, hydrogens=self.H, @@ -248,7 +246,6 @@ def test_bond_type_VE(self): sample_time=0.06, ) - @dec.skipif(module_not_found('scipy')) def test_solve_before_run_VE(self): hbond = HBAC(self.u, hydrogens=self.H, diff --git a/testsuite/MDAnalysisTests/analysis/test_leaflet.py b/testsuite/MDAnalysisTests/analysis/test_leaflet.py index e83ec0fec69..3f358034f6e 100644 --- a/testsuite/MDAnalysisTests/analysis/test_leaflet.py +++ b/testsuite/MDAnalysisTests/analysis/test_leaflet.py @@ -29,8 +29,6 @@ from MDAnalysisTests.datafiles import Martini_membrane_gro class TestLeafletFinder(TestCase): - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def setUp(self): self.universe = MDAnalysis.Universe(Martini_membrane_gro, Martini_membrane_gro) self.lipid_heads = self.universe.select_atoms("name PO4") diff --git a/testsuite/MDAnalysisTests/analysis/test_pca.py b/testsuite/MDAnalysisTests/analysis/test_pca.py index e182d3d99a4..2d410365a35 100644 --- a/testsuite/MDAnalysisTests/analysis/test_pca.py +++ b/testsuite/MDAnalysisTests/analysis/test_pca.py @@ -87,8 +87,6 @@ def test_transform_universe(): pca_test.transform(u2) @staticmethod - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_cosine_content(): rand = MDAnalysis.Universe(RANDOM_WALK_TOPO, RANDOM_WALK) pca_random = pca.PCA(rand).run() diff --git a/testsuite/MDAnalysisTests/analysis/test_persistencelength.py b/testsuite/MDAnalysisTests/analysis/test_persistencelength.py index fc023ba95fc..4c72a8dee9c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_persistencelength.py +++ b/testsuite/MDAnalysisTests/analysis/test_persistencelength.py @@ -24,7 +24,10 @@ import MDAnalysis from MDAnalysis.analysis import polymer from MDAnalysis.exceptions import NoDataError + import numpy as np +import matplotlib + from numpy.testing import ( assert_, assert_almost_equal, @@ -61,8 +64,6 @@ def test_run(self): assert_(len(p.results) == 280) assert_almost_equal(p.lb, 1.485, 3) - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_fit(self): p = self._make_p() p.run() @@ -71,14 +72,9 @@ def test_fit(self): assert_almost_equal(p.lp, 6.504, 3) assert_(len(p.fit) == len(p.results)) - @dec.skipif(module_not_found('matplotlib'), - "Test skipped because matplotlib is not available.") - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_plot_ax_return(self): '''Ensure that a matplotlib axis object is returned when plot() is called.''' - import matplotlib p = self._make_p() p.run() p.perform_fit() @@ -104,14 +100,10 @@ def tearDown(self): del self.a_ref del self.y - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_fit_simple(self): a = polymer.fit_exponential_decay(self.x, self.y) assert_(a == self.a_ref) - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def test_fit_noisy(self): noise = np.sin(self.x) * 0.01 y2 = noise + self.y diff --git a/testsuite/MDAnalysisTests/analysis/test_psa.py b/testsuite/MDAnalysisTests/analysis/test_psa.py index f4c3b234873..e62621aa2db 100644 --- a/testsuite/MDAnalysisTests/analysis/test_psa.py +++ b/testsuite/MDAnalysisTests/analysis/test_psa.py @@ -28,6 +28,8 @@ assert_array_almost_equal, assert_, assert_almost_equal, assert_equal) import numpy as np +import scipy +import scipy.spatial from MDAnalysisTests.datafiles import PSF, DCD, DCD2 from MDAnalysisTests import parser_not_found, tempdir, module_not_found @@ -36,10 +38,6 @@ class TestPSAnalysis(TestCase): @dec.skipif(parser_not_found('DCD'), 'DCD parser not available. Are you using python 3?') - @dec.skipif(module_not_found('matplotlib'), - "Test skipped because matplotlib is not available.") - @dec.skipif(module_not_found('scipy'), - "Test skipped because scipy is not available.") def setUp(self): self.tmpdir = tempdir.TempDir() self.iu1 = np.triu_indices(3, k=1) @@ -187,9 +185,6 @@ class _BaseHausdorffDistance(TestCase): for various Hausdorff distance calculation properties.''' - @dec.skipif(module_not_found('scipy'), - 'scipy not available') - def setUp(self): self.random_angles = np.random.random((100,)) * np.pi * 2 self.random_columns = np.column_stack((self.random_angles, @@ -247,10 +242,9 @@ def setUp(self): class TestWeightedAvgHausdorffSymmetric(_BaseHausdorffDistance): '''Tests for weighted average and symmetric (undirected) Hausdorff distance between point sets in 3D.''' + def setUp(self): super(TestWeightedAvgHausdorffSymmetric, self).setUp() - import scipy - import scipy.spatial self.h = PSA.hausdorff_wavg self.distance_matrix = scipy.spatial.distance.cdist(self.path_1, self.path_2) @@ -270,10 +264,9 @@ def test_asymmetric_weight(self): class TestAvgHausdorffSymmetric(_BaseHausdorffDistance): '''Tests for unweighted average and symmetric (undirected) Hausdorff distance between point sets in 3D.''' + def setUp(self): super(TestAvgHausdorffSymmetric, self).setUp() - import scipy - import scipy.spatial self.h = PSA.hausdorff_avg self.distance_matrix = scipy.spatial.distance.cdist(self.path_1, self.path_2) From f2f45f87e51de55e683a108fd632e00f51442c44 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sat, 17 Jun 2017 13:38:24 +0200 Subject: [PATCH 4/4] minor clean ups --- .../MDAnalysis/analysis/hbonds/hbond_autocorrel.py | 1 + package/MDAnalysis/analysis/psa.py | 11 ++++++----- testsuite/MDAnalysisTests/analysis/test_encore.py | 12 ++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py index bf81e3529c5..296100addd8 100644 --- a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py +++ b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py @@ -423,6 +423,7 @@ def solve(self, p_guess=None): Initial guess for the leastsq fit, must match the shape of the expected coefficients + Continuous defition results are fitted to a double exponential with :func:`scipy.optimize.leastsq`, intermittent definition are fit to a triple exponential. diff --git a/package/MDAnalysis/analysis/psa.py b/package/MDAnalysis/analysis/psa.py index f7178d678c1..f4562ee4e36 100644 --- a/package/MDAnalysis/analysis/psa.py +++ b/package/MDAnalysis/analysis/psa.py @@ -409,26 +409,27 @@ def hausdorff(P, Q): still has to calculate the *symmetric* Hausdorff distance as `max(directed_hausdorff(P, Q)[0], directed_hausdorff(Q, P)[0])`. + References ---------- .. [Huttenlocher1993] D. P. Huttenlocher, G. A. Klanderman, and W. J. Rucklidge. Comparing images using the Hausdorff distance. IEEE Transactions on Pattern Analysis and Machine Intelligence, 15(9):850–863, 1993. - .. [Taha2015] A. A. Taha and A. Hanbury. An efficient algorithm for calculating the exact Hausdorff distance. IEEE Transactions On Pattern Analysis And Machine Intelligence, 37:2153-63, 2015. - SeeAlso - ------- + + See Also + -------- scipy.spatial.distance.directed_hausdorff """ N, axis = get_coord_axes(P) d = get_msd_matrix(P, Q, axis=axis) - return ( max( np.amax(np.amin(d, axis=0)), \ - np.amax(np.amin(d, axis=1)) ) / N )**0.5 + return (max(np.amax(np.amin(d, axis=0)), + np.amax(np.amin(d, axis=1))) / N)**0.5 def hausdorff_wavg(P, Q): diff --git a/testsuite/MDAnalysisTests/analysis/test_encore.py b/testsuite/MDAnalysisTests/analysis/test_encore.py index 78bdfae9e27..3348d2e289c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_encore.py +++ b/testsuite/MDAnalysisTests/analysis/test_encore.py @@ -118,18 +118,18 @@ def test_triangular_matrix(): incremented_triangular_matrix = triangular_matrix + scalar assert_equal(incremented_triangular_matrix[0,1], expected_value + scalar, - err_msg="Error in TriangularMatrix: addition of scalar gave\ -inconsistent results") + err_msg="Error in TriangularMatrix: addition of scalar gave" + "inconsistent results") triangular_matrix += scalar assert_equal(triangular_matrix[0,1], expected_value + scalar, - err_msg="Error in TriangularMatrix: addition of scalar gave\ -inconsistent results") + err_msg="Error in TriangularMatrix: addition of scalar gave" + "inconsistent results") multiplied_triangular_matrix_2 = triangular_matrix_2 * scalar assert_equal(multiplied_triangular_matrix_2[0,1], expected_value * scalar, - err_msg="Error in TriangularMatrix: multiplication by scalar gave\ -inconsistent results") + err_msg="Error in TriangularMatrix: multiplication by scalar gave" + "inconsistent results") triangular_matrix_2 *= scalar assert_equal(triangular_matrix_2[0,1], expected_value * scalar,