Skip to content

Commit 5dd6a62

Browse files
vlad-perevezentsevantonwolfy
authored andcommitted
Add implementation of dpnp.matrix_transpose() and .mT attribute for dpnp.array (#2095)
* Add implementaion of dpnp.matrix_transpose() * Add TestMatrixtranspose to dpnp tests * Add .mT attribute for ndarray * Add tests for .mT arrribute * Add matrix_transpose() for dpnp.linalg module * Add test for dpnp.linalg.matrix_transpose() * Expand docs for .mT attribute * Reuse dpctl for matrix_transpose * Update dpnp.matrix_transpose() --------- Co-authored-by: Anton <[email protected]>
1 parent 4519933 commit 5dd6a62

File tree

7 files changed

+238
-0
lines changed

7 files changed

+238
-0
lines changed

doc/reference/ndarray.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Other attributes
9292
:nosignatures:
9393

9494
dpnp.ndarray.T
95+
dpnp.ndarray.mT
9596
dpnp.ndarray.real
9697
dpnp.ndarray.imag
9798
dpnp.ndarray.flat

dpnp/dpnp_array.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,49 @@ def T(self):
108108
"""View of the transposed array."""
109109
return self.transpose()
110110

111+
@property
112+
def mT(self):
113+
"""
114+
View of the matrix transposed array.
115+
116+
The matrix transpose is the transpose of the last two dimensions, even
117+
if the array is of higher dimension.
118+
119+
Raises
120+
------
121+
ValueError
122+
If the array is of dimension less than 2.
123+
124+
Examples
125+
--------
126+
>>> import dpnp as np
127+
>>> a = np.array([[1, 2], [3, 4]])
128+
>>> a
129+
array([[1, 2],
130+
[3, 4]])
131+
>>> a.mT
132+
array([[1, 3],
133+
[2, 4]])
134+
135+
>>> a = np.arange(8).reshape((2, 2, 2))
136+
>>> a
137+
array([[[0, 1],
138+
[2, 3]],
139+
[[4, 5],
140+
[6, 7]]])
141+
>>> a.mT
142+
array([[[0, 2],
143+
[1, 3]],
144+
[[4, 6],
145+
[5, 7]]])
146+
147+
"""
148+
149+
if self.ndim < 2:
150+
raise ValueError("matrix transpose with ndim < 2 is undefined")
151+
152+
return self._array_obj.mT
153+
111154
def to_device(self, target_device):
112155
"""Transfer array to target device."""
113156

dpnp/dpnp_iface_manipulation.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"flipud",
7474
"hsplit",
7575
"hstack",
76+
"matrix_transpose",
7677
"moveaxis",
7778
"ndim",
7879
"permute_dims",
@@ -1752,6 +1753,58 @@ def hstack(tup, *, dtype=None, casting="same_kind"):
17521753
return dpnp.concatenate(arrs, axis=1, dtype=dtype, casting=casting)
17531754

17541755

1756+
def matrix_transpose(x, /):
1757+
"""
1758+
Transposes a matrix (or a stack of matrices) `x`.
1759+
1760+
For full documentation refer to :obj:`numpy.matrix_transpose`.
1761+
1762+
Parameters
1763+
----------
1764+
x : (..., M, N) {dpnp.ndarray, usm_ndarray}
1765+
Input array with ``x.ndim >= 2`` and whose two innermost
1766+
dimensions form ``MxN`` matrices.
1767+
1768+
Returns
1769+
-------
1770+
out : dpnp.ndarray
1771+
An array containing the transpose for each matrix and having shape
1772+
(..., N, M).
1773+
1774+
See Also
1775+
--------
1776+
:obj:`dpnp.transpose` : Returns an array with axes transposed.
1777+
:obj:`dpnp.linalg.matrix_transpose` : Equivalent function.
1778+
:obj:`dpnp.ndarray.mT` : Equivalent method.
1779+
1780+
Examples
1781+
--------
1782+
>>> import dpnp as np
1783+
>>> a = np.array([[1, 2], [3, 4]])
1784+
>>> np.matrix_transpose(a)
1785+
array([[1, 3],
1786+
[2, 4]])
1787+
1788+
>>> b = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
1789+
>>> np.matrix_transpose(b)
1790+
array([[[1, 3],
1791+
[2, 4]],
1792+
[[5, 7],
1793+
[6, 8]]])
1794+
1795+
"""
1796+
1797+
usm_x = dpnp.get_usm_ndarray(x)
1798+
if usm_x.ndim < 2:
1799+
raise ValueError(
1800+
"Input array must be at least 2-dimensional, "
1801+
f"but it is {usm_x.ndim}"
1802+
)
1803+
1804+
usm_res = dpt.matrix_transpose(usm_x)
1805+
return dpnp_array._create_from_usm_ndarray(usm_res)
1806+
1807+
17551808
def moveaxis(a, source, destination):
17561809
"""
17571810
Move axes of an array to new positions. Other axes remain in their original

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"matrix_norm",
8282
"matrix_power",
8383
"matrix_rank",
84+
"matrix_transpose",
8485
"multi_dot",
8586
"norm",
8687
"outer",
@@ -1118,6 +1119,50 @@ def matrix_rank(A, tol=None, hermitian=False):
11181119
return dpnp_matrix_rank(A, tol=tol, hermitian=hermitian)
11191120

11201121

1122+
def matrix_transpose(x, /):
1123+
"""
1124+
Transposes a matrix (or a stack of matrices) `x`.
1125+
1126+
For full documentation refer to :obj:`numpy.linalg.matrix_transpose`.
1127+
1128+
Parameters
1129+
----------
1130+
x : (..., M, N) {dpnp.ndarray, usm_ndarray}
1131+
Input array with ``x.ndim >= 2`` and whose two innermost
1132+
dimensions form ``MxN`` matrices.
1133+
1134+
Returns
1135+
-------
1136+
out : dpnp.ndarray
1137+
An array containing the transpose for each matrix and having shape
1138+
(..., N, M).
1139+
1140+
See Also
1141+
--------
1142+
:obj:`dpnp.transpose` : Returns an array with axes transposed.
1143+
:obj:`dpnp.matrix_transpose` : Equivalent function.
1144+
:obj:`dpnp.ndarray.mT` : Equivalent method.
1145+
1146+
Examples
1147+
--------
1148+
>>> import dpnp as np
1149+
>>> a = np.array([[1, 2], [3, 4]])
1150+
>>> np.linalg.matrix_transpose(a)
1151+
array([[1, 3],
1152+
[2, 4]])
1153+
1154+
>>> b = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
1155+
>>> np.linalg.matrix_transpose(b)
1156+
array([[[1, 3],
1157+
[2, 4]],
1158+
[[5, 7],
1159+
[6, 8]]])
1160+
1161+
"""
1162+
1163+
return dpnp.matrix_transpose(x)
1164+
1165+
11211166
def multi_dot(arrays, *, out=None):
11221167
"""
11231168
Compute the dot product of two or more arrays in a single function call.

tests/test_arraymanipulation.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,50 @@ def test_one_element(self):
556556
assert_array_equal(res, a)
557557

558558

559+
# numpy.matrix_transpose() is available since numpy >= 2.0
560+
@testing.with_requires("numpy>=2.0")
561+
class TestMatrixtranspose:
562+
@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True))
563+
@pytest.mark.parametrize(
564+
"shape",
565+
[(3, 5), (4, 2), (2, 5, 2), (2, 3, 3, 6)],
566+
ids=["(3,5)", "(4,2)", "(2,5,2)", "(2,3,3,6)"],
567+
)
568+
def test_matrix_transpose(self, dtype, shape):
569+
a = numpy.arange(numpy.prod(shape), dtype=dtype).reshape(shape)
570+
dp_a = dpnp.array(a)
571+
572+
expected = numpy.matrix_transpose(a)
573+
result = dpnp.matrix_transpose(dp_a)
574+
575+
assert_allclose(result, expected)
576+
577+
@pytest.mark.parametrize(
578+
"shape",
579+
[(0, 0), (1, 0, 0), (0, 2, 2), (0, 1, 0, 4)],
580+
ids=["(0,0)", "(1,0,0)", "(0,2,2)", "(0, 1, 0, 4)"],
581+
)
582+
def test_matrix_transpose_empty(self, shape):
583+
a = numpy.empty(shape, dtype=dpnp.default_float_type())
584+
dp_a = dpnp.array(a)
585+
586+
expected = numpy.matrix_transpose(a)
587+
result = dpnp.matrix_transpose(dp_a)
588+
589+
assert_allclose(result, expected)
590+
591+
def test_matrix_transpose_errors(self):
592+
a_dp = dpnp.array([[1, 2], [3, 4]], dtype="float32")
593+
594+
# unsupported type
595+
a_np = dpnp.asnumpy(a_dp)
596+
assert_raises(TypeError, dpnp.matrix_transpose, a_np)
597+
598+
# a.ndim < 2
599+
a_dp_ndim_1 = a_dp.flatten()
600+
assert_raises(ValueError, dpnp.matrix_transpose, a_dp_ndim_1)
601+
602+
559603
class TestRollaxis:
560604
data = [
561605
(0, 0),

tests/test_linalg.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,6 +2124,25 @@ def test_matrix_rank_errors(self):
21242124
)
21252125

21262126

2127+
# numpy.linalg.matrix_transpose() is available since numpy >= 2.0
2128+
@testing.with_requires("numpy>=2.0")
2129+
# dpnp.linalg.matrix_transpose() calls dpnp.matrix_transpose()
2130+
# 1 test to increase code coverage
2131+
def test_matrix_transpose():
2132+
a = numpy.arange(6).reshape((2, 3))
2133+
a_dp = inp.array(a)
2134+
2135+
expected = numpy.linalg.matrix_transpose(a)
2136+
result = inp.linalg.matrix_transpose(a_dp)
2137+
2138+
assert_allclose(expected, result)
2139+
2140+
with assert_raises_regex(
2141+
ValueError, "array must be at least 2-dimensional"
2142+
):
2143+
inp.linalg.matrix_transpose(a_dp[:, 0])
2144+
2145+
21272146
class TestNorm:
21282147
def setup_method(self):
21292148
numpy.random.seed(42)

tests/test_ndarray.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from numpy.testing import assert_allclose, assert_array_equal
55

66
import dpnp
7+
from tests.third_party.cupy import testing
78

89
from .helper import (
910
get_all_dtypes,
@@ -258,6 +259,38 @@ def test_array_as_index(shape, index_dtype):
258259
assert a[tuple(ind_arr)] == a[1]
259260

260261

262+
# numpy.ndarray.mT is available since numpy >= 2.0
263+
@testing.with_requires("numpy>=2.0")
264+
@pytest.mark.parametrize(
265+
"shape",
266+
[(3, 5), (2, 5, 2), (2, 3, 3, 6)],
267+
ids=["(3,5)", "(2,5,2)", "(2,3,3,6)"],
268+
)
269+
def test_matrix_transpose(shape):
270+
a = numpy.arange(numpy.prod(shape)).reshape(shape)
271+
dp_a = dpnp.array(a)
272+
273+
expected = a.mT
274+
result = dp_a.mT
275+
276+
assert_allclose(result, expected)
277+
278+
# result is a view of dp_a:
279+
# changing result, modifies dp_a
280+
first_elem = (0,) * dp_a.ndim
281+
282+
result[first_elem] = -1.0
283+
assert dp_a[first_elem] == -1.0
284+
285+
286+
@testing.with_requires("numpy>=2.0")
287+
def test_matrix_transpose_error():
288+
# 1D array
289+
dp_a = dpnp.arange(6)
290+
with pytest.raises(ValueError):
291+
dp_a.mT
292+
293+
261294
def test_ravel():
262295
a = dpnp.ones((2, 2))
263296
b = a.ravel()

0 commit comments

Comments
 (0)