9.9. Universal functions

A universal function (ufunc) is a math function that applies to every element of an array in one call. The arithmetic operators on the previous page are universal functions wearing operator syntax; this page is the catalogue of the named ones that cover trig, exp / log, rounding, and a few others.

Each ufunc accepts a scalar, a Python iterable, or an ndarray, and returns either a single float (when the input was scalar) or a float ndarray:

from ulab import numpy as np

np.exp(2.0)                    # 7.389...
np.sin(range(4))               # 1-D float ndarray
np.sqrt([1, 4, 9, 16])         # array([1.0, 2.0, 3.0, 4.0])

a = np.arange(9).reshape((3, 3))
np.exp(a)                      # 3x3 float ndarray

9.9.1. The catalogue

numpy exposes the math functions an embedded application reaches for most often:

  • Trigsin(), cos(), tan(), asin(), acos(), atan(), arctan2(), sinh(), cosh(), tanh(), asinh(), acosh(), atanh(), sinc().

  • Angle conversiondegrees(), radians().

  • Exponentials and logsexp(), expm1(), log(), log10(), log2(), sqrt().

  • Roundingceil(), floor(), around().

Each function processes the whole array in one library call. The speedup over a Python list comprehension that calls math.sin() element by element is 10-30x on a typical buffer.

9.9.2. The out= keyword

Each ufunc call normally allocates a fresh result array to hold its output. In a loop that runs many times a second, those allocations add up and waste RAM. Passing out= – a float array that already exists, of the same shape as the input – writes the result into that array instead of allocating a new one:

x = np.linspace(0, 2 * np.pi, num=256)
y = np.zeros(256)

while True:
    np.sin(x, out=y)
    # use y ...

If out has the wrong dtype or shape, the function raises an exception. The keyword is supported on every ufunc on this page; it is the cleanest way to keep a streaming signal-processing loop allocation-free.

9.9.3. Two-argument ufuncs

arctan2() is the only true two-argument ufunc in the list above – it returns the quadrant-aware arctangent of y / x and broadcasts the two operands:

y = np.array([1, 2.2, 33.33, 444.444])
np.arctan2(y, 1.0)             # against a scalar
np.arctan2(1.0, y)             # the other way
np.arctan2(y, y)               # against another array

9.9.4. Composing universal functions

Universal functions compose like any other array expression. A few patterns that come up on the camera:

Gamma correction (in float space):

gamma = 0.5
out = 255.0 * (frame / 255.0) ** gamma

A simple low-pass IIR (alpha close to 1.0 means slow update):

alpha = 0.95
filtered = alpha * filtered + (1.0 - alpha) * sample

Sigmoid:

sigmoid = 1.0 / (1.0 + np.exp(-x))

Power spectrum in dB:

spectrum = 20.0 * np.log10(np.abs(real) + 1e-12)

In a loop, rewrite each expression to use out= and in-place operators so no temporary is allocated per iteration. Performance covers the rewrite.

9.9.5. np.vectorize

A regular Python function can be promoted to a ufunc-shaped one by vectorize(). The resulting callable accepts scalars, iterables, or ndarrays:

def f(x):
    return x * x

vf = np.vectorize(f)

vf(44.0)                          # array([1936.0])
vf(np.array([1, 2, 3, 4]))        # array([1.0, 4.0, 9.0, 16.0])
vf([2, 3, 4])                     # array([4.0, 9.0, 16.0])

By default the result dtype is float. otypes= overrides it:

vf_u8 = np.vectorize(f, otypes=np.uint8)
vf_u8([1, 2, 3, 4])
# array([1, 4, 9, 16], dtype=uint8)

The Python function must take a single argument and return a single number.

vectorize() is mostly syntactic: the wrapped Python function still has to run once per element, so most of the per-element interpreter cost that a true ufunc avoids is back. Expect a modest 30%-50% speedup over a list comprehension, not the 30x of a true universal function. The right tool when one function has to work on scalars, lists, and arrays under the same name – not when raw speed is the goal.

For the full call signatures of every universal function listed above, see numpy — numpy-compatible array operations.