关键词搜索

源码搜索 ×
×

Python小白的数学建模课-10.微分方程边值问题

发布2021-08-28浏览971次

详情内容

1. 常微分方程的边值问题(BVP)

1.1 基本概念

微分方程是指含有未知函数及其导数的关系式。

微分方程是描述系统的状态随时间和空间演化的数学工具。物理中许多涉及变力的运动学、动力学问题,如空气的阻力为速度函数的落体运动等问题,很多可以用微分方程求解。微分方程在化学、工程学、经济学和人口统计等领域也有广泛应用。

微分方程分为初值问题和边值问题。初值问题是已知微分方程的初始条件,即自变量为零时的函数值,一般可以用欧拉法、龙哥库塔法来求解。边值问题则是已知微分方程的边界条件,即自变量在边界点时的函数值。

边值问题的提出和发展,与流体力学、材料力学、波动力学以及核物理学等密切相关,并且在现代控制理论等学科中有重要应用。例如,力学问题中的悬链线问题、弹簧振动问题,热学问题中的导热细杆问题、细杆端点冷却问题,流体力学问题、结构强度问题。

上节我们介绍的常微分方程,主要是微分方程的初值问题。本节介绍二阶常微分方程边值问题的建模与求解。

1.2 常微分方程边值问题的数学模型

只含边界条件作为定解条件的常微分方程求解问题,称为常微分方程的边值问题(boundary value problem)。

一般形式的二阶常微分方程边值问题:

y ′′=f(x,y,y ′),a<x<by ″=f(x,y,y ′),a<x<b

有三种情况的边界条件:

(1)第一类边界条件(两点边值问题):

y(a)=ya,y(b)=yby(a)=ya,y(b)=yb

(2)第二类边界条件:

y ′(a)=ya,y ′(b)=yby ′(a)=ya,y ′(b)=yb

(3)第三类边界条件:

{y ′(a)−a0 y(a)=a1y ′(b)−b0 y(b)=b1{y ′(a)−a0 y(a)=a1y ′(b)−b0 y(b)=b1

其中:a0≥0,b0≥0,a0+b0>0a0≥0,b0≥0,a0+b0>0

1.3 常微分方程边值问题的数值解法

简单介绍求解常微分方程边值问题的数值解法,常用方法有:打靶算法、有限差分法和有限元法。打靶算法把边值问题转化为初值问题求解,是根据边界条件反复迭代调整初始点的斜率,使初值问题的数值解在边界上“命中”问题的边值条件。有限差分法把空间离散为网格节点,用差商代替微商,将微分方程离散化为线性或非线性方程组来求解。 有限元法将微分方程离散化,有限元就是指近似连续域的离散单元,对每一单元假定一个近似解,然后推导求解域满足条件,从而得到问题的解。

按照本系列“编程方案”的概念,不涉及这些算法的具体内容,只探讨如何使用 Python 的工具包、库函数,零基础求解微分方程模型边值问题。我们的选择还是 python教程 常用工具包三剑客:Scipy、Numpy 和 Matplotlib。


2. SciPy 求解常微分方程边值问题

2.1 BVP 问题的标准形式

Scipy 用 solve_bvp() 函数求解常微分方程的边值问题,定义微分方程的标准形式为:

{y ′=f(x,y),a<x<bg(y(a),y(b)=0){y ′=f(x,y),a<x<bg(y(a),y(b)=0)

因此要将第一类边界条件 y(a)=ya,y(b)=yby(a)=ya,y(b)=yb 改写为:

{y(a)−ya=0y(b)−yb=0{y(a)−ya=0y(b)−yb=0

2.2 scipy.integrate.solve_bvp() 函数

**scipy.integrate.solve_bvp() **是求解微分方程边值问题的具体方法,可以求解一阶微分方程(组)的两点边值问题(第一类边界条件)。在 odeint 函数内部使用 FORTRAN 库 odepack 中的 lsoda,可以求解一阶刚性系统和非刚性系统的初值问题。官网介绍详见: scipy.integrate.solve_bvp — SciPy v1.7.0 Manual 。

scipy.integrate.solve_bvp(fun, bc, x, y, p=None, S=None, fun_jac=None, bc_jac=None, tol=0.001, max_nodes=1000, verbose=0, bc_tol=None)

solve_bvp 的主要参数:

求解标准形式的微分方程(组)主要使用前 4 个参数:

  • func: callable fun(x, y, ..)   导数函数 f(y,x)f(y,x) , y 在 x 处的导数,以函数的形式表示。可以带有参数 p。
  • bc: callable bc(ya, yb, ..)   边界条件,y 在两点边界的函数,以函数的形式表示。可以带有参数 p。
  • x: array:  初始网格的序列,shape (m,)。必须是单调递增的实数序列,起止于两点边界值 xa,xb。
  • y: array:  网格节点处函数值的初值,shape (n,m),第 i 列对应于 x[i]。
  • p: array:  可选项,向导数函数 func、边界条件函数 bc 传递参数。

其它参数用于控制求解算法的参数,一般情况可以忽略。

solve_bvp 的主要返回值:

  • sol: PPoly   通过 PPoly (如三次连续样条函数)插值求出网格节点处的 y 值。
  • x: array   数组,形状为 (m,),最终输出的网格节点。
  • y: array   二维数组,形状为 (n,m),输出的网格节点处的 y 值。
  • yp: array   二维数组,形状为 (n,m),输出的网格节点处的 y' 值。

3. 实例 1:一阶常微分方程边值问题

3.1 例题 1:一阶常微分方程边值问题

求常微分方程边值问题的数值解。

⎧⎩⎨⎪⎪y ′′+|y|=0y(x=0)=0.5y(x=4)=−1.5{y ″+|y|=0y(x=0)=0.5y(x=4)=−1.5

引入变量 y0=y,y1=y ′y0=y,y1=y ′,通过变量替换就把原方程化为如下的标准形式的微分方程组:

⎧⎩⎨⎪⎪⎪⎪⎪⎪y′0=y1y′1=−|y0|y(x=0)−0.5=0y(x=4)+1.5=0{y0′=y1y1′=−|y0|y(x=0)−0.5=0y(x=4)+1.5=0

这样就可以用 solve_bvp() 求解该常微分方程的边值问题。

3.2 常微分方程的编程步骤

以该题为例讲解scipy.integrate.solve_bvp 求解常微分方程边值问题的步骤:

  1. 导入 scipy、numpy、matplotlib 包;

  2. 定义导数函数 dydx(x,y)

    注意本问题中 y 表示向量,记为 y=[y0,y1]y=[y0,y1],导数定义函数 dydx(x,y) 编程如下:

  1. # 导数函数,计算导数 dY/dx
  2. def dydx(x, y):
  3. dy0 = y[1]
  4. dy1 = -abs(y[0])
  5. return np.vstack((dy0, dy1))
  1. 定义边界条件函数 boundCond(ya,yb)

    本问题中边界条件为常数,具体编程如下。注意对照 3.1中的边值条件,没有为什么,函数就是这么定义的。

  1. # 计算 边界条件
  2. def boundCond(ya, yb):
  3. fa = 0.5 # 边界条件 y(xa=0) = 0.5
  4. fb = -1.5 # 边界条件 y(xb=4) = -1.5
  5. return np.array([ya[0]-fa,yb[0]-fb])
  1. 设置 x、y 的初值

  2. 调用 solve_bvp() 求解常微分方程在区间 [xa,xb] 的数值解

  3. 由 solve_bvp() 的返回值 sol,获得网格节点的处的 y值。

3.3 Python 例程

  1. # mathmodel11_v1.py
  2. # Demo10 of mathematical modeling algorithm
  3. # Solving ordinary differential equations (boundary value problem) with scipy.
  4. from scipy.integrate import odeint, solve_bvp
  5. import numpy as np
  6. import matplotlib.pyplot as plt
  7. # 1. 求解微分方程组边值问题,DEMO
  8. # y'' + abs(y) = 0, y(0)=0.5, y(4)=-1.5
  9. # 导数函数,计算导数 dY/dx
  10. def dydx(x, y):
  11. dy0 = y[1]
  12. dy1 = -abs(y[0])
  13. return np.vstack((dy0, dy1))
  14. # 计算 边界条件
  15. def boundCond(ya, yb):
  16. fa = 0.5 # 边界条件 y(xa=0) = 0.5
  17. fb = -1.5 # 边界条件 y(xb=4) = -1.5
  18. return np.array([ya[0]-fa,yb[0]-fb])
  19. xa, xb = 0, 4 # 边界点 (xa,xb)
  20. # fa, fb = 0.5, -1.5 # 边界点的 y值
  21. xini = np.linspace(xa, xb, 11) # 确定 x 的初值
  22. yini = np.zeros((2, xini.size)) # 确定 y 的初值
  23. res = solve_bvp(dydx, boundCond, xini, yini) # 求解 BVP
  24. xSol = np.linspace(xa, xb, 100) # 输出的网格节点
  25. ySol = res.sol(xSol)[0] # 网格节点处的 y 值
  26. plt.plot(xSol, ySol, label='y')
  27. # plt.legend()
  28. plt.xlabel("x")
  29. plt.ylabel("y")
  30. plt.title("scipy.integrate.solve_bvp")
  31. plt.show()

3.4 Python 例程运行结果


4. 实例 2:水滴横截面的形状

4.1 例题 2:水滴横截面形状问题

水平面上的水滴横截面形状,可以用如下的微分方程描述:

⎧⎩⎨d2hdx2+[1−h]∗[1+(dhdx)2]3https://cdn.jxasp.com:9143/image/2=0h(x=−1)=h(x=1)=0{d2hdx2+[1−h]∗[1+(dhdx)2]3https://cdn.jxasp.com:9143/image/2=0h(x=−1)=h(x=1)=0

引入变量 h0=h,h1=h ′h0=h,h1=h ′,通过变量替换就把原方程化为如下的标准形式的微分方程组:

⎧⎩⎨⎪⎪h′0=h1h′1=(h0−1)∗[1+h21]3https://cdn.jxasp.com:9143/image/2h0(x=−1)=h0(x=1)=0{h0′=h1h1′=(h0−1)∗[1+h12]3https://cdn.jxasp.com:9143/image/2h0(x=−1)=h0(x=1)=0

这样就可以用 solve_bvp() 求解该常微分方程的边值问题。

注:本问题来自 司守奎 等,数学建模算法与应用(第2版),国防工业出版社,2015

4.2 Python 例程:水滴横截面形状问题

  1. # mathmodel11_v1.py
  2. # Demo10 of mathematical modeling algorithm
  3. # Solving ordinary differential equations (boundary value problem) with scipy.
  4. from scipy.integrate import odeint, solve_bvp
  5. import numpy as np
  6. import matplotlib.pyplot as plt
  7. # 3. 求解微分方程边值问题,水滴的横截面
  8. # 导数函数,计算 h=[h0,h1] 点的导数 dh/dx
  9. def dhdx(x,h):
  10. # 计算 dh0/dx, dh1/dx 的值
  11. dh0 = h[1] # 计算 dh0/dx
  12. dh1 = (h[0]-1)*(1+h[1]*h[1])**1.5 # 计算 dh1/dx
  13. return np.vstack((dh0, dh1))
  14. # 计算 边界条件
  15. def boundCond(ha,hb):
  16. # ha = 0 # 边界条件:h0(x=-1) = 0
  17. # hb = 0 # 边界条件:h0(x=1) = 0
  18. return np.array([ha[0],hb[0]])
  19. xa, xb = -1, 1 # 边界点 (xa=0, xb=1)
  20. xini = np.linspace(xa, xb, 11) # 设置 x 的初值
  21. hini = np.zeros((2, xini.size)) # 设置 h 的初值
  22. res = solve_bvp(dhdx, boundCond, xini, hini) # 求解 BVP
  23. # scipy.integrate.solve_bvp(fun, bc, x, y,..)
  24. # fun(x, y, ..), 导数函数 f(y,x),y在 x 处的导数。
  25. # bc(ya, yb, ..), 边界条件,y 在两点边界的函数。
  26. # x: shape (m),初始网格的序列,起止于两点边界值 xa,xb。
  27. # y: shape (n,m),网格节点处函数值的初值,第 i 列对应于 x[i]。
  28. xSol = np.linspace(xa, xb, 100) # 输出的网格节点
  29. hSol = res.sol(xSol)[0] # 网格节点处的 h 值
  30. plt.plot(xSol, hSol, label='h(x)')
  31. plt.xlabel("x")
  32. plt.ylabel("h(x)")
  33. plt.axis([-1, 1, 0, 1])
  34. plt.title("Cross section of water drop by BVP xupt")
  35. plt.show()

4.3 Python 例程运行结果


5. 实例 3:带有未知参数的微分方程边值问题

5.1 例题 3:Mathieu 方程的特征函数

Mathieu 在研究椭圆形膜的边界值问题时,导出了一个二阶常微分方程,其形式为:

d2ydx2+[λ−2q cos(2x)] y=0d2ydx2+[λ−2q cos(2x)] y=0

用这种形式的数学方程可以描述自然中的物理现象,包括振动椭圆鼓、四极质谱仪和四极离子阱、周期介质中的波动、强制振荡器参数共振现象、广义相对论中的平面波解决方案、量子摆哈密顿函数的本征函数、旋转电偶极子的斯塔克效应。

式中 λ、qλ、q 是两个实参数,方程的系数是以 ππ 或 2π2π 为周期的,但只有在 λ、qλ、q 满足一定关系时 Mathieu 方程才有周期解。

引入变量 y0=y,y1=y ′y0=y,y1=y ′,通过变量替换就把原方程化为如下的标准形式的微分方程组:

⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪y′0=y1y′1=−[λ−2q cos(2x)] y0y0(x=0)=1y1(x=0)=0y1(x=π)=0{y0′=y1y1′=−[λ−2q cos(2x)] y0y0(x=0)=1y1(x=0)=0y1(x=π)=0

这样就可以用 solve_bvp() 求解该常微分方程的边值问题。

5.2 常微分方程的编程步骤

以该题为例讲解scipy.integrate.solve_bvp 求解常微分方程边值问题的步骤。

需要注意的是:(1)本案例涉及一个待定参数 λλ 需要通过 solve_bvp(fun, bc, x, y, p=None) 中的可选项 p 传递到导数函数和边界条件函数,(2)本案例涉及 3 个边界条件,要注意边界条件函数的定义。

  1. 导入 scipy、numpy、matplotlib 包;

  2. 定义导数函数 dydx(x,y,p)

    本问题中 y 表示向量,记为 y=[y0,y1]y=[y0,y1],定义函数 dydx(x,y,p) 中的 p 是待定参数。

  1. # 导数函数,计算导数 dY/dx
  2. def dydx(x, y, p): # p 是待定参数
  3. lam = p[0] # 待定参数,从 solve-bvp() 传递过来
  4. q = 10 # 设置参数
  5. dy0 = y[1]
  6. dy1 = -(lam-2*q*np.cos(2*x))*y[0]
  7. return np.vstack((dy0, dy1))
  1. 定义边界条件函数 boundCond(ya,yb,p)

    注意,虽然边界条件定义函数并没有用到参数 p,但也必须写在输入变量中,函数就是这么要求的。

  1. # 计算 边界条件
  2. def boundCond(ya, yb, p):
  3. lam = p[0]
  4. return np.array([ya[0]-1,ya[0],yb[0]])
  1. 设置 x、y 的初值

  2. 调用 solve_bvp() 求解常微分方程在区间 [xa,xb] 的数值解

  3. 由 solve_bvp() 的返回值 sol,获得网格节点的处的 y值。

5.3 Python 例程

  1. # mathmodel11_v1.py
  2. # Demo10 of mathematical modeling algorithm
  3. # Solving ordinary differential equations (boundary value problem) with scipy.
  4. from scipy.integrate import odeint, solve_bvp
  5. import numpy as np
  6. import matplotlib.pyplot as plt
  7. # 4. 求解微分方程组边值问题,Mathieu 方程
  8. # y0' = y1, y1' = -(lam-2*q*cos(2x))y0)
  9. # y0(0)=1, y1(0)=0, y1(pi)=0
  10. # 导数函数,计算导数 dY/dx
  11. def dydx(x, y, p): # p 是待定参数
  12. lam = p[0]
  13. q = 10
  14. dy0 = y[1]
  15. dy1 = -(lam-2*q*np.cos(2*x))*y[0]
  16. return np.vstack((dy0, dy1))
  17. # 计算 边界条件
  18. def boundCond(ya, yb, p):
  19. lam = p[0]
  20. return np.array([ya[0]-1,ya[0],yb[0]])
  21. xa, xb = 0, np.pi # 边界点 (xa,xb)
  22. xini = np.linspace(xa, xb, 11) # 确定 x 的初值
  23. xSol = np.linspace(xa, xb, 100) # 输出的网格节点
  24. for k in range(5):
  25. A = 0.75*k
  26. y0ini = np.cos(8*xini) # 设置 y0 的初值
  27. y1ini = -A*np.sin(8*xini) # 设置 y1 的初值
  28. yini = np.vstack((y0ini, y1ini)) # 确定 y=[y0,y1] 的初值
  29. res = solve_bvp(dydx, boundCond, xini, yini, p=[10]) # 求解 BVP
  30. y0 = res.sol(xSol)[0] # 网格节点处的 y 值
  31. y1 = res.sol(xSol)[1] # 网格节点处的 y 值
  32. plt.plot(xSol, y0, '--')
  33. plt.plot(xSol, y1,'-',label='A = {:.2f}'.format(A))
  34. plt.xlabel("xupt")
  35. plt.ylabel("y")
  36. plt.title("Characteristic function of Mathieu equation")
  37. plt.axis([0, np.pi, -5, 5])
  38. plt.legend(loc='best')
  39. plt.text(2,-4,"youcans-xupt",color='whitesmoke')
  40. plt.show()

5.4 Python 例程运行结果

初值 A从 0~3.0 变化时,y-x 曲线(图中虚线)几乎不变,但 y'-x 的振幅增大;当 A 再稍微增大,系统就进入不稳定区, y-x 曲线振荡发散(图中未表示)。

关于 Mathieu 方程解的稳定性的讨论,已经不是数学建模课的内容,不再讨论。


6. 小结

  1. 微分方程的边值问题相对初值问题来说更为复杂,但是用 Scipy 工具包求解标准形式的微分方程边值问题,编程实现还是不难掌握的。
  2. 关于边值问题的模型稳定性、灵敏度的分析,是更为专业的问题。除非找到专业课程教材或范文中有相关内容可以参考套用,否则不建议小白自己摸索,这些问题不是调整参数试试就能试出来的。

【本节完】

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载