DW学习笔记|数学建模task2

本章主要涉及到的知识点有:

  • 微分方程的解法
  • 如何用 Python 解微分方程
  • 偏微分方程及其求解方法
  • 微分方程的基本案例
  • 差分方程的求解
  • 数值计算方法
  • 元胞自动机

2.1 微分方程的理论基础

微分方程是什么?如果你参加过高考,可能在高三备考中遇到过这样的问题:给定函数 f ( x ) f(x) f(x) 及其导数之间的等式,然后分析函数的性质,如单调性、零点等,但没有给出函数的解析式。这时你可能会想,如果能通过这个方程求出函数的通项形式该多好!微分方程的目的就是这样,它通过将函数 f f f 和它的若干阶导数联系起来形成一个方程(组),来求出函数的解析式或函数值随自变量变化的曲线。

2.1.1 函数、导数与微分

微分和导数其实是紧密相关的概念。我们通常将导数理解为函数在某一点处切线的斜率。而微分则描述的是当我们对自变量 d x dx dx 施加一个非常小的增量 d x dx dx 时,函数值相应的变化量与 d x dx dx 之间的关系。当 d x dx dx 非常小的时候,函数的变化量就接近于在该点处切线的变化量 d y dy dy。因此,我们可以用这种方式来理解微分:
d y d x = f ′ ( x ) . \frac{dy}{dx} = f'(x). dxdy=f′(x).

微分实际上描述的是点 M M M 处切线的斜率;导数则描述的是割线 M N MN MN 的斜率。但当 d x dx dx 足够小的时候,切线的斜率和割线的斜率就会非常接近,这就是微分的核心概念。而微分方程,就是描述函数与其导数之间关系的方程。

2.1.2 一阶线性微分方程的解

一阶线性微分方程描述的是怎么一回事呢?它是指形如下方的方程:
d y d x + P ( x ) y = Q ( x ) . \frac{dy}{dx} + P(x)y = Q(x). dxdy+P(x)y=Q(x).

这里的 y y y 是一个未知函数,而 P P P 和 Q Q Q 是已知的函数。我们的目标是找出 y y y 的解,即它的通解形式。为了解这个方程,我们通常会使用分离变量积分法和常数变易法这两种方法。首先,我们尝试解一个特殊情况的齐次方程,即当 Q ( x ) = 0 Q(x) = 0 Q(x)=0 时:
d y d x + P ( x ) y = 0. \frac{dy}{dx} + P(x)y = 0. dxdy+P(x)y=0.

通过变量分离和变形,我们可以得到:
1 y d y = − P ( x ) d x . \frac{1}{y} dy = -P(x) dx. y1dy=−P(x)dx.

接着,对两边进行不定积分,我们可以得到解的通式为
y = C exp ⁡ { − ∫ P ( x )   d x } , y = C \exp\left\{-\int P(x) \, dx\right\}, y=Cexp{−∫P(x)dx},

其中 C C C 是一个常数。但在一般情况下, Q ( x ) Q(x) Q(x) 不一定为 0,所以我们需要将常数 C C C 替换为一个函数 C ( x ) C(x) C(x),然后对 y y y 求导并将其代入原方程中以求得 C ( x ) C(x) C(x) 的通解。这就是所谓的常数变易法。有兴趣的读者可以进一步推导出方程的通解为(其中 C C C 为常数):
y = exp ⁡ { − ∫ P ( x )   d x } [ ∫ Q ( x ) exp ⁡ { ∫ P ( x )   d x }   d x + C ] . y = \exp\left\{-\int P(x) \, dx\right\} \left[\int Q(x) \exp\left\{\int P(x) \, dx\right\} \, dx + C \right]. y=exp{−∫P(x)dx}[∫Q(x)exp{∫P(x)dx}dx+C].

2.1.3 二阶常系数线性微分方程的解

二阶常系数线性微分方程可以表示为:
f ′ ′ ( x ) + p f ′ ( x ) + q f ( x ) = C ( x ) . f''(x) + p f'(x) + q f(x) = C(x). f′′(x)+pf′(x)+qf(x)=C(x).

这个方程关联了二阶导数、一阶导数和函数本身。解决这个方程的一般策略是先考虑对应的齐次方程,即让 C ( x ) C(x) C(x) 为 0:
f ′ ′ ( x ) + p f ′ ( x ) + q f ( x ) = 0. f''(x) + p f'(x) + q f(x) = 0. f′′(x)+pf′(x)+qf(x)=0.

解这种二阶常系数齐次线性微分方程时,我们通常使用特征根法。这个方法的关键是求解特征方程:
r 2 + p r + q = 0. r^2 + pr + q = 0. r2+pr+q=0.

这个齐次方程的解的形式取决于特征方程的根。根据特征方程的不同实根、相同实根、或共轭复根,齐次微分方程的解会有不同的形式:
{ y = C 1 e α 1 x + C 2 e α 2 x , r 1 = α 1 , r 2 = α 2 y = ( C 1 x + C 2 ) e α x , r 1 = r 2 = α y = e α x [ C 1 sin ⁡ ( β x ) + C 2 cos ⁡ ( β x ) ] , r = α ± β i \begin{cases} y = C_1 e^{\alpha_1 x} + C_2 e^{\alpha_2 x}, & r_1 = \alpha_1, r_2 = \alpha_2 \\ y = (C_1 x + C_2) e^{\alpha x}, & r_1 = r_2 = \alpha \\ y = e^{\alpha x} [C_1 \sin(\beta x) + C_2 \cos(\beta x)], & r = \alpha \pm \beta i \end{cases} ⎩ ⎨ ⎧y=C1eα1x+C2eα2x,y=(C1x+C2)eαx,y=eαx[C1sin(βx)+C2cos(βx)],r1=α1,r2=α2r1=r2=αr=α±βi

对于一般的二阶非齐次线性微分方程,我们可以根据右侧 C ( x ) C(x) C(x) 的形式推导出一个特解。非齐次方程的通解等于齐次方程的通解加上非齐次方程的特解。求微分方程的特解有时需要观察法,但幸运的是,存在两种特殊形式:
C ( x ) = P m ( x ) e λ x , C ( x ) = e λ x [ P m cos ⁡ ( ω x ) + Q n ( x ) sin ⁡ ( ω x ) ] . C(x) = P_m(x) e^{\lambda x}, \quad C(x) = e^{\lambda x} [P_m \cos(\omega x) + Q_n(x) \sin(\omega x)]. C(x)=Pm(x)eλx,C(x)=eλx[Pmcos(ωx)+Qn(x)sin(ωx)].

其中, P m ( x ) P_m(x) Pm(x) 是一个 m m m 次多项式, Q n ( x ) Q_n(x) Qn(x) 是一个 n n n 次多项式。这两种形式的特解分别为:
f ( x ) = x k P m ( x ) e λ x , f ( x ) = x k e λ x [ P i cos ⁡ ( ω x ) + Q i ( x ) sin ⁡ ( ω x ) ] . f(x) = x^k P_m(x) e^{\lambda x}, \quad f(x) = x^k e^{\lambda x} [P_i \cos(\omega x) + Q_i(x) \sin(\omega x)]. f(x)=xkPm(x)eλx,f(x)=xkeλx[Picos(ωx)+Qi(x)sin(ωx)].

其中 k k k 的取值取决于特征方程根的个数:如果有两个不同的实根,则 k = 2 k=2 k=2;如果有两个相同的实根,则 k = 1 k=1 k=1;如果没有实根,则 k = 0 k=0 k=0。通过上述形式,我们可以解出二阶线性微分方程。

2.1.4 利用 Python 求函数的微分与积分

在 Python 中,我们可以使用 Numpy 和 SciPy 这两个库来进行函数的微分和积分计算。下面将通过具体示例来说明如何使用这些库来求解函数的微分和积分。 假设我们需要计算函数 f ( x ) = cos ⁡ ( 2 π x ) exp ⁡ ( − x ) + 1.2 f(x) = \cos(2\pi x) \exp(-x) + 1.2 f(x)=cos(2πx)exp(−x)+1.2 在区间 [ 0 , 0.7 ] [0, 0.7] [0,0.7] 上的定积分。我们可以使用 SciPy 库中的 quad 函数来完成这个任务:

python 复制代码
import numpy as np
from scipy.integrate import quad

# 定义函数
def f(x):
    return np.cos(2 * np.pi * x) * np.exp(-x) + 1.2

# 计算定积分
integral, error = quad(f, 0, 0.7)
print(f'定积分的结果是:{integral}')

输出结果:

定积分的结果是:0.7951866427656943

除了使用 SciPy 库中的 quad 函数求解定积分外,我们还可以使用数值积分的方法来近似计算。一种常见的数值积分方法是梯形法则。下面我们将通过一个示例来说明如何使用梯形法则来近似计算函数的定积分。 假设我们需要计算函数 f ( x ) = cos ⁡ ( 2 π x ) exp ⁡ ( − x ) + 1.2 f(x) = \cos(2\pi x) \exp(-x) + 1.2 f(x)=cos(2πx)exp(−x)+1.2 在区间 [ 0 , 0.7 ] [0, 0.7] [0,0.7] 上的定积分。我们可以使用 Numpy 库中的 trapz 函数来完成这个任务:

python 复制代码
import numpy as np

# 定义函数
def f(x):
    return np.cos(2 * np.pi * x) * np.exp(-x) + 1.2

# 定义积分区间
a, b = 0, 0.7

# 定义积分步长
n = 1000
x = np.linspace(a, b, n)
y = f(x)

# 计算定积分
integral = np.trapz(y, x)
print(f'定积分的结果是:{integral}')

输出结果:

定积分的结果是:0.7951866427656956

由此可见,使用数值积分的方法可以获得非常接近的结果。通过这些方法,我们可以方便地在 Python 中求解函数的微分和积分问题,为工程和科学计算提供了有力的工具。

  • 简易且类似的示例

  • 使用Numpy和SciPy库进行函数的微分与积分计算。

  • 定积分示例:

    python 复制代码
    from scipy.integrate import quad
    import numpy as np
    
    def f(x):
        return np.cos(2 * np.pi * x) * np.exp(-x) + 1.2
    
    integral, error = quad(f, 0, 0.7)
    print(f'定积分的结果是:{integral}')
  • 数值微分示例:

    python 复制代码
    import numpy as np
    
    x = np.linspace(0, 2, 100)
    y = x**2
    dydx = np.gradient(y, x)
    derivative_at_1 = dydx[np.argmin(abs(x - 1))]
    print(f'在x=1处的导数值是:{derivative_at_1}')

2.2 偏微分方程的数值求解

偏微分方程(Partial Differential Equations, PDEs)是针对多元函数的方程,在物理学中有着重要的应用。然而,偏微分方程的求解通常比常微分方程复杂。虽然Python没有专用的PDE求解工具包,但可以通过将连续问题离散化来求解。本节将通过一系列物理案例,展示如何用Python求解典型的偏微分方程。

2.2.1 偏微分方程数值解的理论基础

偏微分方程描述多元函数与其偏导数之间的关系,广泛应用于波动力学、热学、电磁学等领域。常见的二元函数的二阶偏微分方程形式为:
A ∂ 2 f ∂ x 2 + 2 B ∂ 2 f ∂ x ∂ y + C ∂ 2 f ∂ y 2 + D ∂ f ∂ x + E ∂ f ∂ y + F f = 0 (2.3.1) A \frac{\partial^2 f}{\partial x^2} + 2B \frac{\partial^2 f}{\partial x \partial y} + C \frac{\partial^2 f}{\partial y^2} + D \frac{\partial f}{\partial x} + E \frac{\partial f}{\partial y} + F f = 0 \tag{2.3.1} A∂x2∂2f+2B∂x∂y∂2f+C∂y2∂2f+D∂x∂f+E∂y∂f+Ff=0(2.3.1)

其中,根据判别式 (\Delta = B^2 - 4AC) 的值,可以将PDE分为三类:

  • 双曲线方程:(\Delta > 0)
  • 抛物线方程:(\Delta = 0)
  • 椭圆方程:(\Delta < 0)

双曲线方程常用于描述振动与波动,椭圆方程用于稳态问题,如静电场、引力场,抛物线方程用于瞬态问题,如热传导和扩散。

2.2.2 偏微分方程数值解的应用案例

2.2.2.1 RC电路放电过程

使用偏微分方程对RC电路建模,分析电容放电过程中电量随时间的变化。RC电路的方程为:
{ I R + Q C = 0 , I = d Q d t (2.3.2) \begin{cases} IR + \frac{Q}{C} = 0, \\ I = \frac{dQ}{dt} \end{cases} \tag{2.3.2} {IR+CQ=0,I=dtdQ(2.3.2)

通过离散化,得到递推关系并求解:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

rc = 2.0
dt = 0.5
n = 1000
t = 0.0
q = 1.0
qt = []
qt0 = []
time = []

for i in range(n):
    t = t + dt
    q1 = q - q*dt/rc
    q = q - 0.5*(q1*dt/rc + q*dt/rc)
    q0 = np.exp(-t/rc)
    qt.append(q)
    qt0.append(q0)
    time.append(t)

plt.plot(time, qt, 'o', label='Euler-Modify')
plt.plot(time, qt0, 'r-', label='Analytical')
plt.xlabel('time')
plt.ylabel('charge')
plt.xlim(0, 20)
plt.ylim(-0.2, 1.0)
plt.legend(loc='upper right')
plt.show()
2.2.2.2 一维热传导方程

一维热传导方程是典型的抛物型二阶偏微分方程:

∂ u ∂ t = λ ∂ 2 u ∂ x 2 (2.3.7) \frac{\partial u}{\partial t} = \lambda \frac{\partial^2 u}{\partial x^2} \tag{2.3.7} ∂t∂u=λ∂x2∂2u(2.3.7)

使用有限差分法求解,并绘制温度随时间和空间的变化图:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

h = 0.1
N = 30
dt = 0.0001
M = 10000
A = dt / (h**2)
U = np.zeros([N+1, M+1])
Space = np.arange(0, (N+1)*h, h)

for k in np.arange(0, M+1):
    U[0, k] = 0.0
    U[N, k] = 0.0

for i in np.arange(0, N):
    U[i, 0] = 4 * i * h * (3 - i * h)

for k in np.arange(0, M):
    for i in np.arange(1, N):
        U[i, k+1] = A * U[i+1, k] + (1 - 2 * A) * U[i, k] + A * U[i-1, k]

plt.plot(Space, U[:, 0], 'g-', label='t=0')
plt.plot(Space, U[:, 3000], 'b-', label='t=3/10')
plt.plot(Space, U[:, 6000], 'k-', label='t=6/10')
plt.plot(Space, U[:, 9000], 'r-', label='t=9/10')
plt.plot(Space, U[:, 10000], 'y-', label='t=1')
plt.xlabel('x')
plt.ylabel('u(x,t)')
plt.xlim(0, 3)
plt.ylim(-2, 10)
plt.legend(loc='upper right')
plt.show()
2.2.2.3 平流方程

一维平流方程描述某一物理量的平流作用:

∂ u ∂ t + v ∂ u ∂ x = 0 (2.3.11) \frac{\partial u}{\partial t} + v \frac{\partial u}{\partial x} = 0 \tag{2.3.11} ∂t∂u+v∂x∂u=0(2.3.11)

使用一阶迎风格式的差分方法求解,并绘制解的变化图:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

def funcUx_0(x, p):
    u0 = np.sin(2 * (x - p)**2)
    return u0

v1 = 1.0
p = 0.25
tc = 0
te = 1.0
xa = 0.0
xb = np.pi
dt = 0.02
nNodes = 100

nsteps = round(te / dt)
dx = (xb - xa) / nNodes
x = np.arange(xa - dx, xb + 2 * dx, dx)
ux_0 = funcUx_0(x, p)
u = ux_0.copy()
ujp = ux_0.copy()

for i in range(nsteps):
    plt.clf()
    for j in range(nNodes + 2):
        ujp[j] = u[j] - (v1 * dt / dx) * (u[j] - u[j - 1])
    u = ujp.copy()
    u[0] = u[nNodes + 1]
    u[nNodes + 2] = u[1]
    tc += dt

plt.plot(x, u, 'b-', label="v1= 1.0")
plt.xlabel("x")
plt.ylabel("U(x)")
plt.legend(loc=(0.05, 0.05))
plt.show()
2.2.2.4 波动方程

二维波动方程的初边值问题描述波的传播:

∂ 2 u ∂ t 2 = c 2 ( ∂ 2 u ∂ x 2 + ∂ 2 u ∂ y 2 ) (2.3.14) \frac{\partial^2 u}{\partial t^2} = c^2 \left( \frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2} \right) \tag{2.3.14} ∂t2∂2u=c2(∂x2∂2u+∂y2∂2u)(2.3.14)

使用有限差分法求解波动方程,并绘制波的传播图:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

c = 1.0
tc, te = 0.0, 1.0
xa, xb = 0.0, 1.0
ya, yb = 0.0, 1.0

c2 = c * c
dt = 0.01
dx = dy = 0.02
tNodes = round(te / dt)
xNodes = round((xb - xa) / dx)
yNodes = round((yb - ya) / dy)
tZone = np.arange(0, (tNodes + 1) * dt, dt)
xZone = np.arange(xa, xb, dx)
yZone = np.arange(ya, yb, dy)
xx, yy = np.meshgrid(xZone, yZone)
r = 4 * c2 * dt * dt / (dx * dx + dy * dy)
rx = c * c * dt ** 2 / dx ** 2
ry = c * c * dt ** 2 / dy ** 2

U = np.zeros([tNodes + 1, xNodes + 1, yNodes + 1])
U[0] = np.sin(6 * np.pi * xx) + np.cos(4 * np.pi * yy)
U[1] = np.sin(6 * np.pi * xx) + np.cos(4 * np.pi * yy)

fig = plt.figure(figsize=(8, 6))
ax1 = fig.add_subplot(111, projection='3d')
surf = ax1.plot_surface(xx, yy, U[

0, :, :], rstride=2, cstride=2, cmap=plt.cm.coolwarm)

for k in range(2, tNodes + 1):
    for i in range(1, xNodes):
        for j in range(1, yNodes):
            U[k, i, j] = rx * (U[k - 1, i - 1, j] + U[k - 1, i + 1, j]) + ry * (U[k - 1, i, j - 1] + U[k - 1, i, j + 1]) + 2 * (1 - rx - ry) * U[k - 1, i, j] - U[k - 2, i, j]

surf = ax1.plot_surface(xx, yy, U[k, :, :], rstride=2, cstride=2, cmap='rainbow')
ax1.set_xlim3d(0, 1.0)
ax1.set_ylim3d(0, 1.0)
ax1.set_zlim3d(-2, 2)
ax1.set_title("2D wave equation (t= %.2f)" % (k * dt))
ax1.set_xlabel("x")
ax1.set_ylabel("y")
plt.show()
2.2.2.5 二维热传导方程

二维热传导方程的初边值问题描述热量在平板中的传导:

∂ u ∂ t = λ ( ∂ 2 u ∂ x 2 + ∂ 2 u ∂ y 2 ) + q v (2.3.17) \frac{\partial u}{\partial t} = \lambda \left( \frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2} \right) + q_v \tag{2.3.17} ∂t∂u=λ(∂x2∂2u+∂y2∂2u)+qv(2.3.17)

使用有限差分法求解,并绘制热传导过程中的温度分布图:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

def showcontourf(zMat, xyRange, tNow):
    x = np.linspace(xyRange[0], xyRange[1], zMat.shape[1])
    y = np.linspace(xyRange[2], xyRange[3], zMat.shape[0])
    xx, yy = np.meshgrid(x, y)
    zMax = np.max(zMat)
    yMax, xMax = np.where(zMat == zMax)[0][0], np.where(zMat == zMax)[1][0]
    levels = np.arange(0, 100, 1)
    showText = "time = {:.1f} s\nhotpoint = {:.1f} C".format(tNow, zMax)
    plt.plot(x[xMax], y[yMax], 'ro')
    plt.contourf(xx, yy, zMat, 100, cmap=plt.cm.get_cmap('jet'), origin='lower', levels=levels)
    plt.annotate(showText, xy=(x[xMax], y[yMax]), xytext=(x[xMax], y[yMax]), fontsize=10)
    plt.colorbar()
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('Temperature distribution of the plate')
    plt.show()

uIni = 25
uBound = 25.0
c = 1.0
qv = 50.0
x_0, y0 = 0.0, 3.0
vx, vy = 2.0, 1.0
tc, te = 0.0, 5.0
xa, xb = 0.0, 16.0
ya, yb = 0.0, 12.0

dt = 0.002
dx = dy = 0.1
tNodes = round(te / dt)
xNodes = round((xb - xa) / dx)
yNodes = round((yb - ya) / dy)
xyRange = np.array([xa, xb, ya, yb])
xZone = np.linspace(xa, xb, xNodes + 1)
yZone = np.linspace(ya, yb, yNodes + 1)
xx, yy = np.meshgrid(xZone, yZone)

A = (-2) * np.eye(xNodes + 1, k=0) + (1) * np.eye(xNodes + 1, k=-1) + (1) * np.eye(xNodes + 1, k=1)
B = (-2) * np.eye(yNodes + 1, k=0) + (1) * np.eye(yNodes + 1, k=-1) + (1) * np.eye(yNodes + 1, k=1)
rx, ry, ft = c * dt / (dx * dx), c * dt / (dy * dy), qv * dt

U = uIni * np.ones((yNodes + 1, xNodes + 1))

for k in range(tNodes + 1):
    t = k * dt
    xt, yt = x_0 + vx * t, y0 + vy * t
    Qv = qv * np.exp(-((xx - xt)**2 + (yy - yt)**2))
    U[:, 0] = U[:, -1] = uBound
    U[0, :] = U[-1, :] = uBound
    U = U + rx * np.dot(U, A) + ry * np.dot(B, U) + Qv * dt
    if k % 100 == 0:
        print('t={:.2f}s\tTmax={:.1f}  Tmin={:.1f}'.format(t, np.max(U), np.min(U)))

showcontourf(U, xyRange, k * dt)
2.2.2.6 二维椭圆方程

二维Poisson方程的差分表达式和数值求解方法:

∂ 2 u ∂ x 2 + ∂ 2 u ∂ y 2 = f ( x , y ) (2.3.21) \frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2} = f(x, y) \tag{2.3.21} ∂x2∂2u+∂y2∂2u=f(x,y)(2.3.21)

采用迭代松弛法递推求得二维椭圆方程的数值解:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

xa, xb = 0.0, 1.0
ya, yb = 0.0, 1.0
h = 0.01
w = 0.5
nodes = round((xb - xa) / h)

u = np.zeros((nodes + 1, nodes + 1))
for i in range(nodes + 1):
    u[i, 0] = 1.0 + np.sin(0.5 * (i - 50) / np.pi)
    u[i, -1] = -1.0 + 0.5 * np.sin((i - 50) / np.pi)
    u[0, i] = -1.0 + 0.5 * np.sin((i - 50) / np.pi)
    u[-1, i] = 1.0 + np.sin(0.5 * (50 - i) / np.pi)

for iter in range(100):
    for i in range(1, nodes):
        for j in range(1, nodes):
            u[i, j] = w / 4 * (u[i - 1, j] + u[i + 1, j] + u[i, j - 1] + u[i, j + 1]) + (1 - w) * u[i, j]

x = np.linspace(0, 1, nodes + 1)
y = np.linspace(0, 1, nodes + 1)
xx, yy = np.meshgrid(x, y)
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(xx, yy, u, cmap=plt.get_cmap('rainbow'))
fig.colorbar(surf, shrink=0.5)
ax.set_xlim3d(0, 1.0)
ax.set_ylim3d(0, 1.0)
ax.set_zlim3d(-2, 2.5)
ax.set_title("2D elliptic partial differential equation")
ax.set_xlabel("X")
ax.set_ylabel("Y")
plt.show()

2.2.3 Sympy中的偏微分方程求解

虽然Sympy库不支持复杂的二阶偏微分方程的解析求解,但对于一些简单的PDE可以使用pdsolve函数。示例如下:

python 复制代码
from sympy.solvers.pde import pdsolve
from sympy import Function, exp
from sympy.abc import x, y

f = Function('f')
eq = -2 * f(x, y).diff(x) + 4 * f(x, y).diff(y) + 5 * f(x, y) - exp(x + 3 * y)
pdsolve(eq)

结果为:

Eq ( f ( x , y ) , ( F ( 4 x + 2 y ) exp ⁡ ( x 2 ) + exp ⁡ ( x + 4 y ) / 15 ) exp ⁡ ( − y ) ) \text{Eq}(f(x, y), (F(4x + 2y) \exp(\frac{x}{2}) + \exp(x + 4y) / 15) \exp(-y)) Eq(f(x,y),(F(4x+2y)exp(2x)+exp(x+4y)/15)exp(−y))

通过上述案例和代码,我们学习了如何利用数值方法和Python库求解偏微分方程。尽管数值方法有其局限性,但在许多工程和科学问题中,这些方法是不可或缺的工具。

2.3 差分方程与元胞自动机

差分方程描述的是函数在离散点上的变化规律。与微分方程不同的是,差分方程不再是连续变量的导数,而是离散变量之间的差值。差分方程广泛应用于数值计算和计算机仿真,尤其是在工程和物理学中。

2.3.1 差分方程的基本解法

差分方程通常表示为:
y n + 1 − y n = f ( n , y n ) . y_{n+1} - y_n = f(n, y_n). yn+1−yn=f(n,yn).

这种形式称为一阶差分方程。求解这种方程的过程可以看作是迭代求解,通过逐步计算每一个离散点上的函数值来获得整个函数的值序列。

2.3.2 数值方法与数值稳定性

数值方法是解决实际工程中微分方程和差分方程的主要工具。在数值求解中,我们通常会遇到数值稳定性的问题,即数值解是否会随着迭代的进行而逐渐偏离真实解。为了确保数值解的稳定性,我们通常需要选择合适的数值方法和步长。

2.3.3 元胞自动机

元胞自动机是一种离散模型,用于模拟复杂系统的演化。它由一组规则定义,用于确定每个元胞在下一个时间步的状态。元胞自动机广泛应用于物理学、化学、生物学和计算机科学等领域。元胞自动机的基本思想是通过简单的局部规则来模拟复杂的整体行为。

2.4 实战案例

在这一节,我们将结合实际工程案例,展示微分方程和差分方程在实际问题中的应用。通过这些案例,你将更好地理解这些数学工具的实际意义,并掌握在实际工程中应用它们的方法。

案例1:人口增长模型

假设我们需要模拟一个城市的人口增长情况。我们可以使用如下的微分方程来描述人口的增长过程:
d P d t = r P ( 1 − P K ) , \frac{dP}{dt} = rP(1 - \frac{P}{K}), dtdP=rP(1−KP),

其中, P P P 是人口数量, r r r 是增长率, K K K 是环境承载能力。这个方程称为 Logistic 增长方程。我们可以使用 Python 来模拟这个过程:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint

# 定义微分方程
def logistic_growth(P, t, r, K):
    return r * P * (1 - P / K)

# 参数设置
r = 0.1
K = 1000
P0 = 10
t = np.linspace(0, 100, 200)

# 求解微分方程
P = odeint(logistic_growth, P0, t, args=(r, K))

# 绘制结果
plt.plot(t, P)
plt.xlabel('时间')
plt.ylabel('人口数量')
plt.title('人口增长模型')
plt.show()

输出结果将显示人口数量随时间的变化曲线,从中可以观察到人口增长的动态过程。

案例2:热传导问题

假设我们需要模拟一个一维热传导过程。我们可以使用如下的偏微分方程来描述热传导过程:
∂ T ∂ t = α ∂ 2 T ∂ x 2 , \frac{\partial T}{\partial t} = \alpha \frac{\partial^2 T}{\partial x^2}, ∂t∂T=α∂x2∂2T,

其中, T T T 是温度, α \alpha α 是热扩散系数。我们可以使用差分方法来求解这个偏微分方程:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# 参数设置
alpha = 0.01
L = 1.0
dx = 0.01
dt = 0.0001
nx = int(L / dx) + 1
nt = 1000

# 初始条件
T = np.zeros(nx)
T[int(nx/4):int(3*nx/4)] = 1.0

# 差分法求解
for n in range(nt):
    T_new = T.copy()
    for i in range(1, nx-1):
        T_new[i] = T[i] + alpha * dt / dx**2 * (T[i-1] - 2*T[i] + T[i+1])
    T = T_new

# 绘制结果
x = np.linspace(0, L, nx)
plt.plot(x, T)
plt.xlabel('位置')
plt.ylabel('温度')
plt.title('热传导问题')
plt.show()

输出结果将显示温度分布随时间的变化过程,从中可以观察到热传导的动态过程。

2.5 数值计算方法与微分方程求解

在使用Python求解微分方程的数值解或函数极值时,其背后的计算方法往往与MATLAB等软件有所不同。为了更好地理解这些数值计算方法及其在微分方程求解中的应用,以下将详细探讨几种经典的数值计算方法,并结合具体案例进行说明。

2.5.1 Python通过什么求数值解

Python依赖于丰富的库来进行数值计算,其中包括NumPy、SciPy和SymPy等。这些库提供了大量的数学函数和算法,用于处理线性代数、微积分、优化问题等。例如:

  • NumPy:用于高效的数组操作和基本的线性代数运算。
  • SciPy:提供了数值积分、优化、线性代数、统计等高级功能。
  • SymPy:用于符号计算,可以处理解析解和符号微积分等。

这些工具的组合使得Python在数值计算方面非常强大和灵活。

2.5.2 梯度下降法

梯度下降法是一种用于优化问题的常用算法,特别是在机器学习和深度学习中。其基本思想是从一个初始点出发,沿着函数梯度的反方向逐步更新变量值,直到找到函数的最小值点。

梯度下降法的迭代公式为:

[ x_{t+1} \leftarrow x_t - \alpha \cdot \text{grad}(f) ]

其中,(\alpha)是学习率,控制步长大小。

以下是使用Python实现梯度下降法求解函数极值的示例代码:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# 定义目标函数
def func(x):
    return x**2 + 2*x + 5

# 梯度下降法
x_iter = 1  # 初始值
learning_rate = 0.06  # 学习率
iterations = 0  # 迭代次数

while True:
    iterations += 1
    y_last = func(x_iter)
    x_iter -= learning_rate * (2 * x_iter + 2)
    y_next = func(x_iter)
    plt.scatter(x_iter, y_last)
    if abs(y_next - y_last) < 1e-10:
        break

print('最小值点x=', x_iter, '最小值y=', y_next, '迭代次数=', iterations)

# 绘制函数曲线
x = np.linspace(-4, 6, 100)
y = func(x)
plt.plot(x, y, '--')
plt.show()

最终结果显示梯度下降法能够快速逼近函数的极小值点。

2.5.3 Newton法

Newton法(也称为切线法)是一种寻找函数零点的有效方法。其基本思想是从一个初始估计值开始,通过迭代找到零点或达到预定精度。

Newton法的迭代公式为:

[ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} ]

以下是使用Python实现Newton法的示例代码:

python 复制代码
import numpy as np

# 定义目标函数及其导数
def f(x):
    return x**3 - x - 1

def f_prime(x):
    return 3*x**2 - 1

# Newton法求解
def newton_method(x0, tol=1e-9, max_iter=100):
    x = x0
    for i in range(max_iter):
        x_new = x - f(x) / f_prime(x)
        if abs(f(x_new)) < tol:
            break
        x = x_new
    return x, i

# 初始值
x0 = 1.5
root, iterations = newton_method(x0)

print(f"零点x={root}, 迭代次数={iterations}")

Newton法能够快速收敛到函数的零点,但需要注意初始值的选择对收敛速度和结果的影响。

2.5.4 Euler法与Runge-Kutta法

在数值求解微分方程时,常用的两种方法是Euler法和Runge-Kutta法。它们都基于将微分方程离散化,以近似其解。

  • Euler法:通过使用当前点的斜率来估计下一点的值。
  • Runge-Kutta法:通过在一个步长内使用多个斜率的平均值来估计下一点的值。

以下是使用Python实现四阶Runge-Kutta法求解微分方程的示例代码:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

def runge_kutta(y, x, dx, f):
    k1 = dx * f(y, x)
    k2 = dx * f(y + 0.5 * k1, x + 0.5 * dx)
    k3 = dx * f(y + 0.5 * k2, x + 0.5 * dx)
    k4 = dx * f(y + k3, x + dx)
    return y + (k1 + 2 * k2 + 2 * k3 + k4) / 6.

def func(y, t):
    return  1 / (1 + t**2) - 2 * y**2

t = 0
y = 0
dt = 0.1
ys, ts = [0], [0]

while t <= 10:
    y = runge_kutta(y, t, dt, func)
    t += dt
    ys.append(y)
    ts.append(t)

plt.plot(ts, ys, label='runge_kutta')
plt.legend()
plt.show()

Runge-Kutta法能够提供更高精度的数值解,适用于各种微分方程的求解。

2.5.5 Crank-Nicolson法在热传导问题中的应用

Crank-Nicolson法是一种常用于求解热传导方程和类似偏微分方程的时间推进技术。它结合了显式和隐式方法的优点,既稳定又准确。

以下是使用Python实现Crank-Nicolson法求解一维热传导方程的示例代码:

python 复制代码
import numpy as np
import pandas as pd

def crank_nicolson(T, m, n, r):
    A = np.eye(m-1) * (1 + r) - np.eye(m-1, k=1) * (r/2) - np.eye(m-1, k=-1) * (r/2)
    C = np.eye(m-1) * (1 - r) + np.eye(m-1, k=1) * (r/2) + np.eye(m-1, k=-1) * (r/2)

    for k in range(n):
        F = C @ T[k, 1:m]
        F[0] += (r/2) * T[k+1, 0]
        F[-1] += (r/2) * T[k+1, m]
        T[k+1, 1:m] = np.linalg.solve(A, F)

    return T

# 初始化参数
a = 1.0
x_max, t_max = 1.0, 1.0
dx, dt = 0.1, 0.1
m, n = int(x_max / dx), int(t_max / dt)
r = a * dt / dx**2

T = np.zeros((n+1, m+1))
T[:, 0] = np.exp(np.arange(0, t_max+dt, dt))
T[:, -1] = np.exp(np.arange(0, t_max+dt, dt) + 1)
T[0, :] = np.exp(np.arange(0, x_max+dx, dx))

T_cn = crank_nicolson(T.copy(), m, n, r)

# 输出结果
pd.DataFrame(T_cn).to_excel('crank_nicolson.xlsx')

Crank-Nicolson法能够有效地处理时间步长较大的问题,具有较高的计算稳定性和精度。

通过这些数值计算方法的介绍和具体实现,我们可以更好地理解Python在求解微分方程中的应用,并通过调整算法参数和优化代码提高计算效率和准确性。

2.6 小结

本章主要介绍了微分方程与动力系统的基本理论和应用方法。我们讨论了微分方程的基本概念、求解方法和数值方法,并结合实际案例展示了微分方程和差分方程在工程中的应用。希望通过本章的学习,你能更好地理解微分方程的基本原理,并掌握在实际工程中应用这些数学工具的方法。

相关推荐
Octopus20777 分钟前
【C++】读取数量不定的输入数据
开发语言·c++·笔记·学习
Lostgreen12 分钟前
计算机网络----基本概念
网络·笔记·学习·计算机网络
孤邑26 分钟前
【Linux】网络通信
linux·服务器·网络·笔记·学习
Hacker_xingchen27 分钟前
Web 学习笔记 - 网络安全
前端·笔记·学习
PaLu-LI31 分钟前
ORB-SLAM2源码学习:Initializer.cc:Initializer::CheckFundamental地图初始化——检查基础矩阵F并评分
c++·opencv·学习·线性代数·ubuntu·计算机视觉·矩阵
9毫米的幻想36 分钟前
【Linux系统】—— 基本指令(四)
linux·c语言·c++·学习
青い月の魔女1 小时前
数据结构初阶---复杂度
c语言·数据结构·笔记·学习·算法
汤姆和杰瑞在瑞士吃糯米粑粑1 小时前
【优先算法学习】双指针--结合题目讲解学习
c++·学习·算法
坚硬果壳_1 小时前
《硬件架构的艺术》笔记(九):电磁兼容性能设计指南
笔记·硬件架构
分才2 小时前
用天翼云搭建一个HivisionIDPhoto证件照处理网站
人工智能·学习·html5