diff --git a/arraycontext/impl/pyopencl.py b/arraycontext/impl/pyopencl/__init__.py similarity index 63% rename from arraycontext/impl/pyopencl.py rename to arraycontext/impl/pyopencl/__init__.py index fa2aa7493b1d4bb074c75a6b9e54a8c95acdfe11..ca5b2dd725c3a121e87256859d5de37f68e01a11 100644 --- a/arraycontext/impl/pyopencl.py +++ b/arraycontext/impl/pyopencl/__init__.py @@ -28,153 +28,16 @@ THE SOFTWARE. from warnings import warn from typing import Sequence, Union -from functools import partial -import operator import numpy as np from pytools.tag import Tag from arraycontext.metadata import FirstAxisIsElementsTag -from arraycontext.fake_numpy import \ - BaseFakeNumpyNamespace, BaseFakeNumpyLinalgNamespace -from arraycontext.container.traversal import (rec_multimap_array_container, - rec_map_array_container) from arraycontext.context import ArrayContext from numbers import Number -# {{{ fake numpy - -class PyOpenCLFakeNumpyNamespace(BaseFakeNumpyNamespace): - def _get_fake_numpy_linalg_namespace(self): - return _PyOpenCLFakeNumpyLinalgNamespace(self._array_context) - - # {{{ comparisons - - # FIXME: This should be documentation, not a comment. - # These are here mainly because some arrays may choose to interpret - # equality comparison as a binary predicate of structural identity, - # i.e. more like "are you two equal", and not like numpy semantics. - # These operations provide access to numpy-style comparisons in that - # case. - - def equal(self, x, y): - return rec_multimap_array_container(operator.eq, x, y) - - def not_equal(self, x, y): - return rec_multimap_array_container(operator.ne, x, y) - - def greater(self, x, y): - return rec_multimap_array_container(operator.gt, x, y) - - def greater_equal(self, x, y): - return rec_multimap_array_container(operator.ge, x, y) - - def less(self, x, y): - return rec_multimap_array_container(operator.lt, x, y) - - def less_equal(self, x, y): - return rec_multimap_array_container(operator.le, x, y) - - # }}} - - def ones_like(self, ary): - def _ones_like(subary): - ones = self._array_context.empty_like(subary) - ones.fill(1) - return ones - - return self._new_like(ary, _ones_like) - - def maximum(self, x, y): - import pyopencl.array as cl_array - return rec_multimap_array_container( - partial(cl_array.maximum, queue=self._array_context.queue), - x, y) - - def minimum(self, x, y): - import pyopencl.array as cl_array - return rec_multimap_array_container( - partial(cl_array.minimum, queue=self._array_context.queue), - x, y) - - def where(self, criterion, then, else_): - import pyopencl.array as cl_array - - def where_inner(inner_crit, inner_then, inner_else): - if isinstance(inner_crit, bool): - return inner_then if inner_crit else inner_else - return cl_array.if_positive(inner_crit != 0, inner_then, inner_else, - queue=self._array_context.queue) - - return rec_multimap_array_container(where_inner, criterion, then, else_) - - def sum(self, a, dtype=None): - import pyopencl.array as cl_array - return cl_array.sum( - a, dtype=dtype, queue=self._array_context.queue).get()[()] - - def min(self, a): - import pyopencl.array as cl_array - return cl_array.min(a, queue=self._array_context.queue).get()[()] - - def max(self, a): - import pyopencl.array as cl_array - return cl_array.max(a, queue=self._array_context.queue).get()[()] - - def stack(self, arrays, axis=0): - import pyopencl.array as cla - return rec_multimap_array_container( - lambda *args: cla.stack(arrays=args, axis=axis, - queue=self._array_context.queue), - *arrays) - - def reshape(self, a, newshape): - import pyopencl.array as cla - return cla.reshape(a, newshape) - - def concatenate(self, arrays, axis=0): - import pyopencl.array as cla - return cla.concatenate( - arrays, axis, - self._array_context.queue, - self._array_context.allocator - ) - - def ravel(self, a, order="C"): - def _rec_ravel(a): - if order in "FC": - return a.reshape(-1, order=order) - elif order == "A": - # TODO: upstream this to pyopencl.array - if a.flags.f_contiguous: - return a.reshape(-1, order="F") - elif a.flags.c_contiguous: - return a.reshape(-1, order="C") - else: - raise ValueError("For `order='A'`, array should be either" - " F-contiguous or C-contiguous.") - elif order == "K": - raise NotImplementedError("PyOpenCLArrayContext.np.ravel not " - "implemented for 'order=K'") - else: - raise ValueError("`order` can be one of 'F', 'C', 'A' or 'K'. " - f"(got {order})") - - return rec_map_array_container(_rec_ravel, a) - -# }}} - - -# {{{ fake np.linalg - -class _PyOpenCLFakeNumpyLinalgNamespace(BaseFakeNumpyLinalgNamespace): - pass - -# }}} - - # {{{ PyOpenCLArrayContext class PyOpenCLArrayContext(ArrayContext): @@ -222,6 +85,8 @@ class PyOpenCLArrayContext(ArrayContext): For now, *wait_event_queue_length* should be regarded as an experimental feature that may change or disappear at any minute. """ + import pyopencl as cl + super().__init__() self.context = queue.context self.queue = queue @@ -233,7 +98,6 @@ class PyOpenCLArrayContext(ArrayContext): self._wait_event_queue_length = wait_event_queue_length self._kernel_name_to_wait_event_queue = {} - import pyopencl as cl if queue.device.type & cl.device_type.GPU: if allocator is None: warn("PyOpenCLArrayContext created without an allocator on a GPU. " @@ -250,23 +114,24 @@ class PyOpenCLArrayContext(ArrayContext): self._loopy_transform_cache = {} def _get_fake_numpy_namespace(self): + from arraycontext.impl.pyopencl.fake_numpy import PyOpenCLFakeNumpyNamespace return PyOpenCLFakeNumpyNamespace(self) # {{{ ArrayContext interface def empty(self, shape, dtype): - import pyopencl.array as cla - return cla.empty(self.queue, shape=shape, dtype=dtype, + import pyopencl.array as cl_array + return cl_array.empty(self.queue, shape=shape, dtype=dtype, allocator=self.allocator) def zeros(self, shape, dtype): - import pyopencl.array as cla - return cla.zeros(self.queue, shape=shape, dtype=dtype, + import pyopencl.array as cl_array + return cl_array.zeros(self.queue, shape=shape, dtype=dtype, allocator=self.allocator) def from_numpy(self, array: np.ndarray): - import pyopencl.array as cla - return cla.to_device(self.queue, array, allocator=self.allocator) + import pyopencl.array as cl_array + return cl_array.to_device(self.queue, array, allocator=self.allocator) def to_numpy(self, array): if isinstance(array, Number): diff --git a/arraycontext/impl/pyopencl/fake_numpy.py b/arraycontext/impl/pyopencl/fake_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..e2bfe00df269b62b435f1d8d3e8a6c57bd8c4da0 --- /dev/null +++ b/arraycontext/impl/pyopencl/fake_numpy.py @@ -0,0 +1,165 @@ +""" +.. currentmodule:: arraycontext +.. autoclass:: PyOpenCLArrayContext +""" +__copyright__ = """ +Copyright (C) 2020-1 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from functools import partial +import operator + +from arraycontext.fake_numpy import \ + BaseFakeNumpyNamespace, BaseFakeNumpyLinalgNamespace +from arraycontext.container.traversal import (rec_multimap_array_container, + rec_map_array_container) + +try: + import pyopencl as cl # noqa: F401 + import pyopencl.array as cl_array +except ImportError: + pass + + +# {{{ fake numpy + +class PyOpenCLFakeNumpyNamespace(BaseFakeNumpyNamespace): + def _get_fake_numpy_linalg_namespace(self): + return _PyOpenCLFakeNumpyLinalgNamespace(self._array_context) + + # {{{ comparisons + + # FIXME: This should be documentation, not a comment. + # These are here mainly because some arrays may choose to interpret + # equality comparison as a binary predicate of structural identity, + # i.e. more like "are you two equal", and not like numpy semantics. + # These operations provide access to numpy-style comparisons in that + # case. + + def equal(self, x, y): + return rec_multimap_array_container(operator.eq, x, y) + + def not_equal(self, x, y): + return rec_multimap_array_container(operator.ne, x, y) + + def greater(self, x, y): + return rec_multimap_array_container(operator.gt, x, y) + + def greater_equal(self, x, y): + return rec_multimap_array_container(operator.ge, x, y) + + def less(self, x, y): + return rec_multimap_array_container(operator.lt, x, y) + + def less_equal(self, x, y): + return rec_multimap_array_container(operator.le, x, y) + + # }}} + + def ones_like(self, ary): + def _ones_like(subary): + ones = self._array_context.empty_like(subary) + ones.fill(1) + return ones + + return self._new_like(ary, _ones_like) + + def maximum(self, x, y): + return rec_multimap_array_container( + partial(cl_array.maximum, queue=self._array_context.queue), + x, y) + + def minimum(self, x, y): + return rec_multimap_array_container( + partial(cl_array.minimum, queue=self._array_context.queue), + x, y) + + def where(self, criterion, then, else_): + def where_inner(inner_crit, inner_then, inner_else): + if isinstance(inner_crit, bool): + return inner_then if inner_crit else inner_else + return cl_array.if_positive(inner_crit != 0, inner_then, inner_else, + queue=self._array_context.queue) + + return rec_multimap_array_container(where_inner, criterion, then, else_) + + def sum(self, a, dtype=None): + return cl_array.sum( + a, dtype=dtype, queue=self._array_context.queue).get()[()] + + def min(self, a): + return cl_array.min(a, queue=self._array_context.queue).get()[()] + + def max(self, a): + return cl_array.max(a, queue=self._array_context.queue).get()[()] + + def stack(self, arrays, axis=0): + return rec_multimap_array_container( + lambda *args: cl_array.stack(arrays=args, axis=axis, + queue=self._array_context.queue), + *arrays) + + def reshape(self, a, newshape): + return cl_array.reshape(a, newshape) + + def concatenate(self, arrays, axis=0): + return cl_array.concatenate( + arrays, axis, + self._array_context.queue, + self._array_context.allocator + ) + + def ravel(self, a, order="C"): + def _rec_ravel(a): + if order in "FC": + return a.reshape(-1, order=order) + elif order == "A": + # TODO: upstream this to pyopencl.array + if a.flags.f_contiguous: + return a.reshape(-1, order="F") + elif a.flags.c_contiguous: + return a.reshape(-1, order="C") + else: + raise ValueError("For `order='A'`, array should be either" + " F-contiguous or C-contiguous.") + elif order == "K": + raise NotImplementedError("PyOpenCLArrayContext.np.ravel not " + "implemented for 'order=K'") + else: + raise ValueError("`order` can be one of 'F', 'C', 'A' or 'K'. " + f"(got {order})") + + return rec_map_array_container(_rec_ravel, a) + +# }}} + + +# {{{ fake np.linalg + +class _PyOpenCLFakeNumpyLinalgNamespace(BaseFakeNumpyLinalgNamespace): + pass + +# }}} + + +# vim: foldmethod=marker