Skip to content

Commit 0a73f81

Browse files
Merge pull request #375 from ricardosp4/transpose
Transpose
2 parents cd897c9 + 9c05ebc commit 0a73f81

File tree

6 files changed

+356
-5
lines changed

6 files changed

+356
-5
lines changed

bench/ndarray/transpose.ipynb

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
{
2+
"cells": [
3+
{
4+
"metadata": {},
5+
"cell_type": "code",
6+
"source": [
7+
"import numpy as np\n",
8+
"import blosc2\n",
9+
"import time\n",
10+
"import plotly.express as px\n",
11+
"import pandas as pd"
12+
],
13+
"id": "55765646130156ef",
14+
"outputs": [],
15+
"execution_count": null
16+
},
17+
{
18+
"metadata": {},
19+
"cell_type": "code",
20+
"source": [
21+
"sizes = [(100, 100), (500, 500), (500, 1000), (1000, 1000), (2000, 2000), (3000, 3000), (4000, 4000), (5000, 5000)]\n",
22+
"sizes_mb = [(np.prod(size) * 8) / 2**20 for size in sizes] # Convert to MB\n",
23+
"results = {\"numpy\": [], \"blosc2\": []}"
24+
],
25+
"id": "1cfb7daa6eee1401",
26+
"outputs": [],
27+
"execution_count": null
28+
},
29+
{
30+
"metadata": {},
31+
"cell_type": "code",
32+
"source": [
33+
"for method in [\"numpy\", \"blosc2\"]:\n",
34+
" for size in sizes:\n",
35+
" arr = np.random.rand(*size)\n",
36+
" arr_b2 = blosc2.asarray(arr)\n",
37+
"\n",
38+
" start_time = time.perf_counter()\n",
39+
"\n",
40+
" if method == \"numpy\":\n",
41+
" np.transpose(arr).copy()\n",
42+
" elif method == \"blosc2\":\n",
43+
" blosc2.transpose(arr_b2)\n",
44+
"\n",
45+
" end_time = time.perf_counter()\n",
46+
" time_b = end_time - start_time\n",
47+
"\n",
48+
" print(f\"{method}: shape={size}, Performance = {time_b:.6f} s\")\n",
49+
" results[method].append(time_b)"
50+
],
51+
"id": "384d0ad7983a8d26",
52+
"outputs": [],
53+
"execution_count": null
54+
},
55+
{
56+
"metadata": {},
57+
"cell_type": "code",
58+
"source": [
59+
"df = pd.DataFrame({\n",
60+
" \"Matrix Size (MB)\": sizes_mb,\n",
61+
" \"NumPy Time (s)\": results[\"numpy\"],\n",
62+
" \"Blosc2 Time (s)\": results[\"blosc2\"]\n",
63+
"})\n",
64+
"\n",
65+
"fig = px.line(df,\n",
66+
" x=\"Matrix Size (MB)\",\n",
67+
" y=[\"NumPy Time (s)\", \"Blosc2 Time (s)\"],\n",
68+
" title=\"Performance of Matrix Transposition (NumPy vs Blosc2)\",\n",
69+
" labels={\"value\": \"Time (s)\", \"variable\": \"Method\"},\n",
70+
" markers=True)\n",
71+
"\n",
72+
"fig.show()"
73+
],
74+
"id": "c71ffb39eb28992c",
75+
"outputs": [],
76+
"execution_count": null
77+
},
78+
{
79+
"metadata": {},
80+
"cell_type": "code",
81+
"source": [
82+
"%%time\n",
83+
"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",
91+
"]\n",
92+
"chunkshapes = [None, (150, 300), (200, 500), (500, 200), (1000, 1000)]\n",
93+
"\n",
94+
"sizes = []\n",
95+
"time_total = []\n",
96+
"chunk_labels = []\n",
97+
"\n",
98+
"for shape in shapes:\n",
99+
" size_mb = (np.prod(shape) * 8) / (2 ** 20)\n",
100+
"\n",
101+
" matrix_np = np.linspace(0, 1, np.prod(shape)).reshape(shape)\n",
102+
"\n",
103+
" t0 = time.perf_counter()\n",
104+
" result_numpy = np.transpose(matrix_np).copy()\n",
105+
" numpy_time = time.perf_counter() - t0\n",
106+
"\n",
107+
" time_total.append(numpy_time)\n",
108+
" sizes.append(size_mb)\n",
109+
" chunk_labels.append(\"NumPy\")\n",
110+
"\n",
111+
" print(f\"NumPy: Shape={shape}, Time = {numpy_time:.6f} s\")\n",
112+
"\n",
113+
" for chunk in chunkshapes:\n",
114+
" matrix_blosc2 = blosc2.asarray(matrix_np, chunks=chunk)\n",
115+
"\n",
116+
" t0 = time.perf_counter()\n",
117+
" result_blosc2 = blosc2.transpose(matrix_blosc2)\n",
118+
" blosc2_time = time.perf_counter() - t0\n",
119+
"\n",
120+
" sizes.append(size_mb)\n",
121+
" time_total.append(blosc2_time)\n",
122+
" chunk_labels.append(f\"{chunk[0]}x{chunk[1]}\" if chunk else \"Auto\")\n",
123+
"\n",
124+
" print(f\"Blosc2: Shape={shape}, Chunks = {matrix_blosc2.chunks}, Time = {blosc2_time:.6f} s\")\n",
125+
"\n",
126+
"df = pd.DataFrame({\n",
127+
" \"Matrix Size (MB)\": sizes,\n",
128+
" \"Time (s)\": time_total,\n",
129+
" \"Chunk Shape\": chunk_labels\n",
130+
"})\n",
131+
"\n",
132+
"fig = px.line(df,\n",
133+
" x=\"Matrix Size (MB)\",\n",
134+
" y=\"Time (s)\",\n",
135+
" color=\"Chunk Shape\",\n",
136+
" title=\"Performance of Matrix Transposition (Blosc2 vs NumPy)\",\n",
137+
" labels={\"value\": \"Time (s)\", \"variable\": \"Metric\"},\n",
138+
" markers=True)\n",
139+
"fig.show()"
140+
],
141+
"id": "bcdd8aa5f65df561",
142+
"outputs": [],
143+
"execution_count": null
144+
},
145+
{
146+
"metadata": {},
147+
"cell_type": "code",
148+
"source": "",
149+
"id": "1d2f48f370ba7e7a",
150+
"outputs": [],
151+
"execution_count": null
152+
}
153+
],
154+
"metadata": {
155+
"kernelspec": {
156+
"name": "python3",
157+
"language": "python",
158+
"display_name": "Python 3 (ipykernel)"
159+
}
160+
},
161+
"nbformat": 5,
162+
"nbformat_minor": 9
163+
}

doc/reference/linear_algebra.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ The next functions can be used for computing linear algebra operations with :ref
1212
:nosignatures:
1313

1414
matmul
15+
transpose

src/blosc2/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ class Tuner(Enum):
248248
full,
249249
save,
250250
matmul,
251+
transpose,
251252
)
252253

253254
from .c2array import c2context, C2Array, URLPath

src/blosc2/ndarray.py

+45-2
Original file line numberDiff line numberDiff line change
@@ -3772,9 +3772,9 @@ def matmul(x1: NDArray, x2: NDArray, **kwargs: Any) -> NDArray:
37723772
37733773
Parameters
37743774
----------
3775-
x1: `NDArray`
3775+
x1: :ref:`NDArray`
37763776
The first input array.
3777-
x2: `NDArray`
3777+
x2: :ref:`NDArray`
37783778
The second input array.
37793779
kwargs: Any, optional
37803780
Keyword arguments that are supported by the :func:`empty` constructor.
@@ -3873,6 +3873,49 @@ def matmul(x1: NDArray, x2: NDArray, **kwargs: Any) -> NDArray:
38733873
return result.squeeze()
38743874

38753875

3876+
def transpose(x, **kwargs: Any) -> NDArray:
3877+
"""
3878+
Returns a Blosc2 NDArray with axes transposed.
3879+
3880+
Parameters
3881+
----------
3882+
x: :ref:`NDArray`
3883+
The input array.
3884+
kwargs: Any, optional
3885+
Keyword arguments that are supported by the :func:`empty` constructor.
3886+
3887+
Returns
3888+
-------
3889+
out: :ref:`NDArray`
3890+
The Blosc2 NDArray with axes transposed.
3891+
3892+
References
3893+
----------
3894+
`numpy.transpose <https://numpy.org/doc/2.2/reference/generated/numpy.transpose.html>`_
3895+
"""
3896+
3897+
# If arguments are dimension < 2 they are returned
3898+
if np.isscalar(x) or x.ndim < 2:
3899+
return x
3900+
3901+
# Validate arguments are dimension 2
3902+
if x.ndim > 2:
3903+
raise ValueError("Transposing arrays with dimension greater than 2 is not supported yet.")
3904+
3905+
n, m = x.shape
3906+
p, q = x.chunks
3907+
result = blosc2.zeros((m, n), dtype=np.result_type(x), **kwargs)
3908+
3909+
for row in range(0, n, p):
3910+
row_end = (row + p) if (row + p) < n else n
3911+
for col in range(0, m, q):
3912+
col_end = (col + q) if (col + q) < m else m
3913+
aux = x[row:row_end, col:col_end]
3914+
result[col:col_end, row:row_end] = np.transpose(aux).copy()
3915+
3916+
return result
3917+
3918+
38763919
# Class for dealing with fields in an NDArray
38773920
# This will allow to access fields by name in the dtype of the NDArray
38783921
class NDField(Operand):

tests/ndarray/test_matmul.py

+42-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,50 @@
2121
)
2222
@pytest.mark.parametrize(
2323
"dtype",
24-
{np.float32, np.float64, np.complex64, np.complex128},
24+
{np.float32, np.float64},
2525
)
2626
def test_matmul(ashape, achunks, ablocks, bshape, bchunks, bblocks, dtype):
27-
a = blosc2.linspace(0, 10, dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks)
28-
b = blosc2.linspace(0, 10, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks)
27+
a = blosc2.linspace(0, 1, dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks)
28+
b = blosc2.linspace(0, 1, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks)
29+
c = blosc2.matmul(a, b)
30+
31+
na = a[:]
32+
nb = b[:]
33+
nc = np.matmul(na, nb)
34+
35+
np.testing.assert_allclose(c, nc, rtol=1e-6)
36+
37+
38+
@pytest.mark.parametrize(
39+
("ashape", "achunks", "ablocks"),
40+
{
41+
((12, 10), (7, 5), (3, 3)),
42+
((10,), (9,), (7,)),
43+
},
44+
)
45+
@pytest.mark.parametrize(
46+
("bshape", "bchunks", "bblocks"),
47+
{
48+
((10,), (4,), (2,)),
49+
((10, 5), (3, 4), (1, 3)),
50+
((10, 12), (2, 4), (1, 2)),
51+
},
52+
)
53+
@pytest.mark.parametrize(
54+
"dtype",
55+
{np.complex64, np.complex128},
56+
)
57+
def test_complex(ashape, achunks, ablocks, bshape, bchunks, bblocks, dtype):
58+
real_part = blosc2.linspace(0, 1, shape=ashape, chunks=achunks, blocks=ablocks, dtype=dtype)
59+
imag_part = blosc2.linspace(0, 1, shape=ashape, chunks=achunks, blocks=ablocks, dtype=dtype)
60+
complex_matrix_a = real_part + 1j * imag_part
61+
a = blosc2.asarray(complex_matrix_a)
62+
63+
real_part = blosc2.linspace(1, 2, shape=bshape, chunks=bchunks, blocks=bblocks, dtype=dtype)
64+
imag_part = blosc2.linspace(1, 2, shape=bshape, chunks=bchunks, blocks=bblocks, dtype=dtype)
65+
complex_matrix_b = real_part + 1j * imag_part
66+
b = blosc2.asarray(complex_matrix_b)
67+
2968
c = blosc2.matmul(a, b)
3069

3170
na = a[:]

tests/ndarray/test_transpose.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import numpy as np
2+
import pytest
3+
4+
import blosc2
5+
6+
7+
@pytest.fixture(
8+
params=[
9+
((3, 3), (2, 2), (1, 1)),
10+
((12, 11), (7, 5), (6, 2)),
11+
((1, 5), (1, 4), (1, 3)),
12+
((51, 603), (22, 99), (13, 29)),
13+
((10,), (5,), None),
14+
((31,), (14,), (9,)),
15+
]
16+
)
17+
def shape_chunks_blocks(request):
18+
return request.param
19+
20+
21+
@pytest.mark.parametrize(
22+
"dtype",
23+
{np.int32, np.int64, np.float32, np.float64},
24+
)
25+
def test_transpose(shape_chunks_blocks, dtype):
26+
shape, chunks, blocks = shape_chunks_blocks
27+
a = blosc2.linspace(0, 1, shape=shape, chunks=chunks, blocks=blocks, dtype=dtype)
28+
at = blosc2.transpose(a)
29+
30+
na = a[:]
31+
nat = np.transpose(na)
32+
33+
np.testing.assert_allclose(at, nat)
34+
35+
36+
@pytest.mark.parametrize(
37+
"dtype",
38+
{np.complex64, np.complex128},
39+
)
40+
def test_complex(shape_chunks_blocks, dtype):
41+
shape, chunks, blocks = shape_chunks_blocks
42+
real_part = blosc2.linspace(0, 1, shape=shape, chunks=chunks, blocks=blocks, dtype=dtype)
43+
imag_part = blosc2.linspace(1, 0, shape=shape, chunks=chunks, blocks=blocks, dtype=dtype)
44+
complex_matrix = real_part + 1j * imag_part
45+
46+
a = blosc2.asarray(complex_matrix)
47+
at = blosc2.transpose(a)
48+
49+
na = a[:]
50+
nat = np.transpose(na)
51+
52+
np.testing.assert_allclose(at, nat)
53+
54+
55+
@pytest.mark.parametrize(
56+
"scalar",
57+
{
58+
1, # int
59+
5.1, # float
60+
1 + 2j, # complex
61+
np.int8(2), # NumPy int8
62+
np.int16(3), # NumPy int16
63+
np.int32(4), # NumPy int32
64+
np.int64(5), # NumPy int64
65+
np.float32(5.2), # NumPy float32
66+
np.float64(5.3), # NumPy float64
67+
np.complex64(0 + 3j), # NumPy complex64
68+
np.complex128(2 - 4j), # NumPy complex128
69+
},
70+
)
71+
def test_scalars(scalar):
72+
at = blosc2.transpose(scalar)
73+
nat = np.transpose(scalar)
74+
75+
np.testing.assert_allclose(at, nat)
76+
77+
78+
@pytest.mark.parametrize(
79+
"shape",
80+
[
81+
(3, 3, 3),
82+
(12, 10, 10),
83+
(10, 10, 10, 11),
84+
(5, 4, 3, 2, 1, 1),
85+
],
86+
)
87+
def test_dims(shape):
88+
a = blosc2.linspace(0, 1, shape=shape)
89+
90+
with pytest.raises(ValueError):
91+
blosc2.transpose(a)
92+
93+
94+
def test_disk():
95+
a = blosc2.linspace(0, 1, shape=(3, 4), urlpath="a_test.b2nd", mode="w")
96+
c = blosc2.transpose(a, urlpath="c_test.b2nd", mode="w")
97+
98+
na = a[:]
99+
nc = np.transpose(na)
100+
101+
np.testing.assert_allclose(c, nc, rtol=1e-6)
102+
103+
blosc2.remove_urlpath("a_test.b2nd")
104+
blosc2.remove_urlpath("c_test.b2nd")

0 commit comments

Comments
 (0)