Tại sao tích hợp của Matlab vượt trội so với integ.quad trong Scipy?


13

Tôi đang trải qua một số thất vọng về cách MATLAB xử lý tích hợp số so với Scipy. Tôi quan sát sự khác biệt sau đây trong mã kiểm tra của tôi dưới đây:

  1. Phiên bản của Matlab chạy nhanh hơn trung bình 24 lần so với con trăn của tôi!
  2. Phiên bản của Matlab có thể tính toán tích phân mà không cần cảnh báo, trong khi python trả về nan+nanj

Tôi có thể làm gì để đảm bảo tôi có được hiệu suất tương tự trong python đối với hai điểm được đề cập? Theo tài liệu, cả hai phương pháp nên sử dụng "phương pháp thích nghi toàn cầu" để tính gần đúng tích phân.

Dưới đây là mã trong hai phiên bản (khá giống nhau mặc dù python yêu cầu một hàm tách rời được tạo để nó có thể xử lý các tích phân phức tạp.)

Con trăn

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
Bạn nên vui mừng khi Python chỉ chậm hơn 25 lần (và không phải 250 lần).
stali

4
Bởi vì bạn đang gọi một hàm python trong một vòng lặp lặp đi lặp lại (ẩn bởi np.vectorize). Hãy thử làm các phép tính trên toàn bộ mảng cùng một lúc. Điều đó là không thể, hãy xem numba hoặc Cython, nhưng tôi hy vọng điều này là không cần thiết.
sebix

2
"phương trình thích nghi toàn cầu" chỉ ra rằng nó thích nghi cho đến khi đạt được độ chính xác nhất định. Để chắc chắn rằng bạn đang so sánh cùng một thứ, hãy tìm tham số (chắc chắn có một) đặt độ chính xác và đặt nó cho cả hai.
bgschaid

2
Về nhận xét của @ bgschaid, integraldung sai tuyệt đối và tương đối mặc định tương ứng là 1e-101e-6. integrate.quadchỉ định cả hai như 1.49e-8. Tôi không thấy nơi nào integrate.quadđược mô tả là phương pháp "thích ứng toàn cầu" và nó chắc chắn khác với phương pháp (Gauss-Kronrod thích nghi, tôi tin) được sử dụng bởi integral. Bản thân tôi không chắc phần "toàn cầu" nghĩa là gì. Ngoài ra, không bao giờ nên sử dụng cputimethay vì tic/ tochoặc time it.
horchler

5
Trước bất cứ điều gì tôi sẽ kiểm tra xem vấn đề là thuật toán hay ngôn ngữ: thêm một biến truy cập toàn cầu được tăng lên bên trong các hàm. Sau khi tích hợp, điều này sẽ cho bạn biết tần suất mỗi chức năng được đánh giá. Nếu các bộ đếm này khác nhau đáng kể thì ít nhất một phần của vấn đề là MATLAB sử dụng thuật toán tốt hơn
bgschaid

Câu trả lời:


15

Câu hỏi có hai câu hỏi rất khác nhau. Tôi sẽ chỉ giải quyết cái đầu tiên.

Phiên bản của Matlab chạy nhanh hơn trung bình 24 lần so với con trăn của tôi!

Cái thứ hai là chủ quan. Tôi sẽ nói rằng việc cho người dùng biết rằng có một số vấn đề với tích phân là một điều tốt và hành vi SciPy này vượt trội hơn so với người Matlab để giữ im lặng và bằng cách nào đó cố gắng xử lý nội bộ theo cách mà các kỹ sư Matlab chỉ biết quyết định nó là tốt nhất

Tôi đã thay đổi khoảng tích hợp thành từ 0 đến 30 (thay vì từ 0 đến np.inf ) để tránh gây nhiễu NaN và thêm phần biên dịch JIT. Để đánh giá giải pháp tôi đã lặp lại tích hợp trong 300 lần, kết quả là từ máy tính xách tay của tôi.

Không có biên dịch JIT:

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

Với phần tổng hợp JIT:

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

Cách này thêm hai dòng mã dẫn đến hệ số tăng tốc khoảng 40 lần mã Python so với phiên bản không phải JIT. Tôi không có Matlab trên máy tính xách tay của mình để cung cấp một so sánh tốt hơn, tuy nhiên, nếu nó có tỷ lệ tốt với PC của bạn hơn 24/40 = 0,6, do đó, Python với JIT sẽ nhanh gấp đôi Matlab cho thuật toán người dùng cụ thể này. Mã đầy đủ:

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

Nhận xét dòng @jit để thấy sự khác biệt cho PC của bạn.


1

Đôi khi chức năng tích hợp không thể được JITed. Trong trường hợp đó, sử dụng phương pháp tích hợp khác sẽ là giải pháp.

Tôi muốn giới thiệu scipy.integrate.romberg (ref) . rombergcó thể tích hợp các hàm phức tạp và có thể đánh giá hàm với mảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.