Implementation NotesΒΆ
Since NumPy, CuPy, and to a degree, Dask, are nearly identical in behavior,
most wrapping logic can be shared between them. Wrapped functions that have
the same logic between multiple libraries are in array_api_compat/common/
.
These functions are defined like
# In array_api_compat/common/_aliases.py
def acos(x, /, xp):
return xp.arccos(x)
The xp
argument refers to the original array namespace (e.g., numpy
or
cupy
). Then in the specific array_api_compat/numpy/
and
array_api_compat/cupy/
namespaces, the @get_xp
decorator is applied to
these functions, which automatically removes the xp
argument from the
function signature and replaces it with the corresponding array library, like
# In array_api_compat/numpy/_aliases.py
from ..common import _aliases
import numpy as np
acos = get_xp(np)(_aliases.acos)
This acos
now has the signature acos(x, /)
and calls numpy.arccos
.
Similarly, for CuPy:
# In array_api_compat/cupy/_aliases.py
from ..common import _aliases
import cupy as cp
acos = get_xp(cp)(_aliases.acos)
Most NumPy and CuPy are defined in this way, since their behaviors are nearly
identical PyTorch uses a similar layout in array_api_compat/torch/
, but it
differs enough from NumPy/CuPy that very few common wrappers for those
libraries are reused. Dask is close to NumPy in behavior and so most Dask
functions also reuse the NumPy/CuPy common wrappers.
Occasionally, a wrapper implementation will need to reference another wrapper
implementation, rather than the base xp
version. The easiest way to do this
is to call array_namespace
, like
wrapped_xp = array_namespace(x)
wrapped_xp.wrapped_func(...)
Also, if there is a very minor difference required for wrapping, say, CuPy and
NumPy, they can still use a common implementation in common/_aliases.py
and
use the is_*_namespace()
or is_*_function()
helper
functions to branch as necessary.