Skip to content

Commit

Permalink
Merge pull request #3955 from keewis/numpy-2.0
Browse files Browse the repository at this point in the history
compatibility with `numpy>=2.0`
  • Loading branch information
Zac-HD committed Apr 26, 2024
2 parents 5578efc + f8e048e commit eccba85
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Expand Up @@ -77,6 +77,7 @@ jobs:
# - check-crosshair-nocover
# - check-crosshair-niche
- check-py38-oldestnumpy
- check-numpy-nightly
fail-fast: false
steps:
- uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.rst
Expand Up @@ -96,6 +96,7 @@ their individual contributions.
* `Jonty Wareing <https://www.github.com/Jonty>`_ (jonty@jonty.co.uk)
* `Joshua Boone <https://www.github.com/patchedwork>`_ (joshuaboone4190@gmail.com)
* `jmhsi <https://www.github.com/jmhsi>`_
* `Justus Magin <https://github.com/keewis>`_
* `jwg4 <https://www.github.com/jwg4>`_
* `Kai Chen <https://www.github.com/kx-chen>`_ (kaichen120@gmail.com)
* `Karthikeyan Singaravelan <https://www.github.com/tirkarthi>`_ (tir.karthi@gmail.com)
Expand Down
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

Explicitly cast :obj:`numpy.finfo.smallest_normal` to builtin `float` in
preparation for the :pypi:`numpy==2.0 <numpy>` release (:issue:`3950`)
4 changes: 2 additions & 2 deletions hypothesis-python/src/hypothesis/extra/array_api.py
Expand Up @@ -282,7 +282,7 @@ def check_valid_minmax(prefix, val, info_obj):
if allow_subnormal is not None:
kw["allow_subnormal"] = allow_subnormal
else:
subnormal = next_down(finfo.smallest_normal, width=finfo.bits)
subnormal = next_down(float(finfo.smallest_normal), width=finfo.bits)
ftz = bool(xp.asarray(subnormal, dtype=dtype) == 0)
if ftz:
kw["allow_subnormal"] = False
Expand All @@ -303,7 +303,7 @@ def check_valid_minmax(prefix, val, info_obj):
# complex array, in case complex arrays have different FTZ behaviour
# than arrays of the respective composite float.
if allow_subnormal is None:
subnormal = next_down(finfo.smallest_normal, width=finfo.bits)
subnormal = next_down(float(finfo.smallest_normal), width=finfo.bits)
x = xp.asarray(complex(subnormal, subnormal), dtype=dtype)
builtin_x = complex(x)
allow_subnormal = builtin_x.real != 0 and builtin_x.imag != 0
Expand Down
Expand Up @@ -908,9 +908,9 @@ def _collection_ish_functions():
np.diag,
# bonus undocumented functions from tab-completion:
np.asarray_chkfinite,
np.asfarray,
np.asfortranarray,
]

return funcs


Expand Down
3 changes: 2 additions & 1 deletion hypothesis-python/tests/array_api/conftest.py
Expand Up @@ -35,7 +35,8 @@
f"HYPOTHESIS_TEST_ARRAY_API_VERSION='{test_version_option}' is not "
f"'default' or a valid api_version {NOMINAL_VERSIONS}."
)
with pytest.warns(HypothesisWarning):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=HypothesisWarning)
mock_version = "draft" if test_version_option == "default" else test_version_option
mock_xps = make_strategies_namespace(mock_xp, api_version=mock_version)
api_version = None if test_version_option == "default" else test_version_option
Expand Down
21 changes: 21 additions & 0 deletions hypothesis-python/tests/array_api/test_arrays.py
Expand Up @@ -8,6 +8,8 @@
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.

import sys

import pytest

from hypothesis import given, settings, strategies as st
Expand Down Expand Up @@ -296,6 +298,25 @@ def test_may_not_fill_unique_array_with_non_nan(xp, xps):
check_can_generate_examples(strat)


@pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason="no complex")
def test_floating_point_array():
import warnings
from hypothesis.extra.array_api import make_strategies_namespace

try:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import numpy.array_api as nxp
except ModuleNotFoundError:
import numpy as nxp
xps = make_strategies_namespace(nxp)
dtypes = xps.floating_dtypes() | xps.complex_dtypes()

strat = xps.arrays(dtype=dtypes, shape=10)

check_can_generate_examples(strat)


@pytest.mark.parametrize(
"kwargs",
[
Expand Down
65 changes: 62 additions & 3 deletions hypothesis-python/tests/array_api/test_partial_adoptors.py
Expand Up @@ -11,6 +11,8 @@
from copy import copy
from types import SimpleNamespace
from typing import Tuple
import functools
import warnings

import pytest

Expand All @@ -31,17 +33,73 @@
MOCK_WARN_MSG = f"determine.*{mock_xp.__name__}.*Array API"


def make_mock_xp(*, exclude: Tuple[str, ...] = ()) -> SimpleNamespace:
class MockedArray:
def __init__(self, wrapped, *, exclude=()):
self.wrapped = wrapped
self.exclude = exclude

def __getattr__(self, name):
if name in self.exclude:
raise AttributeError(f"removed on the mock: {name}")

return object.__getattr__(self, name)


def wrap_array(func: callable, exclude: Tuple[str, ...] = ()) -> callable:
@functools.wraps(func)
def wrapped(*args, **kwargs):
result = func(*args, **kwargs)

if isinstance(result, tuple):
return tuple(MockedArray(arr, exclude=exclude) for arr in result)

return MockedArray(result, exclude=exclude)

return wrapped


def make_mock_xp(
*, exclude: Tuple[str, ...] = (), exclude_methods: Tuple[str, ...] = ()
) -> SimpleNamespace:
xp = copy(mock_xp)
assert isinstance(exclude, tuple) # sanity check
assert isinstance(exclude_methods, tuple) # sanity check
for attr in exclude:
delattr(xp, attr)

array_returning_funcs = (
"astype",
"broadcast_arrays",
"arange",
"asarray",
"empty",
"zeros",
"ones",
"reshape",
"isnan",
"isfinite",
"logical_or",
"sum",
"nonzero",
"sort",
"unique_values",
"any",
"all",
)

for name in array_returning_funcs:
func = getattr(xp, name, None)
if func is None:
# removed in the step before
continue
setattr(xp, name, wrap_array(func, exclude=exclude_methods))

return xp


def test_warning_on_noncompliant_xp():
"""Using non-compliant array modules raises helpful warning"""
xp = make_mock_xp()
xp = make_mock_xp(exclude_methods=("__array_namespace__",))
with pytest.warns(HypothesisWarning, match=MOCK_WARN_MSG):
make_strategies_namespace(xp, api_version="draft")

Expand All @@ -62,7 +120,8 @@ def test_error_on_missing_attr(stratname, args, attr):


dtypeless_xp = make_mock_xp(exclude=tuple(DTYPE_NAMES))
with pytest.warns(HypothesisWarning):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=HypothesisWarning)
dtypeless_xps = make_strategies_namespace(dtypeless_xp, api_version="draft")


Expand Down
9 changes: 8 additions & 1 deletion hypothesis-python/tests/numpy/test_from_dtype.py
Expand Up @@ -21,6 +21,8 @@

from tests.common.debug import assert_no_examples, check_can_generate_examples, find_any

np_version = tuple(int(x) for x in np.__version__.split(".")[:2])

STANDARD_TYPES = [
np.dtype(t)
for t in (
Expand Down Expand Up @@ -126,7 +128,12 @@ def test_byte_string_dtypes_generate_unicode_strings(data):
assert isinstance(result, bytes)


@pytest.mark.parametrize("dtype", ["U", "S", "a"])
skipif_np2 = pytest.mark.skipif(np_version >= (2, 0), reason="removed in new version")

@pytest.mark.parametrize(
"dtype",
["U", "S", pytest.param("a", marks=skipif_np2)],
)
def test_unsized_strings_length_gt_one(dtype):
# See https://github.com/HypothesisWorks/hypothesis/issues/2229
find_any(nps.arrays(dtype=dtype, shape=1), lambda arr: len(arr[0]) >= 2)
Expand Down
6 changes: 5 additions & 1 deletion hypothesis-python/tests/numpy/test_gen_data.py
Expand Up @@ -348,7 +348,11 @@ def test_may_not_fill_with_non_nan_when_unique_is_set_and_type_is_not_number(arr

@pytest.mark.parametrize("fill", [False, True])
# Overflowing elements deprecated upstream in Numpy 1.24 :-)
@fails_with(InvalidArgument if np_version < (1, 24) else DeprecationWarning)
@fails_with(
InvalidArgument
if np_version < (1, 24)
else (DeprecationWarning if np_version < (2, 0) else OverflowError)
)
@given(st.data())
def test_overflowing_integers_are_deprecated(fill, data):
kw = {"elements": st.just(300)}
Expand Down
13 changes: 13 additions & 0 deletions hypothesis-python/tox.ini
Expand Up @@ -55,6 +55,19 @@ commands=
bash -c "pip install --only-binary=:all: numpy==$(grep 'numpy>=' setup.py | grep -oE '[0-9.]+')"
python -bb -X dev -m pytest tests/numpy/ -n auto

# This test job runs against the nightly version of `numpy`
[testenv:numpy-nightly]
deps=
-r../requirements/test.txt
pandas
black
click
allowlist_externals =
bash
commands=
bash -c "pip install --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy"
python -bb -X dev -m pytest tests/numpy/ tests/array_api/ tests/pandas/ tests/ghostwriter/ tests/conjecture/ tests/cover -n auto

# Note: when adding or removing tested Pandas versions, make sure to update the
# docs in numpy.rst too. To see current download rates of each minor version:
# https://pepy.tech/project/pandas?versions=1.1.*&versions=1.2.*&versions=1.3.*&versions=1.4.*&versions=1.5.*&versions=2.0.*
Expand Down
1 change: 1 addition & 0 deletions tooling/src/hypothesistooling/__main__.py
Expand Up @@ -502,6 +502,7 @@ def standard_tox_task(name, py=ci_version):
standard_tox_task(f"crosshair-{kind}")

standard_tox_task("py38-oldestnumpy", py="3.8")
standard_tox_task("numpy-nightly", py="3.12")

standard_tox_task("coverage")
standard_tox_task("conjecture-coverage")
Expand Down

0 comments on commit eccba85

Please sign in to comment.