Skip to content

Commit fa63ec3

Browse files
committed
[FEAT] New permute_dims() and matrix_transpose(), and .T. transpose() is deprecated now.
1 parent e0f0cd7 commit fa63ec3

File tree

6 files changed

+332
-59
lines changed

6 files changed

+332
-59
lines changed

bench/ndarray/transpose.ipynb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@
8181
"source": [
8282
"%%time\n",
8383
"shapes = [\n",
84-
" (100, 100), (2000, 2000), (3000, 3000), (4000, 4000), (3000, 7000),\n",
85-
" (5000, 5000), (6000, 6000), (7000, 7000), (8000, 8000), (6000, 12000),\n",
86-
" (9000, 9000), (10000, 10000),\n",
87-
" (10500, 10500), (11000, 11000), (11500, 11500), (12000, 12000),\n",
88-
" (12500, 12500), (13000, 13000), (13500, 13500), (14000, 14000),\n",
89-
" (14500, 14500), (15000, 15000), (15500, 15500), (16000, 16000),\n",
90-
" (16500, 16500), (17000, 17000)\n",
84+
" (100, 100), (2000, 2000), (3000, 3000), (4000, 4000), (3000, 7000)\n",
85+
" # (5000, 5000), (6000, 6000), (7000, 7000), (8000, 8000), (6000, 12000),\n",
86+
" # (9000, 9000), (10000, 10000),\n",
87+
" # (10500, 10500), (11000, 11000), (11500, 11500), (12000, 12000),\n",
88+
" # (12500, 12500), (13000, 13000), (13500, 13500), (14000, 14000),\n",
89+
" # (14500, 14500), (15000, 15000), (15500, 15500), (16000, 16000),\n",
90+
" # (16500, 16500), (17000, 17000)\n",
9191
"]\n",
9292
"chunkshapes = [None, (150, 300), (200, 500), (500, 200), (1000, 1000)]\n",
9393
"\n",

doc/reference/linear_algebra.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ The next functions can be used for computing linear algebra operations with :ref
1313

1414
matmul
1515
transpose
16+
matrix_transpose
17+
permute_dims

doc/reference/ndarray.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Attributes
4040
.. autosummary::
4141
:toctree: autofiles/ndarray
4242

43+
T
4344
ndim
4445
shape
4546
ext_shape

src/blosc2/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,9 @@ class Tuner(Enum):
248248
full,
249249
save,
250250
matmul,
251+
permute_dims,
251252
transpose,
253+
matrix_transpose,
252254
)
253255

254256
from .c2array import c2context, C2Array, URLPath
@@ -267,7 +269,6 @@ class Tuner(Enum):
267269

268270
from .schunk import SChunk, open
269271

270-
271272
# Registry for postfilters
272273
postfilter_funcs = {}
273274
"""

src/blosc2/ndarray.py

Lines changed: 143 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import builtins
1212
import inspect
1313
import math
14+
import warnings
1415
from collections import OrderedDict, namedtuple
1516
from functools import reduce
1617
from itertools import product
@@ -1412,6 +1413,13 @@ def blocksize(self) -> int:
14121413
"""
14131414
return self._schunk.blocksize
14141415

1416+
@property
1417+
def T(self):
1418+
"""Return the transpose of a 2-dimensional array."""
1419+
if self.ndim != 2:
1420+
raise ValueError("This property only works for 2-dimensional arrays.")
1421+
return permute_dims(self)
1422+
14151423
def __getitem__( # noqa: C901
14161424
self,
14171425
key: int | slice | Sequence[slice | int] | np.ndarray[np.bool_] | NDArray | blosc2.LazyExpr | str,
@@ -3878,6 +3886,114 @@ def matmul(x1: NDArray, x2: NDArray, **kwargs: Any) -> NDArray:
38783886
return result.squeeze()
38793887

38803888

3889+
def permute_dims(arr: NDArray, axes: tuple[int] | list[int] | None = None, **kwargs: Any) -> NDArray:
3890+
"""
3891+
Permutes the axes (dimensions) of an array.
3892+
3893+
Parameters
3894+
----------
3895+
arr: :ref:`NDArray`
3896+
The input array.
3897+
axes: tuple[int], list[int], optional
3898+
The desired permutation of axes. If None, the axes are reversed by default.
3899+
If specified, axes must be a tuple or list representing a permutation of
3900+
``[0, 1, ..., N-1]``, where ``N`` is the number of dimensions of the input array.
3901+
Negative indices are also supported. The *i*-th axis of the result will correspond
3902+
to the axis numbered ``axes[i]`` of the input.
3903+
kwargs: Any, optional
3904+
Keyword arguments that are supported by the :func:`empty` constructor.
3905+
3906+
Returns
3907+
-------
3908+
out: :ref:`NDArray`
3909+
A Blosc2 :ref:`NDArray` with axes transposed.
3910+
3911+
Raises
3912+
------
3913+
ValueError
3914+
If ``axes`` is not a valid permutation of the dimensions of ``arr``.
3915+
3916+
References
3917+
----------
3918+
`numpy.transpose <https://numpy.org/doc/2.2/reference/generated/numpy.transpose.html>`_
3919+
3920+
`permute_dims <https://data-apis.org/array-api/latest/API_specification/generated/array_api.permute_dims.html#permute-dims>`_
3921+
3922+
Examples
3923+
--------
3924+
For 2-D arrays it is the matrix transposition as usual:
3925+
3926+
>>> import blosc2
3927+
>>> a = blosc2.arange(1, 10).reshape((3, 3))
3928+
>>> a[:]
3929+
array([[1, 2, 3],
3930+
[4, 5, 6],
3931+
[7, 8, 9]])
3932+
>>> at = blosc2.permute_dims(a)
3933+
>>> at[:]
3934+
array([[1, 4, 7],
3935+
[2, 5, 8],
3936+
[3, 6, 9]])
3937+
3938+
For 3-D arrays:
3939+
3940+
>>> import blosc2
3941+
>>> a = blosc2.arange(1, 25).reshape((2, 3, 4))
3942+
>>> a[:]
3943+
array([[[ 1, 2, 3, 4],
3944+
[ 5, 6, 7, 8],
3945+
[ 9, 10, 11, 12]],
3946+
[[13, 14, 15, 16],
3947+
[17, 18, 19, 20],
3948+
[21, 22, 23, 24]]])
3949+
3950+
3951+
3952+
>>> at = blosc2.permute_dims(a, axes=(1, 0, 2))
3953+
>>> at[:]
3954+
array([[[ 1, 2, 3, 4],
3955+
[13, 14, 15, 16]],
3956+
[[ 5, 6, 7, 8],
3957+
[17, 18, 19, 20]],
3958+
[[ 9, 10, 11, 12],
3959+
[21, 22, 23, 24]]])
3960+
3961+
"""
3962+
3963+
if np.isscalar(arr) or arr.ndim < 2:
3964+
return arr
3965+
3966+
if axes is None:
3967+
axes = range(arr.ndim)[::-1]
3968+
else:
3969+
axes_transformed = tuple(axis if axis >= 0 else arr.ndim + axis for axis in axes)
3970+
if sorted(axes_transformed) != list(range(arr.ndim)):
3971+
raise ValueError(f"axes {axes} is not a valid permutation of {arr.ndim} dimensions")
3972+
axes = axes_transformed
3973+
3974+
new_shape = tuple(arr.shape[axis] for axis in axes)
3975+
if "chunks" not in kwargs:
3976+
kwargs["chunks"] = tuple(arr.chunks[axis] for axis in axes)
3977+
3978+
result = blosc2.empty(shape=new_shape, dtype=arr.dtype, **kwargs)
3979+
3980+
chunk_slices = [
3981+
[slice(start, builtins.min(dim, start + chunk)) for start in range(0, dim, chunk)]
3982+
for dim, chunk in zip(arr.shape, arr.chunks, strict=False)
3983+
]
3984+
3985+
block_counts = [len(s) for s in chunk_slices]
3986+
grid = np.indices(block_counts).reshape(len(block_counts), -1).T
3987+
3988+
for idx in grid:
3989+
block_slices = tuple(chunk_slices[axis][i] for axis, i in enumerate(idx))
3990+
block = arr[block_slices]
3991+
target_slices = tuple(block_slices[axis] for axis in axes)
3992+
result[target_slices] = np.transpose(block, axes=axes).copy()
3993+
3994+
return result
3995+
3996+
38813997
def transpose(x, **kwargs: Any) -> NDArray:
38823998
"""
38833999
Returns a Blosc2 NDArray with axes transposed.
@@ -3900,6 +4016,12 @@ def transpose(x, **kwargs: Any) -> NDArray:
39004016
----------
39014017
`numpy.transpose <https://numpy.org/doc/2.2/reference/generated/numpy.transpose.html>`_
39024018
"""
4019+
warnings.warn(
4020+
"transpose is deprecated and will be removed in a future version. "
4021+
"Use matrix_transpose or permute_dims instead.",
4022+
DeprecationWarning,
4023+
stacklevel=2,
4024+
)
39034025

39044026
# If arguments are dimension < 2 they are returned
39054027
if np.isscalar(x) or x.ndim < 2:
@@ -3908,19 +4030,30 @@ def transpose(x, **kwargs: Any) -> NDArray:
39084030
# Validate arguments are dimension 2
39094031
if x.ndim > 2:
39104032
raise ValueError("Transposing arrays with dimension greater than 2 is not supported yet.")
4033+
return permute_dims(x, **kwargs)
39114034

3912-
n, m = x.shape
3913-
p, q = x.chunks
3914-
result = blosc2.zeros((m, n), dtype=np.result_type(x), **kwargs)
39154035

3916-
for row in range(0, n, p):
3917-
row_end = (row + p) if (row + p) < n else n
3918-
for col in range(0, m, q):
3919-
col_end = (col + q) if (col + q) < m else m
3920-
aux = x[row:row_end, col:col_end]
3921-
result[col:col_end, row:row_end] = np.transpose(aux).copy()
4036+
def matrix_transpose(arr: NDArray, **kwargs: Any) -> NDArray:
4037+
"""
4038+
Transposes a matrix (or a stack of matrices).
39224039
3923-
return result
4040+
Parameters
4041+
----------
4042+
arr: :ref:`NDArray`
4043+
The input NDArray having shape ``(..., M, N)`` and whose innermost two dimensions form
4044+
``MxN`` matrices.
4045+
4046+
Returns
4047+
-------
4048+
out: :ref:`NDArray`
4049+
A new :ref:`NDArray` containing the transpose for each matrix and having shape
4050+
``(..., N, M)``.
4051+
"""
4052+
axes = None
4053+
if not np.isscalar(arr) and arr.ndim > 2:
4054+
axes = list(range(arr.ndim))
4055+
axes[-2], axes[-1] = axes[-1], axes[-2]
4056+
return permute_dims(arr, axes, **kwargs)
39244057

39254058

39264059
# Class for dealing with fields in an NDArray

0 commit comments

Comments
 (0)