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
7 changes: 7 additions & 0 deletions doc/release/upcoming_changes/24940.new_feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
``svdvals`` for `numpy.linalg`
------------------------------

`numpy.linalg.svdvals` has been added. It computes singular values for
(stack of) matrices. Executing ``np.svdvals(x)`` is the same as calling
``np.svd(x, compute_uv=False, hermitian=False)``.
This function is compatible with Array API.
3 changes: 0 additions & 3 deletions doc/source/reference/array_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -683,9 +683,6 @@ Linear algebra differences
specification issue
<https://github.com/data-apis/array-api/issues/285>`__ for more
details.
* - New function ``svdvals``.
- **Compatible**
- Equivalent to ``np.linalg.svd(compute_uv=False)``.
* - The ``axis`` keyword to ``tensordot`` must be a tuple.
- **Compatible**
- In ``np.tensordot``, it can also be an array or array-like.
Expand Down
1 change: 1 addition & 0 deletions doc/source/reference/routines.linalg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Decompositions
linalg.cholesky
linalg.qr
linalg.svd
linalg.svdvals

Matrix eigenvalues
------------------
Expand Down
1 change: 1 addition & 0 deletions numpy/linalg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
cholesky
qr
svd
svdvals

Matrix eigenvalues
------------------
Expand Down
1 change: 1 addition & 0 deletions numpy/linalg/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ from numpy.linalg._linalg import (
slogdet as slogdet,
det as det,
svd as svd,
svdvals as svdvals,
eig as eig,
eigh as eigh,
lstsq as lstsq,
Expand Down
45 changes: 42 additions & 3 deletions numpy/linalg/_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

__all__ = ['matrix_power', 'solve', 'tensorsolve', 'tensorinv', 'inv',
'cholesky', 'eigvals', 'eigvalsh', 'pinv', 'slogdet', 'det',
'svd', 'eig', 'eigh', 'lstsq', 'norm', 'qr', 'cond', 'matrix_rank',
'LinAlgError', 'multi_dot', 'trace', 'diagonal', 'cross']
'svd', 'svdvals', 'eig', 'eigh', 'lstsq', 'norm', 'qr', 'cond',
'matrix_rank', 'LinAlgError', 'multi_dot', 'trace', 'diagonal',
'cross']

import functools
import operator
Expand Down Expand Up @@ -1742,6 +1743,43 @@ def svd(a, full_matrices=True, compute_uv=True, hermitian=False):
return s


def _svdvals_dispatcher(x):
return (x,)


@array_function_dispatch(_svdvals_dispatcher)
def svdvals(x, /):
"""
Returns the singular values of a matrix (or a stack of matrices) ``x``.
When x is a stack of matrices, the function will compute the singular
values for each matrix in the stack.

This function is Array API compatible.
Comment thread
rgommers marked this conversation as resolved.

Calling ``np.svdvals(x)`` to get singular values is the same as
``np.svd(x, compute_uv=False, hermitian=False)``.

Parameters
----------
x : (..., M, N) array_like
Input array having shape (..., M, N) and whose last two
dimensions form matrices on which to perform singular value
decomposition. Should have a floating-point data type.

Returns
-------
out : ndarray
An array with shape (..., K) that contains the vector(s)
of singular values of length K, where K = min(M, N).

See Also
--------
scipy.linalg.svdvals : Compute singular values of a matrix.

"""
return svd(x, compute_uv=False, hermitian=False)


def _cond_dispatcher(x, p=None):
return (x,)

Expand Down Expand Up @@ -2912,7 +2950,8 @@ def diagonal(x, /, *, offset=0):
Returns specified diagonals of a matrix (or a stack of matrices) ``x``.

This function is Array API compatible, contrary to
:py:func:`numpy.diagonal`.
:py:func:`numpy.diagonal`, the matrix is assumed
to be defined by the last two dimensions.

Parameters
----------
Expand Down
4 changes: 4 additions & 0 deletions numpy/linalg/_linalg.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ def svd(
hermitian: bool = ...,
) -> NDArray[floating[Any]]: ...

def svdvals(
x: _ArrayLikeInt_co | _ArrayLikeFloat_co | _ArrayLikeComplex_co
) -> NDArray[floating[Any]]: ...

# TODO: Returns a scalar for 2D arrays and
# a `(x.ndim - 2)`` dimensionl array otherwise
def cond(x: _ArrayLikeComplex_co, p: None | float | L["fro", "nuc"] = ...) -> Any: ...
Expand Down
6 changes: 6 additions & 0 deletions numpy/linalg/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,12 @@ def test_empty_identity(self):
assert_equal(vh.shape, (4, 4))
assert_equal(vh, np.eye(4))

def test_svdvals(self):
Comment thread
rgommers marked this conversation as resolved.
x = np.array([[1, 0.5], [0.5, 1]])
s_from_svd = linalg.svd(x, compute_uv=False, hermitian=self.hermitian)
s_from_svdvals = linalg.svdvals(x)
assert_almost_equal(s_from_svd, s_from_svdvals)


class SVDHermitianCases(HermitianTestCase, HermitianGeneralizedTestCase):

Expand Down
3 changes: 3 additions & 0 deletions numpy/linalg/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def test_svd_no_uv(self):
assert_equal(np.linalg.matrix_rank(a), 1)
assert_array_less(1, np.linalg.norm(a, ord=2))

w_svdvals = linalg.svdvals(a)
assert_array_almost_equal(w, w_svdvals)

def test_norm_object_array(self):
# gh-7575
testvector = np.array([np.array([0, 1]), 0, 0], dtype=object)
Expand Down
3 changes: 0 additions & 3 deletions tools/ci/array-api-skips.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ array_api_tests/test_has_names.py::test_has_names[linalg-matmul]
array_api_tests/test_has_names.py::test_has_names[linalg-matrix_norm]
array_api_tests/test_has_names.py::test_has_names[linalg-matrix_transpose]
array_api_tests/test_has_names.py::test_has_names[linalg-outer]
array_api_tests/test_has_names.py::test_has_names[linalg-svdvals]
array_api_tests/test_has_names.py::test_has_names[linalg-tensordot]
array_api_tests/test_has_names.py::test_has_names[linalg-vecdot]
array_api_tests/test_has_names.py::test_has_names[linalg-vector_norm]
Expand Down Expand Up @@ -75,7 +74,6 @@ array_api_tests/test_linalg.py::test_matrix_norm
array_api_tests/test_linalg.py::test_matrix_transpose
array_api_tests/test_linalg.py::test_outer
array_api_tests/test_linalg.py::test_pinv
array_api_tests/test_linalg.py::test_svdvals
array_api_tests/test_linalg.py::test_vecdot

# missing names
Expand Down Expand Up @@ -122,7 +120,6 @@ array_api_tests/test_signatures.py::test_extension_func_signature[linalg.matrix_
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.matrix_transpose]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.outer]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.pinv]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.svdvals]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.tensordot]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.vecdot]
array_api_tests/test_signatures.py::test_extension_func_signature[linalg.vector_norm]
Expand Down