diff --git a/doc/array.rst b/doc/array.rst index e572c900fbbc01ff95fcef8bbc4aa59a2399ba1b..9078f6f166e9cf2f92067fa9a653132f0d6c982a 100644 --- a/doc/array.rst +++ b/doc/array.rst @@ -144,6 +144,12 @@ Constructing :class:`Array` Instances .. autofunction:: take .. autofunction:: concatenate +Manipulating :class:`Array` instances +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: transpose +.. autofunction:: reshape + Conditionals ^^^^^^^^^^^^ diff --git a/pyopencl/array.py b/pyopencl/array.py index 3db01624cba69cadda6544fcc1c57f70c2fb5e43..9fa3c84f1d2d5f200aca1007a8409f08d0365c5f 100644 --- a/pyopencl/array.py +++ b/pyopencl/array.py @@ -338,6 +338,10 @@ class Array(object): The tuple of lengths of each dimension in the array. + .. attribute :: ndim + + The number of dimensions in :attr:`shape`. + .. attribute :: dtype The :class:`numpy.dtype` of the items in the GPU array. @@ -370,6 +374,8 @@ class Array(object): .. automethod :: reshape .. automethod :: ravel .. automethod :: view + .. automethod :: transpose + .. attribute :: T .. automethod :: set .. automethod :: get .. automethod :: copy @@ -559,6 +565,10 @@ class Array(object): self.offset = offset + @property + def ndim(self): + return len(self.shape) + @property def context(self): return self.base_data.context @@ -1280,6 +1290,9 @@ class Array(object): raise ValueError("order must be either 'C' or 'F'") # TODO: add more error-checking, perhaps + if not self.flags.forc: + raise RuntimeError("only contiguous arrays may " + "be used as arguments to this operation") if isinstance(shape[0], tuple) or isinstance(shape[0], list): shape = tuple(shape[0]) @@ -1426,6 +1439,39 @@ class Array(object): shape=new_shape, dtype=dtype, strides=new_strides) + def transpose(self, axes=None): + """Permute the dimensions of an array. + + :arg axes: list of ints, optional. + By default, reverse the dimensions, otherwise permute the axes + according to the values given. + + :returns: :class:`Array` A view of the array with its axes permuted. + + .. versionadded:: 2015.2 + """ + + if axes is None: + axes = range(self.ndim-1, -1, -1) + + if len(axes) != len(self.shape): + raise ValueError("axes don't match array") + + new_shape = [self.shape[axes[i]] for i in xrange(len(axes))] + new_strides = [self.strides[axes[i]] for i in xrange(len(axes))] + + return self._new_with_changes( + self.base_data, self.offset, + shape=tuple(new_shape), + strides=tuple(new_strides)) + + @property + def T(self): # noqa + """ + .. versionadded:: 2015.2 + """ + return self.transpose() + # }}} def map_to_host(self, queue=None, flags=None, is_blocking=True, wait_for=None): @@ -1538,6 +1584,11 @@ class Array(object): "more than one ellipsis not allowed in index") seen_ellipsis = True + elif index_entry is np.newaxis: + new_shape.append(1) + new_strides.append(0) + index_axis += 1 + else: raise IndexError("invalid subindex in axis %d" % index_axis) @@ -2136,6 +2187,32 @@ def hstack(arrays, queue=None): # }}} +# {{{ shape manipulation + +def transpose(a, axes=None): + """Permute the dimensions of an array. + + :arg a: :class:`Array` + :arg axes: list of ints, optional. + By default, reverse the dimensions, otherwise permute the axes + according to the values given. + + :returns: :class:`Array` A view of the array with its axes permuted. + """ + return a.transpose(axes) + + +def reshape(a, shape): + """Gives a new shape to an array without changing its data. + + .. versionadded:: 2015.2 + """ + + return a.reshape(shape) + +# }}} + + # {{{ conditionals @elwise_kernel_runner diff --git a/test/test_array.py b/test/test_array.py index 485a46a62d03976711d6c4170efbc4f4022b63ef..4c545bfeaa3d572c443798f4eb21cac699a43e6b 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -830,6 +830,36 @@ def test_skip_slicing(ctx_factory): assert np.array_equal(b[1].get(), b_host[1]) +def test_transpose(ctx_factory): + context = ctx_factory() + queue = cl.CommandQueue(context) + + from pyopencl.clrandom import rand as clrand + + a_gpu = clrand(queue, (10, 20, 30), dtype=np.float32) + a = a_gpu.get() + + # FIXME: not contiguous + #assert np.allclose(a_gpu.transpose((1,2,0)).get(), a.transpose((1,2,0))) + assert np.array_equal(a_gpu.T.get(), a.T) + + +def test_newaxis(ctx_factory): + context = ctx_factory() + queue = cl.CommandQueue(context) + + from pyopencl.clrandom import rand as clrand + + a_gpu = clrand(queue, (10, 20, 30), dtype=np.float32) + a = a_gpu.get() + + b_gpu = a_gpu[:, np.newaxis] + b = a[:, np.newaxis] + + assert b_gpu.shape == b.shape + assert b_gpu.strides == b.strides + + if __name__ == "__main__": # make sure that import failures get reported, instead of skipping the # tests.