Skip to content

Commit bb43d64

Browse files
committed
Merge branch 'pycuda_merge' into 'main'
Implementing stack() and concatenate() See merge request inducer/pycuda!56
2 parents f65bdbd + 18fc655 commit bb43d64

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

doc/array.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ Constructing :class:`GPUArray` Instances
339339
Return the :class:`GPUArray` ``[a[indices[0]], ..., a[indices[n]]]``.
340340
For the moment, *a* must be a type that can be bound to a texture.
341341

342+
.. function:: concatenate(arrays, axis=0, allocator=None)
343+
344+
Join a sequence of arrays along an existing axis.
345+
346+
.. function:: stack(arrays, axis=0, allocator=None)
347+
348+
Join a sequence of arrays along a new axis.
349+
350+
342351
Conditionals
343352
^^^^^^^^^^^^
344353

pycuda/gpuarray.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,95 @@ def make_func_for_chunk_size(chunk_size):
17671767

17681768
# {{{ shape manipulation
17691769

1770+
def concatenate(arrays, axis=0, allocator=None):
1771+
"""
1772+
Join a sequence of arrays along an existing axis.
1773+
:arg arrays: A sequnce of :class:`GPUArray`.
1774+
:arg axis: Index of the dimension of the new axis in the result array.
1775+
Can be -1, for the new axis to be last dimension.
1776+
:returns: :class:`GPUArray`
1777+
"""
1778+
# implementation is borrowed from pyopencl.array.concatenate()
1779+
# {{{ find properties of result array
1780+
1781+
shape = None
1782+
1783+
def shape_except_axis(ary: GPUArray):
1784+
return ary.shape[:axis] + ary.shape[axis+1:]
1785+
1786+
for i_ary, ary in enumerate(arrays):
1787+
allocator = allocator or ary.allocator
1788+
1789+
if shape is None:
1790+
# first array
1791+
shape = list(ary.shape)
1792+
1793+
else:
1794+
if len(ary.shape) != len(shape):
1795+
raise ValueError("%d'th array has different number of axes "
1796+
"(should have %d, has %d)"
1797+
% (i_ary, len(ary.shape), len(shape)))
1798+
1799+
if (ary.ndim != arrays[0].ndim
1800+
or shape_except_axis(ary) != shape_except_axis(arrays[0])):
1801+
raise ValueError("%d'th array has residual not matching "
1802+
"other arrays" % i_ary)
1803+
1804+
shape[axis] += ary.shape[axis]
1805+
1806+
# }}}
1807+
1808+
shape = tuple(shape)
1809+
dtype = np.find_common_type([ary.dtype for ary in arrays], [])
1810+
result = empty(shape, dtype, allocator=allocator)
1811+
1812+
full_slice = (slice(None),) * len(shape)
1813+
1814+
base_idx = 0
1815+
for ary in arrays:
1816+
my_len = ary.shape[axis]
1817+
result[full_slice[:axis] + (slice(base_idx, base_idx+my_len),) + full_slice[axis+1:]] = ary
1818+
base_idx += my_len
1819+
1820+
return result
1821+
1822+
1823+
def stack(arrays, axis=0, allocator=None):
1824+
"""
1825+
Join a sequence of arrays along a new axis.
1826+
:arg arrays: A sequnce of :class:`GPUArray`.
1827+
:arg axis: Index of the dimension of the new axis in the result array.
1828+
Can be -1, for the new axis to be last dimension.
1829+
:returns: :class:`GPUArray`
1830+
"""
1831+
# implementation is borrowed from pyopencl.array.stack()
1832+
allocator = allocator or arrays[0].allocator
1833+
1834+
if not arrays:
1835+
raise ValueError("need at least one array to stack")
1836+
1837+
input_shape = arrays[0].shape
1838+
input_ndim = arrays[0].ndim
1839+
axis = input_ndim if axis == -1 else axis
1840+
1841+
if not all(ary.shape == input_shape for ary in arrays[1:]):
1842+
raise ValueError("arrays must have the same shape")
1843+
1844+
if not (0 <= axis <= input_ndim):
1845+
raise ValueError("invalid axis")
1846+
1847+
result_shape = input_shape[:axis] + (len(arrays),) + input_shape[axis:]
1848+
result = empty(shape=result_shape,
1849+
dtype=np.result_type(*(ary.dtype for ary in arrays)),
1850+
allocator=allocator, order="C" if axis == 0 else "F")
1851+
1852+
for i, ary in enumerate(arrays):
1853+
1854+
idx = (slice(None),)*axis + (i,) + (slice(None),)*(input_ndim-axis)
1855+
result[idx] = ary
1856+
1857+
return result
1858+
17701859

17711860
def transpose(a, axes=None):
17721861
"""Permute the dimensions of an array.

test/test_gpuarray.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,53 @@ def test_arange(self):
468468
a = gpuarray.arange(12, dtype=np.float32)
469469
assert (np.arange(12, dtype=np.float32) == a.get()).all()
470470

471+
@mark_cuda_test
472+
def test_stack(self):
473+
474+
orders = ["F", "C"]
475+
input_dims_lst = [0, 1, 2]
476+
477+
for order in orders:
478+
for input_dims in input_dims_lst:
479+
shape = (2, 2, 2)[:input_dims]
480+
axis = -1 if order == "F" else 0
481+
482+
from numpy.random import default_rng
483+
rng = default_rng()
484+
x_in = rng.random(size=shape)
485+
y_in = rng.random(size=shape)
486+
x_in = x_in if order == "C" else np.asfortranarray(x_in)
487+
y_in = y_in if order == "C" else np.asfortranarray(y_in)
488+
489+
x_gpu = gpuarray.to_gpu(x_in)
490+
y_gpu = gpuarray.to_gpu(y_in)
491+
492+
numpy_stack = np.stack((x_in, y_in), axis=axis)
493+
gpuarray_stack = gpuarray.stack((x_gpu, y_gpu), axis=axis)
494+
495+
np.testing.assert_allclose(gpuarray_stack.get(), numpy_stack)
496+
497+
assert gpuarray_stack.shape == numpy_stack.shape
498+
499+
@mark_cuda_test
500+
def test_concatenate(self):
501+
502+
from pycuda.curandom import rand as curand
503+
504+
a_dev = curand((5, 15, 20), dtype=np.float32)
505+
b_dev = curand((4, 15, 20), dtype=np.float32)
506+
c_dev = curand((3, 15, 20), dtype=np.float32)
507+
a = a_dev.get()
508+
b = b_dev.get()
509+
c = c_dev.get()
510+
511+
cat_dev = gpuarray.concatenate((a_dev, b_dev, c_dev))
512+
cat = np.concatenate((a, b, c))
513+
514+
np.testing.assert_allclose(cat, cat_dev.get())
515+
516+
assert cat.shape == cat_dev.shape
517+
471518
@mark_cuda_test
472519
def test_reverse(self):
473520
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.float32)

0 commit comments

Comments
 (0)