diff --git a/.circleci/config.yml b/.circleci/config.yml index 86cf420b..abae205e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: # The executor is the environment in which the steps below will be executed - below will use a python 3.8 container # Change the version below to your required version of python docker: - - image: cimg/python:3.9 + - image: cimg/python:3.10 # Checkout the code as the first step. This is a dedicated CircleCI step. # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default. # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt. diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6b4a62be..9054169e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,10 +12,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.11"] + python-version: ["3.10", "3.11", "3.12"] include: - - python-version: "3.9" - numpy-version: "1.26.4" + - python-version: "3.10" + numpy-version: "2.1.3" steps: - uses: actions/checkout@v2 @@ -29,9 +29,9 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install flake8 pytest - python -m pip install mdtraj + #python -m pip install mdtraj # removed as long as mdtraj is not supporting numpy2 python -m pip install . - if [ -f requirements.txt ]; then pip install --upgrade -r requirements.txt; fi + if [ -f requirements.txt ]; then pip install --upgrade -r requirements.txt; fi #- name: Lint with flake8 # run: | # # stop the build if there are Python syntax errors or undefined names diff --git a/pytim/__init__.py b/pytim/__init__.py index fb5701b5..367ffca4 100644 --- a/pytim/__init__.py +++ b/pytim/__init__.py @@ -10,9 +10,11 @@ from . import observables, utilities, datafiles from .version import __version__ import warnings -from .patches import patchNumpy, patchMDTRAJ_ReplacementTables -patchNumpy() -patchMDTRAJ_ReplacementTables() +try: # waiting for numpy2 support in mdtraj>=1.10.2 + from .patches import patchMDTRAJ_ReplacementTables + patchMDTRAJ_ReplacementTables() +except: + pass warnings.filterwarnings( "ignore", diff --git a/pytim/examples/example_mdtraj.py b/pytim/examples/example_mdtraj.py index 518c9f71..72e227b4 100644 --- a/pytim/examples/example_mdtraj.py +++ b/pytim/examples/example_mdtraj.py @@ -6,12 +6,14 @@ loaded with MDTraj (http://mdtraj.org/) (see also the openmm interoperability) """ +try: + import mdtraj + import pytim + from pytim.datafiles import WATER_GRO, WATER_XTC -import mdtraj -import pytim -from pytim.datafiles import WATER_GRO, WATER_XTC - -t = mdtraj.load_xtc(WATER_XTC, top=WATER_GRO) -inter = pytim.ITIM(t) -for step in t[:]: - print("surface atoms: "+repr(inter.atoms.indices)) + t = mdtraj.load_xtc(WATER_XTC, top=WATER_GRO) + inter = pytim.ITIM(t) + for step in t[:]: + print("surface atoms: "+repr(inter.atoms.indices)) +except: + pass # for package testing, in case mdtraj is not available or has compatibility issues diff --git a/pytim/interface.py b/pytim/interface.py index 770681f6..5fba19c9 100644 --- a/pytim/interface.py +++ b/pytim/interface.py @@ -541,7 +541,10 @@ def _(): >>> # mdtraj >>> try: + ... from packaging.version import Version ... import mdtraj + ... if Version(mdtraj.__version__) < Version('1.10.2'): # numpy2 support + ... pass ... try: ... import numpy as np ... import MDAnalysis as mda diff --git a/pytim/itim.py b/pytim/itim.py index 39a83258..663a2dd9 100644 --- a/pytim/itim.py +++ b/pytim/itim.py @@ -132,11 +132,14 @@ class ITIM(Interface): >>> # pytim can be used also on top of mdtraj (MDAnalysis must be present,though) - >>> import mdtraj - >>> import pytim - >>> from pytim.datafiles import WATER_GRO, WATER_XTC - >>> t = mdtraj.load_xtc(WATER_XTC,top=WATER_GRO) - >>> inter = pytim.ITIM(t) + >>> try: + ... import mdtraj + ... import pytim + ... from pytim.datafiles import WATER_GRO, WATER_XTC + ... t = mdtraj.load_xtc(WATER_XTC,top=WATER_GRO) + ... inter = pytim.ITIM(t) + ... except (ModuleNotFoundError,ValueError): # ValueError to handle mdtraj not supporting numpy2 + ... pass .. _MDAnalysis: http://www.mdanalysis.org/ diff --git a/pytim/observables/basic_observables.py b/pytim/observables/basic_observables.py index c083a04f..0cbfc83c 100644 --- a/pytim/observables/basic_observables.py +++ b/pytim/observables/basic_observables.py @@ -159,12 +159,12 @@ class Distance(Observable): >>> u = mda.Universe(pytim.datafiles.WATER_GRO) >>> d1 = pytim.observables.Distance().compute(u.atoms[:9],u.atoms[:9]) >>> d2 = pytim.observables.RelativePosition(spherical=True).compute(u.atoms[:9],u.atoms[:9])[:,0] - >>> np.all(np.isclose(d1,d2)) + >>> all(np.isclose(d1,d2)) True >>> d1 = pytim.observables.Distance('xy').compute(u.atoms[:9],u.atoms[:9]) >>> d2 = pytim.observables.RelativePosition('xy',spherical=True).compute(u.atoms[:9],u.atoms[:9])[:,0] - >>> np.all(np.isclose(d1,d2)) + >>> all(np.isclose(d1,d2)) True """ diff --git a/pytim/observables/contactangle.py b/pytim/observables/contactangle.py index 0bbbebfb..cbcf87eb 100644 --- a/pytim/observables/contactangle.py +++ b/pytim/observables/contactangle.py @@ -52,24 +52,24 @@ class ContactAngle(object): >>> for ts in u.trajectory[::]: ... CA.sample() >>> # Instantaneous contact angle (last frame) by fitting a circle... - >>> np.round(CA.contact_angle,2) + >>> print(np.round(CA.contact_angle,2)) 90.58 >>> >>> # ... and using an elliptical fit: >>> left, right = CA.contact_angles >>> # left angle - >>> np.round(np.abs(left),2) + >>> print(np.round(np.abs(left),2)) 79.95 >>> # right angle - >>> np.round(right,2) + >>> print(np.round(right,2)) 83.84 >>> # Contact angles from the averaged binned statistics of >>> # surface atoms' radial distance as a function of the azimuthal angle - >>> list(np.round(CA.mean_contact_angles,2)) - [96.2, 100.68] + >>> np.around(CA.mean_contact_angles,1).tolist() + [96.2, 100.7] """ diff --git a/pytim/observables/correlator.py b/pytim/observables/correlator.py index 47ef3ab3..91c12ed2 100644 --- a/pytim/observables/correlator.py +++ b/pytim/observables/correlator.py @@ -138,7 +138,7 @@ def sample(self, group): RuntimeError( 'Cannot compute survival probability without a reference') sampled = self.observable.compute(group) - self.timeseries.append(list(sampled.flatten())) + self.timeseries.append(sampled.flatten().tolist()) if self.shape is None: self.shape = sampled.shape @@ -149,13 +149,13 @@ def _sample_intermittent(self, group): # the residence function (1 if in the reference group, 0 otherwise) mask = np.isin(self.reference, group) # append the residence function to its timeseries - self.maskseries.append(list(mask)) + self.maskseries.append(mask.tolist()) if self.observable is not None: # this copies a vector of zeros with the correct shape sampled = self.reference_obs.copy() obs = self.observable.compute(group) sampled[np.where(mask)] = obs - self.timeseries.append(list(sampled.flatten())) + self.timeseries.append(sampled.flatten().tolist()) else: self.timeseries = self.maskseries if self.shape is None: @@ -249,7 +249,7 @@ def correlation(self, normalized=True, continuous=True): >>> print (np.allclose(corr, [ c0, c1, c2, c3])) True >>> # check normalization - >>> np.all(vv.correlation(continuous=False) == corr/corr[0]) + >>> print(np.all(vv.correlation(continuous=False) == corr/corr[0])) True >>> # not normalizd, continuous >>> corr = vv.correlation(normalized=False,continuous=True) @@ -258,7 +258,7 @@ def correlation(self, normalized=True, continuous=True): >>> print (np.allclose(corr, [ c0, c1, c2, c3])) True >>> # check normalization - >>> np.all(vv.correlation(continuous=True) == corr/corr[0]) + >>> print(np.all(vv.correlation(continuous=True) == corr/corr[0])) True """ diff --git a/pytim/observables/distributionfunction.py b/pytim/observables/distributionfunction.py index d6b2a1bb..174ee806 100644 --- a/pytim/observables/distributionfunction.py +++ b/pytim/observables/distributionfunction.py @@ -304,7 +304,7 @@ def sample(self, g1=None, g2=None, kargs1=None, kargs2=None): self.count += count box = self.universe.dimensions - self.volume += np.product(box[:3]) + self.volume += np.prod(box[:3]) if self.g2 is None or len(self.g2) == 0: self.n_normalize += len(self.g1) else: diff --git a/pytim/observables/free_volume.py b/pytim/observables/free_volume.py index 484b0930..ae9093d4 100644 --- a/pytim/observables/free_volume.py +++ b/pytim/observables/free_volume.py @@ -48,11 +48,11 @@ class FreeVolume(object): >>> FV = pytim.observables.FreeVolume(u,npoints = nsamples) >>> np.random.seed(1) # ensure reproducibility of test >>> free, err = FV.compute() - >>> np.isclose(free,1.0-0.6802,rtol=1e-3) + >>> print(np.isclose(free,1.0-0.6802,rtol=1e-3)) True >>> np.random.seed(1) # ensure reproducibility of test >>> lst, _ = FV._compute() - >>> np.isclose(free,1.0-len(lst)*1.0/nsamples, rtol=1e-6) + >>> print(np.isclose(free,1.0-len(lst)*1.0/nsamples, rtol=1e-6)) True """ diff --git a/pytim/observables/orientation.py b/pytim/observables/orientation.py index 37b514ce..1af0a6c3 100644 --- a/pytim/observables/orientation.py +++ b/pytim/observables/orientation.py @@ -109,7 +109,7 @@ def compute(self, inp, **kargs): >>> condition = np.logical_and(u.atoms.sides==0,u.atoms.layers==1) >>> group = u.atoms[condition] >>> costheta, phi = biv.compute(group) - >>> np.all(np.isclose([costheta[0],phi[0]], [0.6533759236335754, 0.10778185716460659])) + >>> print(all(np.isclose([costheta[0],phi[0]], [0.6533759236335754, 0.10778185716460659]))) True """ diff --git a/pytim/observables/profile.py b/pytim/observables/profile.py index 6451771a..2e7bddc9 100644 --- a/pytim/observables/profile.py +++ b/pytim/observables/profile.py @@ -311,18 +311,19 @@ def _(): >>> inter = pytim.ITIM(u,cluster_cut=3.5,alpha=2.5) >>> print(inter.normal) 2 - >>> np.set_printoptions(precision=8) >>> np.random.seed(1) # for the MC normalization >>> stdprof = pytim.observables.Profile() >>> stdprof.sample(u.atoms) - >>> print(stdprof.get_values(binwidth=0.5)[2][:6]) - [0.09229169 0.10959639 0.08075523 0.10959639 0.09805993 0.09805993] + >>> vals = stdprof.get_values(binwidth=0.5)[2] + >>> print(np.around(vals[:6],decimals=3)) + [0.092 0.11 0.081 0.11 0.098 0.098] + >>> prof = pytim.observables.Profile(interface=inter) >>> prof.sample(u.atoms) >>> vals = prof.get_values(binwidth=0.5)[2] - >>> print(vals[len(vals)//2-3:len(vals)//2+3]) - [0.07344066 0.04300743 0.02803522 inf 0. 0. ] + >>> print(np.around(vals[len(vals)//2-3:len(vals)//2+3],decimals=3)) + [0.073 0.043 0.028 inf 0. 0. ] @@ -353,15 +354,15 @@ def _(): >>> np.random.seed(1) # for the MC normalization >>> stdprof = pytim.observables.Profile() >>> stdprof.sample(u.atoms) - >>> print(stdprof.get_values(binwidth=0.5)[2][:6]) - [0.09229169 0.10959639 0.08075523 0.10959639 0.09805993 0.09805993] + >>> vals = stdprof.get_values(binwidth=0.5)[2] + >>> print(np.around(vals[:6],decimals=3)) + [0.092 0.11 0.081 0.11 0.098 0.098] >>> prof = pytim.observables.Profile(interface=inter) >>> prof.sample(u.atoms) >>> vals = prof.get_values(binwidth=1.0)[2] - >>> print(vals[len(vals)//2-4:len(vals)//2+2]) - [0.09554818 0.09796541 0.05555127 0. inf 0. ] - + >>> print(np.around(vals[len(vals)//2-4:len(vals)//2+2],decimals=3)) + [0.096 0.098 0.056 0. inf 0. ] """ diff --git a/pytim/patches.py b/pytim/patches.py index 33ff42cd..feffbb1a 100644 --- a/pytim/patches.py +++ b/pytim/patches.py @@ -2,25 +2,14 @@ # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 from __future__ import print_function - -def patchNumpy(): - # this try/except block patches numpy and provides _validate_lengths - # to skimage<=1.14.1 - import numpy - try: - numpy.lib.arraypad._validate_lengths - except AttributeError: - def patch_validate_lengths(ar, crop_width): - return numpy.lib.arraypad._as_pairs(crop_width, ar.ndim, as_index=True) - numpy.lib.arraypad._validate_lengths = patch_validate_lengths - - def patchTrajectory(trajectory, interface): """ Patch the MDAnalysis trajectory class this patch makes the layer assignement being automatically called whenever a new frame is loaded. """ + from importlib.metadata import version + if int(version('numpy').split('.')[0])<2 : return try: trajectory.interface trajectory.interface = interface @@ -110,11 +99,14 @@ def patchMDTRAJ(trajectory, universe): Example: - >>> import mdtraj - >>> import pytim - >>> from pytim.datafiles import WATER_GRO, WATER_XTC - >>> t = mdtraj.load_xtc(WATER_XTC,top=WATER_GRO) - >>> inter = pytim.ITIM(t) + >>> try: + ... import mdtraj + ... import pytim + ... from pytim.datafiles import WATER_GRO, WATER_XTC + ... t = mdtraj.load_xtc(WATER_XTC,top=WATER_GRO) + ... inter = pytim.ITIM(t) + ... except: + ... pass """ diff --git a/pytim/sanity_check.py b/pytim/sanity_check.py index bda1c1a2..8bef1f72 100644 --- a/pytim/sanity_check.py +++ b/pytim/sanity_check.py @@ -77,7 +77,7 @@ def _check_universe(self, input_obj): patchMDTRAJ(input_obj, self.interface.universe) os.remove(_file.name) return 'mdtraj' - except ImportError: + except (ImportError,ValueError): # ValueError to handle mdtraj not supporting numpy2 pass try: import os diff --git a/pytim/utilities_dbscan.py b/pytim/utilities_dbscan.py index 13d12b85..ca679322 100644 --- a/pytim/utilities_dbscan.py +++ b/pytim/utilities_dbscan.py @@ -104,7 +104,7 @@ def _(): >>> print (np.sort(c2)[-2:]) [ 0 9335] - >>> print ((np.all(c1==c2), np.all(l1==l2))) + >>> print ((all(c1==c2), all(l1==l2))) (True, True) """ diff --git a/pytim/utilities_geometry.py b/pytim/utilities_geometry.py index 05f23790..9e1a5eaf 100644 --- a/pytim/utilities_geometry.py +++ b/pytim/utilities_geometry.py @@ -62,12 +62,12 @@ def polygonalArea(points): >>> s1 = np.sin(2*np.pi/5.) ; s2 = np.sin(4*np.pi/5.) >>> pentagon = np.array([[1,0,0],[c1,s1,0],[-c2,s2,0],[-c2,-s2,0],[c1,-s1,0]]) >>> A = 0.25 * np.sqrt(25+10*np.sqrt(5)) * 100./ (50+10*np.sqrt(5)) - >>> np.isclose(pytim.utilities.polygonalArea(pentagon),A) + >>> print(np.isclose(pytim.utilities.polygonalArea(pentagon),A)) True >>> # now let's rotate it: >>> rotated = np.dot(EulerRotation(0,np.pi/2.,0),pentagon.T).T - >>> np.isclose(pytim.utilities.polygonalArea(rotated),A) + >>> print(np.isclose(pytim.utilities.polygonalArea(rotated),A)) True """ diff --git a/requirements.testing.txt b/requirements.testing.txt index 07a4967f..6108f31b 100644 --- a/requirements.testing.txt +++ b/requirements.testing.txt @@ -1,4 +1,4 @@ -mdtraj pytest==8.1.1 codecov==2.1.13 pytest-cov==5.0.0 +packaging diff --git a/requirements.txt b/requirements.txt index 94b5eb6f..3c576a42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ cython>=0.24.1 -numpy>=1.26.4,<2.0.0 +numpy>=2.1.3 scipy>=1.6.0 gsd>3.0.0 -MDAnalysis>=2.7.0 +setuptools +MDAnalysis>=2.8.0 PyWavelets>=0.5.2 -scikit-image>=0.14.2 +scikit-image>=0.24.0 sphinx>=1.4.3 matplotlib pytest diff --git a/setup.py b/setup.py index 44f8aea1..d024af6d 100644 --- a/setup.py +++ b/setup.py @@ -125,14 +125,14 @@ def run_tests(self): # requirements files see: # https://packaging.python.org/en/latest/requirements.html install_requires=[ - 'numpy>=1.26.4,<2.0.0', 'cython>=0.24.1','gsd>=3.0.0','MDAnalysis>=2.7.0' + 'numpy>=2.1.3', 'cython>=0.24.1','gsd>=3.0.0','MDAnalysis>=2.8.0' ], # List additional groups of dependencies here (e.g. development # dependencies). You can install these using the following syntax, # for example: # $ pip install -e .[dev,test] - tests_require=['nose>=1.3.7', 'coverage'], + tests_require=['nose>=1.3.7', 'coverage', 'scikit-image'], # If there are data files included in your packages that need to be # installed, specify them here. If using Python 2.6 or less, then these # have to be included in MANIFEST.in as well.