6.9. Funções universais

Uma função universal (ufunc) é uma função matemática que se aplica a cada elemento de um array numa única chamada. Os operadores aritméticos da página anterior são funções universais com sintaxe de operador; esta página é o catálogo das que têm nome próprio e cobrem trigonometria, exp/log, arredondamento e outras.

Cada ufunc aceita um escalar, um iterável Python ou um ndarray, e devolve um único float (quando a entrada é escalar) ou um ndarray de floats:

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

6.9.1. O catálogo

numpy expõe as funções matemáticas mais usadas numa aplicação embebida:

Cada função processa todo o array numa única chamada à biblioteca. A aceleração relativamente a uma compreensão de lista Python que chama math.sin() elemento a elemento é de 10x a 30x para um buffer típico.

6.9.2. A palavra-chave out=

Cada chamada a uma ufunc aloca normalmente um novo array de resultado para guardar a saída. Num ciclo que corre muitas vezes por segundo, essas alocações acumulam-se e desperdiçam RAM. Passar out= – um array de floats já existente, com a mesma forma que a entrada – escreve o resultado nesse array em vez de alocar um novo:

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

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

Se out tiver o dtype ou a forma errados, a função lança uma exceção. A palavra-chave é suportada em todas as ufuncs desta página; é a forma mais limpa de manter um ciclo de processamento de sinal em contínuo sem alocações.

6.9.3. Ufuncs de dois argumentos

arctan2() é a única ufunc verdadeira de dois argumentos na lista acima – devolve o arco tangente com deteção de quadrante de y / x e difunde os dois operandos:

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

6.9.4. Composição de funções universais

As funções universais compõem-se como qualquer outra expressão de array. Alguns padrões que surgem na câmara:

Correção gamma (em espaço float)

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

Um suavizador passa-baixo simples (alpha próximo de 1.0 significa atualização lenta):

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

Sigmoid

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

Espetro de potência em dB

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

6.9.5. np.vectorize

Uma função Python regular pode ser promovida a uma com forma de ufunc através de vectorize(). O objeto chamável resultante aceita escalares, iteráveis ou valores ndarray

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])

Por predefinição, o dtype do resultado é float. otypes= substitui esse valor:

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

A função Python deve receber um único argumento e devolver um único número.

vectorize() é essencialmente sintático – a função Python encapsulada continua a ter de correr uma vez por elemento, pelo que grande parte do custo de interpretação por elemento que uma ufunc real evita volta a estar presente. Espere uma aceleração modesta de 30%-50% relativamente a uma compreensão de lista, não os 30x de uma função universal verdadeira. A ferramenta certa quando uma função tem de funcionar com escalares, listas e arrays sob o mesmo nome – não quando a velocidade bruta é o objetivo.

Para as assinaturas de chamada completas de todas as funções universais listadas acima, consulte numpy — operações em arrays compatíveis com numpy.