From 05c226d83cbc2beb6b1828e5ecc613a386e4046a Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sat, 24 Oct 2026 12:47:55 +0200 Subject: [PATCH] Sync CuPy tests with NumPy 2.6 behavior changes Backport test updates from cupy/cupy#10392: uint64 indices/repeats in choose, put, searchsorted and repeat, NumPy 2.6 unwrap promotion, and NaN bins in digitize. --- .../cupy/indexing_tests/test_indexing.py | 7 ++ .../cupy/indexing_tests/test_insert.py | 12 +++ .../cupy/manipulation_tests/test_tiling.py | 19 +++-- .../cupy/math_tests/test_trigonometric.py | 29 ++++--- .../cupy/sorting_tests/test_search.py | 7 ++ .../cupy/statistics_tests/test_histogram.py | 76 +++++++++++-------- 6 files changed, 103 insertions(+), 47 deletions(-) diff --git a/dpnp/tests/third_party/cupy/indexing_tests/test_indexing.py b/dpnp/tests/third_party/cupy/indexing_tests/test_indexing.py index 4ab88f634f1f..84d66cb69856 100644 --- a/dpnp/tests/third_party/cupy/indexing_tests/test_indexing.py +++ b/dpnp/tests/third_party/cupy/indexing_tests/test_indexing.py @@ -208,6 +208,13 @@ def test_choose(self, xp, dtype): c = testing.shaped_arange((3, 4), xp, dtype) return a.choose(c) + @testing.with_requires("numpy>=2.6.0.dev0") + @testing.numpy_cupy_array_equal() + def test_choose_uint64(self, xp): + a = xp.array([0, 2, 1, 2], dtype=xp.uint64) + choices = xp.arange(12).reshape(3, 4) + return xp.choose(a, choices) + @testing.for_all_dtypes() @testing.numpy_cupy_array_equal() def test_choose_broadcast(self, xp, dtype): diff --git a/dpnp/tests/third_party/cupy/indexing_tests/test_insert.py b/dpnp/tests/third_party/cupy/indexing_tests/test_insert.py index 7970862da776..20e7ee0ff31c 100644 --- a/dpnp/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/dpnp/tests/third_party/cupy/indexing_tests/test_insert.py @@ -126,6 +126,18 @@ def test_put_values_scalar(self, xp): return a +class TestPutUint64(unittest.TestCase): + + @testing.with_requires("numpy>=2.6.0.dev0") + @testing.numpy_cupy_array_equal() + def test_put_uint64(self, xp): + a = xp.arange(5) + indices = xp.array([0, 2, 4], dtype=xp.uint64) + # 'raise' mode is not supported, indices are in bounds anyway + xp.put(a, indices, [10, 20, 30], mode="clip") + return a + + @testing.parameterize( *testing.product( { diff --git a/dpnp/tests/third_party/cupy/manipulation_tests/test_tiling.py b/dpnp/tests/third_party/cupy/manipulation_tests/test_tiling.py index 032b0503089d..ac98de503ee1 100644 --- a/dpnp/tests/third_party/cupy/manipulation_tests/test_tiling.py +++ b/dpnp/tests/third_party/cupy/manipulation_tests/test_tiling.py @@ -137,6 +137,7 @@ def test_repeat(self, xp): numpy.uint8, numpy.uint16, numpy.uint32, + numpy.uint64, ] ] ) @@ -146,7 +147,11 @@ class TestRepeatNdarrayRepsDtype: @testing.numpy_cupy_array_equal() def test_repeat(self, xp): x = testing.shaped_arange((4,), xp) - return xp.repeat(x, xp.array([1, 2, 3, 4], dtype=self.rep_dtype), 0) + repeats = xp.array([1, 2, 3, 4], dtype=self.rep_dtype) + if xp is numpy and testing.numpy_satisfies("<2.6.0.dev0"): + # NumPy < 2.6 rejects uint64 repeats even when values fit intp. + repeats = repeats.astype(numpy.intp) + return xp.repeat(x, repeats, 0) @testing.parameterize( @@ -241,17 +246,15 @@ def test_negative(self): cupy.repeat(cupy.arange(3), cupy.array([-1, 1, 2])) def test_float_dtype_matches_numpy(self): - # Both NumPy and CuPy raise TypeError for unsafe cast + # Floating-point repeats cannot be cast to intp with same_kind. for xp in (numpy, cupy): with pytest.raises(TypeError): xp.repeat(xp.arange(3), xp.array([1.0, 1.0, 1.0]), 0) - @pytest.mark.skip("dpnp accepts uint64 repeats, unlike numpy") - def test_uint64_matches_numpy(self): - # Both reject uint64 (unsigned → signed is unsafe) - for xp in (numpy, cupy): - with pytest.raises(TypeError): - xp.repeat(xp.arange(3), xp.array([1, 2, 3], dtype=numpy.uint64)) + @pytest.mark.parametrize("repeats", [[2**64 - 1], [1, 2**64 - 1, 1]]) + def test_uint64_overflow(self, repeats): + with pytest.raises(ValueError, match="int64 maximum"): + cupy.repeat(cupy.arange(3), cupy.array(repeats, dtype=numpy.uint64)) def test_ndim_gt1_matches_numpy(self): for xp in (numpy, cupy): diff --git a/dpnp/tests/third_party/cupy/math_tests/test_trigonometric.py b/dpnp/tests/third_party/cupy/math_tests/test_trigonometric.py index a2283021fcdb..e356a98358eb 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_trigonometric.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_trigonometric.py @@ -5,6 +5,9 @@ from dpnp.tests.helper import has_support_aspect64 from dpnp.tests.third_party.cupy import testing +from dpnp.tests.third_party.cupy.testing._helper import ( + installed_but_not_baseline, +) class TestTrigonometric(unittest.TestCase): @@ -67,16 +70,24 @@ def test_rad2deg(self): @testing.with_requires("numpy>=1.21.0") class TestUnwrap(unittest.TestCase): + def _make_input(self, shape, xp, dtype, period=2 * numpy.pi): + if ( + installed_but_not_baseline(numpy="2.6.0.dev0") + and numpy.dtype(dtype).kind in "ub" + ): + pytest.skip("NumPy 2.6 promotes inputs before differencing.") + return testing.shaped_random(shape, xp, dtype) + @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_allclose(type_check=has_support_aspect64()) def test_unwrap_1dim(self, xp, dtype): - a = testing.shaped_random((5,), xp, dtype) + a = self._make_input((5,), xp, dtype) return xp.unwrap(a) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_allclose(type_check=has_support_aspect64()) def test_unwrap_1dim_with_discont(self, xp, dtype): - a = testing.shaped_random((5,), xp, dtype) + a = self._make_input((5,), xp, dtype) return xp.unwrap(a, discont=1.0) @testing.for_all_dtypes(no_complex=True) @@ -89,7 +100,7 @@ def test_unwrap_1dim_with_period(self, xp, dtype): # float64 and dpnp uses float32, especially with uint8/uint16 # dtypes where overflow occurs pytest.skip("skipping due to large difference of result") - a = testing.shaped_random((5,), xp, dtype) + a = self._make_input((5,), xp, dtype, period=1.2) return xp.unwrap(a, period=1.2) @testing.for_all_dtypes(no_complex=True) @@ -102,7 +113,7 @@ def test_unwrap_1dim_with_discont_and_period(self, xp, dtype): # float64 and dpnp uses float32, especially with uint8/uint16 # dtypes where overflow occurs pytest.skip("skipping due to large difference of result") - a = testing.shaped_random((5,), xp, dtype) + a = self._make_input((5,), xp, dtype, period=1.2) return xp.unwrap(a, discont=1.0, period=1.2) @testing.for_all_dtypes(no_complex=True) @@ -110,7 +121,7 @@ def test_unwrap_1dim_with_discont_and_period(self, xp, dtype): rtol=1e-06, atol=1e-06, type_check=has_support_aspect64() ) def test_unwrap_2dim_without_axis(self, xp, dtype): - a = testing.shaped_random((4, 5), xp, dtype) + a = self._make_input((4, 5), xp, dtype) return xp.unwrap(a) @testing.for_all_dtypes(no_complex=True) @@ -118,23 +129,23 @@ def test_unwrap_2dim_without_axis(self, xp, dtype): rtol=1e-06, atol=1e-06, type_check=has_support_aspect64() ) def test_unwrap_2dim_with_axis(self, xp, dtype): - a = testing.shaped_random((4, 5), xp, dtype) + a = self._make_input((4, 5), xp, dtype) return xp.unwrap(a, axis=1) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_allclose(rtol=1e-06, type_check=has_support_aspect64()) def test_unwrap_2dim_with_discont(self, xp, dtype): - a = testing.shaped_random((4, 5), xp, dtype) + a = self._make_input((4, 5), xp, dtype) return xp.unwrap(a, discont=5.0, axis=1) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_allclose(type_check=has_support_aspect64()) def test_unwrap_2dim_with_period(self, xp, dtype): - a = testing.shaped_random((4, 5), xp, dtype) + a = self._make_input((4, 5), xp, dtype, period=4.5) return xp.unwrap(a, axis=1, period=4.5) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_allclose(type_check=has_support_aspect64()) def test_unwrap_2dim_with_discont_and_period(self, xp, dtype): - a = testing.shaped_random((4, 5), xp, dtype) + a = self._make_input((4, 5), xp, dtype, period=4.5) return xp.unwrap(a, discont=5.0, axis=1, period=4.5) diff --git a/dpnp/tests/third_party/cupy/sorting_tests/test_search.py b/dpnp/tests/third_party/cupy/sorting_tests/test_search.py index 07ef3a2bc896..f9067cf428e1 100644 --- a/dpnp/tests/third_party/cupy/sorting_tests/test_search.py +++ b/dpnp/tests/third_party/cupy/sorting_tests/test_search.py @@ -849,6 +849,13 @@ def test_sorter(self, xp): y = xp.searchsorted(bins, x, sorter=sorter) return (y,) + @testing.with_requires("numpy>=2.6.0.dev0") + @testing.numpy_cupy_array_equal() + def test_sorter_uint64(self, xp): + a = xp.array([30, 10, 20]) + sorter = xp.array([1, 2, 0], dtype=xp.uint64) + return xp.searchsorted(a, xp.array([15, 25]), sorter=sorter) + def test_invalid_sorter(self): for xp in (numpy, cupy): x = testing.shaped_arange((12,), xp, xp.float64) diff --git a/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py b/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py index f014dd0dff8c..8e9dac4e3a99 100644 --- a/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py +++ b/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py @@ -543,48 +543,64 @@ def test_digitize_nan(self, xp): y = xp.digitize(x, bins, right=self.right) return (y,) - @testing.numpy_cupy_array_equal() - def test_digitize_nan_bins(self, xp): - x = testing.shaped_arange((14,), xp, xp.float32) - bins = xp.array([1.0, 3.0, 5.0, 8.0, float("nan")], xp.float32) - y = xp.digitize(x, bins, right=self.right) - return (y,) + def test_digitize_nan_bins(self): + # NumPy >= 2.6 rejects NaN bins during its monotonicity check. + # CuPy skips that check to avoid synchronization, so test it directly. + x = testing.shaped_arange((14,), cupy, cupy.float32) + bins = cupy.array([1.0, 3.0, 5.0, 8.0, float("nan")], cupy.float32) + y = cupy.digitize(x, bins, right=self.right) + if self.right: + expected = [0, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4] + else: + expected = [1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4] + testing.assert_array_equal(y, expected) - @testing.numpy_cupy_array_equal() - def test_digitize_nan_bins_repeated(self, xp): - x = testing.shaped_arange((14,), xp, xp.float32) + def test_digitize_nan_bins_repeated(self): + x = testing.shaped_arange((14,), cupy, cupy.float32) x[5] = float("nan") bins = [1.0, 3.0, 5.0, 8.0, float("nan"), float("nan")] - bins = xp.array(bins, xp.float32) - y = xp.digitize(x, bins, right=self.right) - return (y,) + bins = cupy.array(bins, cupy.float32) + y = cupy.digitize(x, bins, right=self.right) + if self.right: + expected = [0, 1, 1, 2, 2, 4, 3, 3, 4, 4, 4, 4, 4, 4] + else: + expected = [1, 1, 2, 2, 3, 6, 3, 4, 4, 4, 4, 4, 4, 4] + testing.assert_array_equal(y, expected) - @testing.numpy_cupy_array_equal() - def test_digitize_nan_bins_decreasing(self, xp): - x = testing.shaped_arange((14,), xp, xp.float32) + def test_digitize_nan_bins_decreasing(self): + x = testing.shaped_arange((14,), cupy, cupy.float32) x[5] = float("nan") bins = [float("nan"), 8.0, 5.0, 3.0, 1.0] - bins = xp.array(bins, xp.float32) - y = xp.digitize(x, bins, right=self.right) - return (y,) + bins = cupy.array(bins, cupy.float32) + y = cupy.digitize(x, bins, right=self.right) + if self.right: + expected = [5, 4, 4, 3, 3, 1, 2, 2, 1, 1, 1, 1, 1, 1] + else: + expected = [4, 4, 3, 3, 2, 0, 2, 1, 1, 1, 1, 1, 1, 1] + testing.assert_array_equal(y, expected) - @testing.numpy_cupy_array_equal() - def test_digitize_nan_bins_decreasing_repeated(self, xp): - x = testing.shaped_arange((14,), xp, xp.float32) + def test_digitize_nan_bins_decreasing_repeated(self): + x = testing.shaped_arange((14,), cupy, cupy.float32) x[5] = float("nan") bins = [float("nan"), float("nan"), float("nan"), 5.0, 3.0, 1.0] - bins = xp.array(bins, xp.float32) - y = xp.digitize(x, bins, right=self.right) - return (y,) + bins = cupy.array(bins, cupy.float32) + y = cupy.digitize(x, bins, right=self.right) + if self.right: + expected = [6, 5, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3] + else: + expected = [5, 5, 4, 4, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3] + testing.assert_array_equal(y, expected) - @testing.numpy_cupy_array_equal() - def test_digitize_all_nan_bins(self, xp): - x = testing.shaped_arange((14,), xp, xp.float32) + def test_digitize_all_nan_bins(self): + x = testing.shaped_arange((14,), cupy, cupy.float32) x[5] = float("nan") bins = [float("nan"), float("nan"), float("nan"), float("nan")] - bins = xp.array(bins, xp.float32) - y = xp.digitize(x, bins, right=self.right) - return (y,) + bins = cupy.array(bins, cupy.float32) + y = cupy.digitize(x, bins, right=self.right) + expected = [4] * 14 + if not self.right: + expected[5] = 0 + testing.assert_array_equal(y, expected) @testing.numpy_cupy_array_equal() def test_searchsorted_inf(self, xp):