Skip to content

Always return a real dtype from dpnp.linalg.cond #2547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 5, 2025
Merged
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 @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Avoided negating unsigned integers in ceil division used in `dpnp.resize` implementation [#2508](https://github.com/IntelPython/dpnp/pull/2508)
* Fixed `dpnp.unique` with 1d input array and `axis=0`, `equal_nan=True` keywords passed where the produced result doesn't collapse the NaNs [#2530](https://github.com/IntelPython/dpnp/pull/2530)
* Resolved issue when `dpnp.ndarray` constructor is called with `dpnp.ndarray.data` as `buffer` keyword [#2533](https://github.com/IntelPython/dpnp/pull/2533)
* Fixed `dpnp.linalg.cond` to always return a real dtype [#2547](https://github.com/IntelPython/dpnp/pull/2547)

### Security

Expand Down
4 changes: 3 additions & 1 deletion dpnp/linalg/dpnp_utils_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1976,13 +1976,15 @@ def dpnp_cond(x, p=None):
else:
r = s[..., 0] / s[..., -1]
else:
result_t = _common_type(x)
# The result array will contain nans in the entries
# where inversion failed
invx = dpnp.linalg.inv(x)
r = dpnp.linalg.norm(x, p, axis=(-2, -1)) * dpnp.linalg.norm(
invx, p, axis=(-2, -1)
)

# condition number is always real
result_t = _real_type(_common_type(x), device=x.sycl_queue)
r = r.astype(result_t, copy=False)

# Convert nans to infs unless the original array had nan entries
Expand Down
48 changes: 22 additions & 26 deletions dpnp/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,15 +278,12 @@ def test_cholesky_errors(self):


class TestCond:
def setup_method(self):
numpy.random.seed(70)
_norms = [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]

@pytest.mark.parametrize(
"shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 5, 3)", "(4, 0, 2, 3)"]
)
@pytest.mark.parametrize(
"p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]
"shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 4, 4)", "(4, 0, 3, 3)"]
)
@pytest.mark.parametrize("p", _norms)
def test_empty(self, shape, p):
a = numpy.empty(shape)
ia = dpnp.array(a)
Expand All @@ -295,26 +292,27 @@ def test_empty(self, shape, p):
expected = numpy.linalg.cond(a, p=p)
assert_dtype_allclose(result, expected)

# TODO: uncomment once numpy 2.3.3 release is published
# @testing.with_requires("numpy>=2.3.3")
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_none=True, no_bool=True)
)
@pytest.mark.parametrize(
"shape", [(4, 4), (2, 4, 3, 3)], ids=["(4, 4)", "(2, 4, 3, 3)"]
)
@pytest.mark.parametrize(
"p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]
)
@pytest.mark.parametrize("p", _norms)
def test_basic(self, dtype, shape, p):
a = generate_random_numpy_array(shape, dtype)
ia = dpnp.array(a)

result = dpnp.linalg.cond(ia, p=p)
expected = numpy.linalg.cond(a, p=p)
# TODO: remove when numpy#29333 is released
if numpy_version() < "2.3.3":
expected = expected.real
assert_dtype_allclose(result, expected, factor=16)

@pytest.mark.parametrize(
"p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]
)
@pytest.mark.parametrize("p", _norms)
def test_bool(self, p):
a = numpy.array([[True, True], [True, False]])
ia = dpnp.array(a)
Expand All @@ -323,9 +321,7 @@ def test_bool(self, p):
expected = numpy.linalg.cond(a, p=p)
assert_dtype_allclose(result, expected)

@pytest.mark.parametrize(
"p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]
)
@pytest.mark.parametrize("p", _norms)
def test_nan_to_inf(self, p):
a = numpy.zeros((2, 2))
ia = dpnp.array(a)
Expand All @@ -343,9 +339,7 @@ def test_nan_to_inf(self, p):
else:
assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p)

@pytest.mark.parametrize(
"p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"]
)
@pytest.mark.parametrize("p", _norms)
@pytest.mark.parametrize(
"stride",
[(-2, -3, 2, -2), (-2, 4, -4, -4), (2, 3, 4, 4), (-1, 3, 3, -3)],
Expand All @@ -357,21 +351,23 @@ def test_nan_to_inf(self, p):
],
)
def test_strided(self, p, stride):
A = numpy.random.rand(6, 8, 10, 10)
B = dpnp.asarray(A)
A = generate_random_numpy_array(
(6, 8, 10, 10), seed_value=70, low=0, high=1
)
iA = dpnp.array(A)
slices = tuple(slice(None, None, stride[i]) for i in range(A.ndim))
a = A[slices]
b = B[slices]
a, ia = A[slices], iA[slices]

result = dpnp.linalg.cond(b, p=p)
result = dpnp.linalg.cond(ia, p=p)
expected = numpy.linalg.cond(a, p=p)
assert_dtype_allclose(result, expected, factor=24)

def test_error(self):
@pytest.mark.parametrize("xp", [dpnp, numpy])
def test_error(self, xp):
# cond is not defined on empty arrays
ia = dpnp.empty((2, 0))
a = xp.empty((2, 0))
with pytest.raises(ValueError):
dpnp.linalg.cond(ia, p=1)
xp.linalg.cond(a, p=1)


class TestDet:
Expand Down
Loading