Por que o desempenho integral do Matlab supera o integrar.quad no Scipy?

13

Estou com uma certa frustração com a maneira como o matlab lida com a integração numérica versus o Scipy. Observo as seguintes diferenças no meu código de teste abaixo:

  1. A versão do Matlab é executada em média 24 vezes mais rápido que o meu equivalente em python!
  2. A versão do Matlab é capaz de calcular a integral sem avisos, enquanto o python retorna nan+nanj

O que posso fazer para garantir o mesmo desempenho em python em relação aos dois pontos mencionados? De acordo com a documentação, ambos os métodos devem usar uma "quadratura adaptativa global" para aproximar a integral.

Abaixo está o código nas duas versões (bastante semelhante, embora o python exija que uma função integral seja criada para que ele possa lidar com integrandos complexos).

Pitão

import numpy as np
from scipy import integrate
import time

def integral(integrand, a, b,  arg):
    def real_func(x,arg):
        return np.real(integrand(x,arg))
    def imag_func(x,arg):
        return np.imag(integrand(x,arg))
    real_integral = integrate.quad(real_func, a, b, args=(arg))
    imag_integral = integrate.quad(imag_func, a, b, args=(arg))   
    return real_integral[0] + 1j*imag_integral[0]

vintegral = np.vectorize(integral)


def f_integrand(s, omega):
    sigma = np.pi/(np.pi+2)
    xs = np.exp(-np.pi*s/(2*sigma))
    x1 = -2*sigma/np.pi*(np.log(xs/(1+np.sqrt(1-xs**2)))+np.sqrt(1-xs**2))
    x2 = 1-2*sigma/np.pi*(1-xs)
    zeta = x2+x1*1j
    Vc = 1/(2*sigma)
    theta =  -1*np.arcsin(np.exp(-np.pi/(2.0*sigma)*s))
    t1 = 1/np.sqrt(1+np.tan(theta)**2)
    t2 = -1/np.sqrt(1+1/np.tan(theta)**2)
    return np.real((t1-1j*t2)/np.sqrt(zeta**2-1))*np.exp(1j*omega*s/Vc);

t0 = time.time()
omega = 10
result = integral(f_integrand, 0, np.inf, omega)
print time.time()-t0
print result

Matlab

function [ out ] = f_integrand( s, omega )
    sigma = pi/(pi+2); 
    xs = exp(-pi.*s./(2*sigma));
    x1 = -2*sigma./pi.*(log(xs./(1+sqrt(1-xs.^2)))+sqrt(1-xs.^2));
    x2 = 1-2*sigma./pi.*(1-xs);
    zeta = x2+x1*1j;
    Vc = 1/(2*sigma);
    theta =  -1*asin(exp(-pi./(2.0.*sigma).*s));
    t1 = 1./sqrt(1+tan(theta).^2);
    t2 = -1./sqrt(1+1./tan(theta).^2);
    out = real((t1-1j.*t2)./sqrt(zeta.^2-1)).*exp(1j.*omega.*s./Vc);
end

t=cputime;
omega = 10;
result = integral(@(s) f_integrand(s,omega),0,Inf)
time_taken = cputime-t
Dipolo
fonte
4
Você deve estar feliz por o Python ser apenas 25x mais lento (e não 250x).
Stali #
4
Porque você está chamando uma função python em um loop repetidamente (ocultado np.vectorize). Tente fazer cálculos em toda a matriz de uma só vez. Isso não é possível, dê uma olhada no numba ou também no Cython, mas espero que o último não seja necessário.
sebix
2
"quadratura adaptativa global" indica que se adapta até atingir uma certa precisão. Para ter certeza de que você está comparando a mesma coisa, procure o parâmetro (certamente existe um) que define a precisão e o define para ambos.
precisa
2
Em relação ao comentário de @ bgschaid, integralas tolerâncias absolutas e relativas padrão são 1e-10e 1e-6, respectivamente. integrate.quadespecifica esses dois como 1.49e-8. Não vejo onde integrate.quadé descrito como um método "global adaptativo" e certamente é diferente do método (adaptável de Gauss-Kronrod, acredito) usado pelo integral. Não sei ao certo o que significa a parte "global". Além disso, nunca é uma boa ideia usar em cputimevez de tic/ tocou time it.
horchler
5
Antes de qualquer coisa, eu verificaria se o problema é o algoritmo ou a linguagem: adicione uma variável global de contador que é incrementada dentro das funções. Após a integração, isso deve informar com que frequência cada função é avaliada. Se esses contadores diferem significativamente, em seguida, pelo menos, parte do problema é que MATLAB usa o melhor algoritmo
bgschaid

Respostas:

15

A questão tem duas subquestões muito diferentes. Vou abordar apenas o primeiro.

A versão do Matlab é executada em média 24 vezes mais rápido que o meu equivalente em python!

O segundo é subjetivo. Eu diria que informar o usuário de que existe algum problema com a integral é uma coisa boa e esse comportamento SciPy supera o do Matlab para mantê-lo em silêncio e de alguma forma tentar lidar com ele internamente da maneira conhecida apenas pelos engenheiros da Matlab que decidiu que era o melhor.

Alterei o período de integração para 0 a 30 (em vez de 0 para np.inf ) para evitar o aviso de NaN e adicionei uma compilação JIT. Para comparar a solução, repeti a integração 300 vezes, os resultados são do meu laptop.

Sem compilação JIT:

$ ./test_integrate.py
34.20992112159729
(0.2618828053067563+0.24474506983644717j)

Com a compilação JIT:

$ ./test_integrate.py
0.8560323715209961
(0.261882805306756+0.24474506983644712j)

Dessa forma, adicionar duas linhas de código leva ao fator de aceleração de cerca de 40 vezes o código Python em comparação com uma versão não JIT. Não tenho Matlab no meu laptop para fornecer uma comparação melhor; no entanto, se ele for escalável para o seu PC além de 24/40 = 0,6, o Python com JIT deve ser quase duas vezes mais rápido que o Matlab para esse algoritmo de usuário específico. Código completo:

#!/usr/bin/env python3
import numpy as np
from scipy import integrate
from numba import complex128,float64,jit
import time

def integral(integrand, a, b,  arg):
    def real_func(x,arg):
        return np.real(integrand(x,arg))
    def imag_func(x,arg):
        return np.imag(integrand(x,arg))
    real_integral = integrate.quad(real_func, a, b, args=(arg))
    imag_integral = integrate.quad(imag_func, a, b, args=(arg))   
    return real_integral[0] + 1j*imag_integral[0]

vintegral = np.vectorize(integral)


@jit(complex128(float64, float64), nopython=True, cache=True)
def f_integrand(s, omega):
    sigma = np.pi/(np.pi+2)
    xs = np.exp(-np.pi*s/(2*sigma))
    x1 = -2*sigma/np.pi*(np.log(xs/(1+np.sqrt(1-xs**2)))+np.sqrt(1-xs**2))
    x2 = 1-2*sigma/np.pi*(1-xs)
    zeta = x2+x1*1j
    Vc = 1/(2*sigma)
    theta =  -1*np.arcsin(np.exp(-np.pi/(2.0*sigma)*s))
    t1 = 1/np.sqrt(1+np.tan(theta)**2)
    t2 = -1/np.sqrt(1+1/np.tan(theta)**2)
    return np.real((t1-1j*t2)/np.sqrt(zeta**2-1))*np.exp(1j*omega*s/Vc);

t0 = time.time()
omega = 10
for i in range(300): 
    #result = integral(f_integrand, 0, np.inf, omega)
    result = integral(f_integrand, 0, 30, omega)
print (time.time()-t0)
print (result)

Comente a linha @jit para ver a diferença no seu PC.

kostyfisik
fonte
1

Às vezes, a função a integrar não pode ser JITed. Nesse caso, usar outro método de integração seria a solução.

Eu recomendaria scipy.integrate.romberg (ref) . rombergpode integrar funções complexas e pode avaliar a função com matriz.

calsina yo
fonte