2014-05-15 88 views
7

我對一階微分方程組的x(t)數值求解。該系統是:向量化微分方程組的正向歐拉法

DX/DT = Y
DY/DT = -x - 一個* Y(X^2 + Y^2 -1)

我已經實現向前歐拉方法來解決這個問題如下:

def forward_euler(): 
    h = 0.01 
    num_steps = 10000 

    x = np.zeros([num_steps + 1, 2]) # steps, number of solutions 
    y = np.zeros([num_steps + 1, 2]) 
    a = 1. 

    x[0, 0] = 10. # initial condition 1st solution 
    y[0, 0] = 5. 

    x[0, 1] = 0. # initial condition 2nd solution 
    y[0, 1] = 0.0000000001 

    for step in xrange(num_steps): 
     x[step + 1] = x[step] + h * y[step] 
     y[step + 1] = y[step] + h * (-x[step] - a * y[step] * (x[step] ** 2 + y[step] ** 2 - 1)) 

    return x, y 

現在我想進一步向量化的代碼,並保持在同一陣列中的x和y,我已經提出了以下解決方案:

def forward_euler_vector(): 
    num_steps = 10000 
    h = 0.01 

    x = np.zeros([num_steps + 1, 2, 2]) # steps, variables, number of solutions 
    a = 1. 

    x[0, 0, 0] = 10. # initial conditions 1st solution 
    x[0, 1, 0] = 5. 

    x[0, 0, 1] = 0. # initial conditions 2nd solution 
    x[0, 1, 1] = 0.0000000001 

    def f(x): 
     return np.array([x[1], 
         -x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1)]) 

    for step in xrange(num_steps): 
     x[step + 1] = x[step] + h * f(x[step]) 

    return x 

的問題:forward_euler_vector()的作品,但這是最好的方式來矢量化它?我問,因爲矢量版本的運行約20毫秒在我的筆記本電腦慢:

In [27]: %timeit forward_euler() 
1 loops, best of 3: 301 ms per loop 

In [65]: %timeit forward_euler_vector() 
1 loops, best of 3: 320 ms per loop 
+0

「向量化」版本只能實現向量化h * f(x [step])或者兩個操作。創建numpy數組的額外成本抵消了任何速度增益。根據你在做什麼,你可能想看看[scipy.integrate.ode](http://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html#scipy.integrate。頌)。 – Daniel

回答

3

@Ophion意見解釋非常清楚是怎麼回事。在f(x)內對array()的調用引入了一些開銷,這在表達式h * f(x[step])中消除了使用矩陣乘法的好處。

正如他所說,您可能有興趣查看scipy.integrate以獲得一組不錯的數值積分器。

爲了解決矢量化代碼的問題,您希望每次調用f時都不要重新創建數組。您想要初始化數組一次,並在每次調用時將其返回修改。這類似於C/C++中的static變量。

您可以在函數f(x)的定義時使用可變默認參數來實現此功能,該參數會被解釋一次,並且具有局部範圍。由於它必須是可變的,你將其封裝在一個單一的元素的列表:

def f(x,static_tmp=[empty((2,2))]): 
    static_tmp[0][0]=x[1] 
    static_tmp[0][1]=-x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1) 
    return static_tmp[0] 

有了這個修改你的代碼,陣列創建的開銷就消失了,我的機器上我收穫了小的改進:

%timeit forward_euler()  #258ms 
%timeit forward_euler_vector() #248ms 

這意味着優化與numpy的矩陣乘法的收益是相當小的,至少在手頭的問題。

您可能希望立即擺脫函數f,在for循環中執行其操作,從而消除通話開銷。然而,默認參數的這個技巧也可以用於更通用的時間積分器,其中您必須提供函數f

編輯:如由Jaime指出,另一種方式去是治療static_tmp作爲函數f的屬性,並已宣佈後的功能,但稱這是之前來創建它:

def f(x): 
    f.static_tmp[0]=x[1] 
    f.static_tmp[1]=-x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1) 
    return f.static_tmp 
f.static_tmp=empty((2,2)) 
+0

不知道我是否更喜歡你的方法,但我總是看到Python中定義爲函數類成員的「靜態」變量,請參閱[本](http://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function)。 – Jaime

+0

@Jaime,我認爲這是另一種方式,思考它看起來更清晰(不需要每次處理列表的第一個元素)。我會用另一個選項更新答案。 – gg349

3

有總是瑣碎autojit解決方案:

def forward_euler(initial_x, initial_y, num_steps, h): 

    x = np.zeros([num_steps + 1, 2]) # steps, number of solutions 
    y = np.zeros([num_steps + 1, 2]) 
    a = 1. 

    x[0, 0] = initial_x[0] # initial condition 1st solution 
    y[0, 0] = initial_y[0] 

    x[0, 1] = initial_x[1] # initial condition 2nd solution 
    y[0, 1] = initial_y[1] 

    for step in xrange(int(num_steps)): 
     x[step + 1] = x[step] + h * y[step] 
     y[step + 1] = y[step] + h * (-x[step] - a * y[step] * (x[step] ** 2 + y[step] ** 2 - 1)) 

    return x, y 

時序:

from numba import autojit 
jit_forward_euler = autojit(forward_euler) 

%timeit forward_euler([10,0], [5,0.0000000001], 1E4, 0.01) 
1 loops, best of 3: 385 ms per loop 

%timeit jit_forward_euler([10,0], [5,0.0000000001], 1E4, 0.01) 
100 loops, best of 3: 3.51 ms per loop 
+0

令人印象深刻的表現! – gg349

+0

你知道爲什麼在我的機器上這不會導致性能改進嗎?我的'numba.test()'只在'test_pow_floats_array'上失敗,這在這裏是相關的。我在anaconda,mac上有numba版本'0.12.1'。 – gg349

+1

@flebool不知道說實話。在我看來,Numba在1.0版本發佈之前有很多工作要做。因此我沒有在內部工作上投入太多時間。 – Daniel