@@ -874,3 +874,94 @@ Sample usage::
874874 >>> npufunc.add_triplet(a, a)
875875 array([(2, 4, 6), (8, 10, 12)],
876876 dtype=[('f0', '<u8'), ('f1', '<u8'), ('f2', '<u8')])
877+
878+ Alternatives to writing your own ufunc
879+ ======================================
880+
881+ While most people like the speed benefits that come from writing a ufunc
882+ using the Numpy C API, not everyone enjoys writing C code. Fortunately
883+ there are a few alternatives that can create ufuncs with C-like speed
884+ but without having to understand the Numpy C API.
885+
886+ Numba
887+ -----
888+
889+ The `Numba vectorize decorator
890+ <https://numba.readthedocs.io/en/stable/user/vectorize.html> `_ provides
891+ a quick way to compile a subset of Python to an accelerated function that
892+ behaves like a ufunc. It can be used without arguments to support any
893+ numeric type (compiled on a "just in time" basis as you use them):
894+
895+ .. code-block :: python
896+
897+ import numba as nb
898+
899+ @nb.vectorize
900+ def logit (p ):
901+ return log(p/ (1 - p))
902+
903+ or with arguments to support only specific numeric types (compiled when
904+ you define the function):
905+
906+ .. code-block :: python
907+
908+ import numba as nb
909+
910+ @nb.vectorize ([nb.int32(nb.int32),
911+ nb.int64(nb.int64),
912+ nb.float32(nb.float32),
913+ nb.float64(nb.float64)])
914+ def logit (p ):
915+ return log(p/ (1 - p))
916+
917+ In addition to simple ufuncs, Numba can also be used to generate
918+ generalized ufuncs in a very similar way.
919+
920+ Numba is a run-time dependency (i.e. users of your library will need to
921+ have Numba installed), and the way that it dynamically compiles the ufuncs
922+ can cause a noticeable pause either on import or the first time you use
923+ a function with a particular set of argument types.
924+
925+ One (usually) minor detail is that Numba ufuncs are not "true" Numpy
926+ ufuncs. This would normally only matter if you try to use them as a
927+ :c:type: `PyUFuncObject ` from within C. They support most of the same
928+ behavior as Numpy ufuncs, including the ``__array_ufunc__ ``
929+ interoperability protocol that lets them handle other array types.
930+
931+ Cython
932+ ------
933+
934+ Cython provides a `ufunc decorator
935+ <https://cython.readthedocs.io/en/latest/src/userguide/numpy_ufuncs.html> `_
936+ which can transform functions written with Python-like syntax into
937+ Numpy ufuncs. For example:
938+
939+ .. code-block :: cython
940+
941+ cimport cython
942+ from libc.math cimport log
943+
944+ @cython.ufunc
945+ @cython.cdivision(True)
946+ cdef double logit(double p) noexcept:
947+ return log(p/(1-p))
948+
949+ Here ``log `` is taken from the C standard library. The ``cdivision ``
950+ dectorator and ``noexcept `` exception specification are Cython extensions
951+ and ensure that a divide by zero will not raise a Python exception.
952+ They are useful for this specific example but not necessary for ufuncs
953+ generally.
954+
955+ To support multiple numeric types, use "fused types" as the argument types
956+ or return types (in the example above, ``double `` could be replaced with
957+ ``cython.numeric `` to support the most widely available standard C integer,
958+ floating and complex numeric types).
959+
960+ Cython functions need to be compiled ahead of time (and thus Cython is
961+ a compile-time dependency, but users of your function do not need to
962+ have Cython installed themselves). Numpy will be both a compile-time and
963+ a runtime dependency for your compiled module, since Cython itself uses
964+ the ufunc C API described earlier in this section.
965+
966+ As of May 2026, only simple ufuncs are supported and generalized ufuncs
967+ are not yet supported.
0 commit comments