Skip to content
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 @@ -43,6 +43,7 @@ This release is compatible with NumPy 2.5.
* Fixed incorrect `dpnp.tensor.expm1` result for `complex(±0, 0)` special case on CPU to match the Python Array API specification [#2926](https://github.com/IntelPython/dpnp/pull/2926)
* Fixed tests which expected lists from `dpctl` functions which now return tuples (i.e., `dpctl.SyclDevice.create_sub_devices`) [#2945](https://github.com/IntelPython/dpnp/pull/2945)
* Fixed `PytestRemovedIn10Warning` raised by `pytest` 9.1.0 by converting class-scoped fixtures to class methods [#2952](https://github.com/IntelPython/dpnp/pull/2952)
* Fixed `dpnp.linalg.svd(..., hermitian=True)` returning a non-unitary `vh` for singular input arrays due to a zero sign appearing [#2954](https://github.com/IntelPython/dpnp/pull/2954)

### Security

Expand Down
3 changes: 2 additions & 1 deletion dpnp/linalg/dpnp_utils_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,8 @@ def _hermitian_svd(a, compute_uv):
# the eigenvalues and related arrays to have the correct order
if compute_uv:
s, u = dpnp_eigh(a, eigen_mode="V")
sgn = dpnp.sign(s)
# avoid zero sign
sgn = dpnp.copysign(1.0, s)
s = dpnp.abs(s, out=s)
sidx = dpnp.argsort(s)[..., ::-1]
# Rearrange the signs according to sorted indices
Expand Down
22 changes: 16 additions & 6 deletions dpnp/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from .qr_helper import check_qr
from .third_party.cupy import testing

ALL_DTYPES_NO_BOOL = get_all_dtypes(no_none=True, no_bool=True)


def vvsort(val, vec, size, xp):
val_kwargs = {}
Expand Down Expand Up @@ -464,8 +466,6 @@ def test_det_errors(self):


class TestEigenvalue:
ALL_DTYPES_NO_BOOL = get_all_dtypes(no_none=True, no_bool=True)

# Eigenvalue decomposition of a matrix or a batch of matrices
# by checking if the eigen equation A*v=w*v holds for given eigenvalues(w)
# and eigenvectors(v).
Expand Down Expand Up @@ -3974,7 +3974,7 @@ def get_tol(self, dtype):
# Additionally checks for equality of singular values
# between dpnp and numpy decompositions
def check_decomposition(
self, dp_a, dp_u, dp_s, dp_vt, np_u, np_s, np_vt, compute_vt
self, dp_a, dp_u, dp_s, dp_vt, np_u, np_s, np_vt, compute_vt=False
):
tol = self._tol
if compute_vt:
Expand Down Expand Up @@ -4006,13 +4006,13 @@ def check_decomposition(
atol=tol,
)

@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True))
@pytest.mark.parametrize("dtype", ALL_DTYPES_NO_BOOL)
@pytest.mark.parametrize(
"shape",
[(2, 2), (3, 4), (5, 3), (16, 16)],
ids=["(2, 2)", "(3, 4)", "(5, 3)", "(16, 16)"],
)
def test_svd(self, dtype, shape):
def test_basic(self, dtype, shape):
a = numpy.arange(shape[0] * shape[1], dtype=dtype).reshape(shape)
dp_a = dpnp.array(a)

Expand All @@ -4030,7 +4030,7 @@ def test_svd(self, dtype, shape):
@pytest.mark.parametrize(
"shape", [(2, 2), (16, 16)], ids=["(2, 2)", "(16, 16)"]
)
def test_svd_hermitian(self, dtype, compute_vt, shape):
def test_hermitian(self, dtype, compute_vt, shape):
a = generate_random_numpy_array(shape, dtype, hermitian=True)
dp_a = dpnp.array(a)

Expand All @@ -4053,6 +4053,16 @@ def test_svd_hermitian(self, dtype, compute_vt, shape):
dp_a, dp_u, dp_s, dp_vh, np_u, np_s, np_vh, compute_vt
)

@testing.with_requires("numpy>=2.5")
def test_hermitian_singular(self):
a = numpy.array([[1, 0], [0, 0]])
dp_a = dpnp.array(a)

np_u, _, np_vh = numpy.linalg.svd(a, hermitian=True)
u, _, vh = dpnp.linalg.svd(dp_a, hermitian=True)
assert_allclose(u, np_u)
assert_allclose(vh, np_vh)

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

Expand Down
Loading