From 1b66e8bd002dc3b33b8ee8580b723972236773c3 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Tue, 10 Jun 2025 11:10:54 -0500 Subject: [PATCH 1/5] update test to pass with numpy-2.3 --- .github/workflows/conda-package.yml | 2 +- dpnp/linalg/dpnp_utils_linalg.py | 13 +++++++++++-- dpnp/tests/test_linalg.py | 14 ++++++++------ dpnp/tests/test_product.py | 6 ++++-- dpnp/tests/testing/array.py | 5 ++++- .../cupy/manipulation_tests/test_add_remove.py | 3 ++- .../third_party/cupy/math_tests/test_matmul.py | 2 ++ 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index cc47425bc6b5..f8cfd7885faa 100644 --- a/.github/workflows/conda-package.yml +++ b/.github/workflows/conda-package.yml @@ -22,7 +22,7 @@ env: test-env-name: 'test' rerun-tests-on-failure: 'true' rerun-tests-max-attempts: 2 - rerun-tests-timeout: 35 + rerun-tests-timeout: 40 jobs: build: diff --git a/dpnp/linalg/dpnp_utils_linalg.py b/dpnp/linalg/dpnp_utils_linalg.py index 6ea4d42a1852..505fe0e64c2f 100644 --- a/dpnp/linalg/dpnp_utils_linalg.py +++ b/dpnp/linalg/dpnp_utils_linalg.py @@ -1187,7 +1187,11 @@ def _norm_int_axis(x, ord, axis, keepdims): if ord == dpnp.inf: if x.shape[axis] == 0: x = dpnp.moveaxis(x, axis, -1) - return dpnp.zeros_like(x, shape=x.shape[:-1]) + res_shape = x.shape[:-1] + result = dpnp.zeros_like(x, shape=res_shape) + if keepdims: + result = result.reshape(res_shape + (1,)) + return result return dpnp.abs(x).max(axis=axis, keepdims=keepdims) if ord == -dpnp.inf: return dpnp.abs(x).min(axis=axis, keepdims=keepdims) @@ -1222,11 +1226,16 @@ def _norm_tuple_axis(x, ord, row_axis, col_axis, keepdims): """ + # pylint: disable=too-many-branches axis = (row_axis, col_axis) flag = x.shape[row_axis] == 0 or x.shape[col_axis] == 0 if flag and ord in [1, 2, dpnp.inf]: x = dpnp.moveaxis(x, axis, (-2, -1)) - return dpnp.zeros_like(x, shape=x.shape[:-2]) + res_shape = x.shape[:-2] + result = dpnp.zeros_like(x, shape=res_shape) + if keepdims: + result = result.reshape(res_shape + (1, 1)) + return result if row_axis == col_axis: raise ValueError("Duplicate axes given.") if ord == 2: diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 2f567f7a9e5c..a8f97c26a02f 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -23,7 +23,6 @@ get_integer_float_dtypes, has_support_aspect64, is_cpu_device, - is_cuda_device, numpy_version, requires_intel_mkl_version, ) @@ -2104,11 +2103,14 @@ def test_empty(self, shape, ord, axis, keepdims): assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg) assert_raises(ValueError, numpy.linalg.norm, a, **kwarg) else: - # TODO: when similar changes in numpy are available, instead - # of assert_equal with zero, we should compare with numpy - # ord in [None, 1, 2] - assert_equal(dpnp.linalg.norm(ia, **kwarg), 0.0) - assert_raises(ValueError, numpy.linalg.norm, a, **kwarg) + if numpy_version() >= "2.3.0": + result = dpnp.linalg.norm(ia, **kwarg) + expected = numpy.linalg.norm(a, **kwarg) + assert_dtype_allclose(result, expected) + else: + assert_equal( + dpnp.linalg.norm(ia, **kwarg), 0.0, strict=False + ) else: result = dpnp.linalg.norm(ia, **kwarg) expected = numpy.linalg.norm(a, **kwarg) diff --git a/dpnp/tests/test_product.py b/dpnp/tests/test_product.py index 0eccd4deefc1..707257f84344 100644 --- a/dpnp/tests/test_product.py +++ b/dpnp/tests/test_product.py @@ -12,8 +12,6 @@ assert_dtype_allclose, generate_random_numpy_array, get_all_dtypes, - get_complex_dtypes, - is_win_platform, numpy_version, ) from .third_party.cupy import testing @@ -845,6 +843,8 @@ def test_dtype_matrix(self, dt_in1, dt_in2, dt_out, shape1, shape2): assert_raises(TypeError, dpnp.matmul, ia, ib, out=iout) assert_raises(TypeError, numpy.matmul, a, b, out=out) + # TODO: include numpy-2.3 when numpy-issue-29164 is resolved + @testing.with_requires("numpy<2.3") @pytest.mark.parametrize("dtype", _selected_dtypes) @pytest.mark.parametrize("order1", ["C", "F", "A"]) @pytest.mark.parametrize("order2", ["C", "F", "A"]) @@ -882,6 +882,8 @@ def test_order(self, dtype, order1, order2, order, shape1, shape2): assert result.flags.f_contiguous == expected.flags.f_contiguous assert_dtype_allclose(result, expected) + # TODO: include numpy-2.3 when numpy-issue-29164 is resolved + @testing.with_requires("numpy<2.3") @pytest.mark.parametrize("dtype", _selected_dtypes) @pytest.mark.parametrize( "stride", diff --git a/dpnp/tests/testing/array.py b/dpnp/tests/testing/array.py index 3db237387054..96bbf94e9ce5 100644 --- a/dpnp/tests/testing/array.py +++ b/dpnp/tests/testing/array.py @@ -49,7 +49,10 @@ def _assert(assert_func, result, expected, *args, **kwargs): ] # For numpy < 2.0, some tests will fail for dtype mismatch dev = dpctl.select_default_device() - if numpy.__version__ >= "2.0.0" and dev.has_aspect_fp64: + if ( + numpy.lib.NumpyVersion(numpy.__version__) >= "2.0.0" + and dev.has_aspect_fp64 + ): strict = kwargs.setdefault("strict", True) if flag: if strict: diff --git a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py index acd3e706d352..4d4c02f74393 100644 --- a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py +++ b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py @@ -340,7 +340,8 @@ def test_unique_inverse(self, xp, dtype, attr): a = testing.shaped_random((100, 100), xp, dtype) return getattr(xp.unique_inverse(a), attr) - @testing.with_requires("numpy>=2.0") + # TODO: include numpy-2.3 when dpnp-issue-2476 is addressed + @testing.with_requires("numpy>=2.0", "numpy<2.3") @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) @testing.numpy_cupy_array_equal() def test_unique_values(self, xp, dtype): diff --git a/dpnp/tests/third_party/cupy/math_tests/test_matmul.py b/dpnp/tests/third_party/cupy/math_tests/test_matmul.py index fd15ba33110a..138683cae325 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_matmul.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_matmul.py @@ -99,6 +99,8 @@ def test_cupy_matmul(self, xp, dtype1, dtype2): ) class TestMatmulOut(unittest.TestCase): + # TODO: include numpy-2.3 when numpy-issue-29164 is resolved + @testing.with_requires("numpy<2.3") # no_int8=True is added to avoid overflow @testing.for_all_dtypes(name="dtype1", no_int8=True) @testing.for_all_dtypes(name="dtype2", no_int8=True) From acbc442092c3b1b01c1c47589649f71240b68a56 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Tue, 10 Jun 2025 21:20:43 -0500 Subject: [PATCH 2/5] add an entry to changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8524f180594..f69349d03478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* Fixed a bug for calculating the norm (`dpnp.linalg.norm`) of empty arrays when `keepdims=True` is passed [#2477](https://github.com/IntelPython/dpnp/pull/2477) ## [0.18.0] - 06/04/2025 From 4f6125c673653d3bfff950f5648aae612dcedff4 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Wed, 11 Jun 2025 15:04:22 -0500 Subject: [PATCH 3/5] address comments --- dpnp/linalg/dpnp_utils_linalg.py | 11 ++++------- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dpnp/linalg/dpnp_utils_linalg.py b/dpnp/linalg/dpnp_utils_linalg.py index 505fe0e64c2f..b694b730c97c 100644 --- a/dpnp/linalg/dpnp_utils_linalg.py +++ b/dpnp/linalg/dpnp_utils_linalg.py @@ -1188,10 +1188,9 @@ def _norm_int_axis(x, ord, axis, keepdims): if x.shape[axis] == 0: x = dpnp.moveaxis(x, axis, -1) res_shape = x.shape[:-1] - result = dpnp.zeros_like(x, shape=res_shape) if keepdims: - result = result.reshape(res_shape + (1,)) - return result + res_shape += (1,) + return dpnp.zeros_like(x, shape=res_shape) return dpnp.abs(x).max(axis=axis, keepdims=keepdims) if ord == -dpnp.inf: return dpnp.abs(x).min(axis=axis, keepdims=keepdims) @@ -1226,16 +1225,14 @@ def _norm_tuple_axis(x, ord, row_axis, col_axis, keepdims): """ - # pylint: disable=too-many-branches axis = (row_axis, col_axis) flag = x.shape[row_axis] == 0 or x.shape[col_axis] == 0 if flag and ord in [1, 2, dpnp.inf]: x = dpnp.moveaxis(x, axis, (-2, -1)) res_shape = x.shape[:-2] - result = dpnp.zeros_like(x, shape=res_shape) if keepdims: - result = result.reshape(res_shape + (1, 1)) - return result + res_shape += (1, 1) + return dpnp.zeros_like(x, shape=res_shape) if row_axis == col_axis: raise ValueError("Duplicate axes given.") if ord == 2: diff --git a/pyproject.toml b/pyproject.toml index 68cea36a07a2..c0e8fc3c80ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,7 +137,7 @@ exclude-protected = ["_create_from_usm_ndarray"] [tool.pylint.design] max-args = 11 -max-branches = 16 +max-branches = 17 max-locals = 30 max-positional-arguments = 9 max-returns = 8 From abd23bc5f1c9bf39046ee8f7474e937e44790313 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 12 Jun 2025 16:55:10 -0500 Subject: [PATCH 4/5] update vector_norm and matrix_norm tests for empty arrays and improve coverage --- dpnp/tests/test_linalg.py | 59 +++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index a8f97c26a02f..19943b6fcd6f 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -2298,49 +2298,40 @@ def test_matrix_norm(self, ord, keepdims): expected = numpy.linalg.matrix_norm(a, ord=ord, keepdims=keepdims) assert_dtype_allclose(result, expected) - @pytest.mark.parametrize( - "xp", - [ - dpnp, - pytest.param( - numpy, - marks=pytest.mark.skipif( - numpy_version() < "2.3.0", - reason="numpy raises an error", - ), - ), - ], - ) + @testing.with_requires("numpy>=2.3") @pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32]) @pytest.mark.parametrize( "shape, axis", [[(2, 0), None], [(2, 0), (0, 1)], [(0, 2), (0, 1)]] ) @pytest.mark.parametrize("ord", [None, "fro", "nuc", 1, 2, dpnp.inf]) - def test_matrix_norm_empty(self, xp, dtype, shape, axis, ord): - x = xp.zeros(shape, dtype=dtype) - sc = dtype(0.0) if dtype == dpnp.float32 else 0.0 - assert_equal(xp.linalg.norm(x, axis=axis, ord=ord), sc) + @pytest.mark.parametrize("keepdims", [True, False]) + def test_matrix_norm_empty(self, dtype, shape, axis, ord, keepdims): + a = numpy.zeros(shape, dtype=dtype) + ia = dpnp.array(a) + result = dpnp.linalg.norm(ia, axis=axis, ord=ord, keepdims=keepdims) + expected = numpy.linalg.norm(a, axis=axis, ord=ord, keepdims=keepdims) + assert_dtype_allclose(result, expected) - @pytest.mark.parametrize( - "xp", - [ - dpnp, - pytest.param( - numpy, - marks=pytest.mark.skipif( - numpy_version() < "2.3.0", - reason="numpy raises an error", - ), - ), - ], - ) + @testing.with_requires("numpy>=2.3") @pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32]) @pytest.mark.parametrize("axis", [None, 0]) @pytest.mark.parametrize("ord", [None, 1, 2, dpnp.inf]) - def test_vector_norm_empty(self, xp, dtype, axis, ord): - x = xp.zeros(0, dtype=dtype) - sc = dtype(0.0) if dtype == dpnp.float32 else 0.0 - assert_equal(xp.linalg.vector_norm(x, axis=axis, ord=ord), sc) + @pytest.mark.parametrize("keepdims", [True, False]) + def test_vector_norm_empty(self, dtype, axis, ord, keepdims): + a = numpy.zeros(0, dtype=dtype) + ia = dpnp.array(a) + result = dpnp.linalg.vector_norm( + ia, axis=axis, ord=ord, keepdims=keepdims + ) + expected = numpy.linalg.vector_norm( + a, axis=axis, ord=ord, keepdims=keepdims + ) + assert_dtype_allclose(result, expected) + if keepdims: + # norm and vector_norm have different paths in dpnp when keepdims=True, + # to cover both of them test with norm as well + result = dpnp.linalg.norm(ia, axis=axis, ord=ord, keepdims=keepdims) + assert_dtype_allclose(result, expected) @testing.with_requires("numpy>=2.0") @pytest.mark.parametrize( From 887dcf81da31be4c8fecdae28d04ac4632d1abe9 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 12 Jun 2025 17:02:11 -0500 Subject: [PATCH 5/5] update cupy test for unique_values to align with changes in cupy-9161 --- .../third_party/cupy/manipulation_tests/test_add_remove.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py index 4d4c02f74393..31bbc9691889 100644 --- a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py +++ b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py @@ -340,13 +340,13 @@ def test_unique_inverse(self, xp, dtype, attr): a = testing.shaped_random((100, 100), xp, dtype) return getattr(xp.unique_inverse(a), attr) - # TODO: include numpy-2.3 when dpnp-issue-2476 is addressed - @testing.with_requires("numpy>=2.0", "numpy<2.3") + @testing.with_requires("numpy>=2.0") @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) @testing.numpy_cupy_array_equal() def test_unique_values(self, xp, dtype): a = testing.shaped_random((100, 100), xp, dtype) - return xp.unique_values(a) + out = xp.unique_values(a) # may not be sorted from NumPy 2.3. + return xp.sort(out) @testing.parameterize(*testing.product({"trim": ["fb", "f", "b"]}))