數值微分 python代碼實現

老規矩,數學原理什麼的就不寫了。

直接貼代碼和實例演示,以下代碼基於python和numpy。

在這裏,我將用代碼實現改進的歐拉公式標準(經典)的四階龍格-庫塔法(改進的 Euler 公式和標準(經典)的四階 Runge-Kutta 法)和線性常微分方程第一邊值問題的差分解法

往期博客:

線性方程組的迭代法 python代碼實現

函數插值法之牛頓插值法 python代碼實現

數值積分 python代碼實現

python計算信息熵、信息增益和信息增益率

沒時間看的同學,可以直接跳到總結。

改進的 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”指的是步長。

實例運行

下圖爲給出的實例,我們一一解釋其中我們要的參數。

實例1

函數中的“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 法,它長什麼樣,如下圖所示:

標準(經典)的四階 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。

實例2

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)

如果哪天數學作業涉及微分方程組的初值問題,那我會再編一個向量形式的標準四階龍格-庫塔。因爲好像挺麻煩的樣子,就不順便編了。

如果哪天數學作業涉及二階微分方程的數值解法,那我會再編一個差分法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章