Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
* Resolved an issue with strides calculation in `dpnp.diagonal` to return correct values for empty diagonals [#2814](https://github.com/IntelPython/dpnp/pull/2814)
* Fixed test tolerance issues for float16 intermediate precision that became visible when testing against conda-forge's NumPy [#2828](https://github.com/IntelPython/dpnp/pull/2828)
* Ensured device aware dtype handling in `dpnp.identity` and `dpnp.gradient` [#2835](https://github.com/IntelPython/dpnp/pull/2835)
* Fixed `dpnp.linalg.matrix_rank` to properly handle an empty input array [#2853](https://github.com/IntelPython/dpnp/pull/2853)

### Security

Expand Down
7 changes: 7 additions & 0 deletions dpnp/linalg/dpnp_utils_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,13 @@ def dpnp_matrix_rank(A, tol=None, hermitian=False, rtol=None):

S = dpnp_svd(A, compute_uv=False, hermitian=hermitian)

# Handle empty matrices: if either dimension is 0, there are no singular
# values and the rank is 0. For stacked matrices, return array of zeros
# with proper shape.
if S.shape[-1] == 0:
# S has shape (..., 0), so result should have shape (...)
return dpnp.count_nonzero(S, axis=-1)

if tol is None:
if rtol is None:
rtol = max(A.shape[-2:]) * dpnp.finfo(S.dtype).eps
Expand Down
49 changes: 42 additions & 7 deletions dpnp/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3104,7 +3104,7 @@ def test_matrix_power_errors(self):


class TestMatrixRank:
@pytest.mark.parametrize("dtype", get_all_dtypes())
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
@pytest.mark.parametrize(
"data",
[
Expand All @@ -3116,15 +3116,15 @@ class TestMatrixRank:
numpy.array(1),
],
)
def test_matrix_rank(self, data, dtype):
def test_basic(self, data, dtype):
a = data.astype(dtype)
a_dp = dpnp.array(a)

np_rank = numpy.linalg.matrix_rank(a)
dp_rank = dpnp.linalg.matrix_rank(a_dp)
assert dp_rank.asnumpy() == np_rank

@pytest.mark.parametrize("dtype", get_all_dtypes())
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
@pytest.mark.parametrize(
"data",
[
Expand All @@ -3134,7 +3134,7 @@ def test_matrix_rank(self, data, dtype):
numpy.diag([1, 1, 1, 0]),
],
)
def test_matrix_rank_hermitian(self, data, dtype):
def test_hermitian(self, data, dtype):
a = data.astype(dtype)
a_dp = dpnp.array(a)

Expand All @@ -3151,7 +3151,7 @@ def test_matrix_rank_hermitian(self, data, dtype):
],
ids=["float", "0-D array", "1-D array"],
)
def test_matrix_rank_tolerance(self, high_tol, low_tol):
def test_tolerance(self, high_tol, low_tol):
a = numpy.eye(4)
a[-1, -1] = 1e-6
a_dp = dpnp.array(a)
Expand Down Expand Up @@ -3190,7 +3190,7 @@ def test_matrix_rank_tolerance(self, high_tol, low_tol):
[0.99e-6, numpy.array(1.01e-6), numpy.ones(4) * [0.99e-6]],
ids=["float", "0-D array", "1-D array"],
)
def test_matrix_rank_tol(self, tol):
def test_tol(self, tol):
a = numpy.zeros((4, 3, 2))
a_dp = dpnp.array(a)

Expand All @@ -3209,7 +3209,7 @@ def test_matrix_rank_tol(self, tol):
result = dpnp.linalg.matrix_rank(a_dp, tol=dp_tol)
assert_dtype_allclose(result, expected)

def test_matrix_rank_errors(self):
def test_errors(self):
a_dp = dpnp.array([[1, 2], [3, 4]], dtype="float32")

# unsupported type `a`
Expand Down Expand Up @@ -3238,6 +3238,41 @@ def test_matrix_rank_errors(self):
ValueError, dpnp.linalg.matrix_rank, a_dp, tol=1e-06, rtol=1e-04
)

# TODO: use below fixture when NumPy 2.5 is released
# @testing.with_requires("numpy>=2.5")
@pytest.mark.parametrize(
"shape",
[
(0, 0),
(0, 5),
(5, 0),
(0, 5, 5),
(3, 0, 5),
(2, 0, 0),
(2, 5, 0),
(2, 3, 0, 4),
],
)
def test_empty(self, shape):
a = numpy.zeros(shape)
ia = dpnp.array(a)

result = dpnp.linalg.matrix_rank(ia)
if numpy_version() < "2.5.0": # TODO: remove
# Expected behavior: rank of empty matrix is 0
# For stacked matrices, return array of zeros
expected = numpy.zeros(shape[:-2], dtype=numpy.intp)
if expected.ndim == 0:
expected = numpy.array(0)
else:
result = numpy.linalg.matrix_rank(a)
assert_array_equal(result, expected, strict=True)

# Also test with hermitian=True
if len(shape) >= 2 and shape[-2] == shape[-1]:
result = dpnp.linalg.matrix_rank(ia, hermitian=True)
assert_array_equal(result, expected, strict=True)


# numpy.linalg.matrix_transpose() is available since numpy >= 2.0
@testing.with_requires("numpy>=2.0")
Expand Down
Loading