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.