老規矩,數學原理什麼的就不寫了。
直接貼代碼和實例演示,以下代碼基於python和numpy。
在這裏,我將用代碼實現改進的歐拉公式、標準(經典)的四階龍格-庫塔法(改進的 Euler 公式和標準(經典)的四階 Runge-Kutta 法)和線性常微分方程第一邊值問題的差分解法。
往期博客:
沒時間看的同學,可以直接跳到總結。
改進的 Euler 方法
什麼是改進的 Euler 公式,它長什麼樣,如下圖所示:
首先
import numpy as np
定義函數
以下便是我定義的函數:
def euler_pro(x0,f,y0,h):
x=np.arange(x0[0],x0[1]+h,h)
y=np.empty(x.shape)
y[0]=y0
for i in range(int((x0[1]-x0[0])/h)):
y[i+1]=y[i]+h/2*(f(x[i],y[i])+f(x[i+1],y[i]+h*f(x[i],y[i])))
z=np.hstack([x.reshape(-1,1),y.reshape(-1,1)])
print(z[1:])
return y[1:]
參數說明
“x0”指的是自變量的定義域。
“f”可以是y的導數與x、y的函數關係,此時y的導數在等號的左側,x、y在等號的右側。
“f”代表的函數就爲等號的右側。講的不是太清楚,大致理解一下,待會兒看實例運行時就懂了。
“y0”代表初值條件,待會兒看實例運行時就知道是什麼了。
“h”指的是步長。
實例運行
下圖爲給出的實例,我們一一解釋其中我們要的參數。
函數中的“x0”就是圖中的“0<x<=1”。
函數中的“f”就是圖中的“y-2×x/y”。
函數中的“y0”就是圖中的“y(0)=1”。
函數中的“h”就是圖中的“步長h=0.1”。
實際寫入操作如下。
f1=lambda x,y: y-2*x/y
x=np.array([0,1])
y0=1
h=0.1
euler_pro(x,f1,y0,h)
得出以下結果:
[[0.1 1.09590909]
[0.2 1.18409657]
[0.3 1.26620136]
[0.4 1.34336015]
[0.5 1.41640193]
[0.6 1.4859556 ]
[0.7 1.55251409]
[0.8 1.61647478]
[0.9 1.67816636]
[1. 1.7378674 ]]
這個數組代表的含義是:
x=0.1時,y=1.09590909;
x=0.2時,y=1.18409657;
x=0.3時,y=1.26620136;
……
x=1時,y=1.7378674。
注意此初值問題的解析解爲y=(1+2x)0.5, 從下圖(因爲精確度不同,所以數值有點略微不同)可以看出, 數值解 yn與解析解 y(xn) 基本上只保證小數點後兩位數的準確性(h2 = 0.01)。
標準(經典)的四階 Runge-Kutta 法
什麼是標準(經典)的四階 Runge-Kutta 法,它長什麼樣,如下圖所示:
定義函數
以下便是我定義的函數:
def runge_kutta_4(x0,f,y0,h):
x=np.arange(x0[0],x0[1]+h,h)
y=np.empty(x.shape)
y[0]=y0
k1=k2=k3=k4=0
for i in range(int((x0[1]-x0[0])/h)):
k1=f(x[i],y[i])
k2=f(x[i]+h/2,y[i]+h*k1/2)
k3=f(x[i]+h/2,y[i]+h*k2/2)
k4=f(x[i]+h,y[i]+h*k3)
y[i+1]=y[i]+h*(k1+2*k2+2*k3+k4)/6
z=np.hstack([x.reshape(-1,1),y.reshape(-1,1)])
print(z[1:])
return y[1:]
參數說明
參數意義和上一個函數一樣,就不再贅述。
實例運行
運行和上面一樣的實例,不過步長改爲0.2。
f1=lambda x,y: y-2*x/y
x=np.array([0,1])
y0=1
h=0.2
runge_kutta_4(x,f1,y0,h)
得出以下結果:
[[0.2 1.18322929]
[0.4 1.34166693]
[0.6 1.48328146]
[0.8 1.61251404]
[1. 1.73214188]]
這個數組代表的含義是:
x=0.2時,y=1.18322929;
……
x=1時,y=1.73214188。
同上理,此初值問題的解析解爲y=(1+2x)0.5, 從下圖可以看出, 數值解 yn與解析解 y(xn) 能保證小數點後四位數的準確性(h2 = 0.0001)。
顯然在計算量大致相同的情況下, 標準的四階 Runge-Kutta 法比改進的 Euler 方法精確度更高。
線性常微分方程第一邊值問題的差分解法
什麼是線性常微分方程,它長什麼樣,大概如下圖所示:
然後我們要求解的是在定義域[a,b]中,步長爲h時,y在各個點上的取值。
定義函數
以下便是我定義的函數:
def first_yjxxcwf(q,f,x0,y0,h):
a=x0[0]
b=x0[1]
c=y0[0]
d=y0[1]
n=int((b-a)/h)
x=np.arange(a,b+h,h)
an=-(2+q(x[1:-1])*h**2)
if type(an) is np.ndarray:
pass
else:
an=np.full(n-1,an)
d1=h**2*f(x[1])-c
dn=h**2*f(x[-2])-d
dh=h**2*f(x[2:-2])
if type(dh) is np.ndarray:
pass
else:
dh=np.full(n-3,dh)
dh=np.insert(dh,0,d1)
dh=np.append(dh,dn)
A=np.zeros((n-1,n-1))
for i in range(n-1):
A[i,i]=an[i]
for i in range(n-2):
A[i,i+1]=A[i+1,i]=1
z=np.hstack([x[1:-1].reshape(-1,1),np.linalg.inv(A).dot(dh).reshape(-1,1)])
print(z)
return np.linalg.inv(A).dot(dh)
參數說明
“q”指的是圖中的“q(x)”。
“f”指的是圖中的“f(x)”。
“x0”指的是自變量的定義域。
“y0”指的是兩個初值條件。
“h”指的是步長。
實例運行
下圖爲給出的實例,步長h=0.1。
q=lambda x: 1
f=lambda x: x
x=[0,1]
y=[0,1]
h=0.1
first_yjxxcwf(q,f,x,y,h)
得出以下結果:
[[0.1 0.07048938]
[0.2 0.14268365]
[0.3 0.21830476]
[0.4 0.29910891]
[0.5 0.38690416]
[0.6 0.48356844]
[0.7 0.59106841]
[0.8 0.71147907]
[0.9 0.84700451]]
這個數組代表的含義和上面兩個例子一樣。
總結
在計算量大致相同的情況下, 標準的 Runge-Kutta 法比改進的 Euler 方法精確度更高。
三者的代碼總結如下:
# 改進的 Euler 方法
def euler_pro(x0,f,y0,h):
x=np.arange(x0[0],x0[1]+h,h)
y=np.empty(x.shape)
y[0]=y0
for i in range(int((x0[1]-x0[0])/h)):
y[i+1]=y[i]+h/2*(f(x[i],y[i])+f(x[i+1],y[i]+h*f(x[i],y[i])))
z=np.hstack([x.reshape(-1,1),y.reshape(-1,1)])
print(z[1:])
return y[1:]
# 標準的四階 Runge-Kutta 法
def runge_kutta_4(x0,f,y0,h):
x=np.arange(x0[0],x0[1]+h,h)
y=np.empty(x.shape)
y[0]=y0
k1=k2=k3=k4=0
for i in range(int((x0[1]-x0[0])/h)):
k1=f(x[i],y[i])
k2=f(x[i]+h/2,y[i]+h*k1/2)
k3=f(x[i]+h/2,y[i]+h*k2/2)
k4=f(x[i]+h,y[i]+h*k3)
y[i+1]=y[i]+h*(k1+2*k2+2*k3+k4)/6
z=np.hstack([x.reshape(-1,1),y.reshape(-1,1)])
print(z[1:])
return y[1:]
# 線性常微分方程第一邊值問題的差分解法
def first_yjxxcwf(q,f,x0,y0,h):
a=x0[0]
b=x0[1]
c=y0[0]
d=y0[1]
n=int((b-a)/h)
x=np.arange(a,b+h,h)
an=-(2+q(x[1:-1])*h**2)
if type(an) is np.ndarray:
pass
else:
an=np.full(n-1,an)
d1=h**2*f(x[1])-c
dn=h**2*f(x[-2])-d
dh=h**2*f(x[2:-2])
if type(dh) is np.ndarray:
pass
else:
dh=np.full(n-3,dh)
dh=np.insert(dh,0,d1)
dh=np.append(dh,dn)
A=np.zeros((n-1,n-1))
for i in range(n-1):
A[i,i]=an[i]
for i in range(n-2):
A[i,i+1]=A[i+1,i]=1
z=np.hstack([x[1:-1].reshape(-1,1),np.linalg.inv(A).dot(dh).reshape(-1,1)])
print(z)
return np.linalg.inv(A).dot(dh)
如果哪天數學作業涉及微分方程組的初值問題,那我會再編一個向量形式的標準四階龍格-庫塔。因爲好像挺麻煩的樣子,就不順便編了。
如果哪天數學作業涉及二階微分方程的數值解法,那我會再編一個差分法。