From 4f891ed3624c71158f77a0be9c04ccd2c5d2e166 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Tue, 18 Jun 2024 05:36:12 -0600 Subject: [PATCH 1/5] BLD: NumPy 2 compat for wheel builds * Fixes #4619 * We should build against NumPy `2.0.0` so that we're backwards compatible to NumPy `1.x` while still supporting 2.x series. This is simpler to maintain as well. In theory, should support as far back as NumPy `1.19` if we needed it. --- package/pyproject.toml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/package/pyproject.toml b/package/pyproject.toml index d1e8e741c5a..1880d88fd0d 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -3,15 +3,13 @@ requires = [ "Cython>=0.28", "packaging", - # lowest NumPy we can use for a given Python, - # In part adapted from: https://github.com/scipy/oldest-supported-numpy/blob/main/setup.cfg - # As per NEP29, we set the minimum version to 1.23.2 for Python <=3.11 - # and 1.26.0 (first to support) for Python 3.12 - "numpy==1.23.2; python_version<='3.11' and platform_python_implementation != 'PyPy'", - "numpy==1.26.0; python_version=='3.12' and platform_python_implementation != 'PyPy'", - # For unreleased versions of Python there is currently no known supported - # NumPy version. In that case we just let it be a bare NumPy install - "numpy<2.0; python_version>='3.13'", + # numpy requirement for wheel builds for distribution on PyPI - building + # against 2.x yields wheels that are also compatible with numpy 1.x at + # runtime. + # Note that building against numpy 1.x works fine too - users and + # redistributors can do this by installing the numpy version they like and + # disabling build isolation. + "numpy>=2.0.0", # Set to minimum version of setuptools that allows pyproject.toml "setuptools >= 40.9.0", "wheel", From 9e9d25fd0cd8e10dbaa0569da97a61227c342ebb Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Wed, 19 Jun 2024 16:05:29 -0600 Subject: [PATCH 2/5] MAINT, TST: PR 4620 revisions * Add NumPy 2-related shims aimed at avoiding control flow that hits `RDKit` or `parmed`, until they have releases supporting NumPy 2. This mostly means skipping a bunch of tests that use those packages for now. --- package/MDAnalysis/converters/RDKit.py | 10 ++++++++-- .../MDAnalysisTests/converters/test_parmed.py | 10 +++++++++- .../MDAnalysisTests/converters/test_rdkit.py | 3 +++ .../converters/test_rdkit_parser.py | 11 +++++++++-- .../MDAnalysisTests/core/test_atomselections.py | 16 +++++++++++++--- testsuite/MDAnalysisTests/core/test_universe.py | 6 +++++- testsuite/MDAnalysisTests/util.py | 7 +++++++ 7 files changed, 54 insertions(+), 9 deletions(-) diff --git a/package/MDAnalysis/converters/RDKit.py b/package/MDAnalysis/converters/RDKit.py index 139528440ab..da52e23b915 100644 --- a/package/MDAnalysis/converters/RDKit.py +++ b/package/MDAnalysis/converters/RDKit.py @@ -87,6 +87,7 @@ from io import StringIO import numpy as np +from numpy.lib import NumpyVersion from . import base from ..coordinates import memory @@ -95,8 +96,13 @@ from ..exceptions import NoDataError try: - from rdkit import Chem - from rdkit.Chem import AllChem + # TODO: remove this guard when RDKit has a release + # that supports NumPy 2 + if NumpyVersion(np.__version__) < "2.0.0": + from rdkit import Chem + from rdkit.Chem import AllChem + else: + raise ImportError except ImportError: pass else: diff --git a/testsuite/MDAnalysisTests/converters/test_parmed.py b/testsuite/MDAnalysisTests/converters/test_parmed.py index 38ef20caaab..46eb9cfad75 100644 --- a/testsuite/MDAnalysisTests/converters/test_parmed.py +++ b/testsuite/MDAnalysisTests/converters/test_parmed.py @@ -23,7 +23,9 @@ import pytest import MDAnalysis as mda +import numpy as np from numpy.testing import (assert_allclose, assert_equal) +from numpy.lib import NumpyVersion from MDAnalysisTests.coordinates.base import _SingleFrameReader from MDAnalysisTests.coordinates.reference import RefAdKSmall @@ -41,7 +43,13 @@ PRM_UreyBradley, ) -pmd = pytest.importorskip('parmed') +# TODO: remove this guard when parmed has a release +# that support NumPy 2 +if NumpyVersion(np.__version__) < "2.0.0": + pmd = pytest.importorskip('parmed') +else: + pmd = pytest.importorskip('parmed_skip_with_numpy2') + class TestParmEdReaderGRO: diff --git a/testsuite/MDAnalysisTests/converters/test_rdkit.py b/testsuite/MDAnalysisTests/converters/test_rdkit.py index 85860162f7b..a75ea606e0e 100644 --- a/testsuite/MDAnalysisTests/converters/test_rdkit.py +++ b/testsuite/MDAnalysisTests/converters/test_rdkit.py @@ -27,6 +27,7 @@ import MDAnalysis as mda import numpy as np +from numpy.lib import NumpyVersion import pytest from MDAnalysis.topology.guessers import guess_atom_element from MDAnalysisTests.datafiles import GRO, PDB_full, PDB_helix, mol2_molecule @@ -55,6 +56,8 @@ reason="only for min dependencies build") class TestRequiresRDKit(object): def test_converter_requires_rdkit(self): + if NumpyVersion(np.__version__) >= "2.0.0": + pytest.skip("RDKit not compatible with NumPy 2") u = mda.Universe(PDB_full) with pytest.raises(ImportError, match="RDKit is required for the RDKitConverter"): diff --git a/testsuite/MDAnalysisTests/converters/test_rdkit_parser.py b/testsuite/MDAnalysisTests/converters/test_rdkit_parser.py index 86d52fe2a0c..c9b3bdcd8f2 100644 --- a/testsuite/MDAnalysisTests/converters/test_rdkit_parser.py +++ b/testsuite/MDAnalysisTests/converters/test_rdkit_parser.py @@ -24,14 +24,21 @@ import warnings import pytest import numpy as np +from numpy.lib import NumpyVersion from numpy.testing import assert_equal import MDAnalysis as mda from MDAnalysisTests.topology.base import ParserBase from MDAnalysisTests.datafiles import mol2_molecule, PDB_helix, SDF_molecule -Chem = pytest.importorskip('rdkit.Chem') -AllChem = pytest.importorskip('rdkit.Chem.AllChem') +# TODO: remove these shims when RDKit +# has a release supporting NumPy 2 +if NumpyVersion(np.__version__) < "2.0.0": + Chem = pytest.importorskip('rdkit.Chem') + AllChem = pytest.importorskip('rdkit.Chem.AllChem') +else: + Chem = pytest.importorskip("RDKit_does_not_support_NumPy_2") + AllChem = pytest.importorskip("RDKit_does_not_support_NumPy_2") class RDKitParserBase(ParserBase): parser = mda.converters.RDKitParser.RDKitParser diff --git a/testsuite/MDAnalysisTests/core/test_atomselections.py b/testsuite/MDAnalysisTests/core/test_atomselections.py index 6915e6a95d1..7db3611282f 100644 --- a/testsuite/MDAnalysisTests/core/test_atomselections.py +++ b/testsuite/MDAnalysisTests/core/test_atomselections.py @@ -25,6 +25,7 @@ from io import StringIO import itertools import numpy as np +from numpy.lib import NumpyVersion from numpy.testing import( assert_equal, ) @@ -539,7 +540,10 @@ def test_molnum(self, universe, selection_string, reference): class TestSelectionRDKit(object): def setup_class(self): - pytest.importorskip("rdkit.Chem") + if NumpyVersion(np.__version__) < "2.0.0": + pytest.importorskip("rdkit.Chem") + else: + pytest.skip("RDKit does not support NumPy 2") @pytest.fixture def u(self): @@ -1413,13 +1417,19 @@ def test_negative_resid(): ("aromaticity False", 15), ]) def test_bool_sel(selstr, n_atoms): - pytest.importorskip("rdkit.Chem") + if NumpyVersion(np.__version__) >= "2.0.0": + pytest.skip("RDKit does not support NumPy 2") + else: + pytest.importorskip("rdkit.Chem") u = MDAnalysis.Universe.from_smiles("Nc1cc(C[C@H]([O-])C=O)c[nH]1") assert len(u.select_atoms(selstr)) == n_atoms def test_bool_sel_error(): - pytest.importorskip("rdkit.Chem") + if NumpyVersion(np.__version__) >= "2.0.0": + pytest.skip("RDKit does not support NumPy 2") + else: + pytest.importorskip("rdkit.Chem") u = MDAnalysis.Universe.from_smiles("Nc1cc(C[C@H]([O-])C=O)c[nH]1") with pytest.raises(SelectionError, match="'fragrant' is an invalid value"): u.select_atoms("aromaticity fragrant") diff --git a/testsuite/MDAnalysisTests/core/test_universe.py b/testsuite/MDAnalysisTests/core/test_universe.py index ac04457ab31..10509b98139 100644 --- a/testsuite/MDAnalysisTests/core/test_universe.py +++ b/testsuite/MDAnalysisTests/core/test_universe.py @@ -30,6 +30,7 @@ import warnings import numpy as np +from numpy.lib import NumpyVersion from numpy.testing import ( assert_allclose, assert_almost_equal, @@ -224,7 +225,10 @@ def test_universe_empty_ROMol(self): class TestUniverseFromSmiles(object): def setup_class(self): - pytest.importorskip("rdkit.Chem") + if NumpyVersion(np.__version__) < "2.0.0": + pytest.importorskip("rdkit.Chem") + else: + pytest.importorskip("RDKit_does_not_support_NumPy_2") def test_default(self): smi = "CN1C=NC2=C1C(=O)N(C(=O)N2C)C" diff --git a/testsuite/MDAnalysisTests/util.py b/testsuite/MDAnalysisTests/util.py index f5e8f1caefb..8438a95bdbf 100644 --- a/testsuite/MDAnalysisTests/util.py +++ b/testsuite/MDAnalysisTests/util.py @@ -39,6 +39,8 @@ import pytest from numpy.testing import assert_warns +import numpy as np +from numpy.lib import NumpyVersion def block_import(package): @@ -112,6 +114,11 @@ def import_not_available(module_name): msg="skip test as module_name could not be imported") """ + # TODO: remove once these packages have a release + # with NumPy 2 support + if NumpyVersion(np.__version__) >= "2.0.0": + if module_name in {"rdkit", "parmed"}: + return True try: test = importlib.import_module(module_name) except ImportError: From bb0de7141c1cd86b5b88bb41221e20db2da45e40 Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Sat, 22 Jun 2024 14:38:44 +0100 Subject: [PATCH 3/5] Update ParmEd.py --- package/MDAnalysis/converters/ParmEd.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/converters/ParmEd.py b/package/MDAnalysis/converters/ParmEd.py index b98e83ab7c2..e31bf029584 100644 --- a/package/MDAnalysis/converters/ParmEd.py +++ b/package/MDAnalysis/converters/ParmEd.py @@ -81,6 +81,8 @@ import itertools import warnings +from numpy.lib import NumpyVersion + from . import base from ..coordinates.base import SingleFrameReaderBase from ..topology.tables import SYMB2Z @@ -168,11 +170,20 @@ def convert(self, obj): obj : AtomGroup or Universe or :class:`Timestep` """ try: - import parmed as pmd + # TODO: remove this guard when parmed has a release + # that supports NumPy 2 + if NumpyVersion(np.__version__) < "2.0.0": + import parmed as pmd + else: + raise ImportError except ImportError: - raise ImportError('ParmEd is required for ParmEdConverter but ' - 'is not installed. Try installing it with \n' - 'pip install parmed') + if NumpyVersion(np.__version__) >= "2.0.0": + ermsg = "ParmEd is not compatible with NumPy 2.0+" + else: + ermsg = ("ParmEd is required for ParmEdConverter but is not " + "installed. Try installing it with \n" + "pip install parmed") + raise ImportError(errmsg) try: # make sure to use atoms (Issue 46) ag_or_ts = obj.atoms From 3cb32c254997983fe7ab1758b1e09b535a46a54b Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Sat, 22 Jun 2024 14:42:11 +0100 Subject: [PATCH 4/5] Update CHANGELOG --- package/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/CHANGELOG b/package/CHANGELOG index 7e1b56a2775..876a0541de2 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -56,6 +56,8 @@ Enhancements DOI 10.1021/acs.jpcb.7b11988. (Issue #2039, PR #4524) Changes + * MDAnalysis now builds against numpy 2.0 rather than the + minimum supported numpy version (PR #4620) * As per SPEC0 the minimum supported Python version has been raised to 3.10 (PR #4502) * MDAnalysis.analysis.hole2 is now directly imported from the mdakit From 7fd4925a4ac9a50c710bcbc5c046275c18684272 Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Sat, 22 Jun 2024 14:59:31 +0100 Subject: [PATCH 5/5] Update ParmEd.py --- package/MDAnalysis/converters/ParmEd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/MDAnalysis/converters/ParmEd.py b/package/MDAnalysis/converters/ParmEd.py index e31bf029584..174dc9fad3b 100644 --- a/package/MDAnalysis/converters/ParmEd.py +++ b/package/MDAnalysis/converters/ParmEd.py @@ -81,6 +81,7 @@ import itertools import warnings +import numpy as np from numpy.lib import NumpyVersion from . import base