about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py')
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py1133
1 files changed, 1133 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py b/.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py
new file mode 100644
index 00000000..5aff9863
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/array_api/_array_object.py
@@ -0,0 +1,1133 @@
+"""
+Wrapper class around the ndarray object for the array API standard.
+
+The array API standard defines some behaviors differently than ndarray, in
+particular, type promotion rules are different (the standard has no
+value-based casting). The standard also specifies a more limited subset of
+array methods and functionalities than are implemented on ndarray. Since the
+goal of the array_api namespace is to be a minimal implementation of the array
+API standard, we need to define a separate wrapper class for the array_api
+namespace.
+
+The standard compliant class is only a wrapper class. It is *not* a subclass
+of ndarray.
+"""
+
+from __future__ import annotations
+
+import operator
+from enum import IntEnum
+from ._creation_functions import asarray
+from ._dtypes import (
+    _all_dtypes,
+    _boolean_dtypes,
+    _integer_dtypes,
+    _integer_or_boolean_dtypes,
+    _floating_dtypes,
+    _complex_floating_dtypes,
+    _numeric_dtypes,
+    _result_type,
+    _dtype_categories,
+)
+
+from typing import TYPE_CHECKING, Optional, Tuple, Union, Any, SupportsIndex
+import types
+
+if TYPE_CHECKING:
+    from ._typing import Any, PyCapsule, Device, Dtype
+    import numpy.typing as npt
+
+import numpy as np
+
+from numpy import array_api
+
+
+class Array:
+    """
+    n-d array object for the array API namespace.
+
+    See the docstring of :py:obj:`np.ndarray <numpy.ndarray>` for more
+    information.
+
+    This is a wrapper around numpy.ndarray that restricts the usage to only
+    those things that are required by the array API namespace. Note,
+    attributes on this object that start with a single underscore are not part
+    of the API specification and should only be used internally. This object
+    should not be constructed directly. Rather, use one of the creation
+    functions, such as asarray().
+
+    """
+    _array: np.ndarray[Any, Any]
+
+    # Use a custom constructor instead of __init__, as manually initializing
+    # this class is not supported API.
+    @classmethod
+    def _new(cls, x, /):
+        """
+        This is a private method for initializing the array API Array
+        object.
+
+        Functions outside of the array_api submodule should not use this
+        method. Use one of the creation functions instead, such as
+        ``asarray``.
+
+        """
+        obj = super().__new__(cls)
+        # Note: The spec does not have array scalars, only 0-D arrays.
+        if isinstance(x, np.generic):
+            # Convert the array scalar to a 0-D array
+            x = np.asarray(x)
+        if x.dtype not in _all_dtypes:
+            raise TypeError(
+                f"The array_api namespace does not support the dtype '{x.dtype}'"
+            )
+        obj._array = x
+        return obj
+
+    # Prevent Array() from working
+    def __new__(cls, *args, **kwargs):
+        raise TypeError(
+            "The array_api Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead."
+        )
+
+    # These functions are not required by the spec, but are implemented for
+    # the sake of usability.
+
+    def __str__(self: Array, /) -> str:
+        """
+        Performs the operation __str__.
+        """
+        return self._array.__str__().replace("array", "Array")
+
+    def __repr__(self: Array, /) -> str:
+        """
+        Performs the operation __repr__.
+        """
+        suffix = f", dtype={self.dtype.name})"
+        if 0 in self.shape:
+            prefix = "empty("
+            mid = str(self.shape)
+        else:
+            prefix = "Array("
+            mid = np.array2string(self._array, separator=', ', prefix=prefix, suffix=suffix)
+        return prefix + mid + suffix
+
+    # This function is not required by the spec, but we implement it here for
+    # convenience so that np.asarray(np.array_api.Array) will work.
+    def __array__(self, dtype: None | np.dtype[Any] = None) -> npt.NDArray[Any]:
+        """
+        Warning: this method is NOT part of the array API spec. Implementers
+        of other libraries need not include it, and users should not assume it
+        will be present in other implementations.
+
+        """
+        return np.asarray(self._array, dtype=dtype)
+
+    # These are various helper functions to make the array behavior match the
+    # spec in places where it either deviates from or is more strict than
+    # NumPy behavior
+
+    def _check_allowed_dtypes(self, other: bool | int | float | Array, dtype_category: str, op: str) -> Array:
+        """
+        Helper function for operators to only allow specific input dtypes
+
+        Use like
+
+            other = self._check_allowed_dtypes(other, 'numeric', '__add__')
+            if other is NotImplemented:
+                return other
+        """
+
+        if self.dtype not in _dtype_categories[dtype_category]:
+            raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}")
+        if isinstance(other, (int, complex, float, bool)):
+            other = self._promote_scalar(other)
+        elif isinstance(other, Array):
+            if other.dtype not in _dtype_categories[dtype_category]:
+                raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}")
+        else:
+            return NotImplemented
+
+        # This will raise TypeError for type combinations that are not allowed
+        # to promote in the spec (even if the NumPy array operator would
+        # promote them).
+        res_dtype = _result_type(self.dtype, other.dtype)
+        if op.startswith("__i"):
+            # Note: NumPy will allow in-place operators in some cases where
+            # the type promoted operator does not match the left-hand side
+            # operand. For example,
+
+            # >>> a = np.array(1, dtype=np.int8)
+            # >>> a += np.array(1, dtype=np.int16)
+
+            # The spec explicitly disallows this.
+            if res_dtype != self.dtype:
+                raise TypeError(
+                    f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}"
+                )
+
+        return other
+
+    # Helper function to match the type promotion rules in the spec
+    def _promote_scalar(self, scalar):
+        """
+        Returns a promoted version of a Python scalar appropriate for use with
+        operations on self.
+
+        This may raise an OverflowError in cases where the scalar is an
+        integer that is too large to fit in a NumPy integer dtype, or
+        TypeError when the scalar type is incompatible with the dtype of self.
+        """
+        # Note: Only Python scalar types that match the array dtype are
+        # allowed.
+        if isinstance(scalar, bool):
+            if self.dtype not in _boolean_dtypes:
+                raise TypeError(
+                    "Python bool scalars can only be promoted with bool arrays"
+                )
+        elif isinstance(scalar, int):
+            if self.dtype in _boolean_dtypes:
+                raise TypeError(
+                    "Python int scalars cannot be promoted with bool arrays"
+                )
+            if self.dtype in _integer_dtypes:
+                info = np.iinfo(self.dtype)
+                if not (info.min <= scalar <= info.max):
+                    raise OverflowError(
+                        "Python int scalars must be within the bounds of the dtype for integer arrays"
+                    )
+            # int + array(floating) is allowed
+        elif isinstance(scalar, float):
+            if self.dtype not in _floating_dtypes:
+                raise TypeError(
+                    "Python float scalars can only be promoted with floating-point arrays."
+                )
+        elif isinstance(scalar, complex):
+            if self.dtype not in _complex_floating_dtypes:
+                raise TypeError(
+                    "Python complex scalars can only be promoted with complex floating-point arrays."
+                )
+        else:
+            raise TypeError("'scalar' must be a Python scalar")
+
+        # Note: scalars are unconditionally cast to the same dtype as the
+        # array.
+
+        # Note: the spec only specifies integer-dtype/int promotion
+        # behavior for integers within the bounds of the integer dtype.
+        # Outside of those bounds we use the default NumPy behavior (either
+        # cast or raise OverflowError).
+        return Array._new(np.array(scalar, self.dtype))
+
+    @staticmethod
+    def _normalize_two_args(x1, x2) -> Tuple[Array, Array]:
+        """
+        Normalize inputs to two arg functions to fix type promotion rules
+
+        NumPy deviates from the spec type promotion rules in cases where one
+        argument is 0-dimensional and the other is not. For example:
+
+        >>> import numpy as np
+        >>> a = np.array([1.0], dtype=np.float32)
+        >>> b = np.array(1.0, dtype=np.float64)
+        >>> np.add(a, b) # The spec says this should be float64
+        array([2.], dtype=float32)
+
+        To fix this, we add a dimension to the 0-dimension array before passing it
+        through. This works because a dimension would be added anyway from
+        broadcasting, so the resulting shape is the same, but this prevents NumPy
+        from not promoting the dtype.
+        """
+        # Another option would be to use signature=(x1.dtype, x2.dtype, None),
+        # but that only works for ufuncs, so we would have to call the ufuncs
+        # directly in the operator methods. One should also note that this
+        # sort of trick wouldn't work for functions like searchsorted, which
+        # don't do normal broadcasting, but there aren't any functions like
+        # that in the array API namespace.
+        if x1.ndim == 0 and x2.ndim != 0:
+            # The _array[None] workaround was chosen because it is relatively
+            # performant. broadcast_to(x1._array, x2.shape) is much slower. We
+            # could also manually type promote x2, but that is more complicated
+            # and about the same performance as this.
+            x1 = Array._new(x1._array[None])
+        elif x2.ndim == 0 and x1.ndim != 0:
+            x2 = Array._new(x2._array[None])
+        return (x1, x2)
+
+    # Note: A large fraction of allowed indices are disallowed here (see the
+    # docstring below)
+    def _validate_index(self, key):
+        """
+        Validate an index according to the array API.
+
+        The array API specification only requires a subset of indices that are
+        supported by NumPy. This function will reject any index that is
+        allowed by NumPy but not required by the array API specification. We
+        always raise ``IndexError`` on such indices (the spec does not require
+        any specific behavior on them, but this makes the NumPy array API
+        namespace a minimal implementation of the spec). See
+        https://data-apis.org/array-api/latest/API_specification/indexing.html
+        for the full list of required indexing behavior
+
+        This function raises IndexError if the index ``key`` is invalid. It
+        only raises ``IndexError`` on indices that are not already rejected by
+        NumPy, as NumPy will already raise the appropriate error on such
+        indices. ``shape`` may be None, in which case, only cases that are
+        independent of the array shape are checked.
+
+        The following cases are allowed by NumPy, but not specified by the array
+        API specification:
+
+        - Indices to not include an implicit ellipsis at the end. That is,
+          every axis of an array must be explicitly indexed or an ellipsis
+          included. This behaviour is sometimes referred to as flat indexing.
+
+        - The start and stop of a slice may not be out of bounds. In
+          particular, for a slice ``i:j:k`` on an axis of size ``n``, only the
+          following are allowed:
+
+          - ``i`` or ``j`` omitted (``None``).
+          - ``-n <= i <= max(0, n - 1)``.
+          - For ``k > 0`` or ``k`` omitted (``None``), ``-n <= j <= n``.
+          - For ``k < 0``, ``-n - 1 <= j <= max(0, n - 1)``.
+
+        - Boolean array indices are not allowed as part of a larger tuple
+          index.
+
+        - Integer array indices are not allowed (with the exception of 0-D
+          arrays, which are treated the same as scalars).
+
+        Additionally, it should be noted that indices that would return a
+        scalar in NumPy will return a 0-D array. Array scalars are not allowed
+        in the specification, only 0-D arrays. This is done in the
+        ``Array._new`` constructor, not this function.
+
+        """
+        _key = key if isinstance(key, tuple) else (key,)
+        for i in _key:
+            if isinstance(i, bool) or not (
+                isinstance(i, SupportsIndex)  # i.e. ints
+                or isinstance(i, slice)
+                or i == Ellipsis
+                or i is None
+                or isinstance(i, Array)
+                or isinstance(i, np.ndarray)
+            ):
+                raise IndexError(
+                    f"Single-axes index {i} has {type(i)=}, but only "
+                    "integers, slices (:), ellipsis (...), newaxis (None), "
+                    "zero-dimensional integer arrays and boolean arrays "
+                    "are specified in the Array API."
+                )
+
+        nonexpanding_key = []
+        single_axes = []
+        n_ellipsis = 0
+        key_has_mask = False
+        for i in _key:
+            if i is not None:
+                nonexpanding_key.append(i)
+                if isinstance(i, Array) or isinstance(i, np.ndarray):
+                    if i.dtype in _boolean_dtypes:
+                        key_has_mask = True
+                    single_axes.append(i)
+                else:
+                    # i must not be an array here, to avoid elementwise equals
+                    if i == Ellipsis:
+                        n_ellipsis += 1
+                    else:
+                        single_axes.append(i)
+
+        n_single_axes = len(single_axes)
+        if n_ellipsis > 1:
+            return  # handled by ndarray
+        elif n_ellipsis == 0:
+            # Note boolean masks must be the sole index, which we check for
+            # later on.
+            if not key_has_mask and n_single_axes < self.ndim:
+                raise IndexError(
+                    f"{self.ndim=}, but the multi-axes index only specifies "
+                    f"{n_single_axes} dimensions. If this was intentional, "
+                    "add a trailing ellipsis (...) which expands into as many "
+                    "slices (:) as necessary - this is what np.ndarray arrays "
+                    "implicitly do, but such flat indexing behaviour is not "
+                    "specified in the Array API."
+                )
+
+        if n_ellipsis == 0:
+            indexed_shape = self.shape
+        else:
+            ellipsis_start = None
+            for pos, i in enumerate(nonexpanding_key):
+                if not (isinstance(i, Array) or isinstance(i, np.ndarray)):
+                    if i == Ellipsis:
+                        ellipsis_start = pos
+                        break
+            assert ellipsis_start is not None  # sanity check
+            ellipsis_end = self.ndim - (n_single_axes - ellipsis_start)
+            indexed_shape = (
+                self.shape[:ellipsis_start] + self.shape[ellipsis_end:]
+            )
+        for i, side in zip(single_axes, indexed_shape):
+            if isinstance(i, slice):
+                if side == 0:
+                    f_range = "0 (or None)"
+                else:
+                    f_range = f"between -{side} and {side - 1} (or None)"
+                if i.start is not None:
+                    try:
+                        start = operator.index(i.start)
+                    except TypeError:
+                        pass  # handled by ndarray
+                    else:
+                        if not (-side <= start <= side):
+                            raise IndexError(
+                                f"Slice {i} contains {start=}, but should be "
+                                f"{f_range} for an axis of size {side} "
+                                "(out-of-bounds starts are not specified in "
+                                "the Array API)"
+                            )
+                if i.stop is not None:
+                    try:
+                        stop = operator.index(i.stop)
+                    except TypeError:
+                        pass  # handled by ndarray
+                    else:
+                        if not (-side <= stop <= side):
+                            raise IndexError(
+                                f"Slice {i} contains {stop=}, but should be "
+                                f"{f_range} for an axis of size {side} "
+                                "(out-of-bounds stops are not specified in "
+                                "the Array API)"
+                            )
+            elif isinstance(i, Array):
+                if i.dtype in _boolean_dtypes and len(_key) != 1:
+                    assert isinstance(key, tuple)  # sanity check
+                    raise IndexError(
+                        f"Single-axes index {i} is a boolean array and "
+                        f"{len(key)=}, but masking is only specified in the "
+                        "Array API when the array is the sole index."
+                    )
+                elif i.dtype in _integer_dtypes and i.ndim != 0:
+                    raise IndexError(
+                        f"Single-axes index {i} is a non-zero-dimensional "
+                        "integer array, but advanced integer indexing is not "
+                        "specified in the Array API."
+                    )
+            elif isinstance(i, tuple):
+                raise IndexError(
+                    f"Single-axes index {i} is a tuple, but nested tuple "
+                    "indices are not specified in the Array API."
+                )
+
+    # Everything below this line is required by the spec.
+
+    def __abs__(self: Array, /) -> Array:
+        """
+        Performs the operation __abs__.
+        """
+        if self.dtype not in _numeric_dtypes:
+            raise TypeError("Only numeric dtypes are allowed in __abs__")
+        res = self._array.__abs__()
+        return self.__class__._new(res)
+
+    def __add__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __add__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__add__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__add__(other._array)
+        return self.__class__._new(res)
+
+    def __and__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __and__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__and__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__and__(other._array)
+        return self.__class__._new(res)
+
+    def __array_namespace__(
+        self: Array, /, *, api_version: Optional[str] = None
+    ) -> types.ModuleType:
+        if api_version is not None and not api_version.startswith("2021."):
+            raise ValueError(f"Unrecognized array API version: {api_version!r}")
+        return array_api
+
+    def __bool__(self: Array, /) -> bool:
+        """
+        Performs the operation __bool__.
+        """
+        # Note: This is an error here.
+        if self._array.ndim != 0:
+            raise TypeError("bool is only allowed on arrays with 0 dimensions")
+        res = self._array.__bool__()
+        return res
+
+    def __complex__(self: Array, /) -> complex:
+        """
+        Performs the operation __complex__.
+        """
+        # Note: This is an error here.
+        if self._array.ndim != 0:
+            raise TypeError("complex is only allowed on arrays with 0 dimensions")
+        res = self._array.__complex__()
+        return res
+
+    def __dlpack__(self: Array, /, *, stream: None = None) -> PyCapsule:
+        """
+        Performs the operation __dlpack__.
+        """
+        return self._array.__dlpack__(stream=stream)
+
+    def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]:
+        """
+        Performs the operation __dlpack_device__.
+        """
+        # Note: device support is required for this
+        return self._array.__dlpack_device__()
+
+    def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array:
+        """
+        Performs the operation __eq__.
+        """
+        # Even though "all" dtypes are allowed, we still require them to be
+        # promotable with each other.
+        other = self._check_allowed_dtypes(other, "all", "__eq__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__eq__(other._array)
+        return self.__class__._new(res)
+
+    def __float__(self: Array, /) -> float:
+        """
+        Performs the operation __float__.
+        """
+        # Note: This is an error here.
+        if self._array.ndim != 0:
+            raise TypeError("float is only allowed on arrays with 0 dimensions")
+        if self.dtype in _complex_floating_dtypes:
+            raise TypeError("float is not allowed on complex floating-point arrays")
+        res = self._array.__float__()
+        return res
+
+    def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __floordiv__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__floordiv__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__floordiv__(other._array)
+        return self.__class__._new(res)
+
+    def __ge__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __ge__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__ge__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__ge__(other._array)
+        return self.__class__._new(res)
+
+    def __getitem__(
+        self: Array,
+        key: Union[
+            int,
+            slice,
+            ellipsis,
+            Tuple[Union[int, slice, ellipsis, None], ...],
+            Array,
+        ],
+        /,
+    ) -> Array:
+        """
+        Performs the operation __getitem__.
+        """
+        # Note: Only indices required by the spec are allowed. See the
+        # docstring of _validate_index
+        self._validate_index(key)
+        if isinstance(key, Array):
+            # Indexing self._array with array_api arrays can be erroneous
+            key = key._array
+        res = self._array.__getitem__(key)
+        return self._new(res)
+
+    def __gt__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __gt__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__gt__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__gt__(other._array)
+        return self.__class__._new(res)
+
+    def __int__(self: Array, /) -> int:
+        """
+        Performs the operation __int__.
+        """
+        # Note: This is an error here.
+        if self._array.ndim != 0:
+            raise TypeError("int is only allowed on arrays with 0 dimensions")
+        if self.dtype in _complex_floating_dtypes:
+            raise TypeError("int is not allowed on complex floating-point arrays")
+        res = self._array.__int__()
+        return res
+
+    def __index__(self: Array, /) -> int:
+        """
+        Performs the operation __index__.
+        """
+        res = self._array.__index__()
+        return res
+
+    def __invert__(self: Array, /) -> Array:
+        """
+        Performs the operation __invert__.
+        """
+        if self.dtype not in _integer_or_boolean_dtypes:
+            raise TypeError("Only integer or boolean dtypes are allowed in __invert__")
+        res = self._array.__invert__()
+        return self.__class__._new(res)
+
+    def __le__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __le__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__le__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__le__(other._array)
+        return self.__class__._new(res)
+
+    def __lshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __lshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__lshift__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__lshift__(other._array)
+        return self.__class__._new(res)
+
+    def __lt__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __lt__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__lt__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__lt__(other._array)
+        return self.__class__._new(res)
+
+    def __matmul__(self: Array, other: Array, /) -> Array:
+        """
+        Performs the operation __matmul__.
+        """
+        # matmul is not defined for scalars, but without this, we may get
+        # the wrong error message from asarray.
+        other = self._check_allowed_dtypes(other, "numeric", "__matmul__")
+        if other is NotImplemented:
+            return other
+        res = self._array.__matmul__(other._array)
+        return self.__class__._new(res)
+
+    def __mod__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __mod__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__mod__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__mod__(other._array)
+        return self.__class__._new(res)
+
+    def __mul__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __mul__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__mul__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__mul__(other._array)
+        return self.__class__._new(res)
+
+    def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array:
+        """
+        Performs the operation __ne__.
+        """
+        other = self._check_allowed_dtypes(other, "all", "__ne__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__ne__(other._array)
+        return self.__class__._new(res)
+
+    def __neg__(self: Array, /) -> Array:
+        """
+        Performs the operation __neg__.
+        """
+        if self.dtype not in _numeric_dtypes:
+            raise TypeError("Only numeric dtypes are allowed in __neg__")
+        res = self._array.__neg__()
+        return self.__class__._new(res)
+
+    def __or__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __or__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__or__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__or__(other._array)
+        return self.__class__._new(res)
+
+    def __pos__(self: Array, /) -> Array:
+        """
+        Performs the operation __pos__.
+        """
+        if self.dtype not in _numeric_dtypes:
+            raise TypeError("Only numeric dtypes are allowed in __pos__")
+        res = self._array.__pos__()
+        return self.__class__._new(res)
+
+    def __pow__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __pow__.
+        """
+        from ._elementwise_functions import pow
+
+        other = self._check_allowed_dtypes(other, "numeric", "__pow__")
+        if other is NotImplemented:
+            return other
+        # Note: NumPy's __pow__ does not follow type promotion rules for 0-d
+        # arrays, so we use pow() here instead.
+        return pow(self, other)
+
+    def __rshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __rshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__rshift__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rshift__(other._array)
+        return self.__class__._new(res)
+
+    def __setitem__(
+        self,
+        key: Union[
+            int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array
+        ],
+        value: Union[int, float, bool, Array],
+        /,
+    ) -> None:
+        """
+        Performs the operation __setitem__.
+        """
+        # Note: Only indices required by the spec are allowed. See the
+        # docstring of _validate_index
+        self._validate_index(key)
+        if isinstance(key, Array):
+            # Indexing self._array with array_api arrays can be erroneous
+            key = key._array
+        self._array.__setitem__(key, asarray(value)._array)
+
+    def __sub__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __sub__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__sub__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__sub__(other._array)
+        return self.__class__._new(res)
+
+    # PEP 484 requires int to be a subtype of float, but __truediv__ should
+    # not accept int.
+    def __truediv__(self: Array, other: Union[float, Array], /) -> Array:
+        """
+        Performs the operation __truediv__.
+        """
+        other = self._check_allowed_dtypes(other, "floating-point", "__truediv__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__truediv__(other._array)
+        return self.__class__._new(res)
+
+    def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __xor__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__xor__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__xor__(other._array)
+        return self.__class__._new(res)
+
+    def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __iadd__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__iadd__")
+        if other is NotImplemented:
+            return other
+        self._array.__iadd__(other._array)
+        return self
+
+    def __radd__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __radd__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__radd__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__radd__(other._array)
+        return self.__class__._new(res)
+
+    def __iand__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __iand__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__iand__")
+        if other is NotImplemented:
+            return other
+        self._array.__iand__(other._array)
+        return self
+
+    def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __rand__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__rand__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rand__(other._array)
+        return self.__class__._new(res)
+
+    def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __ifloordiv__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__ifloordiv__")
+        if other is NotImplemented:
+            return other
+        self._array.__ifloordiv__(other._array)
+        return self
+
+    def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __rfloordiv__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__rfloordiv__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rfloordiv__(other._array)
+        return self.__class__._new(res)
+
+    def __ilshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __ilshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__ilshift__")
+        if other is NotImplemented:
+            return other
+        self._array.__ilshift__(other._array)
+        return self
+
+    def __rlshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __rlshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__rlshift__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rlshift__(other._array)
+        return self.__class__._new(res)
+
+    def __imatmul__(self: Array, other: Array, /) -> Array:
+        """
+        Performs the operation __imatmul__.
+        """
+        # matmul is not defined for scalars, but without this, we may get
+        # the wrong error message from asarray.
+        other = self._check_allowed_dtypes(other, "numeric", "__imatmul__")
+        if other is NotImplemented:
+            return other
+        res = self._array.__imatmul__(other._array)
+        return self.__class__._new(res)
+
+    def __rmatmul__(self: Array, other: Array, /) -> Array:
+        """
+        Performs the operation __rmatmul__.
+        """
+        # matmul is not defined for scalars, but without this, we may get
+        # the wrong error message from asarray.
+        other = self._check_allowed_dtypes(other, "numeric", "__rmatmul__")
+        if other is NotImplemented:
+            return other
+        res = self._array.__rmatmul__(other._array)
+        return self.__class__._new(res)
+
+    def __imod__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __imod__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__imod__")
+        if other is NotImplemented:
+            return other
+        self._array.__imod__(other._array)
+        return self
+
+    def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __rmod__.
+        """
+        other = self._check_allowed_dtypes(other, "real numeric", "__rmod__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rmod__(other._array)
+        return self.__class__._new(res)
+
+    def __imul__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __imul__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__imul__")
+        if other is NotImplemented:
+            return other
+        self._array.__imul__(other._array)
+        return self
+
+    def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __rmul__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__rmul__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rmul__(other._array)
+        return self.__class__._new(res)
+
+    def __ior__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __ior__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__ior__")
+        if other is NotImplemented:
+            return other
+        self._array.__ior__(other._array)
+        return self
+
+    def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __ror__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__ror__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__ror__(other._array)
+        return self.__class__._new(res)
+
+    def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __ipow__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__ipow__")
+        if other is NotImplemented:
+            return other
+        self._array.__ipow__(other._array)
+        return self
+
+    def __rpow__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __rpow__.
+        """
+        from ._elementwise_functions import pow
+
+        other = self._check_allowed_dtypes(other, "numeric", "__rpow__")
+        if other is NotImplemented:
+            return other
+        # Note: NumPy's __pow__ does not follow the spec type promotion rules
+        # for 0-d arrays, so we use pow() here instead.
+        return pow(other, self)
+
+    def __irshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __irshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__irshift__")
+        if other is NotImplemented:
+            return other
+        self._array.__irshift__(other._array)
+        return self
+
+    def __rrshift__(self: Array, other: Union[int, Array], /) -> Array:
+        """
+        Performs the operation __rrshift__.
+        """
+        other = self._check_allowed_dtypes(other, "integer", "__rrshift__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rrshift__(other._array)
+        return self.__class__._new(res)
+
+    def __isub__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __isub__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__isub__")
+        if other is NotImplemented:
+            return other
+        self._array.__isub__(other._array)
+        return self
+
+    def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array:
+        """
+        Performs the operation __rsub__.
+        """
+        other = self._check_allowed_dtypes(other, "numeric", "__rsub__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rsub__(other._array)
+        return self.__class__._new(res)
+
+    def __itruediv__(self: Array, other: Union[float, Array], /) -> Array:
+        """
+        Performs the operation __itruediv__.
+        """
+        other = self._check_allowed_dtypes(other, "floating-point", "__itruediv__")
+        if other is NotImplemented:
+            return other
+        self._array.__itruediv__(other._array)
+        return self
+
+    def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array:
+        """
+        Performs the operation __rtruediv__.
+        """
+        other = self._check_allowed_dtypes(other, "floating-point", "__rtruediv__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rtruediv__(other._array)
+        return self.__class__._new(res)
+
+    def __ixor__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __ixor__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__ixor__")
+        if other is NotImplemented:
+            return other
+        self._array.__ixor__(other._array)
+        return self
+
+    def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array:
+        """
+        Performs the operation __rxor__.
+        """
+        other = self._check_allowed_dtypes(other, "integer or boolean", "__rxor__")
+        if other is NotImplemented:
+            return other
+        self, other = self._normalize_two_args(self, other)
+        res = self._array.__rxor__(other._array)
+        return self.__class__._new(res)
+
+    def to_device(self: Array, device: Device, /, stream: None = None) -> Array:
+        if stream is not None:
+            raise ValueError("The stream argument to to_device() is not supported")
+        if device == 'cpu':
+            return self
+        raise ValueError(f"Unsupported device {device!r}")
+
+    @property
+    def dtype(self) -> Dtype:
+        """
+        Array API compatible wrapper for :py:meth:`np.ndarray.dtype <numpy.ndarray.dtype>`.
+
+        See its docstring for more information.
+        """
+        return self._array.dtype
+
+    @property
+    def device(self) -> Device:
+        return "cpu"
+
+    # Note: mT is new in array API spec (see matrix_transpose)
+    @property
+    def mT(self) -> Array:
+        from .linalg import matrix_transpose
+        return matrix_transpose(self)
+
+    @property
+    def ndim(self) -> int:
+        """
+        Array API compatible wrapper for :py:meth:`np.ndarray.ndim <numpy.ndarray.ndim>`.
+
+        See its docstring for more information.
+        """
+        return self._array.ndim
+
+    @property
+    def shape(self) -> Tuple[int, ...]:
+        """
+        Array API compatible wrapper for :py:meth:`np.ndarray.shape <numpy.ndarray.shape>`.
+
+        See its docstring for more information.
+        """
+        return self._array.shape
+
+    @property
+    def size(self) -> int:
+        """
+        Array API compatible wrapper for :py:meth:`np.ndarray.size <numpy.ndarray.size>`.
+
+        See its docstring for more information.
+        """
+        return self._array.size
+
+    @property
+    def T(self) -> Array:
+        """
+        Array API compatible wrapper for :py:meth:`np.ndarray.T <numpy.ndarray.T>`.
+
+        See its docstring for more information.
+        """
+        # Note: T only works on 2-dimensional arrays. See the corresponding
+        # note in the specification:
+        # https://data-apis.org/array-api/latest/API_specification/array_object.html#t
+        if self.ndim != 2:
+            raise ValueError("x.T requires x to have 2 dimensions. Use x.mT to transpose stacks of matrices and permute_dims() to permute dimensions.")
+        return self.__class__._new(self._array.T)