Skip to content

Commit 18d34b0

Browse files
Merge 238f0e4 into b57d71f
2 parents b57d71f + 238f0e4 commit 18d34b0

File tree

9 files changed

+422
-204
lines changed

9 files changed

+422
-204
lines changed

dpnp/backend/extensions/lapack/heevd.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extern std::pair<sycl::event, sycl::event>
4444
const std::int8_t upper_lower,
4545
dpctl::tensor::usm_ndarray eig_vecs,
4646
dpctl::tensor::usm_ndarray eig_vals,
47-
const std::vector<sycl::event> &depends);
47+
const std::vector<sycl::event> &depends = {});
4848

4949
extern void init_heevd_dispatch_table(void);
5050
} // namespace lapack

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"eig",
6969
"eigh",
7070
"eigvals",
71+
"eigvalsh",
7172
"inv",
7273
"matrix_power",
7374
"matrix_rank",
@@ -246,6 +247,19 @@ def eigh(a, UPLO="L"):
246247
247248
For full documentation refer to :obj:`numpy.linalg.eigh`.
248249
250+
Parameters
251+
----------
252+
a : (..., M, M) {dpnp.ndarray, usm_ndarray}
253+
A complex- or real-valued array whose eigenvalues and eigenvectors are to be computed.
254+
UPLO : {"L", "U"}, optional
255+
Specifies the calculation uses either the lower ("L") or upper ("U")
256+
triangular part of the matrix.
257+
Regardless of this choice, only the real parts of the diagonal are
258+
considered to preserve the Hermite matrix property.
259+
It therefore follows that the imaginary part of the diagonal
260+
will always be treated as zero.
261+
Default: "L".
262+
249263
Returns
250264
-------
251265
w : (..., M) dpnp.ndarray
@@ -255,15 +269,13 @@ def eigh(a, UPLO="L"):
255269
The column ``v[:, i]`` is the normalized eigenvector corresponding
256270
to the eigenvalue ``w[i]``.
257271
258-
Limitations
259-
-----------
260-
Parameter `a` is supported as :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`.
261-
Input array data types are limited by supported DPNP :ref:`Data types`.
262-
263272
See Also
264273
--------
265-
:obj:`dpnp.eig` : eigenvalues and right eigenvectors for non-symmetric arrays.
266-
:obj:`dpnp.eigvals` : eigenvalues of non-symmetric arrays.
274+
:obj:`dpnp.linalg.eigvalsh` : Compute the eigenvalues of a complex Hermitian or
275+
real symmetric matrix.
276+
:obj:`dpnp.linalg.eig` : Compute the eigenvalues and right eigenvectors of
277+
a square array.
278+
:obj:`dpnp.linalg.eigvals` : Compute the eigenvalues of a general matrix.
267279
268280
Examples
269281
--------
@@ -281,20 +293,13 @@ def eigh(a, UPLO="L"):
281293
"""
282294

283295
dpnp.check_supported_arrays_type(a)
296+
check_stacked_2d(a)
297+
check_stacked_square(a)
284298

299+
UPLO = UPLO.upper()
285300
if UPLO not in ("L", "U"):
286301
raise ValueError("UPLO argument must be 'L' or 'U'")
287302

288-
if a.ndim < 2:
289-
raise ValueError(
290-
"%d-dimensional array given. Array must be "
291-
"at least two-dimensional" % a.ndim
292-
)
293-
294-
m, n = a.shape[-2:]
295-
if m != n:
296-
raise ValueError("Last 2 dimensions of the array must be square")
297-
298303
return dpnp_eigh(a, UPLO=UPLO)
299304

300305

@@ -326,6 +331,64 @@ def eigvals(input):
326331
return call_origin(numpy.linalg.eigvals, input)
327332

328333

334+
def eigvalsh(a, UPLO="L"):
335+
"""
336+
eigvalsh(a, UPLO="L")
337+
338+
Compute the eigenvalues of a complex Hermitian or real symmetric matrix.
339+
340+
Main difference from :obj:`dpnp.linalg.eigh`: the eigenvectors are not computed.
341+
342+
For full documentation refer to :obj:`numpy.linalg.eigvalsh`.
343+
344+
Parameters
345+
----------
346+
a : (..., M, M) {dpnp.ndarray, usm_ndarray}
347+
A complex- or real-valued array whose eigenvalues are to be computed.
348+
UPLO : {"L", "U"}, optional
349+
Specifies the calculation uses either the lower ("L") or upper ("U")
350+
triangular part of the matrix.
351+
Regardless of this choice, only the real parts of the diagonal are
352+
considered to preserve the Hermite matrix property.
353+
It therefore follows that the imaginary part of the diagonal
354+
will always be treated as zero.
355+
Default: "L".
356+
357+
Returns
358+
-------
359+
w : (..., M) dpnp.ndarray
360+
The eigenvalues in ascending order, each repeated according to
361+
its multiplicity.
362+
363+
See Also
364+
--------
365+
:obj:`dpnp.linalg.eigh` : Return the eigenvalues and eigenvectors of a complex Hermitian
366+
(conjugate symmetric) or a real symmetric matrix.
367+
:obj:`dpnp.linalg.eigvals` : Compute the eigenvalues of a general matrix.
368+
:obj:`dpnp.linalg.eig` : Compute the eigenvalues and right eigenvectors of
369+
a general matrix.
370+
371+
Examples
372+
--------
373+
>>> import dpnp as np
374+
>>> from dpnp import linalg as LA
375+
>>> a = np.array([[1, -2j], [2j, 5]])
376+
>>> LA.eigvalsh(a)
377+
array([0.17157288, 5.82842712])
378+
379+
"""
380+
381+
dpnp.check_supported_arrays_type(a)
382+
check_stacked_2d(a)
383+
check_stacked_square(a)
384+
385+
UPLO = UPLO.upper()
386+
if UPLO not in ("L", "U"):
387+
raise ValueError("UPLO argument must be 'L' or 'U'")
388+
389+
return dpnp_eigh(a, UPLO=UPLO, eigen_mode="N")
390+
391+
329392
def inv(a):
330393
"""
331394
Compute the (multiplicative) inverse of a matrix.

dpnp/linalg/dpnp_utils_linalg.py

Lines changed: 90 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -857,72 +857,91 @@ def dpnp_det(a):
857857
return det.reshape(shape)
858858

859859

860-
def dpnp_eigh(a, UPLO):
860+
def dpnp_eigh(a, UPLO, eigen_mode="V"):
861861
"""
862-
dpnp_eigh(a, UPLO)
862+
dpnp_eigh(a, UPLO, eigen_mode="V")
863863
864864
Return the eigenvalues and eigenvectors of a complex Hermitian
865865
(conjugate symmetric) or a real symmetric matrix.
866+
Can return both eigenvalues and eigenvectors (`eigen_mode="V"`) or
867+
only eigenvalues (`eigen_mode="N"`).
866868
867869
The main calculation is done by calling an extension function
868870
for LAPACK library of OneMKL. Depending on input type of `a` array,
869871
it will be either ``heevd`` (for complex types) or ``syevd`` (for others).
870872
871873
"""
872874

873-
a_usm_type = a.usm_type
874-
a_sycl_queue = a.sycl_queue
875-
a_order = "C" if a.flags.c_contiguous else "F"
876-
a_usm_arr = dpnp.get_usm_ndarray(a)
875+
# get resulting type of arrays with eigenvalues and eigenvectors
876+
v_type = _common_type(a)
877+
w_type = _real_type(v_type)
877878

878-
# 'V' means both eigenvectors and eigenvalues will be calculated
879-
jobz = _jobz["V"]
879+
if a.size == 0:
880+
w = dpnp.empty_like(a, shape=a.shape[:-1], dtype=w_type)
881+
if eigen_mode == "V":
882+
v = dpnp.empty_like(a, dtype=v_type)
883+
return w, v
884+
return w
885+
886+
# `eigen_mode` can be either "N" or "V", specifying the computation mode
887+
# for OneMKL LAPACK `syevd` and `heevd` routines.
888+
# "V" (default) means both eigenvectors and eigenvalues will be calculated
889+
# "N" means only eigenvalues will be calculated
890+
jobz = _jobz[eigen_mode]
880891
uplo = _upper_lower[UPLO]
881892

882-
# get resulting type of arrays with eigenvalues and eigenvectors
883-
a_dtype = a.dtype
884-
lapack_func = "_syevd"
885-
if dpnp.issubdtype(a_dtype, dpnp.complexfloating):
886-
lapack_func = "_heevd"
887-
v_type = a_dtype
888-
w_type = dpnp.float64 if a_dtype == dpnp.complex128 else dpnp.float32
889-
elif dpnp.issubdtype(a_dtype, dpnp.floating):
890-
v_type = w_type = a_dtype
891-
elif a_sycl_queue.sycl_device.has_aspect_fp64:
892-
v_type = w_type = dpnp.float64
893-
else:
894-
v_type = w_type = dpnp.float32
893+
# Get LAPACK function (_syevd for real or _heevd for complex data types)
894+
# to compute all eigenvalues and, optionally, all eigenvectors
895+
lapack_func = (
896+
"_heevd" if dpnp.issubdtype(v_type, dpnp.complexfloating) else "_syevd"
897+
)
898+
899+
a_sycl_queue = a.sycl_queue
900+
a_order = "C" if a.flags.c_contiguous else "F"
895901

896902
if a.ndim > 2:
897-
w = dpnp.empty(
898-
a.shape[:-1],
903+
is_cpu_device = a.sycl_device.has_aspect_cpu
904+
orig_shape = a.shape
905+
# get 3d input array by reshape
906+
a = a.reshape(-1, orig_shape[-2], orig_shape[-1])
907+
a_usm_arr = dpnp.get_usm_ndarray(a)
908+
909+
# allocate a memory for dpnp array of eigenvalues
910+
w = dpnp.empty_like(
911+
a,
912+
shape=orig_shape[:-1],
899913
dtype=w_type,
900-
usm_type=a_usm_type,
901-
sycl_queue=a_sycl_queue,
902914
)
915+
w_orig_shape = w.shape
916+
# get 2d dpnp array with eigenvalues by reshape
917+
w = w.reshape(-1, w_orig_shape[-1])
903918

904919
# need to loop over the 1st dimension to get eigenvalues and eigenvectors of 3d matrix A
905-
op_count = a.shape[0]
906-
if op_count == 0:
907-
return w, dpnp.empty_like(a, dtype=v_type)
908-
909-
eig_vecs = [None] * op_count
910-
ht_copy_ev = [None] * op_count
911-
ht_lapack_ev = [None] * op_count
912-
for i in range(op_count):
920+
batch_size = a.shape[0]
921+
eig_vecs = [None] * batch_size
922+
ht_list_ev = [None] * batch_size * 2
923+
for i in range(batch_size):
913924
# oneMKL LAPACK assumes fortran-like array as input, so
914925
# allocate a memory with 'F' order for dpnp array of eigenvectors
915926
eig_vecs[i] = dpnp.empty_like(a[i], order="F", dtype=v_type)
916927

917928
# use DPCTL tensor function to fill the array of eigenvectors with content of input array
918-
ht_copy_ev[i], copy_ev = ti._copy_usm_ndarray_into_usm_ndarray(
929+
ht_list_ev[2 * i], copy_ev = ti._copy_usm_ndarray_into_usm_ndarray(
919930
src=a_usm_arr[i],
920931
dst=eig_vecs[i].get_array(),
921932
sycl_queue=a_sycl_queue,
922933
)
923934

935+
# TODO: Remove this w/a when MKLD-17201 is solved.
936+
# Waiting for a host task executing an OneMKL LAPACK syevd call
937+
# on CPU causes deadlock due to serialization of all host tasks
938+
# in the queue.
939+
# We need to wait for each host tasks before calling _seyvd to avoid deadlock.
940+
if lapack_func == "_syevd" and is_cpu_device:
941+
ht_list_ev[2 * i].wait()
942+
924943
# call LAPACK extension function to get eigenvalues and eigenvectors of a portion of matrix A
925-
ht_lapack_ev[i], _ = getattr(li, lapack_func)(
944+
ht_list_ev[2 * i + 1], _ = getattr(li, lapack_func)(
926945
a_sycl_queue,
927946
jobz,
928947
uplo,
@@ -931,29 +950,43 @@ def dpnp_eigh(a, UPLO):
931950
depends=[copy_ev],
932951
)
933952

934-
for i in range(op_count):
935-
ht_lapack_ev[i].wait()
936-
ht_copy_ev[i].wait()
953+
dpctl.SyclEvent.wait_for(ht_list_ev)
954+
955+
w = w.reshape(w_orig_shape)
956+
957+
if eigen_mode == "V":
958+
# combine the list of eigenvectors into a single array
959+
v = dpnp.array(eig_vecs, order=a_order).reshape(orig_shape)
960+
return w, v
961+
return w
937962

938-
# combine the list of eigenvectors into a single array
939-
v = dpnp.array(eig_vecs, order=a_order)
940-
return w, v
941963
else:
942-
# oneMKL LAPACK assumes fortran-like array as input, so
943-
# allocate a memory with 'F' order for dpnp array of eigenvectors
944-
v = dpnp.empty_like(a, order="F", dtype=v_type)
964+
a_usm_arr = dpnp.get_usm_ndarray(a)
965+
ht_list_ev = []
966+
copy_ev = dpctl.SyclEvent()
945967

946-
# use DPCTL tensor function to fill the array of eigenvectors with content of input array
947-
ht_copy_ev, copy_ev = ti._copy_usm_ndarray_into_usm_ndarray(
948-
src=a_usm_arr, dst=v.get_array(), sycl_queue=a_sycl_queue
949-
)
968+
# When `eigen_mode == "N"` (jobz == 0), OneMKL LAPACK does not overwrite the input array.
969+
# If the input array 'a' is already F-contiguous and matches the target data type,
970+
# we can avoid unnecessary memory allocation and data copying.
971+
if eigen_mode == "N" and a_order == "F" and a.dtype == v_type:
972+
v = a
973+
974+
else:
975+
# oneMKL LAPACK assumes fortran-like array as input, so
976+
# allocate a memory with 'F' order for dpnp array of eigenvectors
977+
v = dpnp.empty_like(a, order="F", dtype=v_type)
978+
979+
# use DPCTL tensor function to fill the array of eigenvectors with content of input array
980+
ht_copy_ev, copy_ev = ti._copy_usm_ndarray_into_usm_ndarray(
981+
src=a_usm_arr, dst=v.get_array(), sycl_queue=a_sycl_queue
982+
)
983+
ht_list_ev.append(ht_copy_ev)
950984

951985
# allocate a memory for dpnp array of eigenvalues
952-
w = dpnp.empty(
953-
a.shape[:-1],
986+
w = dpnp.empty_like(
987+
a,
988+
shape=a.shape[:-1],
954989
dtype=w_type,
955-
usm_type=a_usm_type,
956-
sycl_queue=a_sycl_queue,
957990
)
958991

959992
# call LAPACK extension function to get eigenvalues and eigenvectors of matrix A
@@ -965,8 +998,9 @@ def dpnp_eigh(a, UPLO):
965998
w.get_array(),
966999
depends=[copy_ev],
9671000
)
1001+
ht_list_ev.append(ht_lapack_ev)
9681002

969-
if a_order != "F":
1003+
if eigen_mode == "V" and a_order != "F":
9701004
# need to align order of eigenvectors with one of input matrix A
9711005
out_v = dpnp.empty_like(v, order=a_order)
9721006
ht_copy_out_ev, _ = ti._copy_usm_ndarray_into_usm_ndarray(
@@ -975,14 +1009,13 @@ def dpnp_eigh(a, UPLO):
9751009
sycl_queue=a_sycl_queue,
9761010
depends=[lapack_ev],
9771011
)
978-
ht_copy_out_ev.wait()
1012+
ht_list_ev.append(ht_copy_out_ev)
9791013
else:
9801014
out_v = v
9811015

982-
ht_lapack_ev.wait()
983-
ht_copy_ev.wait()
1016+
dpctl.SyclEvent.wait_for(ht_list_ev)
9841017

985-
return w, out_v
1018+
return (w, out_v) if eigen_mode == "V" else w
9861019

9871020

9881021
def dpnp_inv_batched(a, res_type):

0 commit comments

Comments
 (0)