先纵览一下题目,主要分为常微分方程的编程练习和建模两部分。
# 通常的库
import numpy as np
import matplotlib.pyplot as plt
# 通用设置
plt.rc('font', family='SimHei') # 显示中文字体
plt.rc('axes',unicode_minus=False) # 显示负号
np.set_printoptions(precision=3, suppress=True) # 设置print()时的小数位数为3,并关闭科学计数法
np.random.seed(2022) # 设置随机数种子
# 比较重要的两个库
from scipy.integrate import odeint # 求数值解
import sympy as sp # 求解析解
关于 scipy.integrate下的odeint方法
关于sympy库的官方文档
详细程序编写流程请看 1.编程练习-例 4.3 马尔萨斯人口改进模型
详细建模流程请看 2.应用练习-题1:介壳虫,澳洲瓢虫和DDT 和 3.拓展练习-艾滋病发展模型
# 定义自变量
sp.var('t')
# 定义关于t的函数,或者说是因变量
h = sp.var('h', cls=sp.Function)
# 定义微分方程,这里要都移项到左边
eq = h(t).diff(t, 1) + 1.24*sp.sqrt(5)*sqrt(h(t))/(pi*(200*h(t)-h(t)**2))
# 求解微分方程
s1 = sp.dsolve(eq)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in
4 h = sp.var('h', cls=sp.Function)
5 # 定义微分方程,这里要都移项到左边
----> 6 eq = h(t).diff(t, 1) + 1.24*sp.sqrt(5)*sqrt(h(t))/(pi*(200*h(t)-h(t)**2))
7 # 求解微分方程
8 s1 = sp.dsolve(eq)
NameError: name 'sqrt' is not defined
把微分方程左边只保留一阶导数,然后把右端项写成如下函数形式
dh = lambda h,t: - 0.62*np.sqrt(20*h)/np.pi*(200*h-h**2)
# def dh(h,t):
# return - 0.62*np.sqrt(20*h)/np.pi*(200*h-h**2)
t = np.arange(0, 0.02, 0.0001) # 要求的数值点 [0,0.02) 左闭右开 间隔0.0001 根据实际情况调整
s1 = odeint(dh, 100, t) # 采用龙格库塔方法求解,一般默认是四阶的方法。
关于龙格库塔方法的简单介绍可以参考mooc浙江大学的常微分方程的第八章-数值常微分方程入门。
plt.plot(t, s1) # 绘制水面高度随着时间的变化曲线
plt.grid()
注意:Python只能求解一阶常微分方程组或方程组的数值解,高阶常微分方程必须转化成一阶方程组。
如果你忘记怎么转化了,可以参考mooc中天津师范大学的常微分方程-第五章-第一节。
ps:讲得还蛮不错的!
m = 10000
r = 0.2
m: 人口增长数量
r: 固有增长率
dx = lambda x,t: r*(1 - x/m)*x
t = np.arange(0, 100, 1)
X = odeint(dx, 100, t)
plt.plot(t, X)
plt.grid()
# 装饰性函数
plt.legend(['人口数'])
plt.xlabel('时间(年)')
plt.ylabel('人口数(个)')
plt.title('基于马尔萨斯人口改进模型的人口数量增长曲线图')
Text(0.5, 1.0, '基于马尔萨斯人口改进模型的人口数量增长曲线图')
r = 0.1
m = 10000
sp.var('t') # 自变量
x = sp.var('x', cls=sp.Function) # 未知函数
eq = x(t).diff(t,1) - r*(1 - x(t)/m)*x(t)
con = {x(0):100}
s = sp.dsolve(eq, ics=con) # ics承接初始条件
print(s)
Eq(x(t), -10000.0/(-1.0 - 99.0*exp(-0.1*t)))
s.args
(x(t), -10000.0/(-1.0 - 99.0*exp(-0.1*t)))
s.evalf(subs={t:10})
x ( 10 ) = 267.236309893952 \displaystyle x{\left(10 \right)} = 267.236309893952 x(10)=267.236309893952
s.evalf(subs={t:10}).args[1]
267.236309893952 \displaystyle 267.236309893952 267.236309893952
s.subs(t,10) # 或者
x ( 10 ) = 267.236309893952 \displaystyle x{\left(10 \right)} = 267.236309893952 x(10)=267.236309893952
s.args[1]
(等式的右端项)s.args[1]
− 10000.0 − 1.0 − 99.0 e − 0.1 t \displaystyle - \frac{10000.0}{-1.0 - 99.0 e^{- 0.1 t}} −−1.0−99.0e−0.1t10000.0
看一下s.args[1]的类型
print(type(s.args[1]))
sp.Mul(x,x) : 乘法
sp.plot(s.args[1], (t,-20,20))
关于sp.plot的官方文档
# 生成[-10,9]的函数值
ls = []
for i in range(-10,10):
ls.append(s.evalf(subs={t:i}).args[1])
# 用matplotlib绘制函数图像
plt.plot(range(-10,10), ls)
plt.show()
sp.var('t')
x,y = sp.var('x,y', cls=sp.Function) # 未知函数 中心室和周边室内的药物质量
f = 2*sp.exp(-2*t) # 关于时间t的给药函数 我用指数分布函数E(2)来模拟
k12 = 1 # 药物由中心室流向周边室的速率系数
k21 = 3 # 药物由周边室流向中心室的速率系数
k13 = 2 # 药物由中心室排除的速率系数
eqs = [x(t).diff(t) - (- k12*x(t) - k13*x(t) + k21*y(t) + f),
y(t).diff(t) - (k12*x(t) - k21*y(t))]
# 定义初值条件
con = {x(0):100, y(0):5}
# 求解微分方程组
s = sp.dsolve(eqs, ics = con) #; print(s.args[1])
s # 是一个列表,里面有关于两个未知函数的等式
[Eq(x(t), (101 - 6*sqrt(3))*exp(-t*(sqrt(3) + 3))/2 + (6*sqrt(3) + 101)*exp(-t*(3 - sqrt(3)))/2 - exp(-2*t)),
Eq(y(t), (18 - 101*sqrt(3))*exp(-t*(sqrt(3) + 3))/6 + (18 + 101*sqrt(3))*exp(-t*(3 - sqrt(3)))/6 - exp(-2*t))]
sp.plot(s[0].args[1],s[1].args[1], (t, -5, 5))
# 定义相关参数值
k12 = 1
k21 = 3.6
k13 = 10
m = 10000
r = 0.2
# 定义给药函数
f = lambda t:200*np.sin(t)
# 定义微分方程组
d = lambda x,t:[- k12*x[0] - k13*x[0] + k21*x[1] + f(t),
k12*x[0] - k21*x[1]]
# 求[0,99)的未知函数的数值解
t = np.arange(0, 100, 1)
s1 = odeint(d, [100,300], t)
# 绘制函数图像
plt.plot(t, s1)
plt.grid()
plt.legend(['中心室', '周边室'])
plt.title('中心室和周边室的药物质量随时间变化的曲线图')
Text(0.5, 1.0, '中心室和周边室的药物质量随时间变化的曲线图')
发现由于排药物速度较快,药物浓度迅速下降,由于给(gei)药函数是正弦型函数,所以连个室的药物质量也呈现出了近似的波动,
此外由于药物是仅仅从中心排出体外,故中心室的浓度要低一些。
前面的题目都已经建立好模型了,只要代码实现就可以了,但下面的题目就要自己想办法建立模型了,会有更大的挑战,但其实也不难。
符号 | 含义 |
---|---|
k21 | 每只澳洲瓢虫吃的介壳虫数量 |
k31 | 单位时间DDT杀死的介壳虫的比例 |
k32 | 单位时间DDT杀死的澳洲瓢虫的比例 |
r1 | 介壳虫的固有增长率 |
r2 | 澳洲瓢虫的固有增长率 |
m1 | 介壳虫的最大可持续容量 |
m2 | 澳洲瓢虫的最大可持续容量 |
下图是我第一次微分方程建模手稿,主要运用书中介绍的房室建模法。
由于美国首先是引入天敌澳洲瓢虫,后来檬园主才使用DDT,故不妨以开始使用DDT为界限分为前后两个时间段,分别建立微分方程。
第一阶段(引入澳洲瓢虫)
根据假设条件写出介壳虫和澳洲瓢虫的数量 X 1 ( t ) X_1(t) X1(t), X 2 ( t ) X_2(t) X2(t)所满足的微分方程。
X 1 ( t ) X_1(t) X1(t)的变化率由自身的逻辑斯蒂增长速率 r 1 ( 1 − X 1 ( t ) m 1 ) r_1(1-\frac{X_1(t)}{m_1}) r1(1−m1X1(t))、单位时间自身被澳洲瓢虫吃掉的数量 − k 21 X 2 -k_{21}X_2 −k21X2组成, X 2 ( t ) X_2(t) X2(t)的变化率仅由自身的逻辑斯蒂增长速率 r 2 ( 1 − X 2 ( t ) m 2 ) r_2(1-\frac{X_2(t)}{m_2}) r2(1−m2X2(t))。于是有
{ d X 1 d t = r 1 ( 1 − X 1 m 1 ) − k 21 X 2 , d X 2 d t = r 2 ( 1 − X 2 m 2 ) , \begin{cases} \frac{dX_1}{dt}=r_1(1-\frac{X_1}{m_1})-k_{21}X_2,\quad \\ \frac{dX_2}{dt}=r_2(1-\frac{X_2}{m_2}), \quad \\ \end{cases} {dtdX1=r1(1−m1X1)−k21X2,dtdX2=r2(1−m2X2),
考虑被DDT杀死的介壳虫和澳洲瓢虫的数量 − k 31 X 1 -k_{31}X_1 −k31X1, − k 32 X 2 -k_{32}X_2 −k32X2。
{ d X 1 d t = r 1 ( 1 − X 1 m 1 ) − k 21 X 2 − k 31 X 1 , d X 2 d t = r 2 ( 1 − X 2 m 2 ) − k 32 X 2 , \begin{cases} \frac{dX_1}{dt}=r_1(1-\frac{X_1}{m_1})-k_{21}X_2-k_{31}X_1,\quad \\ \frac{dX_2}{dt}=r_2(1-\frac{X_2}{m_2})-k_{32}X_2, \quad \\ \end{cases} {dtdX1=r1(1−m1X1)−k21X2−k31X1,dtdX2=r2(1−m2X2)−k32X2,
先求第一阶段的方程的数值解,采用的一组参数如下:
k21 = 0.2只/天
k31 = 0.1/天
k32 = 0.5/天
r1 = 0.2/天
r2 = 0.2/天
最大可持续容量:
m1 = 1000只
m2 = 1000只
两个种群的初始数量都是300只
编写程序如下:
k21 = 0.2
k31 = 0.1
k32 = 0.5
r1 = 0.2
r2 = 0.2
# 最大可持续容量
m1 = 1000
m2 = 1000
d1 = lambda x,t1:[r1*(1 - x[0]/m1)*x[0] - k21*x[1],r2*(1 - x[1]/m2)*x[1]] # 要把x[1]加上 不然就只是环比增长率(无单位) 而不增长速率
t1 = np.arange(0, 10, 0.01)
s1 = odeint(d1, [300,300], t1)
plt.plot(t1, s1)
plt.grid()
plt.legend(['介壳虫', '澳洲瓢虫'])
s1[199]
array([242.4 , 389.528])
m = 10000
r = 0.2
d2 = lambda x,t2:[r1*(1 - x[0]/m1)*x[0] - k31*x[0] - k21*x[1],
r2*(1 - x[1]/m2)*x[1] - k32*x[1]]
t2 = np.arange(2, 40, 0.01)
s2 = odeint(d2, [242,390], t2)
plt.plot(t2, s2)
plt.grid()
plt.legend(['介壳虫', '澳洲瓢虫'])
plt.plot(np.arange(0,39.99,0.01), np.r_[s1[:199],s2]) # 两个阶段的时间和数量 s[199]数量四舍五入和s2[0]是一样的,避免重复故切片取到199(不包含199)
plt.legend(['介壳虫', '澳洲瓢虫'])
plt.annotate('DDT"大显身手"', xy=(2, 380), xytext=(12.5, 250),
arrowprops=dict(arrowstyle="->",connectionstyle="angle3,angleA=0,angleB=-90"),
horizontalalignment='center',verticalalignment ='bottom', fontsize=20)
plt.annotate('介壳虫:我又行了', xy=(8, 110), xytext=(20, 10),
arrowprops=dict(arrowstyle="->",connectionstyle="angle3,angleA=0,angleB=-90"),
horizontalalignment='center',verticalalignment ='bottom', fontsize=20)
plt.legend(['介壳虫', '澳洲瓢虫'])
白蚁和披发虫时一个共生的关系,故不妨套用已有的共生演化模型。
sp.var('t')
x,y= sp.var('x,y', cls=sp.Function)
N1 = 100
N2 = 100
r1 = 0.2 # 白蚁
r2 = 0.1
a21 = -0.3
a12 = -0.3
eqs = [x(t).diff(t) - r1*x(t)*(1 - x(t)/N1 - a21*y(t)/N2),
y(t).diff(t) - r2*y(t)*(1 - y(t)/N2 - a12*x(t)/N1)]
# sp.dsolve(eqs) # 运行后会卡死
参考文献:
[1]张影,高长元,王京.跨界创新联盟生态系统共生演化模型及实证研究[J/OL].中国管理科学:1-14[2022-05-30].DOI:10.16381/j.cnki.issn1003-207x.2019.0314.
这时应该进行平衡点分析(不用求出解析解)。
d = lambda x,t: [r1*x[0]*(1 - x[0]/N1 - a21*x[1]/N2),
r2*x[1]*(1 - x[1]/N2 - a12*x[0]/N1)]
t = np.arange(0, 100, 1)
s1 = odeint(d, [100,100], t)
plt.plot(t, s1)
plt.grid()
plt.legend(['白蚁', '披发虫'])
sp.var('t')
x = sp.var('x', cls = sp.Function)
E = 0.1 # 捕捞率
m = 1000 # 鱼量上限
r = 0.1
eq = [x(t).diff(t) - r*(1 - x(t)/m) + E*x(t)] # 注意右端项移到左边要变号
s1 = sp.dsolve(eq, ics = {x(0):100})
sp.plot(s1[0].args[1])
# 定义微分方程
dx = lambda x,t: r*(1 - x/m) - E*x # 一个等式时不要加[]
# 定义要求的时间点
t = np.arange(0, 100, 1)
# 调用odeint函数求数值解
s1 = odeint(dx, 100, t)
# 绘制鱼儿数量的变化图像
plt.plot(t, s1)
plt.grid()
plt.legend(['鱼数量'])
假设鱼为球体,体积为V,表面积为S,半径为R,初始重量为 G 0 {G_0} G0,鱼的密度为 ρ \rho ρ,没有考虑捕鱼。
详细建模过程可参考 最优捕鱼模型。
模型把鱼假设成一个球体,讲道理我不太能接受,如果假设成一个圆柱体,长方体,扁的椭球,那又该怎么建立模型呢?
# 设定参数值
k1 = 0.5
k2 = 0.1
b = 1
# 定义微分方程 #
dG = lambda G,t: k1*b*G**(2/3) - k2*G # 一个等式时不要加[]
# 定义要求的时间点
t = np.arange(0, 100, 1)
# 求解,其中100为初始条件
s1 = odeint(dG, 100, t)
# 绘图
plt.plot(t, s1)
plt.grid()
plt.legend(['鱼重量'])
题目要求考虑至少易感染人群,被HIV感染人群,接种疫苗的人群,接种疫苗后又被感染的人群,已患艾滋病的人群。此外我还考虑了死亡人群。通过对比是否打疫苗,比较不同人群被HIV感染的情况,建立在有疫苗情况下的艾滋病传播模型。模型主要在SEIR模型的基础上修改得到新的模型,其中将康复人群替换为死亡人群,并且把潜伏人群和易感人群按照是否打过疫苗各分为两类以研究疫苗的作用。
(1)假设这里的E,VE和I人群都具有相同传染能力。
(2)易感人群向接种疫苗的人群的转化速率和易感人群数量成正比。
(3)疫苗在人发病后不再起作用。
(4)没有康复人群。
(5)假设E,VE,I接触到的人群中S和VS的比例和总人群中S和VS的比例一样。
我这里用Vaccinated:打疫苗的,表明接种疫苗的易感人群VS,和被HIV感染的疫苗人群VE。
S:易感人群数量
VS:接种疫苗的人群数量
E:未打疫苗的潜伏者数量
VE:打了疫苗的潜伏者,或者说被HIV感染的疫苗人群数量
I:发病人群数量
G:死亡人群数量
N:初始人口数
下图是模型示意图
a还需要假设一个区域内总人数为 N - G,即N - G = S + VS + E + VE + I ,
每天每位感染者(E,VE,I)接触的人数为 P,
健康人比例为 (S + VS)/(N - G),
其中易感人群S按照比例 A 转化为潜伏人群E,
潜伏人群E按照比例 C 转化为发病人群,
发病人群按照比例 g 转化为死亡人群。
易感人群S按照比例k转化为接种疫苗的人群VS,
接种疫苗的人群VS按照比例B转化为打了疫苗的潜伏着VE,
打了疫苗的潜伏着VE按照比例D转化为发病人群I。
此时,在该艾滋病传播模型上可以建立如下微分方程:
{ d S d t = − ( E + V E + I ) p S N − G A − k S , d V S d t = − ( E + V E + I ) p V S N − G B + k S , d E d t = ( E + V E + I ) p S N − G A − C E , d V E d t = ( E + V E + I ) p V S N − G B − D ∗ V E , d I d t = C E + D ∗ V E − g I , d G d t = g I , \begin{cases} \frac{dS}{dt}=-(E+VE+I)p\frac{S}{N-G}A-kS,\quad \\ \frac{dVS}{dt}=-(E+VE+I)p\frac{VS}{N-G}B+kS, \quad \\ \frac{dE}{dt}=(E+VE+I)p\frac{S}{N-G}A - CE,\quad \\ \frac{dVE}{dt}=(E+VE+I)p\frac{VS}{N-G}B - D*VE, \quad \\ \frac{dI}{dt}=CE+D*VE-gI,\quad \\ \frac{dG}{dt}=gI, \quad \\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧dtdS=−(E+VE+I)pN−GSA−kS,dtdVS=−(E+VE+I)pN−GVSB+kS,dtdE=(E+VE+I)pN−GSA−CE,dtdVE=(E+VE+I)pN−GVSB−D∗VE,dtdI=CE+D∗VE−gI,dtdG=gI,
我参考了该论文的SEIR模型的建立
# 设置参数
N = 10000
A = 0.2
B = 0.1
k = 0.1 # 剩下的都是不肯打疫苗的顽固分子 或者不符合条件
C = 0.1
D = 0.1
g = 0.0655
p = 0.3 # 密切接触0.3个人
# 定义微分方程组
d = lambda x,t: [-(x[4] + x[2] + x[3])*p*x[0]/(N-x[5])*A - k*x[0],
k*x[0] - (x[4] + x[2] + x[3])*p*x[1]/(N-x[5])*B,
(x[4] + x[2] + x[3])*p*x[0]/(N-x[5])*A - C*x[2],
(x[4] + x[2] + x[3])*p*x[1]/(N-x[5])*B - D*x[3],
C*x[2] + D*x[3] - g*x[4],
g*x[4]
]
# 定义求解范围
t = np.arange(0, 800, 1)
# 设置初始条件并求解
s1 = odeint(d, [4500,4500,500,500,0,0], t)
# 绘制函数
plt.plot(t, s1)
plt.grid()
plt.legend(['S','VS','E','VE','I','G'])
#对应 x[0] x[1] x[2] x[3] x[4] x[5]
死亡率太高,传染速度没死亡率高,所以被感染的都死了,就没有可以继续感染的人了。。。。。
通过调整不同参数,会得到不同结果,对应不同的情况。
时光飞逝,自从我把常微分方程作为下一个学习目标,不知不觉已然近两个月过去了。虽然临近期末,但我还是完成了它,因为它是重要的!