Skip to content

Commit 03d7a98

Browse files
Linalg svd func (#492)
* linalg.svd() impl
1 parent c082884 commit 03d7a98

File tree

8 files changed

+230
-8
lines changed

8 files changed

+230
-8
lines changed

dpnp/backend/include/dpnp_iface_fptr.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ enum class DPNPFuncName : size_t
153153
DPNP_FN_STD, /**< Used in numpy.std() implementation */
154154
DPNP_FN_SUBTRACT, /**< Used in numpy.subtract() implementation */
155155
DPNP_FN_SUM, /**< Used in numpy.sum() implementation */
156+
DPNP_FN_SVD, /**< Used in numpy.linalg.svd() implementation */
156157
DPNP_FN_TAN, /**< Used in numpy.tan() implementation */
157158
DPNP_FN_TANH, /**< Used in numpy.tanh() implementation */
158159
DPNP_FN_TRANSPOSE, /**< Used in numpy.transpose() implementation */

dpnp/backend/kernels/dpnp_krnl_linalg.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "queue_sycl.hpp"
3333

3434
namespace mkl_blas = oneapi::mkl::blas::row_major;
35+
namespace mkl_lapack = oneapi::mkl::lapack;
3536

3637
template <typename _DataType>
3738
class dpnp_cholesky_c_kernel;
@@ -309,6 +310,58 @@ void dpnp_matrix_rank_c(void* array1_in, void* result1, size_t* shape, size_t nd
309310
return;
310311
}
311312

313+
template <typename _InputDT, typename _ComputeDT, typename _SVDT>
314+
void dpnp_svd_c(void* array1_in, void* result1, void* result2, void* result3, size_t size_m, size_t size_n)
315+
{
316+
cl::sycl::event event;
317+
318+
_InputDT* in_array = reinterpret_cast<_InputDT*>(array1_in);
319+
320+
// math lib gesvd func overrides input
321+
_ComputeDT* in_a = reinterpret_cast<_ComputeDT*>(dpnp_memory_alloc_c(size_m * size_n * sizeof(_ComputeDT)));
322+
for (size_t it = 0; it < size_m * size_n; ++it)
323+
{
324+
in_a[it] = in_array[it];
325+
}
326+
327+
_ComputeDT* res_u = reinterpret_cast<_ComputeDT*>(result1);
328+
_SVDT* res_s = reinterpret_cast<_SVDT*>(result2);
329+
_ComputeDT* res_vt = reinterpret_cast<_ComputeDT*>(result3);
330+
331+
const std::int64_t m = size_m;
332+
const std::int64_t n = size_n;
333+
334+
const std::int64_t lda = std::max<size_t>(1UL, n);
335+
const std::int64_t ldu = std::max<size_t>(1UL, m);
336+
const std::int64_t ldvt = std::max<size_t>(1UL, n);
337+
338+
const std::int64_t scratchpad_size1 = mkl_lapack::gesvd_scratchpad_size<_ComputeDT>(
339+
DPNP_QUEUE, oneapi::mkl::jobsvd::vectors, oneapi::mkl::jobsvd::vectors, n, m, lda, ldvt, ldu);
340+
341+
const std::int64_t scratchpad_size = scratchpad_size1;
342+
343+
_ComputeDT* scratchpad = reinterpret_cast<_ComputeDT*>(dpnp_memory_alloc_c(scratchpad_size * sizeof(_ComputeDT)));
344+
345+
event = mkl_lapack::gesvd(DPNP_QUEUE,
346+
oneapi::mkl::jobsvd::vectors, // onemkl::job jobu,
347+
oneapi::mkl::jobsvd::vectors, // onemkl::job jobvt,
348+
n,
349+
m,
350+
in_a,
351+
lda,
352+
res_s,
353+
res_vt,
354+
ldvt,
355+
res_u,
356+
ldu,
357+
scratchpad,
358+
scratchpad_size);
359+
360+
event.wait();
361+
362+
dpnp_memory_free_c(scratchpad);
363+
}
364+
312365
void func_map_init_linalg_func(func_map_t& fmap)
313366
{
314367
fmap[DPNPFuncName::DPNP_FN_CHOLESKY][eft_INT][eft_INT] = {eft_INT, (void*)dpnp_cholesky_c<int>};
@@ -331,5 +384,12 @@ void func_map_init_linalg_func(func_map_t& fmap)
331384
fmap[DPNPFuncName::DPNP_FN_MATRIX_RANK][eft_FLT][eft_FLT] = {eft_FLT, (void*)dpnp_matrix_rank_c<float>};
332385
fmap[DPNPFuncName::DPNP_FN_MATRIX_RANK][eft_DBL][eft_DBL] = {eft_DBL, (void*)dpnp_matrix_rank_c<double>};
333386

387+
fmap[DPNPFuncName::DPNP_FN_SVD][eft_INT][eft_INT] = {eft_DBL, (void*)dpnp_svd_c<int, double, double>};
388+
fmap[DPNPFuncName::DPNP_FN_SVD][eft_LNG][eft_LNG] = {eft_DBL, (void*)dpnp_svd_c<long, double, double>};
389+
fmap[DPNPFuncName::DPNP_FN_SVD][eft_FLT][eft_FLT] = {eft_FLT, (void*)dpnp_svd_c<float, float, float>};
390+
fmap[DPNPFuncName::DPNP_FN_SVD][eft_DBL][eft_DBL] = {eft_DBL, (void*)dpnp_svd_c<double, double, double>};
391+
fmap[DPNPFuncName::DPNP_FN_SVD][eft_C128][eft_C128] = {
392+
eft_C128, (void*)dpnp_svd_c<std::complex<double>, std::complex<double>, double>};
393+
334394
return;
335395
}

dpnp/dpnp_algo/dpnp_algo.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ cdef extern from "dpnp_iface_fptr.hpp" namespace "DPNPFuncName": # need this na
126126
DPNP_FN_STD
127127
DPNP_FN_SUBTRACT
128128
DPNP_FN_SUM
129+
DPNP_FN_SVD
129130
DPNP_FN_TAN
130131
DPNP_FN_TANH
131132
DPNP_FN_TRANSPOSE

dpnp/linalg/dpnp_algo_linalg.pyx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,16 @@ __all__ = [
4848
"dpnp_eigvals",
4949
"dpnp_inv",
5050
"dpnp_matrix_rank",
51-
"dpnp_norm"
51+
"dpnp_norm",
52+
"dpnp_svd",
5253
]
5354

5455

5556
# C function pointer to the C library template functions
5657
ctypedef void(*custom_linalg_1in_1out_func_ptr_t)(void *, void * , size_t * , size_t)
57-
58-
59-
# C function pointer to the C library template functions
6058
ctypedef void(*custom_linalg_1in_1out_func_ptr_t_)(void * , void * , size_t * )
61-
62-
63-
# C function pointer to the C library template functions
6459
ctypedef void(*custom_linalg_1in_1out_with_size_func_ptr_t_)(void *, void * , size_t)
60+
ctypedef void(*custom_linalg_1in_3out_shape_t)(void *, void * , void * , void * , size_t , size_t )
6561

6662

6763
cpdef dparray dpnp_cholesky(dparray input):
@@ -300,3 +296,30 @@ cpdef dparray dpnp_norm(dparray input, ord=None, axis=None):
300296
return ret
301297
else:
302298
raise ValueError("Improper number of dimensions to norm.")
299+
300+
301+
cpdef tuple dpnp_svd(dparray x1, full_matrices, compute_uv, hermitian):
302+
cdef size_t size_m = x1.shape[0]
303+
cdef size_t size_n = x1.shape[1]
304+
305+
cdef DPNPFuncType param1_type = dpnp_dtype_to_DPNPFuncType(x1.dtype)
306+
cdef DPNPFuncData kernel_data = get_dpnp_function_ptr(DPNP_FN_SVD, param1_type, param1_type)
307+
308+
result_type = dpnp_DPNPFuncType_to_dtype(< size_t > kernel_data.return_type)
309+
310+
if x1.dtype == dpnp.float32:
311+
type_s = dpnp.float32
312+
else:
313+
type_s = dpnp.float64
314+
315+
size_s = min(size_m, size_n)
316+
317+
cdef dparray res_u = dparray((size_m, size_m), dtype=result_type)
318+
cdef dparray res_s = dparray((size_s, ), dtype=type_s)
319+
cdef dparray res_vt = dparray((size_n, size_n), dtype=result_type)
320+
321+
cdef custom_linalg_1in_3out_shape_t func = < custom_linalg_1in_3out_shape_t > kernel_data.ptr
322+
323+
func(x1.get_data(), res_u.get_data(), res_s.get_data(), res_vt.get_data(), size_m, size_n)
324+
325+
return (res_u, res_s, res_vt)

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"matrix_power",
5959
"matrix_rank",
6060
"multi_dot",
61-
"norm"
61+
"norm",
62+
"svd",
6263
]
6364

6465

@@ -394,3 +395,77 @@ def norm(input, ord=None, axis=None, keepdims=False):
394395
return result
395396

396397
return call_origin(numpy.linalg.norm, input, ord, axis, keepdims)
398+
399+
400+
def svd(a, full_matrices=True, compute_uv=True, hermitian=False):
401+
"""
402+
Singular Value Decomposition.
403+
404+
For full documentation refer to :obj:`numpy.linalg.svd`.
405+
406+
Examples
407+
--------
408+
>>> import dpnp as np
409+
>>> a = np.random.randn(9, 6) + 1j*np.random.randn(9, 6)
410+
>>> b = np.random.randn(2, 7, 8, 3) + 1j*np.random.randn(2, 7, 8, 3)
411+
412+
Reconstruction based on full SVD, 2D case:
413+
414+
>>> u, s, vh = np.linalg.svd(a, full_matrices=True)
415+
>>> u.shape, s.shape, vh.shape
416+
((9, 9), (6,), (6, 6))
417+
>>> np.allclose(a, np.dot(u[:, :6] * s, vh))
418+
True
419+
>>> smat = np.zeros((9, 6), dtype=complex)
420+
>>> smat[:6, :6] = np.diag(s)
421+
>>> np.allclose(a, np.dot(u, np.dot(smat, vh)))
422+
True
423+
424+
Reconstruction based on reduced SVD, 2D case:
425+
426+
>>> u, s, vh = np.linalg.svd(a, full_matrices=False)
427+
>>> u.shape, s.shape, vh.shape
428+
((9, 6), (6,), (6, 6))
429+
>>> np.allclose(a, np.dot(u * s, vh))
430+
True
431+
>>> smat = np.diag(s)
432+
>>> np.allclose(a, np.dot(u, np.dot(smat, vh)))
433+
True
434+
435+
Reconstruction based on full SVD, 4D case:
436+
437+
>>> u, s, vh = np.linalg.svd(b, full_matrices=True)
438+
>>> u.shape, s.shape, vh.shape
439+
((2, 7, 8, 8), (2, 7, 3), (2, 7, 3, 3))
440+
>>> np.allclose(b, np.matmul(u[..., :3] * s[..., None, :], vh))
441+
True
442+
>>> np.allclose(b, np.matmul(u[..., :3], s[..., None] * vh))
443+
True
444+
445+
Reconstruction based on reduced SVD, 4D case:
446+
447+
>>> u, s, vh = np.linalg.svd(b, full_matrices=False)
448+
>>> u.shape, s.shape, vh.shape
449+
((2, 7, 8, 3), (2, 7, 3), (2, 7, 3, 3))
450+
>>> np.allclose(b, np.matmul(u * s[..., None, :], vh))
451+
True
452+
>>> np.allclose(b, np.matmul(u, s[..., None] * vh))
453+
True
454+
455+
"""
456+
457+
if not use_origin_backend(a):
458+
if not isinstance(a, dparray):
459+
pass
460+
if not a.ndim == 2:
461+
pass
462+
if not full_matrices == True:
463+
pass
464+
if not compute_uv == True:
465+
pass
466+
if not hermitian == False:
467+
pass
468+
else:
469+
return dpnp_svd(a, full_matrices, compute_uv, hermitian)
470+
471+
return call_origin(numpy.linalg.svd, a, full_matrices, compute_uv, hermitian)

tests/skipped_tests.tbl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ tests/test_linalg.py::test_norm3[(1, 2)-2-[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]]
137137
tests/test_linalg.py::test_norm3[(1, 2)-2-[[[1, 0], [3, 0]], [[5, 0], [7, 0]]]]
138138
tests/test_linalg.py::test_norm3[(1, 2)-3-[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]]
139139
tests/test_linalg.py::test_norm3[(1, 2)-3-[[[1, 0], [3, 0]], [[5, 0], [7, 0]]]]
140+
tests/test_linalg.py::test_svd[(3,4)-complex128]
141+
tests/test_linalg.py::test_svd[(5,3)-complex128]
140142
tests/test_random.py::TestDistributionsRayleigh::test_moments
141143
tests/third_party/cupy/binary_tests/test_elementwise.py::TestElementwise::test_bitwise_and
142144
tests/third_party/cupy/binary_tests/test_elementwise.py::TestElementwise::test_bitwise_or

tests/skipped_tests_gpu.tbl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ tests/test_linalg.py::test_norm3[None--numpy.Inf-[[[1, 0], [3, 0]], [[5, 0], [7,
142142
tests/test_linalg.py::test_norm3[None-numpy.Inf-[[[1, 0], [3, 0]], [[5, 0], [7, 0]]]]
143143
tests/test_linalg.py::test_norm3[None--numpy.Inf-[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]]
144144
tests/test_linalg.py::test_norm3[None-numpy.Inf-[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]]
145+
tests/test_linalg.py::test_svd[(3,4)-float64]
146+
tests/test_linalg.py::test_svd[(3,4)-float32]
147+
tests/test_linalg.py::test_svd[(3,4)-int64]
148+
tests/test_linalg.py::test_svd[(3,4)-int32]
149+
tests/test_linalg.py::test_svd[(3,4)-complex128]
150+
tests/test_linalg.py::test_svd[(5,3)-float64]
151+
tests/test_linalg.py::test_svd[(5,3)-float32]
152+
tests/test_linalg.py::test_svd[(5,3)-int64]
153+
tests/test_linalg.py::test_svd[(5,3)-int32]
154+
tests/test_linalg.py::test_svd[(5,3)-complex128]
155+
tests/test_linalg.py::test_svd[(16,16)-float64]
156+
tests/test_linalg.py::test_svd[(16,16)-float32]
157+
tests/test_linalg.py::test_svd[(16,16)-int64]
158+
tests/test_linalg.py::test_svd[(16,16)-int32]
145159
tests/test_random.py::TestDistributionsRayleigh::test_moments
146160
tests/test_statistics.py::test_median[2-float32]
147161
tests/test_statistics.py::test_median[2-float64]

tests/test_linalg.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,49 @@ def test_norm3(array, ord, axis):
197197
result = inp.linalg.norm(ia, ord=ord, axis=axis)
198198
expected = numpy.linalg.norm(a, ord=ord, axis=axis)
199199
numpy.testing.assert_array_equal(expected, result)
200+
201+
202+
@pytest.mark.parametrize("type",
203+
[numpy.float64, numpy.float32, numpy.int64, numpy.int32, numpy.complex128],
204+
ids=['float64', 'float32', 'int64', 'int32', 'complex128'])
205+
@pytest.mark.parametrize("shape",
206+
[(2,2), (3,4), (5,3), (16,16)],
207+
ids=['(2,2)', '(3,4)', '(5,3)', '(16,16)'])
208+
def test_svd(type, shape):
209+
a = numpy.arange(shape[0] * shape[1], dtype=type).reshape(shape)
210+
ia = inp.array(a)
211+
212+
np_u, np_s, np_vt = numpy.linalg.svd(a)
213+
dpnp_u, dpnp_s, dpnp_vt = inp.linalg.svd(ia)
214+
215+
assert (dpnp_u.dtype == np_u.dtype)
216+
assert (dpnp_s.dtype == np_s.dtype)
217+
assert (dpnp_vt.dtype == np_vt.dtype)
218+
assert (dpnp_u.shape == np_u.shape)
219+
assert (dpnp_s.shape == np_s.shape)
220+
assert (dpnp_vt.shape == np_vt.shape)
221+
222+
if type == numpy.float32:
223+
tol = 1e-03
224+
else:
225+
tol = 1e-12
226+
227+
# check decomposition
228+
dpnp_diag_s = numpy.zeros(shape, dtype=dpnp_s.dtype)
229+
for i in range(len(dpnp_s)):
230+
dpnp_diag_s[i, i] = dpnp_s[i]
231+
numpy.testing.assert_allclose(ia, numpy.dot(dpnp_u, numpy.dot(dpnp_diag_s, dpnp_vt)), rtol=tol, atol=tol)
232+
233+
# compare singular values
234+
numpy.testing.assert_allclose(dpnp_s, np_s, rtol=tol, atol=tol)
235+
236+
# change sign of vectors
237+
for i in range(min(shape[0], shape[1])):
238+
if np_u[0, i] * dpnp_u[0, i] < 0:
239+
np_u[:, i] = -np_u[:, i]
240+
np_vt[i, :] = -np_vt[i, :]
241+
242+
# compare vectors for non-zero values
243+
for i in range(numpy.count_nonzero(np_s > tol)):
244+
numpy.testing.assert_allclose(numpy.array(dpnp_u)[:, i], np_u[:, i], rtol=tol, atol=tol)
245+
numpy.testing.assert_allclose(numpy.array(dpnp_vt)[i, :], np_vt[i, :], rtol=tol, atol=tol)

0 commit comments

Comments
 (0)