From 1ea61180cf81513148cb90a23893ae03b001036b Mon Sep 17 00:00:00 2001 From: Mit Kotak Date: Mon, 26 Jul 2021 04:16:58 +0000 Subject: [PATCH 1/5] Implemented gpuarray.(stack|concatenate) + docs --- doc/array.rst | 9 +++++ pycuda/gpuarray.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/doc/array.rst b/doc/array.rst index de1cc66e..23f22ce0 100644 --- a/doc/array.rst +++ b/doc/array.rst @@ -339,6 +339,15 @@ Constructing :class:`GPUArray` Instances Return the :class:`GPUArray` ``[a[indices[0]], ..., a[indices[n]]]``. For the moment, *a* must be a type that can be bound to a texture. +.. function:: concatenate(arrays, axis=0, allocator=None) + + Join a sequence of arrays along an existing axis. + + .. function:: stack(arrays, axis=0, allocator=None) + + Join a sequence of arrays along a new axis. + + Conditionals ^^^^^^^^^^^^ diff --git a/pycuda/gpuarray.py b/pycuda/gpuarray.py index 373cf005..e5d853be 100644 --- a/pycuda/gpuarray.py +++ b/pycuda/gpuarray.py @@ -1767,6 +1767,95 @@ def multi_put(arrays, dest_indices, dest_shape=None, out=None, stream=None): # {{{ shape manipulation +def concatenate(arrays, axis=0, allocator=None): + """ + Join a sequence of arrays along an existing axis. + :arg arrays: A sequnce of :class:`GPUArray`. + :arg axis: Index of the dimension of the new axis in the result array. + Can be -1, for the new axis to be last dimension. + :returns: :class:`GPUArray` + """ + # implementation is borrowed from pyopencl.array.concatenate() + # {{{ find properties of result array + + shape = None + + def shape_except_axis(ary: GPUArray): + return ary.shape[:axis] + ary.shape[axis+1:] + + for i_ary, ary in enumerate(arrays): + allocator = allocator or ary.allocator + + if shape is None: + # first array + shape = list(ary.shape) + + else: + if len(ary.shape) != len(shape): + raise ValueError("%d'th array has different number of axes " + "(should have %d, has %d)" + % (i_ary, len(ary.shape), len(shape))) + + if (ary.ndim != arrays[0].ndim + or shape_except_axis(ary) != shape_except_axis(arrays[0])): + raise ValueError("%d'th array has residual not matching " + "other arrays" % i_ary) + + shape[axis] += ary.shape[axis] + + # }}} + + shape = tuple(shape) + dtype = np.find_common_type([ary.dtype for ary in arrays], []) + result = empty(shape, dtype, allocator=allocator) + + full_slice = (slice(None),) * len(shape) + + base_idx = 0 + for ary in arrays: + my_len = ary.shape[axis] + result[full_slice[:axis] + (slice(base_idx, base_idx+my_len),) + full_slice[axis+1:]] = ary + base_idx += my_len + + return result + + + def stack(arrays, axis=0, allocator=None): + """ + Join a sequence of arrays along a new axis. + :arg arrays: A sequnce of :class:`GPUArray`. + :arg axis: Index of the dimension of the new axis in the result array. + Can be -1, for the new axis to be last dimension. + :returns: :class:`GPUArray` + """ + # implementation is borrowed from pyopencl.array.stack() + allocator = allocator or arrays[0].allocator + + if not arrays: + raise ValueError("need at least one array to stack") + + input_shape = arrays[0].shape + input_ndim = arrays[0].ndim + axis = input_ndim if axis == -1 else axis + + if not all(ary.shape == input_shape for ary in arrays[1:]): + raise ValueError("arrays must have the same shape") + + if not (0 <= axis <= input_ndim): + raise ValueError("invalid axis") + + result_shape = input_shape[:axis] + (len(arrays),) + input_shape[axis:] + result = empty(shape=result_shape, + dtype=np.result_type(*(ary.dtype for ary in arrays)), + allocator=allocator, order="C" if axis == 0 else "F") + + for i, ary in enumerate(arrays): + + idx = (slice(None),)*axis + (i,) + (slice(None),)*(input_ndim-axis) + result[idx] = ary + + return result + def transpose(a, axes=None): """Permute the dimensions of an array. -- GitLab From a3bb7144f1e7b399e015f94c8e42c5ef849bf1df Mon Sep 17 00:00:00 2001 From: Mit Kotak Date: Mon, 26 Jul 2021 04:20:51 +0000 Subject: [PATCH 2/5] Implemented test_gpuarray.test_(stack|concatenate) --- test/test_gpuarray.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/test_gpuarray.py b/test/test_gpuarray.py index 4b53aca7..2b8b66b3 100644 --- a/test/test_gpuarray.py +++ b/test/test_gpuarray.py @@ -468,6 +468,53 @@ class TestGPUArray: a = gpuarray.arange(12, dtype=np.float32) assert (np.arange(12, dtype=np.float32) == a.get()).all() + @mark_cuda_test + def test_stack(self): + + orders = ["F", "C"] + input_dims_lst = [0, 1, 2] + + for order in orders: + for input_dims in input_dims_lst: + shape = (2, 2, 2)[:input_dims] + axis = -1 if order == "F" else 0 + + from numpy.random import default_rng + rng = default_rng() + x_in = rng.random(size=shape) + y_in = rng.random(size=shape) + x_in = x_in if order == "C" else np.asfortranarray(x_in) + y_in = y_in if order == "C" else np.asfortranarray(y_in) + + x_gpu = gpuarray.to_gpu(x_in) + y_gpu = gpuarray.to_gpu(y_in) + + numpy_stack = np.stack((x_in, y_in), axis=axis) + gpuarray_stack = gpuarray.stack((x_gpu, y_gpu), axis=axis) + + np.testing.assert_allclose(gpuarray_stack.get(), numpy_stack) + + assert gpuarray_stack.shape == numpy_stack.shape + + @mark_cuda_test + def test_concatenate(self): + + from pycuda.curandom import rand as curand + + a_dev = curand((5, 15, 20), dtype=np.float32) + b_dev = curand((4, 15, 20), dtype=np.float32) + c_dev = curand((3, 15, 20), dtype=np.float32) + a = a_dev.get() + b = b_dev.get() + c = c_dev.get() + + cat_dev = gpuarray.concatenate((a_dev, b_dev, c_dev)) + cat = np.concatenate((a, b, c)) + + np.testing.assert_allclose(cat, cat_dev.get()) + + assert cat.shape == cat_dev.shape + @mark_cuda_test def test_reverse(self): a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.float32) -- GitLab From bef20492f8571b1f2f13fd3926ebc7b55ce81d05 Mon Sep 17 00:00:00 2001 From: Mit Kotak Date: Mon, 26 Jul 2021 04:28:06 +0000 Subject: [PATCH 3/5] Implemented gpuarray.(stack|concatenate) + docs --- doc/array.rst | 2 +- pycuda/gpuarray.py | 128 ++++++++++++++++++++++----------------------- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/doc/array.rst b/doc/array.rst index 23f22ce0..34c65693 100644 --- a/doc/array.rst +++ b/doc/array.rst @@ -343,7 +343,7 @@ Constructing :class:`GPUArray` Instances Join a sequence of arrays along an existing axis. - .. function:: stack(arrays, axis=0, allocator=None) +.. function:: stack(arrays, axis=0, allocator=None) Join a sequence of arrays along a new axis. diff --git a/pycuda/gpuarray.py b/pycuda/gpuarray.py index e5d853be..4d09c77a 100644 --- a/pycuda/gpuarray.py +++ b/pycuda/gpuarray.py @@ -1768,93 +1768,93 @@ def multi_put(arrays, dest_indices, dest_shape=None, out=None, stream=None): # {{{ shape manipulation def concatenate(arrays, axis=0, allocator=None): - """ - Join a sequence of arrays along an existing axis. - :arg arrays: A sequnce of :class:`GPUArray`. - :arg axis: Index of the dimension of the new axis in the result array. - Can be -1, for the new axis to be last dimension. - :returns: :class:`GPUArray` - """ - # implementation is borrowed from pyopencl.array.concatenate() - # {{{ find properties of result array + """ + Join a sequence of arrays along an existing axis. + :arg arrays: A sequnce of :class:`GPUArray`. + :arg axis: Index of the dimension of the new axis in the result array. + Can be -1, for the new axis to be last dimension. + :returns: :class:`GPUArray` + """ + # implementation is borrowed from pyopencl.array.concatenate() + # {{{ find properties of result array - shape = None + shape = None - def shape_except_axis(ary: GPUArray): - return ary.shape[:axis] + ary.shape[axis+1:] + def shape_except_axis(ary: GPUArray): + return ary.shape[:axis] + ary.shape[axis+1:] - for i_ary, ary in enumerate(arrays): - allocator = allocator or ary.allocator + for i_ary, ary in enumerate(arrays): + allocator = allocator or ary.allocator - if shape is None: - # first array - shape = list(ary.shape) + if shape is None: + # first array + shape = list(ary.shape) - else: - if len(ary.shape) != len(shape): - raise ValueError("%d'th array has different number of axes " - "(should have %d, has %d)" - % (i_ary, len(ary.shape), len(shape))) + else: + if len(ary.shape) != len(shape): + raise ValueError("%d'th array has different number of axes " + "(should have %d, has %d)" + % (i_ary, len(ary.shape), len(shape))) - if (ary.ndim != arrays[0].ndim - or shape_except_axis(ary) != shape_except_axis(arrays[0])): - raise ValueError("%d'th array has residual not matching " - "other arrays" % i_ary) + if (ary.ndim != arrays[0].ndim + or shape_except_axis(ary) != shape_except_axis(arrays[0])): + raise ValueError("%d'th array has residual not matching " + "other arrays" % i_ary) - shape[axis] += ary.shape[axis] + shape[axis] += ary.shape[axis] - # }}} + # }}} - shape = tuple(shape) - dtype = np.find_common_type([ary.dtype for ary in arrays], []) - result = empty(shape, dtype, allocator=allocator) + shape = tuple(shape) + dtype = np.find_common_type([ary.dtype for ary in arrays], []) + result = empty(shape, dtype, allocator=allocator) - full_slice = (slice(None),) * len(shape) + full_slice = (slice(None),) * len(shape) - base_idx = 0 - for ary in arrays: - my_len = ary.shape[axis] - result[full_slice[:axis] + (slice(base_idx, base_idx+my_len),) + full_slice[axis+1:]] = ary - base_idx += my_len + base_idx = 0 + for ary in arrays: + my_len = ary.shape[axis] + result[full_slice[:axis] + (slice(base_idx, base_idx+my_len),) + full_slice[axis+1:]] = ary + base_idx += my_len - return result + return result def stack(arrays, axis=0, allocator=None): - """ - Join a sequence of arrays along a new axis. - :arg arrays: A sequnce of :class:`GPUArray`. - :arg axis: Index of the dimension of the new axis in the result array. - Can be -1, for the new axis to be last dimension. - :returns: :class:`GPUArray` - """ - # implementation is borrowed from pyopencl.array.stack() - allocator = allocator or arrays[0].allocator + """ + Join a sequence of arrays along a new axis. + :arg arrays: A sequnce of :class:`GPUArray`. + :arg axis: Index of the dimension of the new axis in the result array. + Can be -1, for the new axis to be last dimension. + :returns: :class:`GPUArray` + """ + # implementation is borrowed from pyopencl.array.stack() + allocator = allocator or arrays[0].allocator - if not arrays: - raise ValueError("need at least one array to stack") + if not arrays: + raise ValueError("need at least one array to stack") - input_shape = arrays[0].shape - input_ndim = arrays[0].ndim - axis = input_ndim if axis == -1 else axis + input_shape = arrays[0].shape + input_ndim = arrays[0].ndim + axis = input_ndim if axis == -1 else axis - if not all(ary.shape == input_shape for ary in arrays[1:]): - raise ValueError("arrays must have the same shape") + if not all(ary.shape == input_shape for ary in arrays[1:]): + raise ValueError("arrays must have the same shape") - if not (0 <= axis <= input_ndim): - raise ValueError("invalid axis") + if not (0 <= axis <= input_ndim): + raise ValueError("invalid axis") - result_shape = input_shape[:axis] + (len(arrays),) + input_shape[axis:] - result = empty(shape=result_shape, - dtype=np.result_type(*(ary.dtype for ary in arrays)), - allocator=allocator, order="C" if axis == 0 else "F") + result_shape = input_shape[:axis] + (len(arrays),) + input_shape[axis:] + result = empty(shape=result_shape, + dtype=np.result_type(*(ary.dtype for ary in arrays)), + allocator=allocator, order="C" if axis == 0 else "F") - for i, ary in enumerate(arrays): + for i, ary in enumerate(arrays): - idx = (slice(None),)*axis + (i,) + (slice(None),)*(input_ndim-axis) - result[idx] = ary + idx = (slice(None),)*axis + (i,) + (slice(None),)*(input_ndim-axis) + result[idx] = ary - return result + return result def transpose(a, axes=None): -- GitLab From 6ffd0b5e6641cc8398e29f08ccf07e78ca57fefb Mon Sep 17 00:00:00 2001 From: Mit Kotak Date: Mon, 26 Jul 2021 04:44:09 +0000 Subject: [PATCH 4/5] Removed Indentation errors --- pycuda/gpuarray.py | 2 +- test/test_gpuarray.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pycuda/gpuarray.py b/pycuda/gpuarray.py index 4d09c77a..dfd360eb 100644 --- a/pycuda/gpuarray.py +++ b/pycuda/gpuarray.py @@ -1779,7 +1779,7 @@ def concatenate(arrays, axis=0, allocator=None): # {{{ find properties of result array shape = None - + def shape_except_axis(ary: GPUArray): return ary.shape[:axis] + ary.shape[axis+1:] diff --git a/test/test_gpuarray.py b/test/test_gpuarray.py index 2b8b66b3..dbf4b2f7 100644 --- a/test/test_gpuarray.py +++ b/test/test_gpuarray.py @@ -499,7 +499,7 @@ class TestGPUArray: @mark_cuda_test def test_concatenate(self): - from pycuda.curandom import rand as curand + from pycuda.curandom import rand as curand a_dev = curand((5, 15, 20), dtype=np.float32) b_dev = curand((4, 15, 20), dtype=np.float32) -- GitLab From 18fc655f35689fe5525fa037af769bf6cd5a2425 Mon Sep 17 00:00:00 2001 From: Mit Kotak Date: Mon, 26 Jul 2021 04:54:05 +0000 Subject: [PATCH 5/5] Removed Indentation in stack() --- pycuda/gpuarray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycuda/gpuarray.py b/pycuda/gpuarray.py index dfd360eb..a0bf84c4 100644 --- a/pycuda/gpuarray.py +++ b/pycuda/gpuarray.py @@ -1779,7 +1779,7 @@ def concatenate(arrays, axis=0, allocator=None): # {{{ find properties of result array shape = None - + def shape_except_axis(ary: GPUArray): return ary.shape[:axis] + ary.shape[axis+1:] @@ -1820,7 +1820,7 @@ def concatenate(arrays, axis=0, allocator=None): return result - def stack(arrays, axis=0, allocator=None): +def stack(arrays, axis=0, allocator=None): """ Join a sequence of arrays along a new axis. :arg arrays: A sequnce of :class:`GPUArray`. -- GitLab