diff --git a/arraycontext/context.py b/arraycontext/context.py index 36a7acee..e1528387 100644 --- a/arraycontext/context.py +++ b/arraycontext/context.py @@ -299,9 +299,19 @@ def zeros(self, pass def empty_like(self, ary: Array) -> Array: + from warnings import warn + warn(f"{type(self).__name__}.empty_like is deprecated and will stop " + "working in 2023. Prefer actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) + return self.empty(shape=ary.shape, dtype=ary.dtype) def zeros_like(self, ary: Array) -> Array: + from warnings import warn + warn(f"{type(self).__name__}.zeros_like is deprecated and will stop " + "working in 2023. Use actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) + return self.zeros(shape=ary.shape, dtype=ary.dtype) @abstractmethod diff --git a/arraycontext/fake_numpy.py b/arraycontext/fake_numpy.py index d5c8fce9..c3e37f82 100644 --- a/arraycontext/fake_numpy.py +++ b/arraycontext/fake_numpy.py @@ -91,12 +91,6 @@ def _get_fake_numpy_linalg_namespace(self): # "interp", }) - def empty_like(self, ary): - return self._array_context.empty_like(ary) - - def zeros_like(self, ary): - return self._array_context.zeros_like(ary) - def conjugate(self, x): # NOTE: conjugate distributes over object arrays, but it looks for a # `conjugate` ufunc, while some implementations only have the shorter diff --git a/arraycontext/impl/jax/__init__.py b/arraycontext/impl/jax/__init__.py index dfb89c45..f4794e46 100644 --- a/arraycontext/impl/jax/__init__.py +++ b/arraycontext/impl/jax/__init__.py @@ -88,6 +88,11 @@ def _wrapper(ary): # {{{ ArrayContext interface def empty(self, shape, dtype): + from warnings import warn + warn(f"{type(self).__name__}.empty is deprecated and will stop " + "working in 2023. Prefer actx.zeros instead.", + DeprecationWarning, stacklevel=2) + import jax.numpy as jnp return jnp.empty(shape=shape, dtype=dtype) @@ -96,16 +101,23 @@ def zeros(self, shape, dtype): return jnp.zeros(shape=shape, dtype=dtype) def empty_like(self, ary): + from warnings import warn + warn(f"{type(self).__name__}.empty_like is deprecated and will stop " + "working in 2023. Prefer actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) + def _empty_like(array): return self.empty(array.shape, array.dtype) return self._rec_map_container(_empty_like, ary) def zeros_like(self, ary): - def _zeros_like(array): - return self.zeros(array.shape, array.dtype) + from warnings import warn + warn(f"{type(self).__name__}.zeros_like is deprecated and will stop " + "working in 2023. Use actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) - return self._rec_map_container(_zeros_like, ary, default_scalar=0) + return self.np.zeros_like(ary) def from_numpy(self, array): def _from_numpy(ary): diff --git a/arraycontext/impl/jax/fake_numpy.py b/arraycontext/impl/jax/fake_numpy.py index 37c99b4a..daaf8803 100644 --- a/arraycontext/impl/jax/fake_numpy.py +++ b/arraycontext/impl/jax/fake_numpy.py @@ -56,6 +56,25 @@ def __getattr__(self, name): # {{{ array creation routines + def empty_like(self, ary): + from warnings import warn + warn(f"{type(self._array_context).__name__}.np.empty_like is " + "deprecated and will stop working in 2023. Prefer actx.np.zeros_like " + "instead.", + DeprecationWarning, stacklevel=2) + + def _empty_like(array): + return self._array_context.empty(array.shape, array.dtype) + + return self._array_context._rec_map_container(_empty_like, ary) + + def zeros_like(self, ary): + def _zeros_like(array): + return self._array_context.zeros(array.shape, array.dtype) + + return self._array_context._rec_map_container( + _zeros_like, ary, default_scalar=0) + def ones_like(self, ary): return self.full_like(ary, 1) diff --git a/arraycontext/impl/pyopencl/__init__.py b/arraycontext/impl/pyopencl/__init__.py index 71b04c72..aced309c 100644 --- a/arraycontext/impl/pyopencl/__init__.py +++ b/arraycontext/impl/pyopencl/__init__.py @@ -189,6 +189,11 @@ def _wrapper(ary): # {{{ ArrayContext interface def empty(self, shape, dtype): + from warnings import warn + warn(f"{type(self).__name__}.empty is deprecated and will stop " + "working in 2023. Prefer actx.zeros instead.", + DeprecationWarning, stacklevel=2) + import arraycontext.impl.pyopencl.taggable_cl_array as tga return tga.empty(self.queue, shape, dtype, allocator=self.allocator) @@ -197,6 +202,11 @@ def zeros(self, shape, dtype): return tga.zeros(self.queue, shape, dtype, allocator=self.allocator) def empty_like(self, ary): + from warnings import warn + warn(f"{type(self).__name__}.empty_like is deprecated and will stop " + "working in 2023. Prefer actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) + import arraycontext.impl.pyopencl.taggable_cl_array as tga def _empty_like(array): @@ -206,13 +216,12 @@ def _empty_like(array): return self._rec_map_container(_empty_like, ary) def zeros_like(self, ary): - import arraycontext.impl.pyopencl.taggable_cl_array as tga - - def _zeros_like(array): - return tga.zeros(self.queue, array.shape, array.dtype, - allocator=self.allocator, axes=array.axes, tags=array.tags) + from warnings import warn + warn(f"{type(self).__name__}.zeros_like is deprecated and will stop " + "working in 2023. Use actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) - return self._rec_map_container(_zeros_like, ary, default_scalar=0) + return self.np.zeros_like(ary) def from_numpy(self, array): import arraycontext.impl.pyopencl.taggable_cl_array as tga diff --git a/arraycontext/impl/pyopencl/fake_numpy.py b/arraycontext/impl/pyopencl/fake_numpy.py index 2e206a8b..a0180e73 100644 --- a/arraycontext/impl/pyopencl/fake_numpy.py +++ b/arraycontext/impl/pyopencl/fake_numpy.py @@ -63,22 +63,49 @@ def _get_fake_numpy_linalg_namespace(self): # {{{ array creation routines + def empty_like(self, ary): + from warnings import warn + warn(f"{type(self._array_context).__name__}.np.empty_like is " + "deprecated and will stop working in 2023. Prefer actx.np.zeros_like " + "instead.", + DeprecationWarning, stacklevel=2) + + import arraycontext.impl.pyopencl.taggable_cl_array as tga + actx = self._array_context + + def _empty_like(array): + return tga.empty(actx.queue, array.shape, array.dtype, + allocator=actx.allocator, axes=array.axes, tags=array.tags) + + return actx._rec_map_container(_empty_like, ary) + + def zeros_like(self, ary): + import arraycontext.impl.pyopencl.taggable_cl_array as tga + actx = self._array_context + + def _zeros_like(array): + return tga.zeros( + actx.queue, array.shape, array.dtype, + allocator=actx.allocator, axes=array.axes, tags=array.tags) + + return actx._rec_map_container(_zeros_like, ary, default_scalar=0) + def ones_like(self, ary): return self.full_like(ary, 1) def full_like(self, ary, fill_value): import arraycontext.impl.pyopencl.taggable_cl_array as tga + actx = self._array_context def _full_like(subary): filled = tga.empty( - self._array_context.queue, subary.shape, subary.dtype, - allocator=self._array_context.allocator, - axes=subary.axes, tags=subary.tags) + actx.queue, subary.shape, subary.dtype, + allocator=actx.allocator, axes=subary.axes, tags=subary.tags) filled.fill(fill_value) + return filled - return self._array_context._rec_map_container( - _full_like, ary, default_scalar=fill_value) + return actx._rec_map_container(_full_like, ary, default_scalar=fill_value) def copy(self, ary): def _copy(subary): diff --git a/arraycontext/impl/pytato/__init__.py b/arraycontext/impl/pytato/__init__.py index 6b9ac6b7..8ccc7689 100644 --- a/arraycontext/impl/pytato/__init__.py +++ b/arraycontext/impl/pytato/__init__.py @@ -348,10 +348,12 @@ def _wrapper(ary): # {{{ ArrayContext interface def zeros_like(self, ary): - def _zeros_like(array): - return self.zeros(array.shape, array.dtype) + from warnings import warn + warn(f"{type(self).__name__}.zeros_like is deprecated and will stop " + "working in 2023. Use actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) - return self._rec_map_container(_zeros_like, ary, default_scalar=0) + return self.np.zeros_like(ary) def from_numpy(self, array): import pytato as pt @@ -720,10 +722,12 @@ def _wrapper(ary): # {{{ ArrayContext interface def zeros_like(self, ary): - def _zeros_like(array): - return self.zeros(array.shape, array.dtype) + from warnings import warn + warn(f"{type(self).__name__}.zeros_like is deprecated and will stop " + "working in 2023. Use actx.np.zeros_like instead.", + DeprecationWarning, stacklevel=2) - return self._rec_map_container(_zeros_like, ary, default_scalar=0) + return self.np.zeros_like(ary) def from_numpy(self, array): import jax diff --git a/arraycontext/impl/pytato/fake_numpy.py b/arraycontext/impl/pytato/fake_numpy.py index d1890f29..e17f8ee7 100644 --- a/arraycontext/impl/pytato/fake_numpy.py +++ b/arraycontext/impl/pytato/fake_numpy.py @@ -76,6 +76,13 @@ def __getattr__(self, name): # {{{ array creation routines + def zeros_like(self, ary): + def _zeros_like(array): + return self._array_context.zeros(array.shape, array.dtype) + + return self._array_context._rec_map_container( + _zeros_like, ary, default_scalar=0) + def ones_like(self, ary): return self.full_like(ary, 1) diff --git a/doc/index.rst b/doc/index.rst index 1a8cf4d2..48fd25bb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,39 @@ implementations for: :mod:`arraycontext` started life as an array abstraction for use with the :mod:`meshmode` unstrucuted discretization package. +Design Guidelines +----------------- + +Here are some of the guidelines we aim to follow in :mod:`arraycontext`. There +exist numerous other, related efforts, such as the `Python array API standard +`__. These +points may aid in clarifying and differentiating our objectives. + +- The array context is about exposing the common subset of operations + available in immutable and mutable arrays. As a result, the interface + does *not* seek to support interfaces that provide, enable, or are typically + used only with in-place mutation. + + For example: The equivalents of :func:`numpy.empty` were deprecated + and will eventually be removed. + +- Each array context offers a specific subset of of :mod:`numpy` under + :attr:`arraycontext.ArrayContext.np`. Functions under this namespace + must be unconditionally :mod:`numpy`-compatible, that is, they may not + offer an interface beyond what numpy offers. Functions that are + incompatible, for example by supporting tag metadata + (cf. :meth:`arraycontext.ArrayContext.einsum`) should live under the + :class:`~arraycontext.ArrayContext` directly. + +- Similarly, we strive to minimize redundancy between attributes of + :class:`~arraycontext.ArrayContext` and :attr:`arraycontext.ArrayContext.np`. + + For example: ``ArrayContext.empty_like`` was deprecated. + +- Array containers are data structures that may contain arrays. + See :mod:`arraycontext.container`. We strive to support these, where sensible, + in :class:`~arraycontext.ArrayContext` and :attr:`arraycontext.ArrayContext.np`. + Contents -------- diff --git a/test/test_arraycontext.py b/test/test_arraycontext.py index abc61a88..c01c7929 100644 --- a/test/test_arraycontext.py +++ b/test/test_arraycontext.py @@ -37,7 +37,7 @@ PytatoPyOpenCLArrayContext, EagerJAXArrayContext, ArrayContainer, - to_numpy, tag_axes) + tag_axes) from arraycontext import ( # noqa: F401 pytest_generate_tests_for_array_contexts, ) @@ -1145,9 +1145,8 @@ def test_numpy_conversion(actx_factory): enthalpy=np.array(np.random.rand()), ) - from arraycontext import from_numpy, to_numpy - ac_actx = from_numpy(ac, actx) - ac_roundtrip = to_numpy(ac_actx, actx) + ac_actx = actx.from_numpy(ac) + ac_roundtrip = actx.to_numpy(ac_actx) assert np.allclose(ac.mass, ac_roundtrip.mass) assert np.allclose(ac.momentum[0], ac_roundtrip.momentum[0]) @@ -1155,13 +1154,13 @@ def test_numpy_conversion(actx_factory): from dataclasses import replace ac_with_cl = replace(ac, enthalpy=ac_actx.mass) with pytest.raises(TypeError): - from_numpy(ac_with_cl, actx) + actx.from_numpy(ac_with_cl) with pytest.raises(TypeError): - from_numpy(ac_actx, actx) + actx.from_numpy(ac_actx) with pytest.raises(TypeError): - to_numpy(ac, actx) + actx.to_numpy(ac) # }}} @@ -1226,7 +1225,6 @@ def scale_and_orthogonalize(alpha, vel): def test_actx_compile(actx_factory): - from arraycontext import (to_numpy, from_numpy) actx = actx_factory() compiled_rhs = actx.compile(scale_and_orthogonalize) @@ -1234,17 +1232,16 @@ def test_actx_compile(actx_factory): v_x = np.random.rand(10) v_y = np.random.rand(10) - vel = from_numpy(Velocity2D(v_x, v_y, actx), actx) + vel = actx.from_numpy(Velocity2D(v_x, v_y, actx)) scaled_speed = compiled_rhs(np.float64(3.14), vel) - result = to_numpy(scaled_speed, actx) + result = actx.to_numpy(scaled_speed) np.testing.assert_allclose(result.u, -3.14*v_y) np.testing.assert_allclose(result.v, 3.14*v_x) def test_actx_compile_python_scalar(actx_factory): - from arraycontext import (to_numpy, from_numpy) actx = actx_factory() compiled_rhs = actx.compile(scale_and_orthogonalize) @@ -1252,17 +1249,16 @@ def test_actx_compile_python_scalar(actx_factory): v_x = np.random.rand(10) v_y = np.random.rand(10) - vel = from_numpy(Velocity2D(v_x, v_y, actx), actx) + vel = actx.from_numpy(Velocity2D(v_x, v_y, actx)) scaled_speed = compiled_rhs(3.14, vel) - result = to_numpy(scaled_speed, actx) + result = actx.to_numpy(scaled_speed) np.testing.assert_allclose(result.u, -3.14*v_y) np.testing.assert_allclose(result.v, 3.14*v_x) def test_actx_compile_kwargs(actx_factory): - from arraycontext import (to_numpy, from_numpy) actx = actx_factory() compiled_rhs = actx.compile(scale_and_orthogonalize) @@ -1270,11 +1266,11 @@ def test_actx_compile_kwargs(actx_factory): v_x = np.random.rand(10) v_y = np.random.rand(10) - vel = from_numpy(Velocity2D(v_x, v_y, actx), actx) + vel = actx.from_numpy(Velocity2D(v_x, v_y, actx)) scaled_speed = compiled_rhs(3.14, vel=vel) - result = to_numpy(scaled_speed, actx) + result = actx.to_numpy(scaled_speed) np.testing.assert_allclose(result.u, -3.14*v_y) np.testing.assert_allclose(result.v, 3.14*v_x) @@ -1550,7 +1546,7 @@ def test_to_numpy_on_frozen_arrays(actx_factory): actx = actx_factory() u = actx.freeze(actx.zeros(10, dtype="float64")+1) np.testing.assert_allclose(actx.to_numpy(u), 1) - np.testing.assert_allclose(to_numpy(u, actx), 1) + np.testing.assert_allclose(actx.to_numpy(u), 1) def test_tagging(actx_factory):