diff --git a/doc/source/array.rst b/doc/source/array.rst index e49f8305e28bd56ee2a980677cbda05a7f508c80..d72444240c53d006114010a9758311cca2ee925e 100644 --- a/doc/source/array.rst +++ b/doc/source/array.rst @@ -90,6 +90,19 @@ The :class:`Array` Class Returns the size of the leading dimension of *self*. + .. method :: reshape(shape) + + Returns an array containing the same data with a new shape. + + .. method :: ravel() + + Returns flattened array containing the same data. + + .. metod :: view(dtype=None) + + Returns view of array with the same data. If *dtype* is different from + current dtype, the actual bytes of memory will be reinterpreted. + .. method :: set(ary, queue=None, async=False) Transfer the contents the :class:`numpy.ndarray` object *ary* diff --git a/pyopencl/array.py b/pyopencl/array.py index 3a7c8d1b5a97fb6ad6a01b60764c0ace597b49e9..c27ea813cacf4232822ee7972efd694f21cef37f 100644 --- a/pyopencl/array.py +++ b/pyopencl/array.py @@ -50,8 +50,7 @@ class vec: def _create_vector_types(): field_names = ["x", "y", "z", "w"] - name_to_dtype = {} - dtype_to_name = {} + from pyopencl.tools import register_dtype counts = [2, 3, 4, 8, 16] for base_name, base_type in [ @@ -78,8 +77,7 @@ def _create_vector_types(): formats=[base_type]*count, titles=titles)) - name_to_dtype[name] = dtype - dtype_to_name[dtype] = name + register_dtype(dtype, name) setattr(vec, name, dtype) @@ -92,9 +90,6 @@ def _create_vector_types(): % (my_field_names_defaulted, my_field_names), dict(array=np.array, my_dtype=dtype)))) - vec._dtype_to_c_name = dtype_to_name - vec._c_name_to_dtype = name_to_dtype - _create_vector_types() # }}} @@ -293,9 +288,6 @@ class Array(object): self.data = self.allocator(self.size * self.dtype.itemsize) else: self.data = None - - if base is not None: - raise ValueError("If data is specified, base must be None.") else: self.data = data @@ -308,6 +300,28 @@ class Array(object): def flags(self): return _ArrayFlags(self) + def _new_with_changes(self, data, shape=None, dtype=None, strides=None, queue=None, + base=None): + if shape is None: + shape = self.shape + if dtype is None: + dtype = self.dtype + if strides is None: + strides = self.strides + if queue is None: + queue = self.queue + if base is None and data is self.data: + base = self + + if queue is not None: + return Array(queue, shape, dtype, + allocator=self.allocator, strides=strides, base=base) + elif self.allocator is not None: + return Array(self.allocator, shape, dtype, queue=queue, + strides=strides, base=base) + else: + return Array(self.context, shape, dtype, strides=strides, base=base) + #@memoize_method FIXME: reenable def get_sizes(self, queue, kernel_specific_max_wg_size=None): return splay(queue, self.mem_size, @@ -661,6 +675,37 @@ class Array(object): def __gt__(self, other): raise NotImplementedError + # {{{ views + + def reshape(self, *shape): + # TODO: add more error-checking, perhaps + if isinstance(shape[0], tuple) or isinstance(shape[0], list): + shape = tuple(shape[0]) + size = reduce(lambda x, y: x * y, shape, 1) + if size != self.size: + raise ValueError("total size of new array must be unchanged") + + return self._new_with_changes(data=self.data, shape=shape) + + def ravel(self): + return self.reshape(self.size) + + def view(self, dtype=None): + if dtype is None: + dtype = self.dtype + + old_itemsize = self.dtype.itemsize + itemsize = np.dtype(dtype).itemsize + + if self.shape[-1] * old_itemsize % itemsize != 0: + raise ValueError("new type not compatible with array") + + shape = self.shape[:-1] + (self.shape[-1] * old_itemsize // itemsize,) + + return self._new_with_changes(data=self.data, shape=shape, dtype=dtype) + + # }} + # }}} # {{{ creation helpers @@ -711,15 +756,7 @@ def zeros(*args, **kwargs): return _zeros(*args, **kwargs) def empty_like(ary): - if ary.queue is not None: - return Array(ary.queue, ary.shape, ary.dtype, - allocator=ary.allocator, strides=ary.strides) - elif ary.allocator is not None: - return Array(ary.allocator, ary.shape, ary.dtype, queue=ary.queue, - strides=ary.strides) - else: - return Array(ary.context, ary.shape, ary.dtype, - strides=ary.strides) + return ary._new_with_changes(data=None) def zeros_like(ary): result = empty_like(ary) diff --git a/pyopencl/compyte b/pyopencl/compyte index 9bebf5c44e95690139ab0940d39cddcf7fa13593..4118040aa50b5a7ef9b50d4b6b9f20fb1c764781 160000 --- a/pyopencl/compyte +++ b/pyopencl/compyte @@ -1 +1 @@ -Subproject commit 9bebf5c44e95690139ab0940d39cddcf7fa13593 +Subproject commit 4118040aa50b5a7ef9b50d4b6b9f20fb1c764781 diff --git a/pyopencl/reduction.py b/pyopencl/reduction.py index aecc202d34cd06e43adcd5fb23bc7f54414307a9..0a5aaf422217b070cf268df5a12f77b10e2e0d23 100644 --- a/pyopencl/reduction.py +++ b/pyopencl/reduction.py @@ -55,11 +55,10 @@ KERNEL = """ #pragma OPENCL EXTENSION cl_amd_fp64: enable % endif + ${preamble} typedef ${out_type} out_type; - ${preamble} - __kernel void ${name}( __global out_type *out, ${arguments}, unsigned int seq_count, unsigned int n) @@ -217,7 +216,7 @@ def get_reduction_source( -def get_reduction_kernel( +def get_reduction_kernel(stage, ctx, out_type, out_type_size, neutral, reduce_expr, map_expr=None, arguments=None, name="reduce_kernel", preamble="", @@ -225,7 +224,7 @@ def get_reduction_kernel( if map_expr is None: map_expr = "in[i]" - if arguments is None: + if stage == 2: arguments = "__global const %s *in" % out_type inf = get_reduction_source( @@ -266,7 +265,7 @@ class ReductionKernel: trip_count = 0 while True: - self.stage_1_inf = get_reduction_kernel(ctx, + self.stage_1_inf = get_reduction_kernel(1, ctx, dtype_to_ctype(dtype_out), dtype_out.itemsize, neutral, reduce_expr, map_expr, arguments, name=name+"_stage1", options=options, preamble=preamble, @@ -285,9 +284,9 @@ class ReductionKernel: assert trip_count <= 2 # stage 2 has only one input and no map expression - self.stage_2_inf = get_reduction_kernel(ctx, + self.stage_2_inf = get_reduction_kernel(2, ctx, dtype_to_ctype(dtype_out), dtype_out.itemsize, - neutral, reduce_expr, + neutral, reduce_expr, arguments=arguments, name=name+"_stage2", options=options, preamble=preamble, max_group_size=max_group_size) diff --git a/pyopencl/tools.py b/pyopencl/tools.py index 7981a76224d9310e05e06b90ad36d7876289f018..8e1accbbc0e06e6bd1caccb6cdec643719b96af6 100644 --- a/pyopencl/tools.py +++ b/pyopencl/tools.py @@ -33,6 +33,11 @@ import numpy as np from decorator import decorator import pyopencl as cl +from pyopencl.compyte.dtypes import ( + register_dtype, _fill_dtype_registry, + dtype_to_ctype) + +_fill_dtype_registry(respect_windows=True) @@ -186,51 +191,8 @@ def pytest_generate_tests_for_pyopencl(metafunc): -# {{{ C code generation helpers ----------------------------------------------- -def dtype_to_ctype(dtype): - if dtype is None: - raise ValueError("dtype may not be None") - - dtype = np.dtype(dtype) - if dtype == np.int64: - return "long" - elif dtype == np.uint64: - return "unsigned long" - elif dtype == np.int32: - return "int" - elif dtype == np.uint32: - return "unsigned int" - elif dtype == np.int16: - return "short int" - elif dtype == np.uint16: - return "short unsigned int" - elif dtype == np.int8: - return "signed char" - elif dtype == np.uint8: - return "unsigned char" - elif dtype == np.bool: - return "bool" - elif dtype == np.float32: - return "float" - elif dtype == np.float64: - return "double" - elif dtype == np.complex64: - return "complex float" - elif dtype == np.complex128: - return "complex double" - else: - import pyopencl.array as cl_array - try: - return cl_array.vec._dtype_to_c_name[dtype] - except KeyError: - raise ValueError, "unable to map dtype '%s'" % dtype - -# }}} - - - +# {{{ C argument lists -# {{{ C argument lists -------------------------------------------------------- class Argument: def __init__(self, dtype, name): self.dtype = np.dtype(dtype) @@ -256,59 +218,12 @@ class ScalarArg(Argument): def parse_c_arg(c_arg): c_arg = (c_arg - .replace("const", "") - .replace("volatile", "") .replace("__global", "") .replace("__local", "") .replace("__constant", "")) - # process and remove declarator - import re - decl_re = re.compile(r"(\**)\s*([_a-zA-Z0-9]+)(\s*\[[ 0-9]*\])*\s*$") - decl_match = decl_re.search(c_arg) - - if decl_match is None: - raise ValueError("couldn't parse C declarator '%s'" % c_arg) - - name = decl_match.group(2) - - if decl_match.group(1) or decl_match.group(3) is not None: - arg_class = VectorArg - else: - arg_class = ScalarArg - - tp = c_arg[:decl_match.start()] - tp = " ".join(tp.split()) - - type_re = re.compile(r"^([a-z0-9 ]+)$") - type_match = type_re.match(tp) - if not type_match: - raise RuntimeError("type '%s' did not match expected shape of type" - % tp) - - tp = type_match.group(1) - - if tp == "float": dtype = np.float32 - elif tp == "double": dtype = np.float64 - elif tp in ["int", "signed int"]: dtype = np.int32 - elif tp in ["unsigned", "unsigned int"]: dtype = np.uint32 - elif tp in ["long", "long int"]: dtype = np.int64 - elif tp in ["unsigned long", "unsigned long int", "long unsigned int"]: - dtype = np.uint64 - elif tp in ["short", "short int"]: dtype = np.int16 - elif tp in ["unsigned short", "unsigned short int", "short unsigned int"]: - dtype = np.uint16 - elif tp in ["char", "signed char"]: dtype = np.int8 - elif tp in ["unsigned char"]: dtype = np.uint8 - elif tp in ["bool"]: dtype = np.bool - else: - import pyopencl.array as cl_array - try: - dtype = cl_array.vec._c_name_to_dtype[tp] - except KeyError: - raise ValueError("unknown type '%s'" % tp) - - return arg_class(dtype, name) + from pyopencl.compyte.dtypes import parse_c_arg_backend + return parse_c_arg_backend(c_arg, ScalarArg, VectorArg) # }}} diff --git a/test/test_array.py b/test/test_array.py index 120f3132ee19e88f56bd03824e854f7042819057..8dd2fab72231f6e6097e847f2863ee4488ab93b8 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -639,6 +639,90 @@ def test_mem_pool_with_arrays(ctx_factory): assert b_dev.allocator is mem_pool assert result.allocator is mem_pool +@pytools.test.mark_test.opencl +def test_view(ctx_factory): + context = ctx_factory() + queue = cl.CommandQueue(context) + + a = np.arange(128).reshape(8, 16).astype(np.float32) + a_dev = cl_array.to_device(queue, a) + + # same dtype + view = a_dev.view() + assert view.shape == a_dev.shape and view.dtype == a_dev.dtype + + # larger dtype + view = a_dev.view(np.complex64) + assert view.shape == (8, 8) and view.dtype == np.complex64 + + # smaller dtype + view = a_dev.view(np.int16) + assert view.shape == (8, 32) and view.dtype == np.int16 + +mmc_dtype = np.dtype([("cur_min", np.float32), ("cur_max", np.float32)]) + +from pyopencl.tools import register_dtype +register_dtype(mmc_dtype, "minmax_collector") + +@pytools.test.mark_test.opencl +def test_struct_reduce(ctx_factory): + context = ctx_factory() + queue = cl.CommandQueue(context) + + preamble = """//CL// + struct minmax_collector + { + float cur_min; + float cur_max; + }; + + typedef struct minmax_collector minmax_collector; + + minmax_collector mmc_neutral() + { + // FIXME: needs infinity literal in real use, ok here + minmax_collector result = {10000, -10000}; + return result; + } + + minmax_collector mmc_from_scalar(float x) + { + minmax_collector result = {x, x}; + return result; + } + + minmax_collector agg_mmc(minmax_collector a, minmax_collector b) + { + minmax_collector result = { + fmin(a.cur_min, b.cur_min), + fmax(a.cur_max, b.cur_max), + }; + return result; + } + + """ + + + from pyopencl.clrandom import rand as clrand + a_gpu = clrand(queue, (20000,), dtype=np.float32) + a = a_gpu.get() + + + from pyopencl.reduction import ReductionKernel + red = ReductionKernel(context, mmc_dtype, + neutral="mmc_neutral()", + reduce_expr="agg_mmc(a, b)", map_expr="mmc_from_scalar(x[i])", + arguments="__global float *x", preamble=preamble) + + minmax = red(a_gpu).get() + #print minmax["cur_min"], minmax["cur_max"] + #print np.min(a), np.max(a) + + assert minmax["cur_min"] == np.min(a) + assert minmax["cur_max"] == np.max(a) + + + if __name__ == "__main__": # make sure that import failures get reported, instead of skipping the @@ -651,3 +735,5 @@ if __name__ == "__main__": else: from py.test.cmdline import main main([__file__]) + +# vim: filetype=pyopencl