2017-04-10 103 views
2

給定一個功能的具有周期Tt的等距間隔的傅立葉級數的係數(分別爲餘弦和正弦)a[n]b[n]下面的代碼將評估所有點的部分和在區間ta,b,t都是numpy陣列)。有人澄清,len(t)<> len(a)。如何向量化傅里葉級數部分和在numpy的

yn=ones(len(t))*a[0] 
for n in range(1,len(a)): 
    yn=yn+(a[n]*cos(2*pi*n*t/T)-b[n]*sin(2*pi*n*t/T)) 

我的問題是:這個for循環是否可以被矢量化?

+1

在你的例子中錯字,當然你的意思是'len(a)'? –

+0

@Ahmed Fasih,這當然是正確的,更正,謝謝。 – NameOfTheRose

+0

爲什麼不使用傅里葉逆變換?如果你想插入,你可以用零填充高頻。 – Dirklinux

回答

2

這裏有一個量化的方法利用broadcasting創建餘弦/正弦輸入的2D陣列版本:np.dot2*pi*n*t/T,然後使用matrix-multiplicationsum-reduction -

r = np.arange(1,len(a)) 
S = 2*np.pi*r[:,None]*t/T 
cS = np.cos(S) 
sS = np.sin(S) 
out = a[1:].dot(cS) - b[1:].dot(sS) + a[0] 

進一步的性能提升

爲了進一步提升,我們可以利用numexpr module來計算那些三角函數步 -

import numexpr as ne 
cS = ne.evaluate('cos(S)') 
sS = ne.evaluate('sin(S)') 

運行測試 -

途徑 -

def original_app(t,a,b,T): 
    yn=np.ones(len(t))*a[0] 
    for n in range(1,len(a)): 
     yn=yn+(a[n]*np.cos(2*np.pi*n*t/T)-b[n]*np.sin(2*np.pi*n*t/T)) 
    return yn 

def vectorized_app(t,a,b,T):  
    r = np.arange(1,len(a)) 
    S = (2*np.pi/T)*r[:,None]*t 
    cS = np.cos(S) 
    sS = np.sin(S) 
    return a[1:].dot(cS) - b[1:].dot(sS) + a[0] 

def vectorized_app_v2(t,a,b,T):  
    r = np.arange(1,len(a)) 
    S = (2*np.pi/T)*r[:,None]*t 
    cS = ne.evaluate('cos(S)') 
    sS = ne.evaluate('sin(S)') 
    return a[1:].dot(cS) - b[1:].dot(sS) + a[0] 

此外,包括@保羅裝甲的帖子功能PP

計時 -

In [22]: # Setup inputs 
    ...: n = 10000 
    ...: t = np.random.randint(0,9,(n)) 
    ...: a = np.random.randint(0,9,(n)) 
    ...: b = np.random.randint(0,9,(n)) 
    ...: T = 3.45 
    ...: 

In [23]: print np.allclose(original_app(t,a,b,T), vectorized_app(t,a,b,T)) 
    ...: print np.allclose(original_app(t,a,b,T), vectorized_app_v2(t,a,b,T)) 
    ...: print np.allclose(original_app(t,a,b,T), PP(t,a,b,T)) 
    ...: 
True 
True 
True 

In [25]: %timeit original_app(t,a,b,T) 
    ...: %timeit vectorized_app(t,a,b,T) 
    ...: %timeit vectorized_app_v2(t,a,b,T) 
    ...: %timeit PP(t,a,b,T) 
    ...: 
1 loops, best of 3: 6.49 s per loop 
1 loops, best of 3: 6.24 s per loop 
1 loops, best of 3: 1.54 s per loop 
1 loops, best of 3: 1.96 s per loop 
+0

謝謝。你認爲a和t的長度相同,這不是我想到的;但很容易照顧到這一點。我將發佈修改後的代碼。我還沒有研究優化。我也注意到你把標籤從fourier改成了fft。我不敢苟同。 – NameOfTheRose

+0

@NameOfTheRose那麼原始代碼在範圍(1,len(t)):'中有'n,後來改爲使用'len(a)'。所以,我需要編輯'r'部分來代替使用'len(a)'。據此編輯。 – Divakar

+0

我的錯誤然後,我很抱歉。由於你已經編輯過你的帖子,所以沒有理由自己發佈修改後的代碼,這是一個非常簡單的修改任何方式 – NameOfTheRose

2

不能擊敗numexpr,但如果它不提供,我們可以節省上超越數(很大程度上基於@測試和基準測試代碼Divakar在你沒有注意到的情況下代碼; - )):

import numpy as np 
from timeit import timeit 

def PP(t,a,b,T): 
    CS = np.empty((len(t), len(a)-1), np.complex) 
    CS[...] = np.exp(2j*np.pi*(t[:, None])/T) 
    np.cumprod(CS, axis=-1, out=CS) 
    return a[1:].dot(CS.T.real) - b[1:].dot(CS.T.imag) + a[0] 

def original_app(t,a,b,T): 
    yn=np.ones(len(t))*a[0] 
    for n in range(1,len(a)): 
     yn=yn+(a[n]*np.cos(2*np.pi*n*t/T)-b[n]*np.sin(2*np.pi*n*t/T)) 
    return yn 

def vectorized_app(t,a,b,T):  
    r = np.arange(1,len(a)) 
    S = 2*np.pi*r[:,None]*t/T 
    cS = np.cos(S) 
    sS = np.sin(S) 
    return a[1:].dot(cS) - b[1:].dot(sS) + a[0] 

n = 1000 
t = 2000 
t = np.random.randint(0,9,(t)) 
a = np.random.randint(0,9,(n)) 
b = np.random.randint(0,9,(n)) 
T = 3.45 


print(np.allclose(original_app(t,a,b,T), vectorized_app(t,a,b,T))) 
print(np.allclose(original_app(t,a,b,T), PP(t,a,b,T))) 

print('{:18s} {:9.6f}'.format('orig', timeit(lambda: original_app(t,a,b,T), number=10)/10)) 
print('{:18s} {:9.6f}'.format('Divakar no numexpr', timeit(lambda: vectorized_app(t,a,b,T), number=10)/10)) 
print('{:18s} {:9.6f}'.format('PP', timeit(lambda: PP(t,a,b,T), number=10)/10)) 

打印:

True 
True 
orig    0.166903 
Divakar no numexpr 0.179617 
PP     0.060817 

順便說一句。如果增量t除以T可以節省更多,甚至運行完整的fft並丟棄太多。

+0

你對超越性的評論讓我希望Numpy得到['sincos'](https://github.com/numpy/numpy/issues/2626)很快! –

+0

@AhmedFasih有趣。感謝您分享鏈接。我甚至不知道'sin'和'cos'是機器指令... –

+0

聰明的想法與複雜的數組創建! – Divakar

0

這不是一個真正的另一個答案,而是@Paul Panzer的一個評論,寫作答案,因爲我需要發佈一些代碼。如果有方法可以在評論中張貼格式化好的代碼,請諮詢。

通過@保羅裝甲cumprod理念的啓發,我想出了以下內容:

an = ones((len(a)-1,len(te)))*2j*pi*te/T 
CS = exp(cumsum(an,axis=0)) 
out = (a[1:].dot(CS.real) - b[1:].dot(CS.imag)) + a[0] 

雖然它似乎正確地矢量化,併產生正確的結果,它的性能更是苦不堪言。它不僅比cumprod慢得多,預計len(a)-1的指數會更多,但比原來的未引導版本要慢50%。這種糟糕表現的原因是什麼?

+0

我的猜測會是1),如你所建議的,「exp」調用的不同數量。請注意,區別在於'len(a)x len(t)',因爲'an'的元素數是產品,而不是它的尺寸之和。 2)內存佈局:在原始的半矢量化代碼中,所有變量都可能在內存中連續存在,因此很容易從交錯的「.real」和「.imag」數組無法使用的優化代碼中獲益。 –

+0

@Paul Panzer,謝謝你,我的(天真的)直覺表現類似於Divakar的第一個版本。 numpy的表現有時候讓你感到驚訝嗎? – NameOfTheRose

+0

比驚訝更令人印象深刻。每次發佈它們都會讓它變得更好。但如果你的意思是我有時會得到「這應該比這更快」的直覺感覺不對?是的,實際上很常見。 –