Perché la sovraperformance integrale di Matlab integra.quad in Scipy?


13

Sto vivendo una certa frustrazione per il modo in cui matlab gestisce l'integrazione numerica rispetto a Scipy. Osservo le seguenti differenze nel mio codice di prova di seguito:

  1. La versione di Matlab funziona in media 24 volte più veloce del mio equivalente di Python!
  2. La versione di Matlab è in grado di calcolare l'integrale senza avvisi, mentre python ritorna nan+nanj

Cosa posso fare per assicurarmi di ottenere le stesse prestazioni in Python rispetto ai due punti menzionati? Secondo la documentazione, entrambi i metodi dovrebbero usare una "quadratura adattativa globale" per approssimare l'integrale.

Di seguito è riportato il codice nelle due versioni (abbastanza simile sebbene Python richieda che venga creata una funzione integrale in modo che possa gestire integrandi complessi).

Pitone

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

4
Dovresti essere felice che Python sia solo 25 volte più lento (e non 250x).
stali,

4
Perché stai chiamando ripetutamente una funzione python in un ciclo (nascosto da np.vectorize). Prova a fare calcoli sull'intero array contemporaneamente. Se ciò non è possibile, dai un'occhiata a numba o anche a Cython, ma spero che quest'ultimo non sia necessario.
sebix,

2
"quadratura adattativa globale" indica che si adatta fino a raggiungere una certa precisione. Per assicurarti di confrontare la stessa cosa, cerca il parametro (sicuramente ce n'è uno) che imposta la precisione e la imposta per entrambi.
bgschaid,

2
Per quanto riguarda il commento di @ bgschaid, integralle tolleranze assolute e relative predefinite sono 1e-10e 1e-6, rispettivamente. integrate.quadspecifica questi entrambi come 1.49e-8. Non vedo dove integrate.quadsia descritto come un metodo "adattativo globale" ed è sicuramente diverso dal metodo (adattivo Gauss-Kronrod, credo) usato da integral. Non sono sicuro di cosa significhi la parte "globale", me stesso. Inoltre, non è mai una buona idea usare al cputimeposto di tic/ toco time it.
Horchler,

5
Prima di tutto, verificherei se il problema è l'algoritmo o la lingua: aggiungere una variabile contatore globale che viene incrementata all'interno delle funzioni. Dopo l'integrazione, questo dovrebbe dirti con quale frequenza viene valutata ogni funzione. Se questi contatori differiscono in modo significativo, almeno una parte del problema è che MATLAB utilizza l'algoritmo migliore
bgschaid

Risposte:


15

La domanda ha due domande molto diverse. Mi rivolgerò solo al primo.

La versione di Matlab funziona in media 24 volte più veloce del mio equivalente di Python!

Il secondo è soggettivo. Direi che far sapere all'utente che c'è qualche problema con l'integrale è una buona cosa e questo comportamento di SciPy supera quello di Matlab per mantenerlo silenzioso e in qualche modo provare a gestirlo internamente nel modo noto solo dagli ingegneri Matlab che deciso di essere il migliore.

Ho modificato l'intervallo di integrazione da 0 a 30 (anziché da 0 a np.inf ) per evitare segnalazioni NaN e ho aggiunto una compilation JIT. Per confrontare la soluzione con cui ho ripetuto l'integrazione per 300 volte, i risultati provengono dal mio laptop.

Senza compilation JIT:

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

Con la compilation JIT:

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

In questo modo l'aggiunta di due righe di codice porta al fattore di accelerazione di circa 40 volte codice Python rispetto a una versione non JIT. Non ho Matlab sul mio laptop per fornire un confronto migliore, tuttavia, se si adatta bene al tuo PC di 24/40 = 0,6, quindi Python con JIT dovrebbe essere quasi due volte più veloce di Matlab per questo particolare algoritmo utente. Codice 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)

Commenta la riga @jit per vedere la differenza per il tuo PC.


1

A volte la funzione da integrare non può essere JITed. In tal caso, utilizzare un altro metodo di integrazione sarebbe la soluzione.

Vorrei raccomandare scipy.integrate.romberg (rif) . rombergpuò integrare funzioni complesse e può valutare la funzione con l'array.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.