Skip to content

Commit

Permalink
BLD: NumPy 2 compat for wheel builds (#4620)
Browse files Browse the repository at this point in the history
* 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.

* 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.

* Update ParmEd.py

* Update CHANGELOG

* Update ParmEd.py

---------

Co-authored-by: Irfan Alibay <[email protected]>
  • Loading branch information
tylerjereddy and IAlibay authored Jun 22, 2024
1 parent 8dae855 commit cfa4438
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 22 deletions.
2 changes: 2 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions package/MDAnalysis/converters/ParmEd.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
import itertools
import warnings

import numpy as np
from numpy.lib import NumpyVersion

from . import base
from ..coordinates.base import SingleFrameReaderBase
from ..topology.tables import SYMB2Z
Expand Down Expand Up @@ -168,11 +171,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
Expand Down
10 changes: 8 additions & 2 deletions package/MDAnalysis/converters/RDKit.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
from io import StringIO

import numpy as np
from numpy.lib import NumpyVersion

from . import base
from ..coordinates import memory
Expand All @@ -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:
Expand Down
16 changes: 7 additions & 9 deletions package/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 9 additions & 1 deletion testsuite/MDAnalysisTests/converters/test_parmed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions testsuite/MDAnalysisTests/converters/test_rdkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"):
Expand Down
11 changes: 9 additions & 2 deletions testsuite/MDAnalysisTests/converters/test_rdkit_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 13 additions & 3 deletions testsuite/MDAnalysisTests/core/test_atomselections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand Down
6 changes: 5 additions & 1 deletion testsuite/MDAnalysisTests/core/test_universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import warnings

import numpy as np
from numpy.lib import NumpyVersion
from numpy.testing import (
assert_allclose,
assert_almost_equal,
Expand Down Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions testsuite/MDAnalysisTests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit cfa4438

Please sign in to comment.