Skip to content

Start annotating ufuncs as np.ufunc instances - #349

Merged
avalentino merged 1 commit into
liberfa:mainfrom
eerovaher:zpv-annotations
Oct 5, 2026
Merged

avalentino merged 1 commit into
liberfa:mainfrom
eerovaher:zpv-annotations

Conversation

@eerovaher

Copy link
Copy Markdown
Collaborator

I was planning to remove all explicit uses of Any from the generated type stubs before starting to work on the shapes of the ufunc outputs, but it has now become apparent that the type annotations have to be able to distinguish when does a ufunc return an np.ndarray and when does it return an array scalar or else type checkers will report spurious errors in tests/test_ufunc.py.

The type stubs erfa_generator creates in current main declare ufuncs to be functions, but at runtime they are actually instances of the np.ufunc class. For typing purposes it is useful to define generic typing-only subtypes for each combination of nin and nout, which then allow defining the pyerfa ufuncs by specifying the type parameters. Numpy defines suitable generic types for a few nin and nout values (in https://github.com/numpy/numpy/blob/v2.5.3/numpy/_typing/_ufunc.pyi), but the implementation is private and pyerfa has ufuncs with nin and nout not covered by numpy, so it is necessary to implement the generic ufunc types ourselves.

(nin, nout) = (0, 1) is the simplest case and the only ufunc (not gufunc) in pyerfa like that is ufunc.zpv(). This PR therefore annotates only ufunc.zpv() as a np.ufunc instance, but the code implemented here can be reused for other (nin, nout) combinations (although not without any changes). For now the annotations ignore the parameters of the ufunc interface except out because only out is used in the existing tests.

Ufuncs are not simple functions like the type stubs so far have said
they are. They are instead instances of the `np.ufunc` class. For typing
purposes it is useful to define generic typing-only subtypes for each
combination of `nin` and `nout`, which then allow defining the `pyerfa`
ufuncs by specifying the type parameters. Numpy does that for a few
`nin` and `nout` values, but the implementation is private and `pyerfa`
has ufuncs with `nin` and `nout` not covered by numpy, so it is
necessary to implement the generic ufunc types ourselves.

`(nin, nout)=(0, 1)` is the simplest case and the only ufunc (not
gufunc) in `pyerfa` like that is `ufunc.zpv()`. It now has better
annotations that (among other things) distinguish when does it return an
`np.ndarray` and when does it return an array scalar. For now the annotations
ignore the parameters of the ufunc interface except `out` because only
`out` is used in the existing tests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template is based on the implementation of the typing-only _PyFunc_Nin1_Nout1 in current numpy main.

Comment on lines +26 to +27
@overload
def __call__(self, /, out: NDArray[ReturnT] | tuple[NDArray[ReturnT]]) -> NDArray[ReturnT]: ...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this overload does not yet try to track the shapes of the arrays.

@avalentino
avalentino merged commit 343b9c8 into liberfa:main Oct 5, 2026
23 checks passed
@eerovaher
eerovaher deleted the zpv-annotations branch October 6, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants