Skip to content

Commit 10314fd

Browse files
Merge master into impl_tensorsolve
2 parents e737658 + e5d3127 commit 10314fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1339
-1058
lines changed

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
dpnp_det,
5252
dpnp_eigh,
5353
dpnp_inv,
54+
dpnp_matrix_power,
5455
dpnp_matrix_rank,
5556
dpnp_multi_dot,
5657
dpnp_pinv,
@@ -77,6 +78,7 @@
7778
"solve",
7879
"svd",
7980
"slogdet",
81+
"tensorinv",
8082
"tensorsolve",
8183
]
8284

@@ -371,33 +373,65 @@ def inv(a):
371373
return dpnp_inv(a)
372374

373375

374-
def matrix_power(input, count):
376+
def matrix_power(a, n):
375377
"""
376-
Raise a square matrix to the (integer) power `count`.
378+
Raise a square matrix to the (integer) power `n`.
379+
380+
For full documentation refer to :obj:`numpy.linalg.matrix_power`.
377381
378382
Parameters
379383
----------
380-
input : sequence of array_like
384+
a : (..., M, M) {dpnp.ndarray, usm_ndarray}
385+
Matrix to be "powered".
386+
n : int
387+
The exponent can be any integer or long integer, positive, negative, or zero.
381388
382389
Returns
383390
-------
384-
output : array
385-
Returns the dot product of the supplied arrays.
391+
a**n : (..., M, M) dpnp.ndarray
392+
The return value is the same shape and type as `M`;
393+
if the exponent is positive or zero then the type of the
394+
elements is the same as those of `M`. If the exponent is
395+
negative the elements are floating-point.
386396
387-
See Also
388-
--------
389-
:obj:`numpy.linalg.matrix_power`
397+
>>> import dpnp as np
398+
>>> i = np.array([[0, 1], [-1, 0]]) # matrix equiv. of the imaginary unit
399+
>>> np.linalg.matrix_power(i, 3) # should = -i
400+
array([[ 0, -1],
401+
[ 1, 0]])
402+
>>> np.linalg.matrix_power(i, 0)
403+
array([[1, 0],
404+
[0, 1]])
405+
>>> np.linalg.matrix_power(i, -3) # should = 1/(-i) = i, but w/ f.p. elements
406+
array([[ 0., 1.],
407+
[-1., 0.]])
408+
409+
Somewhat more sophisticated example
410+
411+
>>> q = np.zeros((4, 4))
412+
>>> q[0:2, 0:2] = -i
413+
>>> q[2:4, 2:4] = i
414+
>>> q # one of the three quaternion units not equal to 1
415+
array([[ 0., -1., 0., 0.],
416+
[ 1., 0., 0., 0.],
417+
[ 0., 0., 0., 1.],
418+
[ 0., 0., -1., 0.]])
419+
>>> np.linalg.matrix_power(q, 2) # = -np.eye(4)
420+
array([[-1., 0., 0., 0.],
421+
[ 0., -1., 0., 0.],
422+
[ 0., 0., -1., 0.],
423+
[ 0., 0., 0., -1.]])
390424
391425
"""
392426

393-
if not use_origin_backend() and count > 0:
394-
result = input
395-
for _ in range(count - 1):
396-
result = dpnp.matmul(result, input)
427+
dpnp.check_supported_arrays_type(a)
428+
check_stacked_2d(a)
429+
check_stacked_square(a)
397430

398-
return result
431+
if not isinstance(n, int):
432+
raise TypeError("exponent must be an integer")
399433

400-
return call_origin(numpy.linalg.matrix_power, input, count)
434+
return dpnp_matrix_power(a, n)
401435

402436

403437
def matrix_rank(A, tol=None, hermitian=False):
@@ -900,6 +934,64 @@ def slogdet(a):
900934
return dpnp_slogdet(a)
901935

902936

937+
def tensorinv(a, ind=2):
938+
"""
939+
Compute the `inverse` of a tensor.
940+
941+
For full documentation refer to :obj:`numpy.linalg.tensorinv`.
942+
943+
Parameters
944+
----------
945+
a : {dpnp.ndarray, usm_ndarray}
946+
Tensor to `invert`. Its shape must be 'square', i. e.,
947+
``prod(a.shape[:ind]) == prod(a.shape[ind:])``.
948+
ind : int
949+
Number of first indices that are involved in the inverse sum.
950+
Must be a positive integer.
951+
Default: 2.
952+
953+
Returns
954+
-------
955+
out : dpnp.ndarray
956+
The inverse of a tensor whose shape is equivalent to
957+
``a.shape[ind:] + a.shape[:ind]``.
958+
959+
See Also
960+
--------
961+
:obj:`dpnp.linalg.tensordot` : Compute tensor dot product along specified axes.
962+
:obj:`dpnp.linalg.tensorsolve` : Solve the tensor equation ``a x = b`` for x.
963+
964+
Examples
965+
--------
966+
>>> import dpnp as np
967+
>>> a = np.eye(4*6)
968+
>>> a.shape = (4, 6, 8, 3)
969+
>>> ainv = np.linalg.tensorinv(a, ind=2)
970+
>>> ainv.shape
971+
(8, 3, 4, 6)
972+
973+
>>> a = np.eye(4*6)
974+
>>> a.shape = (24, 8, 3)
975+
>>> ainv = np.linalg.tensorinv(a, ind=1)
976+
>>> ainv.shape
977+
(8, 3, 24)
978+
979+
"""
980+
981+
dpnp.check_supported_arrays_type(a)
982+
983+
if ind <= 0:
984+
raise ValueError("Invalid ind argument")
985+
986+
old_shape = a.shape
987+
inv_shape = old_shape[ind:] + old_shape[:ind]
988+
prod = numpy.prod(old_shape[ind:])
989+
a = a.reshape(prod, -1)
990+
a_inv = inv(a)
991+
992+
return a_inv.reshape(*inv_shape)
993+
994+
903995
def tensorsolve(a, b, axes=None):
904996
"""
905997
Solve the tensor equation ``a x = b`` for x.

dpnp/linalg/dpnp_utils_linalg.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"dpnp_det",
4141
"dpnp_eigh",
4242
"dpnp_inv",
43+
"dpnp_matrix_power",
4344
"dpnp_matrix_rank",
4445
"dpnp_multi_dot",
4546
"dpnp_pinv",
@@ -526,9 +527,50 @@ def _stacked_identity(
526527
"""
527528

528529
shape = batch_shape + (n, n)
529-
idx = dpnp.arange(n, usm_type=usm_type, sycl_queue=sycl_queue)
530-
x = dpnp.zeros(shape, dtype=dtype, usm_type=usm_type, sycl_queue=sycl_queue)
531-
x[..., idx, idx] = 1
530+
x = dpnp.empty(shape, dtype=dtype, usm_type=usm_type, sycl_queue=sycl_queue)
531+
x[...] = dpnp.eye(
532+
n, dtype=x.dtype, usm_type=x.usm_type, sycl_queue=x.sycl_queue
533+
)
534+
return x
535+
536+
537+
def _stacked_identity_like(x):
538+
"""
539+
Create stacked identity matrices based on the shape and properties of `x`.
540+
541+
Parameters
542+
----------
543+
x : dpnp.ndarray
544+
Input array based on whose properties (shape, data type, USM type and SYCL queue)
545+
the identity matrices will be created.
546+
547+
Returns
548+
-------
549+
out : dpnp.ndarray
550+
Array of stacked `n x n` identity matrices,
551+
where `n` is the size of the last dimension of `x`.
552+
The returned array has the same shape, data type, USM type
553+
and uses the same SYCL queue as `x`, if applicable.
554+
555+
Example
556+
-------
557+
>>> import dpnp
558+
>>> x = dpnp.zeros((2, 3, 3), dtype=dpnp.int64)
559+
>>> _stacked_identity_like(x)
560+
array([[[1, 0, 0],
561+
[0, 1, 0],
562+
[0, 0, 1]],
563+
564+
[[1, 0, 0],
565+
[0, 1, 0],
566+
[0, 0, 1]]], dtype=int32)
567+
568+
"""
569+
570+
x = dpnp.empty_like(x)
571+
x[...] = dpnp.eye(
572+
x.shape[-2], dtype=x.dtype, usm_type=x.usm_type, sycl_queue=x.sycl_queue
573+
)
532574
return x
533575

534576

@@ -1082,6 +1124,46 @@ def dpnp_inv(a):
10821124
return b_f
10831125

10841126

1127+
def dpnp_matrix_power(a, n):
1128+
"""
1129+
dpnp_matrix_power(a, n)
1130+
1131+
Raise a square matrix to the (integer) power `n`.
1132+
1133+
"""
1134+
1135+
if n == 0:
1136+
return _stacked_identity_like(a)
1137+
elif n < 0:
1138+
a = dpnp.linalg.inv(a)
1139+
n *= -1
1140+
1141+
if n == 1:
1142+
return a
1143+
elif n == 2:
1144+
return dpnp.matmul(a, a)
1145+
elif n == 3:
1146+
return dpnp.matmul(dpnp.matmul(a, a), a)
1147+
1148+
# Use binary decomposition to reduce the number of matrix
1149+
# multiplications for n > 3.
1150+
# `result` will hold the final matrix power,
1151+
# while `acc` serves as an accumulator for the intermediate matrix powers.
1152+
result = None
1153+
acc = a.copy()
1154+
while n > 0:
1155+
n, bit = divmod(n, 2)
1156+
if bit:
1157+
if result is None:
1158+
result = acc.copy()
1159+
else:
1160+
dpnp.matmul(result, acc, out=result)
1161+
if n > 0:
1162+
dpnp.matmul(acc, acc, out=acc)
1163+
1164+
return result
1165+
1166+
10851167
def dpnp_matrix_rank(A, tol=None, hermitian=False):
10861168
"""
10871169
dpnp_matrix_rank(A, tol=None, hermitian=False)

0 commit comments

Comments
 (0)