Skip to content

UFuncs and Vectorization

Universal Functions

1. Definition

UFuncs are vectorized wrappers around C-implemented operations:

import numpy as np

x = np.array([0, np.pi/2, np.pi])
y = np.sin(x)  # UFuncs operates element-wise
print(y)  # [0. 1. 0.]

Characteristics: - Implemented in C for speed - Operate element-wise - Support broadcasting - Type-flexible with automatic promotion

2. Performance

import time

# Python loop (slow)
x_list = [i * 0.001 for i in range(1000000)]
start = time.time()
y_list = [np.sin(xi) for xi in x_list]
print(f"List: {time.time() - start:.3f}s")

# NumPy ufunc (fast)
x_array = np.linspace(0, 1000, 1000000)
start = time.time()
y_array = np.sin(x_array)
print(f"UFuncs: {time.time() - start:.3f}s")

UFuncs are typically 100x faster than Python loops.

3. Type Detection

print(isinstance(np.sin, np.ufunc))  # True
print(isinstance(np.add, np.ufunc))  # True

Common UFuncs

1. Arithmetic

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(np.add(a, b))       # [5 7 9]
print(np.subtract(a, b))  # [-3 -3 -3]
print(np.multiply(a, b))  # [4 10 18]
print(np.divide(a, b))    # [0.25 0.4 0.5]
print(np.power(a, 2))     # [1 4 9]

2. Trigonometric

x = np.array([0, np.pi/4, np.pi/2])

print(np.sin(x))      # [0. 0.707 1.]
print(np.cos(x))      # [1. 0.707 0.]
print(np.tan(x))      # [0. 1. inf]
print(np.arctan2(1, 1))  # 0.785 (π/4)

3. Exponential and Logarithmic

x = np.array([1, 2, 3])

print(np.exp(x))      # [2.718 7.389 20.086]
print(np.log(x))      # [0. 0.693 1.099]
print(np.log10(x))    # [0. 0.301 0.477]
print(np.sqrt(x))     # [1. 1.414 1.732]

Broadcasting with UFuncs

1. Scalar and Array

arr = np.array([1, 2, 3])
result = arr + 10  # Broadcasts scalar
print(result)  # [11 12 13]

2. Array and Array

arr1 = np.array([[1], [2], [3]])  # (3, 1)
arr2 = np.array([10, 20, 30])     # (3,)
result = arr1 + arr2              # (3, 3)
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]

3. Multi-dimensional

a = np.ones((3, 1, 4))
b = np.ones((1, 2, 4))
c = a + b  # Broadcasts to (3, 2, 4)
print(c.shape)  # (3, 2, 4)

Advanced UFuncs Features

1. Output Arrays

x = np.array([1.0, 2.0, 3.0])
out = np.empty_like(x)
np.sin(x, out=out)  # Writes result to out, avoids allocation
print(out)

2. Reduction Methods

arr = np.array([1, 2, 3, 4, 5])

# UFuncs have reduce methods
print(np.add.reduce(arr))      # 15 (sum)
print(np.multiply.reduce(arr)) # 120 (product)

3. Accumulate

arr = np.array([1, 2, 3, 4, 5])
print(np.add.accumulate(arr))  # [1 3 6 10 15] (cumulative sum)

Comparison UFuncs

1. Element-wise Comparison

a = np.array([1, 2, 3])
b = np.array([1, 3, 2])

print(np.greater(a, b))      # [False False True]
print(np.less_equal(a, b))   # [True True False]
print(np.equal(a, b))        # [True False False]
print(np.not_equal(a, b))    # [False True True]

2. Maximum and Minimum

a = np.array([1, 5, 3])
b = np.array([4, 2, 6])

print(np.maximum(a, b))  # [4 5 6] element-wise max
print(np.minimum(a, b))  # [1 2 3] element-wise min

3. Logical Operations

a = np.array([True, False, True])
b = np.array([True, True, False])

print(np.logical_and(a, b))  # [True False False]
print(np.logical_or(a, b))   # [True True True]
print(np.logical_not(a))     # [False True False]

Custom UFuncs

1. Using frompyfunc

def custom_func(x, y):
    return x**2 + y**2

ufunc = np.frompyfunc(custom_func, 2, 1)  # 2 inputs, 1 output

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = ufunc(a, b)
print(result)  # [17 29 45]

2. Numba Vectorize

from numba import vectorize

@vectorize
def fast_func(x, y):
    return x**2 + y**2

result = fast_func(np.array([1, 2, 3]), np.array([4, 5, 6]))

3. Performance Comparison

Custom UFuncs via Numba can match C-level performance while being pure Python.