diff --git a/arraycontext/__init__.py b/arraycontext/__init__.py index 3e0092db314c733339fbefbffcb140f37c32dc8e..a338059d1caa3e27d680321b5e4c58da51308aa3 100644 --- a/arraycontext/__init__.py +++ b/arraycontext/__init__.py @@ -28,9 +28,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import sys from .context import ArrayContext -from .metadata import CommonSubexpressionTag, FirstAxisIsElementsTag +from .transform_metadata import (CommonSubexpressionTag, + ElementwiseMapKernelTag) + +# deprecated, remove in 2022. +from .metadata import _FirstAxisIsElementsTag from .container import ( ArrayContainer, @@ -65,7 +70,7 @@ __all__ = ( "ArrayContext", "CommonSubexpressionTag", - "FirstAxisIsElementsTag", + "ElementwiseMapKernelTag", "ArrayContainer", "is_array_container", "is_array_container_type", @@ -91,7 +96,9 @@ __all__ = ( ) -def _acf(): +# {{{ deprecation handling + +def _deprecated_acf(): """A tiny undocumented function to pass to tests that take an ``actx_factory`` argument when running them from the command line. """ @@ -101,4 +108,32 @@ def _acf(): queue = cl.CommandQueue(context) return PyOpenCLArrayContext(queue) + +_depr_name_to_replacement_and_obj = { + "FirstAxisIsElementsTag": + ("meshmode.transform_metadata.FirstAxisIsElementsTag", + _FirstAxisIsElementsTag), + "_acf": + ("<no replacement yet>", _deprecated_acf), + } + +if sys.version_info >= (3, 7): + def __getattr__(name): + replacement_and_obj = _depr_name_to_replacement_and_obj.get(name, None) + if replacement_and_obj is not None: + replacement, obj = replacement_and_obj + from warnings import warn + warn(f"'arraycontext.{name}' is deprecated. " + f"Use '{replacement}' instead. " + f"'arraycontext.{name}' will continue to work until 2022.", + DeprecationWarning, stacklevel=2) + return obj + else: + raise AttributeError(name) +else: + FirstAxisIsElementsTag = _FirstAxisIsElementsTag + _acf = _deprecated_acf + +# }}} + # vim: foldmethod=marker diff --git a/arraycontext/context.py b/arraycontext/context.py index 5afc942a3936e9355c2b5b7afd5df65e0c0cfd02..04277f1ccefb29f502df6b004b77a385d0a02be5 100644 --- a/arraycontext/context.py +++ b/arraycontext/context.py @@ -219,6 +219,7 @@ class ArrayContext(ABC): import loopy as lp from .loopy import make_loopy_program + from arraycontext.transform_metadata import ElementwiseMapKernelTag return make_loopy_program( [domain_bset], [ @@ -227,7 +228,8 @@ class ArrayContext(ABC): var(c_name)(*[ var("inp%d" % i)[subscript] for i in range(nargs)])) ], - name="actx_special_%s" % c_name) + name="actx_special_%s" % c_name, + tags=(ElementwiseMapKernelTag(),)) @abstractmethod def freeze(self, array): diff --git a/arraycontext/impl/pyopencl/__init__.py b/arraycontext/impl/pyopencl/__init__.py index 788dcdbe2d7096e16d2748104b008f4bfdd38a3a..04319027d8a8d2415fdd0edb41063415356bca3c 100644 --- a/arraycontext/impl/pyopencl/__init__.py +++ b/arraycontext/impl/pyopencl/__init__.py @@ -34,7 +34,6 @@ import numpy as np from pytools.tag import Tag -from arraycontext.metadata import FirstAxisIsElementsTag from arraycontext.context import ArrayContext @@ -101,8 +100,11 @@ class PyOpenCLArrayContext(ArrayContext): to the host. """ if not force_device_scalars: - warn("Returning host scalars from the array context is deprecated. " - "To return device scalars set 'force_device_scalars=True'. " + warn("Configuring the PyOpenCLArrayContext to return host scalars " + "from reductions is deprecated. " + "To configure the PyOpenCLArrayContext to return " + "device scalars, pass 'force_device_scalars=True' to the " + "constructor. " "Support for returning host scalars will be removed in 2022.", DeprecationWarning, stacklevel=2) @@ -165,7 +167,10 @@ class PyOpenCLArrayContext(ArrayContext): try: t_unit = self._loopy_transform_cache[t_unit] except KeyError: + orig_t_unit = t_unit t_unit = self.transform_loopy_program(t_unit) + self._loopy_transform_cache[orig_t_unit] = t_unit + del orig_t_unit evt, result = t_unit(self.queue, **kwargs, allocator=self.allocator) @@ -190,11 +195,15 @@ class PyOpenCLArrayContext(ArrayContext): # }}} def transform_loopy_program(self, t_unit): - try: - return self._loopy_transform_cache[t_unit] - except KeyError: - pass - orig_t_unit = t_unit + from warnings import warn + warn("Using arraycontext.PyOpenCLArrayContext.transform_loopy_program " + "to transform a program. This is deprecated and will stop working " + "in 2022. Instead, subclass PyOpenCLArrayContext and implement " + "the specific logic required to transform the program for your " + "package or application. Check higher-level packages " + "(e.g. meshmode), which may already have subclasses you may want " + "to build on.", + DeprecationWarning, stacklevel=2) # accommodate loopy with and without kernel callables @@ -210,9 +219,13 @@ class PyOpenCLArrayContext(ArrayContext): all_inames = default_entrypoint.all_inames() # FIXME: This could be much smarter. inner_iname = None + + # import with underscore to avoid DeprecationWarning + from arraycontext.metadata import _FirstAxisIsElementsTag + if (len(default_entrypoint.instructions) == 1 and isinstance(default_entrypoint.instructions[0], lp.Assignment) - and any(isinstance(tag, FirstAxisIsElementsTag) + and any(isinstance(tag, _FirstAxisIsElementsTag) # FIXME: Firedrake branch lacks kernel tags for tag in getattr(default_entrypoint, "tags", ()))): stmt, = default_entrypoint.instructions @@ -243,7 +256,6 @@ class PyOpenCLArrayContext(ArrayContext): t_unit = lp.split_iname(t_unit, inner_iname, 16, inner_tag="l.0") t_unit = lp.tag_inames(t_unit, {outer_iname: "g.0"}) - self._loopy_transform_cache[orig_t_unit] = t_unit return t_unit def tag(self, tags: Union[Sequence[Tag], Tag], array): diff --git a/arraycontext/loopy.py b/arraycontext/loopy.py index 8f2816db58fe4b0a93a53542f2113c9cafbeea8a..f4c97754d731961baaaf0191f70dcfeca287b688 100644 --- a/arraycontext/loopy.py +++ b/arraycontext/loopy.py @@ -39,7 +39,7 @@ _DEFAULT_LOOPY_OPTIONS = lp.Options( def make_loopy_program(domains, statements, kernel_data=None, - name="mm_actx_kernel"): + name="mm_actx_kernel", tags=None): """Return a :class:`loopy.LoopKernel` suitable for use with :meth:`ArrayContext.call_loopy`. """ @@ -53,7 +53,8 @@ def make_loopy_program(domains, statements, kernel_data=None, options=_DEFAULT_LOOPY_OPTIONS, default_offset=lp.auto, name=name, - lang_version=MOST_RECENT_LANGUAGE_VERSION) + lang_version=MOST_RECENT_LANGUAGE_VERSION, + tags=tags) def get_default_entrypoint(t_unit): diff --git a/arraycontext/metadata.py b/arraycontext/metadata.py index 1713cd1b4f1fc944867cefca0ef13f8afda711e5..6291a86504563c64faf63be79467784ff65a6cfa 100644 --- a/arraycontext/metadata.py +++ b/arraycontext/metadata.py @@ -1,8 +1,3 @@ -""" -.. autoclass:: CommonSubexpressionTag -.. autoclass:: FirstAxisIsElementsTag -""" - __copyright__ = """ Copyright (C) 2020-1 University of Illinois Board of Trustees """ @@ -27,24 +22,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import sys from pytools.tag import Tag - - -# {{{ program metadata - -class CommonSubexpressionTag(Tag): - """A tag that is applicable to arrays indicating that this same array - may be evaluated multiple times, and that the implementation should - eliminate those redundant evaluations if possible. - """ - - -class FirstAxisIsElementsTag(Tag): - """A tag that is applicable to array outputs indicating that the - first index corresponds to element indices. This suggests that - the implementation should set element indices as the outermost - loop extent. - """ +from warnings import warn + + +# {{{ deprecation handling + +try: + from meshmode.transform_metadata import FirstAxisIsElementsTag \ + as _FirstAxisIsElementsTag +except ImportError: + # placeholder in case meshmode is too old to have it. + class _FirstAxisIsElementsTag(Tag): # type: ignore[no-redef] + pass + + +if sys.version_info >= (3, 7): + def __getattr__(name): + if name == "FirstAxisIsElementsTag": + warn(f"'arraycontext.{name}' is deprecated. " + f"Use 'meshmode.transform_metadata.{name}' instead. " + f"'arraycontext.{name}' will continue to work until 2022.", + DeprecationWarning, stacklevel=2) + return _FirstAxisIsElementsTag + else: + raise AttributeError(name) +else: + FirstAxisIsElementsTag = _FirstAxisIsElementsTag # }}} diff --git a/arraycontext/pytest.py b/arraycontext/pytest.py index e56d903d9e06be576858b7d262cf9772868ef726..6f63a6cfdc555bf423fefba434dfc56fc2c1e03a 100644 --- a/arraycontext/pytest.py +++ b/arraycontext/pytest.py @@ -70,33 +70,39 @@ class PytestPyOpenCLArrayContextFactory: raise NotImplementedError -class _PyOpenCLArrayContextFactory(PytestPyOpenCLArrayContextFactory): +class _PytestPyOpenCLArrayContextFactoryWithClass(PytestPyOpenCLArrayContextFactory): force_device_scalars = True - def __call__(self): + @property + def actx_class(self): from arraycontext import PyOpenCLArrayContext + return PyOpenCLArrayContext + def __call__(self): # The ostensibly pointless assignment to *ctx* keeps the CL context alive # long enough to create the array context, which will then start # holding a reference to the context to keep it alive in turn. # On some implementations (notably Intel CPU), holding a reference # to a queue does not keep the context alive. ctx, queue = self.get_command_queue() - return PyOpenCLArrayContext( + return self.actx_class( queue, force_device_scalars=self.force_device_scalars) def __str__(self): - return ("<PyOpenCLArrayContext for <pyopencl.Device '%s' on '%s'>" % - (self.device.name.strip(), - self.device.platform.name.strip())) + return ("<%s for <pyopencl.Device '%s' on '%s'>" % + ( + self.actx_class.__name__, + self.device.name.strip(), + self.device.platform.name.strip())) -class _DeprecatedPyOpenCLArrayContextFactory(_PyOpenCLArrayContextFactory): +class _PytestPyOpenCLArrayContextFactoryWithClassAndHostScalars( + _PytestPyOpenCLArrayContextFactoryWithClass): force_device_scalars = False -class _PytatoPyOpenCLArrayContextFactory(PytestPyOpenCLArrayContextFactory): +class _PytestPytatoPyOpenCLArrayContextFactory(PytestPyOpenCLArrayContextFactory): force_device_scalars = False def __call__(self): @@ -112,13 +118,14 @@ class _PytatoPyOpenCLArrayContextFactory(PytestPyOpenCLArrayContextFactory): _ARRAY_CONTEXT_FACTORY_REGISTRY: \ Dict[str, Type[PytestPyOpenCLArrayContextFactory]] = { - "pyopencl": _PyOpenCLArrayContextFactory, - "pyopencl-deprecated": _DeprecatedPyOpenCLArrayContextFactory, - "pytato-pyopencl": _PytatoPyOpenCLArrayContextFactory, + "pyopencl": _PytestPyOpenCLArrayContextFactoryWithClass, + "pyopencl-deprecated": + _PytestPyOpenCLArrayContextFactoryWithClassAndHostScalars, + "pytato-pyopencl": _PytestPytatoPyOpenCLArrayContextFactory, } -def register_array_context_factory( +def register_pytest_array_context_factory( name: str, factory: Type[PytestPyOpenCLArrayContextFactory]) -> None: if name in _ARRAY_CONTEXT_FACTORY_REGISTRY: @@ -274,6 +281,15 @@ def pytest_generate_tests_for_pyopencl_array_context(metafunc) -> None: for device selection. """ + from warnings import warn + warn("pytest_generate_tests_for_pyopencl_array_context is deprecated. " + "Use 'pytest_generate_tests = " + "arraycontext.pytest_generate_tests_for_array_contexts" + "([\"pyopencl-deprecated\"])' instead. " + "pytest_generate_tests_for_pyopencl_array_context will stop working " + "in 2022.", + DeprecationWarning, stacklevel=2) + pytest_generate_tests_for_array_contexts([ "pyopencl-deprecated", ], factory_arg_name="actx_factory")(metafunc) diff --git a/arraycontext/transform_metadata.py b/arraycontext/transform_metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..2e0942e95c674434af03ed3bff363aa26433d36c --- /dev/null +++ b/arraycontext/transform_metadata.py @@ -0,0 +1,59 @@ +""" +.. currentmodule:: arraycontext + +.. autoclass:: CommonSubexpressionTag +.. autoclass:: ElementwiseMapKernelTag +""" + +__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 pytools.tag import Tag + + +# {{{ program metadata + +class CommonSubexpressionTag(Tag): + """A tag that is applicable to arrays indicating that this same array + may be evaluated multiple times, and that the implementation should + eliminate those redundant evaluations if possible. + """ + + +class ElementwiseMapKernelTag(Tag): + """A tag that applies to :class:`loopy.LoopKernel` indicating that the kernel + is a "map", i.e. that the output array(s) has/have the same shape as the + input array(s), and that each output element only depends on its corresponding + element(s) in the input array(s). + + .. note:: + + "Element" here refers to a scalar element of an array, not an element + in a finite-element discretization. + """ + +# }}} + + +# vim: foldmethod=marker diff --git a/test/test_arraycontext.py b/test/test_arraycontext.py index df732e717c657359572dc0ecb7fb2b09c9db39e1..e9d9d5eb244238cfc32b2d84b776085b4de9ac38 100644 --- a/test/test_arraycontext.py +++ b/test/test_arraycontext.py @@ -31,19 +31,49 @@ from arraycontext import ( dataclass_array_container, with_container_arithmetic, serialize_container, deserialize_container, freeze, thaw, - FirstAxisIsElementsTag, ArrayContainer) + FirstAxisIsElementsTag, + PyOpenCLArrayContext, + ArrayContainer,) from arraycontext import ( # noqa: F401 pytest_generate_tests_for_array_contexts, _acf) +from arraycontext.pytest import (_PytestPyOpenCLArrayContextFactoryWithClass, + _PytestPytatoPyOpenCLArrayContextFactory) + import logging logger = logging.getLogger(__name__) +# {{{ array context fixture + +class _PyOpenCLArrayContextForTests(PyOpenCLArrayContext): + """Like :class:`PyOpenCLArrayContext`, but applies no program transformations + whatsoever. Only to be used for testing internal to :mod:`arraycontext`. + """ + + def transform_loopy_program(self, t_unit): + return t_unit + + +class _PyOpenCLArrayContextWithHostScalarsForTestsFactory( + _PytestPyOpenCLArrayContextFactoryWithClass): + actx_class = _PyOpenCLArrayContextForTests + + +class _PyOpenCLArrayContextForTestsFactory( + _PyOpenCLArrayContextWithHostScalarsForTestsFactory): + force_device_scalars = True + + pytest_generate_tests = pytest_generate_tests_for_array_contexts([ - "pyopencl", "pyopencl-deprecated", "pytato-pyopencl" + _PyOpenCLArrayContextForTestsFactory, + _PyOpenCLArrayContextWithHostScalarsForTestsFactory, + _PytestPytatoPyOpenCLArrayContextFactory, ]) +# }}} + # {{{ stand-in DOFArray implementation