Чому інтегральний результат Matlab інтегрує.quad в Scipy?


13

Я відчуваю деяке розчарування з приводу того, як matlab обробляє числову інтеграцію проти Scipy. У моєму тестовому коді нижче я спостерігаю такі відмінності:

  1. Версія Matlab працює в середньому в 24 рази швидше, ніж мій еквівалент python!
  2. Версія Matlab здатна обчислити інтеграл без попереджень, тоді як python повертається nan+nanj

Що я можу зробити, щоб досягти однакової продуктивності в python стосовно двох згаданих моментів? Відповідно до документації, обидва методи повинні використовувати "глобальну адаптивну квадратуру" для наближення інтеграла.

Нижче наведено код у двох версіях (досить схожий, хоча 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

Матлаб

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
Ви повинні бути щасливі, що Python лише на 25 разів повільніше (а не 250x).
stali

4
Тому що ви викликаєте функцію python у циклі знову і знову (приховується np.vectorize). Спробуйте зробити обчислення для всього масиву одночасно. Це неможливо, подивіться на numba або також на Cython, але я сподіваюся, що останній не потрібен.
sebix

2
"глобальна адаптивна квадратура" вказує на те, що вона адаптується до досягнення певної точності. Щоб переконатися, що ви порівнюєте одне і те ж, шукайте параметр (напевно, є такий), який встановлює точність і встановлює його для обох.
bgschaid

2
Щодо коментаря @ bgschaid, integralабсолютні та відносні допуски за замовчуванням є 1e-10і 1e-6, відповідно ,. integrate.quadвизначає їх як 1.49e-8. Я не бачу, де integrate.quadописаний як "глобальний адаптивний" метод, і він, безумовно, відрізняється від (адаптивного методу Гаусса-Кронрода, я вважаю) методу integral. Я не впевнений, що означає "глобальна" частина. Крім того, ніколи не є корисною використовувати cputimeзамість tic/ tocабо time it.
хорлер

5
Перш ніж щось перевірити, чи проблема полягає в алгоритмі чи мові: додайте глобальну змінну лічильника, що збільшується всередині функцій. Після інтеграції це повинно повідомити вам, як часто оцінюється кожна функція. Якщо ці лічильники значно відрізняються, то принаймні частина проблеми полягає в тому, що MATLAB використовує кращий алгоритм
bgschaid

Відповіді:


15

Питання має два дуже різні підпитання. Я торкнуся лише першого.

Версія Matlab працює в середньому в 24 рази швидше, ніж мій еквівалент python!

Другий - суб'єктивний. Я б сказав, що повідомити користувача про те, що є якась проблема з інтегралом, це добре, і це поведінка SciPy перевершує програму Matlab, щоб вона мовчала і якось намагалася впоратися з цим внутрішньо так, як це знають лише інженери Matlab, які вирішили, що це найкраще.

Я змінив діапазон інтеграції на 0 - 30 (замість 0 на np.inf ), щоб уникнути попередження NaN і додав компіляцію JIT. Щоб порівняти рішення, я повторив інтеграцію 300 разів, результати - з мого ноутбука.

Без компіляції JIT:

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

З компіляцією JIT:

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

Таким чином, додавання двох рядків коду призводить до коефіцієнта прискорення приблизно в 40 разів з коду Python порівняно з не-JIT-версією. Я не маю Matlab на своєму ноутбуці, щоб забезпечити краще порівняння, якщо він добре масштабується на вашому ПК, ніж 24/40 = 0,6, тому Python з JIT повинен бути майже вдвічі швидшим, ніж Matlab для цього конкретного алгоритму користувача. Повний код:

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

Прокоментуйте рядок @jit, щоб побачити різницю для вашого ПК.


1

Іноді функція інтеграції не може бути JITed. У цьому випадку рішенням буде використовувати інший метод інтеграції.

Я б рекомендував scipy.integrate.romberg (ref) . rombergможе інтегрувати складні функції та може оцінювати функцію за допомогою масиву.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.