Skip to content

Commit 4fc2c9e

Browse files
authored
Merge branch 'master' into update_element_wise_func
2 parents 9ade2ca + b57d71f commit 4fc2c9e

Some content is hidden

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

48 files changed

+1514
-1062
lines changed

.github/workflows/conda-package.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ jobs:
108108
run: conda install conda-build=${{ env.CONDA_BUILD_VERSION}}
109109

110110
- name: Cache conda packages
111-
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
111+
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
112112
env:
113113
CACHE_NUMBER: 1 # Increase to reset cache
114114
with:
@@ -199,7 +199,7 @@ jobs:
199199
TEST_CHANNELS: '-c ${{ env.channel-path }} ${{ env.CHANNELS }}'
200200

201201
- name: Cache conda packages
202-
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
202+
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
203203
env:
204204
CACHE_NUMBER: 1 # Increase to reset cache
205205
with:
@@ -338,7 +338,7 @@ jobs:
338338
run: more lockfile
339339

340340
- name: Cache conda packages
341-
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
341+
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
342342
env:
343343
CACHE_NUMBER: 1 # Increase to reset cache
344344
with:

.github/workflows/openssf-scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ jobs:
6868

6969
# Upload the results to GitHub's code scanning dashboard.
7070
- name: "Upload to code-scanning"
71-
uses: github/codeql-action/upload-sarif@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7
71+
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
7272
with:
7373
sarif_file: results.sarif

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 178 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,8 @@
7778
"solve",
7879
"svd",
7980
"slogdet",
81+
"tensorinv",
82+
"tensorsolve",
8083
]
8184

8285

@@ -370,33 +373,65 @@ def inv(a):
370373
return dpnp_inv(a)
371374

372375

373-
def matrix_power(input, count):
376+
def matrix_power(a, n):
374377
"""
375-
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`.
376381
377382
Parameters
378383
----------
379-
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.
380388
381389
Returns
382390
-------
383-
output : array
384-
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.
385396
386-
See Also
387-
--------
388-
: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.]])
389424
390425
"""
391426

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

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

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

401436

402437
def matrix_rank(A, tol=None, hermitian=False):
@@ -897,3 +932,132 @@ def slogdet(a):
897932
check_stacked_square(a)
898933

899934
return dpnp_slogdet(a)
935+
936+
937+
def tensorinv(a, ind=2):
938+
"""
939+
Compute the 'inverse' of an N-dimensional array.
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, optional
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+
995+
def tensorsolve(a, b, axes=None):
996+
"""
997+
Solve the tensor equation ``a x = b`` for x.
998+
999+
For full documentation refer to :obj:`numpy.linalg.tensorsolve`.
1000+
1001+
Parameters
1002+
----------
1003+
a : {dpnp.ndarray, usm_ndarray}
1004+
Coefficient tensor, of shape ``b.shape + Q``. `Q`, a tuple, equals
1005+
the shape of that sub-tensor of `a` consisting of the appropriate
1006+
number of its rightmost indices, and must be such that
1007+
``prod(Q) == prod(b.shape)`` (in which sense `a` is said to be
1008+
'square').
1009+
b : {dpnp.ndarray, usm_ndarray}
1010+
Right-hand tensor, which can be of any shape.
1011+
axes : tuple of ints, optional
1012+
Axes in `a` to reorder to the right, before inversion.
1013+
If ``None`` , no reordering is done.
1014+
Default: ``None``.
1015+
1016+
Returns
1017+
-------
1018+
out : dpnp.ndarray
1019+
The tensor with shape ``Q`` such that ``b.shape + Q == a.shape``.
1020+
1021+
See Also
1022+
--------
1023+
:obj:`dpnp.linalg.tensordot` : Compute tensor dot product along specified axes.
1024+
:obj:`dpnp.linalg.tensorinv` : Compute the 'inverse' of an N-dimensional array.
1025+
:obj:`dpnp.einsum` : Evaluates the Einstein summation convention on the operands.
1026+
1027+
Examples
1028+
--------
1029+
>>> import dpnp as np
1030+
>>> a = np.eye(2*3*4)
1031+
>>> a.shape = (2*3, 4, 2, 3, 4)
1032+
>>> b = np.random.randn(2*3, 4)
1033+
>>> x = np.linalg.tensorsolve(a, b)
1034+
>>> x.shape
1035+
(2, 3, 4)
1036+
>>> np.allclose(np.tensordot(a, x, axes=3), b)
1037+
array([ True])
1038+
1039+
"""
1040+
1041+
dpnp.check_supported_arrays_type(a, b)
1042+
a_ndim = a.ndim
1043+
1044+
if axes is not None:
1045+
all_axes = list(range(a_ndim))
1046+
for k in axes:
1047+
all_axes.remove(k)
1048+
all_axes.insert(a_ndim, k)
1049+
a = a.transpose(tuple(all_axes))
1050+
1051+
old_shape = a.shape[-(a_ndim - b.ndim) :]
1052+
prod = numpy.prod(old_shape)
1053+
1054+
if a.size != prod**2:
1055+
raise dpnp.linalg.LinAlgError(
1056+
"Input arrays must satisfy the requirement \
1057+
prod(a.shape[b.ndim:]) == prod(a.shape[:b.ndim])"
1058+
)
1059+
1060+
a = a.reshape(-1, prod)
1061+
b = b.ravel()
1062+
res = solve(a, b)
1063+
return res.reshape(old_shape)

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)