Skip to content

Run-time version of API #584

Open
Open
@nstarman

Description

@nstarman

Hi, first time contributor, long-time fan. I've been following the development of the Array API and having discussions with my fellow Astropy maintainers about how Astropy's Quantity class can support the API and how this might impact our users. Overall we feel that the adoption of the API will be beneficial (preaching to the choir here) but we, or at least I, have a point of concern.

Right now NumPy serves as the primary source of mathematical functions: np.cos, etc. Through their interoperability protocols, like __array_function__ we can vendor custom array classes like Quantity and for users most of numpy just works. This API standard has a somewhat different paradigm: where astropy.units will need to have a Quantity namespace that defines ``cos`, etc. In some ways this is good: for Astropy, Quantity can more easily support non-numpy array objects by acting as a wrapper and forwarding actual computations to the wrapped array object. For example,

def cos(x: Quantity, /) -> Quantity:
    unit = ...
    return Quantity(x.__array_namespace__().cos(x.value), unit)

However, we worry the loss of a central dispatching library is a regression in ease of user experience. Yes numpy will continue to __array_(u)func(tion)__ forever/ a long time and so can still serve as a central dispatching library, but base numpy does not conform to the array API standard. So there will not be an array-API-conformant central dispatching library and users will need to uses non-conformant libraries, which seems at odds to the purpose of this standard.

The situation becomes more difficult for Astropy and users when we consider Astropy's number of other array-like objects for which we might want to implement the array API: table Columns, coordinate representations, Distributions. Users will need to import a new array namespace for each — e.g. from astropy.table import column_api as cp.

I'm sure this issue has come up in discussion before. To add to that discussion, I'd like to propose what came up in ours. We felt the best solution might be if array-api published a package just of the supported functions.
These functions would be thin wrappers, dispatching the actual computations to the array namespace of the argument.
We know that a new Array implementation is out of scope, but hopefully this suggestion is within scope.
For example:

def cos(x: ArrayAPIConformant, /) -> ArrayAPIConformant:
    return get_namespace(x).cos(x)

Now the following is possible:

>>> import array_api as ap
>>> import numpy.array_api as np
>>> x = np.linspace(1, 2, num=5)[:, None]
>>> x
Array([[1.  ],
       [2.  ]], dtype=float64)
>>> np.cos(x)  # specific library if a user knows the input type and wants speed
Array([[ 0.54030231],
       [-0.41614684]], dtype=float64)
>>> ap.cos(x)  # universal library for just ease of use.
Array([[ 0.54030231],
       [-0.41614684]], dtype=float64)

For users this is especially convenient because they do not need to worry about the output types of non-standard functions. For example, a function

def angle_between(x1: ArrayAPIConformant | float, x2: ArrayAPIConformant | float) -> Quantity:
    ...

will return a Quantity regardless of the input type. Currently a user would have to switch array namespaces.

>>> x1, x2 = np.Array(...), np.Array(...)
>>> angle = angle_between(x1, x2)
>>> xp = angle.__array_namespace()
>>> xp.cos(angle)

With the import array_api as ap that is unnecessary (though they might choose to if optimizing for speed).

>>> import array_api as ap
>>> import numpy.array_api as np
>>> x1, x2 = np.Array(...), np.Array(...)
>>> angle = angle_between(x1, x2)
>>> ap.cos(angle)

Another aspect to this proposal is inclusion of a (preferably runtime-checkable) typing.Protocol of the Array API definition. In the above examples I called this ArrayAPIConformant.

In summary

To demonstrate this proposal I have created an example implementation of the 2021.12 version of the API at https://github.com/nstarman/array_api.

This library is just copying from here, adding the dispatch code, some static typing, a get_namespace function, and a run-time checkable set of Protocols: in particularArrayAPIConformant which makes it easy to check that an object conforms to the array-API.
Also, I mypyc compile the library for extra performance.
In keeping with the scope of this standard, I have not included array-creation functions like ones, though it may be better to instead just have them raise a NotImplementedError with a helpful error message.

If you are interested, I would be more than happy to submit PRs that add the dispatching, protocols, mypyc compilation, etc.

Regardless, I look forward to working with you to address these concerns so Astropy can start benefitting from the great work being done here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRequest for comments. Feature requests and proposed changes.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions