- w/ [) Y) k s+ g" i* Q# `8 k) E. z5 `! I6 O
1 n- @8 h; [: m1 o) ?" S
7 {% f4 r8 K3 C% }' e: L2.2 SI 模型的假设 & _1 r" W8 D# d& h6 j1 L2 N; @" g考察地区的总人数 N 不变,即不考虑生死或迁移;; q& U' r9 D$ O; q1 P
人群分为易感者(S类)和患病者(I类)两类; ' u- V1 L2 ]1 _) m易感者(S类)与患病者(I类)有效接触即被感染,变为患病者,无潜伏期、无治愈情况、无免疫力;- ?0 v0 c. C; Z; M4 c) u
每个患病者每天有效接触的易感者的平均人数(日接触数)是 λ \lambdaλ,称为日接触率; # j5 g* i) X' Y; @将第 t 天时 S类、I 类人群的占比记为 s ( t ) s(t)s(t)、i ( t ) i(t)i(t),数量为 S ( t ) S(t)S(t)、I ( t ) I(t)I(t);初始日期 t = 0 t=0t=0 时, S类、I 类人群占比的初值为 s 0 s_0s 7 S& T' T1 a! i' J$ ]
0 + g+ N6 i; i e7 `7 i s & L9 z. ^, ^$ M D& V, y$ g# G+ C 、i 0 i_0i ' u6 t# o$ M* P" z5 Z. k5 z* d* ^
0 H7 w1 W5 R( {7 f
( N, P z+ Z& X% Q1 ]: J. a. C
。* H% R/ U% [# r( ?9 R& I& h
2.3 SI 模型的微分方程6 {/ o, @& o$ T& ?/ E
由 ' a5 `3 x* n5 w( H0 o2 i0 GN d i d t = N λ s i N\frac{di}{dt} = N\lambda s i# c1 z4 Q, q9 t: p
N " R1 ?- g) W3 E, T/ y6 b0 c2 n
dt" V9 [$ z% L c! P
di " Q" A& D$ q3 f6 E* |% C+ V / x$ S7 D% a8 H2 i6 i& e" c =Nλsi 2 X8 l% j. m& f2 b9 v" {. U I3 ^/ R4 `
q5 h; B9 r" {3 b# O8 O/ F得:2 O& \2 y; G) D9 v9 ]; K- X
d i d t = λ i ( 1 − i ) , i ( 0 ) = i 0 \frac{di}{dt} = \lambda i (1-i),\ i(0) = i_0+ t; E+ G8 O6 b* C% W
dt! o! a. g0 x! ?1 I
di) P# {4 P' Y$ C2 d
9 l- \ D8 ]! C& U1 w
=λi(1−i), i(0)=i 7 l" T9 T( m- U9 V n! x
03 X8 z# ^4 j; }. m e' [
/ U) {* |! Q1 h8 K1 ]! n" D! N. B
, O% {0 @$ E; _) w2 J3 I9 K9 `+ J
7 i+ y5 v% O4 ~" b4 } Y) b$ Y9 p6 p1 V
这是 Logistic 模型,用分离变量法可以求出其解析解为: 7 h. d' B1 d4 {! f$ v3 Qi ( t ) = 1 1 + ( 1 / i 0 − 1 ) e − λ t I ( t ) = N i ( t ) i(t)=\frac{1}{1+(1/i_0 - 1)\ e^{-\lambda t}}\\ I(t)= N\ i(t) * f8 Q4 E6 n1 Zi(t)= 4 {5 B: A( R5 @9 X- K+ r% U1+(1/i o+ N* p2 _0 X8 ?# i( i
0 ) |: L7 I, r4 K+ q ( b; X# f- U/ } −1) e : M+ {: K2 \* m−λt ' D$ w) L. M& D 2 q; a8 L8 a4 [9 t$ Z1 R11 @7 h) ]; i8 F/ f4 J
) ]$ H1 J0 ?8 d. }
' c3 d1 r2 }) p# G( g. L; Z
I(t)=N i(t) ) @3 [3 H/ z' @1 i& X% \3 ]0 Y; \
4 U3 @, R( Y9 q) t " x% e, o' ~6 E: O4 v 7 e# p+ O- e( q* V# d+ z3. SI 模型的 Python 编程 # _+ c+ E/ p$ R0 ]4 M/ z3.1 SI 模型的解析解* _5 t- K, l Y4 n
上文已经得到 SI 模型的解析解,对此很容易通过 Python 编程实现,详见本文例程。0 r) P3 g* K& x
/ k0 I7 q" ~! \# c# ?' z& C: n1 ?/ f1 x( C! ?1 K
虽然 SI 模型的解析解并不复杂,而且解的精度当然是最好的,但我们仍然不鼓励用解析解的方法。原因在于,一是对于小白求解析解的过程相对复杂困难,而且可能出错,二是对于更复杂的模型是没有解析解的,即便大神也只能用数值方法求解。既然如此,不如从一开始就学习、掌握数值求解方法,熟悉数值解法的编程实现。 9 C/ H5 q* I3 l& W6 Y 7 E) p5 @; M! M2 A d5 ~ $ f* [% ^: k! q7 v! ^* d, o3.2 SI 模型的数值解2 B+ M w! X6 [+ X6 N8 E0 T# X/ _# L
SI 模型是常微分方程初值问题,可以使用 Scipy 工具包的 scipy.integrate.odeint() 函数求数值解,具体方法可以参考前文《Python小白的数学建模课-09 微分方程模型》。 * t' e" n( `6 b, F4 N " x2 V; F) F7 Y, \2 T & y& z0 i8 I5 i$ }' Qscipy.integrate.odeint(func, y0, t, args=())* t" h5 A2 [ m- L0 z! U2 k/ q
1 * Z" p- }% b# n; U**scipy.integrate.odeint()**是求解微分方程的具体方法,通过数值积分来求解常微分方程组。& S6 I- G( L2 V
7 P. F1 e: n2 r0 o7 S- N' e- M' a! M. A/ g
' C* Z8 M7 \6 s2 T+ B. kodeint() 的主要参数:; {! Q D$ h* }6 X( w
% P' M. T/ R+ @( I x/ p* H5 M n0 `. O! a8 [1 G+ ~+ _
func: callable(y, t, …) 导数函数 f ( y , t ) f(y,t)f(y,t) ,即 y 在 t 处的导数,以函数的形式表示* v3 h: }; u! t- ~0 S
y0: array: 初始条件 y 0 y_0y 9 G% X* y7 y# p5 {
0 ' e, T7 Z0 ?0 M( [; E2 B+ b" f2 }8 P $ f2 e! @" l, e; _ S" B ,对于常微分方程组 y 0 y_0y 6 X9 G& c X( P% `
0 ' `: \5 g, L5 M: D4 h " M E& B) `: L0 P" z3 N) t
则为数组向量 " J9 y5 j( O- Y8 K& n: Et: array: 求解函数值对应的时间点的序列。序列的第一个元素是与初始条件 y 0 y_0y $ T6 P# c! _$ |4 c) q1 S9 m02 v4 c- `# ~; n: s4 r9 \* g0 ?% _- f
1 U( u* ^& h" z
对应的初始时间 t 0 t_0t - ?/ z7 m1 p$ F0; a' j& @) P; c' F; ]. S5 F
O: Q6 e, U+ t$ L1 o ;时间序列必须是单调递增或单调递减的,允许重复值。$ m6 w/ H) r# B+ X
args: 向导数函数 func 传递参数。当导数函数 f ( y , t , p 1 , p 2 , . . ) f(y,t,p1,p2,..)f(y,t,p1,p2,..) 包括可变参数 p1,p2… 时,通过 args =(p1,p2,…) 可以将参数p1,p2… 传递给导数函数 func。+ n% x$ M% B" g- X! K8 f7 b
odeint() 的返回值: 9 [# K# c; \6 d" P8 U# `2 E " t7 j& j& f# M% ^/ W3 l. y& ^
y: array 数组,形状为 (len(t),len(y0),给出时间序列 t 中每个时刻的 y 值。* v1 h9 f" c- l7 l, E4 ]0 K% O
odeint() 的编程步骤:) i7 x) @2 K& C
/ W7 ^( ~0 r4 P) f$ J0 \# e& ` s6 k+ A7 o& ^ l- p8 g5 N* {1 A
导入 scipy、numpy、matplotlib 包; 3 Z# ~3 Y" H, [- ?定义导数函数 f ( i , t ) = λ i ( 1 − i ) f(i,t)=\lambda i (1-i)f(i,t)=λi(1−i) ; # d, n2 U g1 L% o$ i定义初值 i 0 i_0i " q, R& W% {9 }6 D0 l
0# {. \ o9 _ y( F8 D, w
' r8 X& v- O: q0 k; K 和 i ii 的定义区间 [ t 0 , t ] [t_0,\ t][t & U& E% a' o3 r- I
0' J) _ K* y; w" |! `6 R
* z* i4 }( x2 F# Z9 p. I/ m- e , t]; , N# q! w1 J# ]& O1 D h调用 odeint() 求 i ii 在定义区间 [ t 0 , t ] [t_0,\ t][t 9 ^) B4 } \6 e2 {* G# i0% A m4 T8 \" d" V
. b" M/ U5 i \& k9 K) z
, t] 的数值解。 Y4 j1 D3 F0 e I3 {7 [+ C* s) y' ?2 W, U; w/ A* N0 G5 d5 b, |
3.3 Python例程:SI 模型的解析解与数值解! f& V0 V* m# c! B' r: R0 _; m
# 1. SI 模型,常微分非常,解析解与数值解的比较 4 e" Y+ g+ Y% q) m5 M( b4 Ufrom scipy.integrate import odeint # 导入 scipy.integrate 模块& u; E0 [3 ^" g
import numpy as np # 导入 numpy包- J; u' y/ ~3 R* N# c- G, }. z
import matplotlib.pyplot as plt # 导入 matplotlib包/ X- M e( u, q+ H
8 g8 L6 h, q" F/ G) U9 U: u1 y
Z" Y( ]% o7 z4 a. y/ qdef dy_dt(y, t, lamda, mu): # 定义导数函数 f(y,t): o# G3 _! c- h, L- C+ ?8 M
dy_dt = lamda*y*(1-y) # di/dt = lamda*i*(1-i)( x( t# l% R: p8 _0 b$ d& t
return dy_dt $ g8 U% D) K' V# a" R3 j3 c. }8 q7 i$ W9 s
" a+ g% q1 k% q% }6 z8 m$ D8 d: P% ?# 设置模型参数 ) d3 y# {0 n2 O7 Pnumber = 1e7 # 总人数* T5 T2 D# Y) V6 j6 A/ V
lamda = 1.0 # 日接触率, 患病者每天有效接触的易感者的平均人数$ t, e# c6 @! R- T
mu1 = 0.5 # 日治愈率, 每天被治愈的患病者人数占患病者总数的比例 ) I' H) |- v- O5 R' W: `5 E: @1 o# cy0 = i0 = 1e-6 # 患病者比例的初值3 F: c( a5 E6 M) ^- T$ C. C! b- z" Z
tEnd = 50 # 预测日期长度5 b& |. \) \% M. f' C z
t = np.arange(0.0,tEnd,1) # (start,stop,step) o/ y" \2 [- Y$ U" e: a2 Y 5 @+ L9 s: w& m2 \1 |; W/ K# p1 ?2 S6 ~0 L
yAnaly = 1/(1+(1/i0-1)*np.exp(-lamda*t)) # 微分方程的解析解( G" w4 G- e) u: \8 a8 A+ K8 y
yInteg = odeint(dy_dt, y0, t, args=(lamda,mu1)) # 求解微分方程初值问题' s4 o- f9 j* B
yDeriv = lamda * yInteg *(1-yInteg) ! h7 q8 ]8 C! f2 E T$ I 6 C+ {$ w _3 |7 M! [' S- ]+ V r: s8 Z, I
# 绘图* n* `. T2 f1 s- X
plt.plot(t, yAnaly, '-ob', label='analytic')9 i; c4 Z$ q% J0 ~0 \
plt.plot(t, yInteg, ':.r', label='numerical'). G& D. |+ }2 g1 b! d3 b, F8 p
plt.plot(t, yDeriv, '-g', label='dy_dt'), j! ~* h2 u9 ]7 t
plt.title("Comparison between analytic and numerical solutions") : r. X0 q! r f% ?' Zplt.legend(loc='right') F! M+ P7 @0 V9 L! L0 p7 J
plt.axis([0, 50, -0.1, 1.1]) , a4 m' b9 | S9 m) n6 p) h, _plt.show() - `6 G& N7 R' Y0 R5 r( y1$ e: ?7 y+ F: J; t* s
2) J# s/ j2 q6 O/ r4 b1 n
3 0 s% j3 r" q7 e# K2 [ }/ r4! ~5 T: n7 f* R, Q: i2 A1 X/ L9 l
5* ^+ P) F" t- u: n+ r' T" t5 K
6# n# p) a8 x' J2 |+ w7 `3 {
7' V, \* L) e! J5 h
8 . W3 ], `- V! u: A2 P9 : o U$ q5 p6 @; Z9 T. W10+ v4 d8 l j# F+ u F1 v
11( x5 W) R1 ]! u0 u4 F( J1 |) u) M
12 7 j; q M4 J2 e1 b5 a z, |130 O( g9 u! M0 C: S
14 * `, u6 E5 d* K9 ]15 * a1 W6 }7 X! G16 M4 Y+ z! ? `" e1 Y
17+ q' U( Q( J5 s5 v$ t
188 P! |7 d1 P9 F9 N0 |/ W% ]
19 . n( b& W& V1 H- c& B20% u" B- E$ k4 s- K0 H% m/ p+ ~; D8 ?
21 . M5 w5 i4 T3 t& l/ r; c0 z( c22/ S A* x7 C' b
233 W d2 s9 v, E/ H" G
24* ]6 O, }0 Y' S/ O1 t- b, z
25 9 M: b' q" j! l$ D1 M. J26 5 L- z( a4 U0 p* p+ {4 \27; M* T; b' p$ \2 R/ x- B
28% Q. v' `, W7 O9 c( e
29 0 s; c* }6 t! C8 J * h, H) _! K) w' X1 o" ~3 [# y* e' P4 L3 t& k
3.4 解析解与数值解的比较# i$ W0 S# ]/ E p9 \
" x; X* V! y/ q: G: r# m) m7 K& ?+ e) C
本图为例程 2.3 的运行结果,图中对解析解(蓝色)与使用 odeint() 得到的数值解(红色)进行比较。在该例中,无法观察到解析解与数值解的差异,表明数值解的误差很小。& U& a! m& N2 Q5 b$ D
0 c9 X. E' u/ G& _2 }6 o- q9 i) B, B& U* a, `, j; x
图中 d i / d t di/dtdi/dt 具有最大值,最大值表示疫情增长的高潮,达到最大值后 d i / d t di/dtdi/dt 逐渐减小,但患病者比例很快增长到 100%,表明所有人都被感染成为患者。 & ]5 X! ^! c# ?9 h9 ?5 k : s" R( f! ^2 G1 |+ b X, o ?( x/ a: Z% {9 K
这是特定参数的结果,还是模型的必然趋势,需要对参数的影响进行更详细的研究。; ]% _8 \; z9 A8 \* J