Skip to content

Instantly share code, notes, and snippets.

@jorenham
Last active July 30, 2026 12:44
Show Gist options
  • Select an option

  • Save jorenham/d977c63cc53ad4f00878d37ee42ab0e8 to your computer and use it in GitHub Desktop.

Select an option

Save jorenham/d977c63cc53ad4f00878d37ee42ab0e8 to your computer and use it in GitHub Desktop.
NumPy 2.5.0 ufunc signatures

1 -> 1

ufunc identity arg out
isnat Mm ?
signbit efdg ?
isfinite ?bBhHiIlLqQefdgFDGmM ?
isinf ?bBhHiIlLqQefdgFDGmM ?
isnan ?bBhHiIlLqQefdgFDGmMT ?
bitwise_count bBhHiIlLqQO BO
logical_not ?bBhHiIlLqQefdgFDGO ?O
spacing efdg efdg
cbrt efdgO efdgO
deg2rad efdgO efdgO
degrees efdgO efdgO
fabs efdgO efdgO
rad2deg efdgO efdgO
radians efdgO efdgO
arccos efdgFDGO efdgFDGO
arccosh efdgFDGO efdgFDGO
arcsin efdgFDGO efdgFDGO
arcsinh efdgFDGO efdgFDGO
arctan efdgFDGO efdgFDGO
arctanh efdgFDGO efdgFDGO
cos efdgFDGO efdgFDGO
cosh efdgFDGO efdgFDGO
exp efdgFDGO efdgFDGO
exp2 efdgFDGO efdgFDGO
expm1 efdgFDGO efdgFDGO
log efdgFDGO efdgFDGO
log10 efdgFDGO efdgFDGO
log1p efdgFDGO efdgFDGO
log2 efdgFDGO efdgFDGO
rint efdgFDGO efdgFDGO
sin efdgFDGO efdgFDGO
sinh efdgFDGO efdgFDGO
sqrt efdgFDGO efdgFDGO
tan efdgFDGO efdgFDGO
tanh efdgFDGO efdgFDGO
invert ?bBhHiIlLqQO ?bBhHiIlLqQO
ceil ?bBhHiIlLqQefdgO ?bBhHiIlLqQefdgO
floor ?bBhHiIlLqQefdgO ?bBhHiIlLqQefdgO
trunc ?bBhHiIlLqQefdgO ?bBhHiIlLqQefdgO
absolute ?bBhHiIlLqQefdgmFDGO ?bBhHiIlLqQefdgmO
conjugate bBhHiIlLqQefdgFDGO bBhHiIlLqQefdgFDGO
reciprocal bBhHiIlLqQefdgFDGO bBhHiIlLqQefdgFDGO
square bBhHiIlLqQefdgFDGO bBhHiIlLqQefdgFDGO
sign bBhHiIlLqQefdgmFDGO bBhHiIlLqQefdgFDGO
negative bBhHiIlLqQefdgmFDGO bBhHiIlLqQefdgmFDGO
positive bBhHiIlLqQefdgmFDGO bBhHiIlLqQefdgmFDGO
strings.isdecimal False UT ?
strings.isnumeric False UT ?
strings.isalnum False SUT ?
strings.isalpha False SUT ?
strings.isdigit False SUT ?
strings.islower False SUT ?
strings.isspace False SUT ?
strings.istitle False SUT ?
strings.isupper False SUT ?
strings.str_len 0 SUT n
_core.umath.imag
_core.umath.real

1 -> 2

ufunc identity arg out0 out1
frexp efdg efdg i
modf efdg efdg efdg

2 -> 1

ufunc identity arg0 arg1 out
logical_and True ?bBhHiIlLqQefdgFDGOSUVT ?bBhHiIlLqQefdgFDGOSUVT ?O
logical_or False ?bBhHiIlLqQefdgFDGOSUVT ?bBhHiIlLqQefdgFDGOSUVT ?O
logical_xor False ?bBhHiIlLqQefdgFDGOSUVT ?bBhHiIlLqQefdgFDGOSUVT ?O
equal ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
greater ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
greater_equal ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
less ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
less_equal ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
not_equal ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGMmOSUT ?O
ldexp efdg il efdg
float_power dgDG dgDG dgDG
copysign efdg efdg efdg
heaviside efdg efdg efdg
logaddexp -inf efdg efdg efdg
logaddexp2 -inf efdg efdg efdg
nextafter efdg efdg efdg
arctan2 efdgO efdgO efdgO
hypot 0 efdgO efdgO efdgO
divide efdgFDGmO efdgFDGqmO efdgFDGmO
gcd 0 bBhHiIlLqQO bBhHiIlLqQO bBhHiIlLqQO
lcm bBhHiIlLqQO bBhHiIlLqQO bBhHiIlLqQO
left_shift bBhHiIlLqQO bBhHiIlLqQO bBhHiIlLqQO
right_shift bBhHiIlLqQO bBhHiIlLqQO bBhHiIlLqQO
bitwise_and -1 ?bBhHiIlLqQO ?bBhHiIlLqQO ?bBhHiIlLqQO
bitwise_or 0 ?bBhHiIlLqQO ?bBhHiIlLqQO ?bBhHiIlLqQO
bitwise_xor 0 ?bBhHiIlLqQO ?bBhHiIlLqQO ?bBhHiIlLqQO
fmod bBhHiIlLqQefdgO bBhHiIlLqQefdgO bBhHiIlLqQefdgO
remainder bBhHiIlLqQefdgmO bBhHiIlLqQefdgmO bBhHiIlLqQefdgmO
floor_divide bBhHiIlLqQefdgmO bBhHiIlLqQefdgmO bBhHiIlLqQefdgmO
power bBhHiIlLqQefdgFDGO bBhHiIlLqQefdgFDGO bBhHiIlLqQefdgFDGO
matmul ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO
matvec ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO
vecdot ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO
vecmat ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO ?bBhHiIlLqQefdgFDGO
multiply 1 ?bBhHiIlLqQefdgFDGmO ?bBhHiIlLqQefdgFDGmO ?bBhHiIlLqQefdgFDGmO
subtract bBhHiIlLqQefdgFDGMmO bBhHiIlLqQefdgFDGmMO bBhHiIlLqQefdgFDGMmO
fmax ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO
fmin ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO
maximum ?bBhHiIlLqQefdgFDGmMOT ?bBhHiIlLqQefdgFDGmMOT ?bBhHiIlLqQefdgFDGmMOT
minimum ?bBhHiIlLqQefdgFDGmMOT ?bBhHiIlLqQefdgFDGmMOT ?bBhHiIlLqQefdgFDGmMOT
add 0 ?bBhHiIlLqQefdgFDGMmOSUT ?bBhHiIlLqQefdgFDGmMOSUT ?bBhHiIlLqQefdgFDGMmOSUT

2 -> 2

ufunc identity arg0 arg1 out0 out1
divmod bBhHiIlLqQefdgm bBhHiIlLqQefdgm bBhHiIlLqQefdg bBhHiIlLqQefdgm

3 -> 1

ufunc identity arg0 arg1 arg2 out
_core.umath.clip ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO ?bBhHiIlLqQefdgFDGmMO
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "numpy==2.5.0",
# "tabulate",
# ]
# ///
"""
UFunc introspection tools.
Usage: uv run tool/ufunc.py [-h] [-p] [-f <TABULATE_FORMAT>] <NIN> <NOUT>
"""
import argparse
import itertools
import sys
from collections.abc import Sequence
from functools import cache
from typing import Any, Final
import numpy as np
import numpy._core.umath as um
type _ScalarLike = np.generic | np.ndarray[tuple[()]]
_EXTRA_SCALARS: Final[tuple[_ScalarLike, ...]] = (
np.str_(""),
np.array("", dtype="T"),
np.bytes_(b""),
np.void(b""),
)
@cache
def _qualname(u: np.ufunc, /) -> str:
name = u.__name__
if hasattr(u, "__module__"):
return f"{u.__module__}.{name}"
if hasattr(np, name) and isinstance(getattr(np, name), np.ufunc):
return f"numpy.{name}"
if hasattr(np.strings, name) and isinstance(getattr(np.strings, name), np.ufunc):
return f"numpy.strings.{name}"
if hasattr(np.linalg._umath_linalg, name):
return f"numpy.linalg._umath_linalg.{name}"
if hasattr(um, name):
return f"numpy._core.umath.{name}"
raise NameError(u.__name__)
@cache
def _all_types(u: np.ufunc) -> tuple[str, ...]:
"""Return all supported ufunc type signatures, including unlisted `'SUVT'` ones.
Returns
-------
extra_types : tuple of string-like type signatures like "ab->c"
"""
extra_types: list[str] = []
# ugly workaround for weird string ufuncs in `_core.umath`
prod_args = [_EXTRA_SCALARS] * u.nin
if u.nin > 1 and not u.types and _qualname(u).startswith("numpy._"):
prod_args = [(np.longlong(0), np.longlong(1), *_EXTRA_SCALARS)] * u.nin
for args in itertools.product(*prod_args):
try:
vouts = u(*args)
except (ValueError, TypeError):
continue
tin = "".join(v.dtype.char for v in args)
tout = ""
if u.nout == 1:
vouts = (vouts,)
for vout in vouts:
if type(vout) is str:
tout += "T"
else:
char = vout.dtype.char
# avoid system dependent return types for `NPY_DEFAULT_INT`
tout += "n" if char == np.dtype("n").char else char
extra_types.append(f"{tin}->{tout}")
return tuple(dict.fromkeys(u.types + extra_types))
def _unzip_types(
u: np.ufunc, /, *, unique: bool = False
) -> tuple[tuple[list[str], ...], tuple[list[str], ...]]:
tinss: tuple[list[str], ...] = tuple([] for _ in range(u.nin))
toutss: tuple[list[str], ...] = tuple([] for _ in range(u.nout))
for t in dict.fromkeys(_all_types(u)):
for targss, targs_str in zip((tinss, toutss), t.split("->"), strict=True):
for targs, targ in zip(targss, targs_str, strict=True):
targs.append(targ)
if unique:
tinss = tuple(list(dict.fromkeys(tins)) for tins in tinss)
toutss = tuple(list(dict.fromkeys(touts)) for touts in toutss)
return tinss, toutss
def _ufunc_list(
*, nin: int | None = None, nout: int | None = None, private: bool = False
) -> list[dict[str, str]]:
def _sort_key(ufunc: np.ufunc, /) -> tuple[Any, ...]:
tins, touts = _unzip_types(ufunc, unique=True)
return (
_qualname(ufunc).count("."),
_qualname(ufunc).split(".", 1)[0],
ufunc.nin,
ufunc.nout,
tuple(map(len, touts)),
tuple(map(len, tins)),
"".join(_all_types(ufunc)),
_qualname(ufunc),
)
out: list[dict[str, str]] = []
for u in sorted(np.testing.overrides.get_overridable_numpy_ufuncs(), key=_sort_key):
if nin is not None and u.nin != nin:
continue
if nout is not None and u.nout != nout:
continue
if not private and u.__name__.startswith("_"):
continue
out.append(entry := {"name": _qualname(u).replace("numpy.", "")})
entry["identity"] = "" if u.identity is None else str(u.identity)
for param, n, targss in zip(
("arg", "out"), (nin, nout), _unzip_types(u, unique=True), strict=True
):
if n is not None and len(targss) == 1:
entry[param] = "".join(targss[0])
else:
for i, targs in enumerate(targss):
entry[f"{param}{i}"] = (
"".join(targs).replace("Tq", "qT").replace("TU", "UT")
)
return out
def _parse_args(args: Sequence[str] | None, /) -> argparse.Namespace:
import tabulate # pyright: ignore[reportMissingModuleSource]
parser = argparse.ArgumentParser(prog="ufunc.py")
parser.add_argument("nin", type=int, help="filter by `ufunc.nin`")
parser.add_argument("nout", type=int, help="filter by `ufunc.nout")
parser.add_argument(
"-p",
"--private",
action="store_true",
required=False,
help="show private ufuncs",
)
parser.add_argument(
"-f",
"--format",
type=str,
choices=sorted(tabulate.tabulate_formats),
default="github",
help="output table format",
)
return parser.parse_args(args)
def main(args: Sequence[str] | None = None, /) -> int:
"""
Run it.
Returns
-------
exit_code : int
"""
namespace = _parse_args(args)
data = _ufunc_list(
nin=namespace.nin, nout=namespace.nout, private=namespace.private
)
match namespace.format:
case "github":
code = "`{}`".format
case "rst":
code = "``{}``".format
case _:
code = "{}".format
def as_code(value: str) -> str:
return code(value) if value else value
if data:
import tabulate # pyright: ignore[reportMissingModuleSource]
print(
tabulate
.tabulate(
[list(map(as_code, entry.values())) for entry in data],
headers=[as_code("ufunc"), *list(map(as_code, data[0]))[1:]],
tablefmt=namespace.format,
)
# needed for consistent ordering of string-like types
.replace("TS", "ST")
.replace("TV", "VT")
.replace("US", "SU")
)
return 0
if __name__ == "__main__":
sys.exit(main())
@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

This comment was marked as outdated.

@jorenham

jorenham commented Jul 30, 2026

Copy link
Copy Markdown
Author

GUFuncs:

name .signature ndmin x1 ndmin x2 ndmin out axis
matmul (n?,k), (k,m?) -> (n?,m?) 1 1 0 Never
matvec (m,n), (n) -> (m) 2 1 1 Never
vecmat (n), (n,m) -> (m) 1 2 1 Never
vecdot (n), (n) -> () 1 1 0 int (not None)

Note that the matmul signature can also be interpreted as an overloaded signature (order matters):

(n,k), (k,m) -> (n,m)  # 2, 2 -> 2
(n,k), (k  ) -> (n  )  # 2, 1 -> 1
(  k), (k,m) -> (  m)  # 1, 2 -> 1
(  k), (k  ) -> (   )  # 1, 1 -> 0

So in that sense matmul can be seen as a generalization of matvec, vecmat, and vecdot all at once.

gh code search hits (example query):

  • matmul: 425k
  • matvec: 5.2k
  • vecmat: 4.8k
  • vecdot: 18.4k

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment