Почему интеграл Matlab превосходит integrate.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

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
Вы должны быть счастливы, что Python только в 25 раз медленнее (а не в 250 раз).
Стали

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.
Horchler

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)

Таким образом, добавление двух строк кода приводит к увеличению скорости кода Python примерно в 40 раз по сравнению с версией без 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 (ссылка) . rombergможет интегрировать сложные функции и может оценивать функцию с массивом.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.