diff --git a/python/mxnet/numpy/linalg.py b/python/mxnet/numpy/linalg.py index a9c0f9b38313..23e8a5106e46 100644 --- a/python/mxnet/numpy/linalg.py +++ b/python/mxnet/numpy/linalg.py @@ -24,7 +24,7 @@ __all__ = ['norm', 'svd', 'cholesky', 'qr', 'inv', 'det', 'slogdet', 'solve', 'tensorinv', 'tensorsolve', 'pinv', 'eigvals', 'eig', 'eigvalsh', 'eigh', 'lstsq', 'matrix_rank', 'cross', 'diagonal', 'outer', - 'tensordot', 'trace', 'matrix_transpose', 'vecdot'] + 'tensordot', 'trace', 'matrix_transpose', 'vecdot', 'svdvals'] __all__ += fallback_linalg.__all__ @@ -703,6 +703,31 @@ def svd(a): return _mx_nd_np.linalg.svd(a) +def svdvals(a): + r""" + Computes the singular values of a matrix (or a stack of matrices) `x`. + + Parameters + ---------- + a : (..., M, N) ndarray + A real array with ``a.ndim >= 2`` and ``M <= N``. + + Returns + ------- + out : (..., M) ndarray + Vector(s) with the singular values, within each vector sorted in + descending order. The first ``a.ndim - 2`` dimensions have the same + size as those of the input `a`. + + .. note:: + `svdvals` is a standard api in + https://data-apis.org/array-api/latest/extensions/linear_algebra_functions.html#linalg-svdvals-x + instead of an official NumPy operator. + """ + _, s, _ = _mx_nd_np.linalg.svd(a) + return s + + def cholesky(a): r""" Cholesky decomposition. diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 1010475c605d..78239d65a909 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -5928,6 +5928,51 @@ def check_svd(UT, L, V, data_np): check_svd(UT, L, V, data_np) +@use_np +@pytest.mark.parametrize('shape', [ + (3, 3), + (3, 5), + (4, 4), + (4, 5), + (5, 5), + (5, 6), + (6, 6), + (0, 1), + (6, 5, 6), + (2, 3, 3, 4), + (4, 2, 1, 2), + (0, 5, 3, 3), + (5, 0, 3, 3), + (3, 3, 0, 0), +]) +@pytest.mark.parametrize('dtype', ['float32', 'float64']) +@pytest.mark.parametrize('hybridize', [False, True]) +def test_np_linalg_svdvals(shape, dtype, hybridize): + class TestSVD(HybridBlock): + def __init__(self): + super(TestSVD, self).__init__() + + def forward(self, data): + return np.linalg.svdvals(data) + + rtol = atol = 0.01 + test_svd = TestSVD() + if hybridize: + test_svd.hybridize() + data_np = onp.random.uniform(-10.0, 10.0, shape) + data_np = onp.array(data_np, dtype=dtype) + data = np.array(data_np, dtype=dtype) + if effective_dtype(data) == onp.dtype(np.float16): + pytest.skip() + mx_out = test_svd(data) + np_out = onp.linalg.svd(data, compute_uv=False) + # check svdvals validity + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=rtol, atol=atol) + # Test imperative once again + mx_out = np.linalg.svdvals(data) + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=rtol, atol=atol) + + @use_np def test_np_linalg_qr(): class TestQR(HybridBlock):