6.17. Solucionadores e números aleatórios

Quando a função em estudo é definida por código Python em vez de um buffer de amostras, uma família diferente de ferramentas é a escolha certa: onde está a raiz da função, seu mínimo, sua integral em um dado intervalo? Os submódulos scipy.integrate e scipy.optimize cobrem esse trabalho. Cada algoritmo faz chamadas de volta para a função Python fornecida pelo usuário, então o custo por iteração é maior que o de uma redução de buffer; a conveniência está em não precisar escrever o solucionador.

O submódulo scipy.special cobre as funções especiais estatísticas (função de erro, gama) que surgem ao computar a função de distribuição cumulativa (CDF, a probabilidade de uma amostra ser no máximo um dado valor) ou a função densidade de probabilidade (PDF, a verossimilhança relativa em um dado valor) de uma distribuição de probabilidade. numpy.random cobre o gerador pseudoaleatório para dithering, simulação e dados de teste sintéticos.

6.17.1. Integração numérica de um callable

Quando o integrando é uma função Python em vez de um buffer de amostras, scipy.integrate expõe quatro algoritmos de quadratura:

  • quad() – Gauss-Kronrod adaptativo. O padrão certo para integrandos suaves. Retorna (value, error).

  • romberg() – Romberg / Newton-Cotes clássico. Retorna um único float. Descontinuado no projeto de origem; incluído por compatibilidade.

  • simpson() – regra de Simpson adaptativa. Retorna um único float.

  • tanhsinh() – quadratura dupla-exponencial. Use quando o integrando tem singularidades nos extremos ou um limite infinito. Retorna (value, error).

A integral gaussiana avaliada com a regra dupla-exponencial (tanhsinh):

from math import exp
from math import pi
from math import sqrt
from ulab import numpy as np
from ulab import scipy as sp

f = lambda x: exp(-x * x)
value, err = sp.integrate.tanhsinh(f, -np.inf, np.inf)
print("approx:", value, "   exact:", sqrt(pi))

Saída:

approx: 1.7724538...   exact: 1.7724538...

6.17.2. Busca de raízes e minimização

scipy.optimize cobre três solucionadores clássicos de variável única. Cada iteração faz uma chamada de volta para a função Python fornecida pelo usuário, então o ganho de velocidade em relação a um solucionador em Python puro é modesto (cerca de 2x); a conveniência está em não precisar escrever o solucionador.

  • bisect() – encontra uma raiz de f em [a, b] dividindo o intervalo pela metade. f(a) e f(b) devem ter sinais opostos:

    def f(x):
        return x * x - 1
    
    sp.optimize.bisect(f, 0, 4)        # ~1.0
    
  • newton() – encontra uma raiz usando iteração da secante / Newton-Raphson:

    def f(x):
        return x * x * x - 2.0
    
    sp.optimize.newton(f, 3., tol=0.001, rtol=0.01)
    # ~1.260
    
  • fmin() – encontra um mínimo local usando o método do simplex descendente (Nelder-Mead):

    def f(x):
        return (x - 1) ** 2 - 1
    
    sp.optimize.fmin(f, 3.0)           # ~1.0
    

O escopo de variável única é suficiente para a maioria das otimizações do lado da câmera – a constante de calibração de um sensor, o ganho que maximiza uma medida de contraste, o limiar onde a bimodalidade de um histograma é mais acentuada. Para problemas multivariáveis, a resposta certa costuma ser reformular o problema como uma pequena resolução de álgebra linear em vez de recorrer a um otimizador não linear genérico.

6.17.3. Funções especiais

scipy.special expõe um punhado de funções estatísticas e de probabilidade que se comportam como funções universais – elas aceitam um escalar, um iterável ou um ndarray e retornam um ndarray de ponto flutuante:

x = np.linspace(0, 4, num=8)

sp.special.erf(x)         # error function
sp.special.erfc(x)        # complementary error function
sp.special.gamma(x + 1)   # gamma function
sp.special.gammaln(x + 1) # log-gamma function

A função de erro e seu complemento aparecem na CDF de uma gaussiana – a aplicação de escolha para converter entre um z-score medido e uma probabilidade, ou para computar a integral da cauda de uma distribuição normal. As funções gama e log-gama surgem em cálculos de beta / qui-quadrado / t de Student; gammaln é a forma numericamente estável para argumentos grandes em que gamma por si só estouraria.

6.17.4. Números aleatórios

numpy.random fornece uma classe Generator que sorteia amostras de distribuições comuns. O gerador é stateful: cada chamada avança seu estado interno, então chamadas consecutivas retornam amostras independentes:

from ulab import numpy as np

rng = np.random.Generator(seed=42)

rng.random(size=5)             # 5 uniform [0.0, 1.0) samples
rng.uniform(low=-1.0, high=1.0, size=10)
rng.normal(loc=0.0, scale=1.0, size=(2, 4))

O dtype de saída é sempre float. size= aceita um inteiro (saída unidimensional) ou uma tupla (saída n-dimensional); quando omitido, um único float Python é retornado.

O gerador é adequado para simulação, dithering, dados de teste sintéticos e qualquer outra aplicação em que força criptográfica não seja necessária. Ele não é adequado para chaves ou tokens; use a fonte aleatória do sistema por meio de os para esses casos.

6.17.5. Disponibilidade em tempo de compilação

Se cada submódulo está de fato presente depende de como a câmera foi compilada. scipy.optimize e scipy.special não estão habilitados em toda câmera; chamar uma função que a câmera não inclui lança AttributeError. dir(sp), dir(sp.optimize), dir(np.random) e similares relatam o que está disponível na câmera alvo.