Warum integriert Matlabs integraler Outperform.quad in Scipy?

13

Ich bin etwas frustriert darüber, wie matlab mit numerischer Integration im Vergleich zu Scipy umgeht. Ich beobachte die folgenden Unterschiede in meinem Testcode:

  1. Matlabs Version läuft im Durchschnitt 24-mal schneller als mein Python-Äquivalent!
  2. Matlabs Version ist in der Lage, das Integral ohne Warnungen zu berechnen, während Python zurückkehrt nan+nanj

Was kann ich tun, um sicherzustellen, dass ich in Bezug auf die beiden genannten Punkte dieselbe Leistung in Python erhalte? Laut Dokumentation sollten beide Methoden eine "globale adaptive Quadratur" verwenden, um das Integral zu approximieren.

Nachfolgend finden Sie den Code in den beiden Versionen (ziemlich ähnlich, obwohl für Python die Erstellung einer Integralfunktion erforderlich ist, damit komplexe Integranden verarbeitet werden können.)

Python

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
Dipol
quelle
4
Sie sollten froh sein, dass Python nur 25x langsamer ist (und nicht 250x).
Stali
4
Weil Sie eine Python-Funktion in einer Schleife immer wieder aufrufen (versteckt von np.vectorize). Versuchen Sie, Berechnungen für das gesamte Array gleichzeitig durchzuführen. Es ist nicht möglich, sich numba oder auch Cython anzuschauen, aber ich hoffe, letzteres ist nicht notwendig.
Sebix
2
"globale adaptive Quadratur" gibt an, dass es sich anpasst, bis es eine bestimmte Genauigkeit erreicht. Um sicherzustellen, dass Sie dasselbe vergleichen, suchen Sie nach dem Parameter (es gibt sicherlich einen), der die Genauigkeit festlegt, und legen Sie ihn für beide fest.
BGSCHAID
2
In Bezug auf @ bgschaids Kommentar sind integraldie absoluten und relativen Standardtoleranzen gleich1e-10 und 1e-6ist. integrate.quadspezifiziert diese beiden als 1.49e-8. Ich sehe nicht, wo integrate.quadeine "globale adaptive" Methode beschrieben wird, und sie unterscheidet sich mit Sicherheit von der (meiner Meinung nach adaptiven Gauß-Kronrod-Methode), die von verwendet wird integral. Ich bin mir nicht sicher, was der "globale" Teil bedeutet. Es ist auch nie eine gute Idee, cputimeanstelle von tic/ tocoder zu verwenden time it.
Horchler
5
Vor allem würde ich prüfen, ob das Problem der Algorithmus oder die Sprache ist: Fügen Sie eine globale Zählervariable hinzu, die in den Funktionen inkrementiert wird. Nach der Integration sollte hier angegeben werden, wie oft jede Funktion ausgewertet wird. Wenn sich diese Zähler erheblich unterscheiden, besteht zumindest ein Teil des Problems darin, dass MATLAB den besseren Algorithmus verwendet
bgschaid 21.10.14

Antworten:

15

Die Frage hat zwei sehr unterschiedliche Teilfragen. Ich werde nur den ersten ansprechen.

Matlabs Version läuft im Durchschnitt 24-mal schneller als mein Python-Äquivalent!

Der zweite ist subjektiv. Ich würde sagen, dass es eine gute Sache ist, den Benutzer wissen zu lassen, dass es ein Problem mit dem Integral gibt, und dieses SciPy-Verhalten übertrifft das des Matlab, um es still zu halten, und irgendwie zu versuchen, damit intern umzugehen, wie es nur den Matlab-Ingenieuren bekannt ist entschieden, dass es das Beste ist.

Ich habe die Integrationsspanne von 0 auf 30 geändert (anstatt von 0 auf np.inf) ), um NaN- zu vermeiden, und eine JIT-Kompilierung hinzugefügt. Um die Lösung zu bewerten, habe ich die Integration 300 Mal wiederholt. Die Ergebnisse stammen von meinem Laptop.

Ohne JIT-Kompilierung:

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

Mit JIT-Kompilierung:

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

Auf diese Weise führt das Hinzufügen von zwei Codezeilen zu einer etwa 40-fachen Geschwindigkeit des Python-Codes im Vergleich zu einer Nicht-JIT-Version. Ich habe kein Matlab auf meinem Laptop, um einen besseren Vergleich zu ermöglichen, aber wenn es gut auf Ihren PC skaliert als 24/40 = 0,6, sollte Python mit JIT fast doppelt so schnell sein wie Matlab für diesen bestimmten Benutzeralgorithmus . Vollständiger Code:

#!/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)

Kommentieren Sie die @ jit-Zeile aus, um den Unterschied für Ihren PC zu sehen.

kostyfisik
quelle
1

Manchmal kann die zu integrierende Funktion nicht JIT-fähig sein. In diesem Fall wäre die Verwendung einer anderen Integrationsmethode die Lösung.

Ich würde empfehlen scipy.integrate.romberg (ref) . rombergkann komplexe Funktionen integrieren und die Funktion mit Array auswerten.

Calsina yo
quelle