diff --git a/pyopencl/__init__.py b/pyopencl/__init__.py index 2dca2218f9a89c1e9b39531e61f2f1155e05bb36..b211765148a7458ddf5230d696d46ce70a42c97b 100644 --- a/pyopencl/__init__.py +++ b/pyopencl/__init__.py @@ -285,17 +285,17 @@ def link_program(context, programs, options=[], devices=None): def _add_functionality(): cls_to_info_cls = { - _cl.Platform: (_cl.Platform.get_info, _cl.platform_info), - _cl.Device: (_cl.Device.get_info, _cl.device_info), - _cl.Context: (_cl.Context.get_info, _cl.context_info), - _cl.CommandQueue: (_cl.CommandQueue.get_info, _cl.command_queue_info), - _cl.Event: (_cl.Event.get_info, _cl.event_info), - _cl.MemoryObjectHolder: (MemoryObjectHolder.get_info, _cl.mem_info), - Image: (_cl.Image.get_image_info, _cl.image_info), - Program: (Program.get_info, _cl.program_info), - Kernel: (Kernel.get_info, _cl.kernel_info), - _cl.Sampler: (Sampler.get_info, _cl.sampler_info), - } + _cl.Platform: (_cl.Platform.get_info, _cl.platform_info), + _cl.Device: (_cl.Device.get_info, _cl.device_info), + _cl.Context: (_cl.Context.get_info, _cl.context_info), + _cl.CommandQueue: (_cl.CommandQueue.get_info, _cl.command_queue_info), + _cl.Event: (_cl.Event.get_info, _cl.event_info), + _cl.MemoryObjectHolder: (MemoryObjectHolder.get_info, _cl.mem_info), + Image: (_cl.Image.get_image_info, _cl.image_info), + Program: (Program.get_info, _cl.program_info), + Kernel: (Kernel.get_info, _cl.kernel_info), + _cl.Sampler: (Sampler.get_info, _cl.sampler_info), + } def to_string(cls, value, default_format=None): for name in dir(cls): @@ -802,8 +802,6 @@ def create_some_context(interactive=None, answers=None): if not platforms: raise Error("no platforms found") - elif len(platforms) == 1: - platform, = platforms else: if not answers: cc_print("Choose platform:") diff --git a/pyopencl/array.py b/pyopencl/array.py index d00253a4a0f7ad16b836ef23064c7f7236dae9fd..8d29b6c1d74db82d4230cb3f513a41e820bad7a8 100644 --- a/pyopencl/array.py +++ b/pyopencl/array.py @@ -1288,20 +1288,118 @@ class Array(object): raise TypeError("unexpected keyword arguments: %s" % kwargs.keys()) + if order not in "CF": + raise ValueError("order must be either 'C' or 'F'") + # TODO: add more error-checking, perhaps + if isinstance(shape[0], tuple) or isinstance(shape[0], list): shape = tuple(shape[0]) + if -1 in shape: + shape = list(shape) + idx = shape.index(-1) + size = -reduce(lambda x, y: x * y, shape, 1) + shape[idx] = self.size // size + if any(s < 0 for s in shape): + raise ValueError("can only specify one unknown dimension") + shape = tuple(shape) + if shape == self.shape: - return self + return self._new_with_changes( + data=self.base_data, offset=self.offset, shape=shape, + strides=self.strides) - size = reduce(lambda x, y: x * y, shape, 1) + import operator + size = reduce(operator.mul, shape, 1) if size != self.size: raise ValueError("total size of new array must be unchanged") + # {{{ determine reshaped strides + + # copied and translated from + # https://github.com/numpy/numpy/blob/4083883228d61a3b571dec640185b5a5d983bf59/numpy/core/src/multiarray/shape.c # noqa + + newdims = shape + newnd = len(newdims) + + # Remove axes with dimension 1 from the old array. They have no effect + # but would need special cases since their strides do not matter. + + olddims = [] + oldstrides = [] + for oi in range(len(self.shape)): + s = self.shape[oi] + if s != 1: + olddims.append(s) + oldstrides.append(self.strides[oi]) + + oldnd = len(olddims) + + newstrides = [-1]*len(newdims) + + # oi to oj and ni to nj give the axis ranges currently worked with + oi = 0 + oj = 1 + ni = 0 + nj = 1 + while ni < newnd and oi < oldnd: + np = newdims[ni] + op = olddims[oi] + + while np != op: + if np < op: + # Misses trailing 1s, these are handled later + np *= newdims[nj] + nj += 1 + else: + op *= olddims[oj] + oj += 1 + + # Check whether the original axes can be combined + for ok in range(oi, oj-1): + if order == "F": + if oldstrides[ok+1] != olddims[ok]*oldstrides[ok]: + raise ValueError("cannot reshape without copy") + else: + # C order + if (oldstrides[ok] != olddims[ok+1]*oldstrides[ok+1]): + raise ValueError("cannot reshape without copy") + + # Calculate new strides for all axes currently worked with + if order == "F": + newstrides[ni] = oldstrides[oi] + for nk in xrange(ni+1, nj): + newstrides[nk] = newstrides[nk - 1]*newdims[nk - 1] + else: + # C order + newstrides[nj - 1] = oldstrides[oj - 1] + for nk in range(nj-1, ni, -1): + newstrides[nk - 1] = newstrides[nk]*newdims[nk] + + ni = nj + nj += 1 + + oi = oj + oj += 1 + + # Set strides corresponding to trailing 1s of the new shape. + if ni >= 1: + last_stride = newstrides[ni - 1] + else: + last_stride = self.dtype.itemsize + + if order == "F": + last_stride *= newdims[ni - 1] + + for nk in range(ni, len(shape)): + newstrides[nk] = last_stride + + # }}} + return self._new_with_changes( data=self.base_data, offset=self.offset, shape=shape, - strides=_make_strides(self.dtype.itemsize, shape, order)) + strides=tuple(newstrides)) def ravel(self): """Returns flattened array containing the same data.""" diff --git a/pyopencl/ipython_ext.py b/pyopencl/ipython_ext.py index 81fbcdf8669f01415966ec183f2677ed0d5f451b..bedbf77f039ba2f04280239ec13d274a10a3e82a 100644 --- a/pyopencl/ipython_ext.py +++ b/pyopencl/ipython_ext.py @@ -3,6 +3,7 @@ from __future__ import division from IPython.core.magic import (magics_class, Magics, cell_magic, line_magic) import pyopencl as cl +import sys def _try_to_utf8(text): @@ -14,8 +15,10 @@ def _try_to_utf8(text): @magics_class class PyOpenCLMagics(Magics): def _run_kernel(self, kernel, options): - kernel = _try_to_utf8(kernel) - options = _try_to_utf8(options).strip() + if sys.version_info < (3,): + kernel = _try_to_utf8(kernel) + options = _try_to_utf8(options).strip() + try: ctx = self.shell.user_ns["cl_ctx"] except KeyError: @@ -34,37 +37,33 @@ class PyOpenCLMagics(Magics): raise RuntimeError("unable to locate cl context, which must be " "present in namespace as 'cl_ctx' or 'ctx'") - prg = cl.Program(ctx, kernel).build(options=options) + prg = cl.Program(ctx, kernel).build(options=options.split()) for knl in prg.all_kernels(): self.shell.user_ns[knl.function_name] = knl - @cell_magic def cl_kernel(self, line, cell): kernel = cell - opts, args = self.parse_options(line,'o:') + opts, args = self.parse_options(line, 'o:') build_options = opts.get('o', '') self._run_kernel(kernel, build_options) - def _load_kernel_and_options(self, line): - opts, args = self.parse_options(line,'o:f:') + opts, args = self.parse_options(line, 'o:f:') build_options = opts.get('o') kernel = self.shell.find_user_code(opts.get('f') or args) return kernel, build_options - @line_magic def cl_kernel_from_file(self, line): kernel, build_options = self._load_kernel_and_options(line) self._run_kernel(kernel, build_options) - @line_magic def cl_load_edit_kernel(self, line): kernel, build_options = self._load_kernel_and_options(line) diff --git a/setup.py b/setup.py index 86a6bdad7e6f570048796aa19d885ab8609e4ff3..b6f26b30f046b0575b27b31531635f307b6d7626 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,14 @@ #!/usr/bin/env python # -*- coding: latin-1 -*- +from setuptools.command.build_ext import build_ext as _build_ext + +class build_ext(_build_ext): + def finalize_options(self): + _build_ext.finalize_options(self) + # Prevent numpy from thinking it is still in its setup process: + __builtins__.__NUMPY_SETUP__ = False + import numpy + self.include_dirs.append(numpy.get_include()) __copyright__ = """ Copyright (C) 2009-14 Andreas Kloeckner @@ -222,6 +231,7 @@ def main(): ], install_requires=[ + "numpy", "pytools>=2014.2", "pytest>=2", "decorator>=3.2.0", diff --git a/test/test_array.py b/test/test_array.py index eb29b9b6cac86e134da49293b975f6c2a90644fc..acb82ec8876b0b96867379f9c129a24c3d15ccb9 100644 --- a/test/test_array.py +++ b/test/test_array.py @@ -148,7 +148,7 @@ def test_mix_complex(ctx_factory): err = la.norm(host_result-dev_result)/la.norm(host_result) print(err) - correct = err < 1e-5 + correct = err < 1e-4 if not correct: print(host_result) print(dev_result) @@ -719,6 +719,24 @@ def test_view_and_strides(ctx_factory): assert (y.get() == X.get()[:3, :5]).all() +def test_meshmode_view(ctx_factory): + context = ctx_factory() + queue = cl.CommandQueue(context) + + n = 2 + result = cl.array.empty(queue, (2, n*6), np.float32) + + def view(z): + return z[..., n*3:n*6].reshape(z.shape[:-1] + (n, 3)) + + result = result.with_queue(queue) + result.fill(0) + view(result)[0].fill(1) + view(result)[1].fill(1) + x = result.get() + assert (view(x) == 1).all() + + def test_event_management(ctx_factory): context = ctx_factory() queue = cl.CommandQueue(context) @@ -758,6 +776,28 @@ def test_event_management(ctx_factory): assert len(x.events) < 100 +def test_reshape(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) + + # different ways to specify the shape + a_dev.reshape(4, 32) + a_dev.reshape((4, 32)) + a_dev.reshape([4, 32]) + + # using -1 as unknown dimension + assert a_dev.reshape(-1, 32).shape == (4, 32) + assert a_dev.reshape((32, -1)).shape == (32, 4) + assert a_dev.reshape(((8, -1, 4))).shape == (8, 4, 4) + + import pytest + with pytest.raises(ValueError): + a_dev.reshape(-1, -1, 4) + + if __name__ == "__main__": # make sure that import failures get reported, instead of skipping the # tests.