奇怪的 SciPy ODE 集成错误

Posted

技术标签:

【中文标题】奇怪的 SciPy ODE 集成错误【英文标题】:Odd SciPy ODE Integration error 【发布时间】:2013-06-03 02:34:51 【问题描述】:

我正在为一个闲置的项目实施一个非常简单的易感感染恢复模型,该模型具有稳定的人口 - 通常是一项非常微不足道的任务。但是我在使用 PysCeS 或 SciPy 时遇到了求解器错误,它们都使用 lsoda 作为其底层求解器。这只发生在参数的特定值上,我不知道为什么。我使用的代码如下:

import numpy as np
from pylab import *
import scipy.integrate as spi

#Parameter Values
S0 = 99.
I0 = 1.
R0 = 0.
PopIn= (S0, I0, R0)
beta= 0.50     
gamma=1/10.  
mu = 1/25550.
t_end = 15000.
t_start = 1.
t_step = 1.
t_interval = np.arange(t_start, t_end, t_step)

#Solving the differential equation. Solves over t for initial conditions PopIn
def eq_system(PopIn,t):
    '''Defining SIR System of Equations'''
    #Creating an array of equations
    Eqs= np.zeros((3))
    Eqs[0]= -beta * (PopIn[0]*PopIn[1]/(PopIn[0]+PopIn[1]+PopIn[2])) - mu*PopIn[0] + mu*(PopIn[0]+PopIn[1]+PopIn[2])
    Eqs[1]= (beta * (PopIn[0]*PopIn[1]/(PopIn[0]+PopIn[1]+PopIn[2])) - gamma*PopIn[1] - mu*PopIn[1])
    Eqs[2]= gamma*PopIn[1] - mu*PopIn[2]
    return Eqs

SIR = spi.odeint(eq_system, PopIn, t_interval)

这会产生以下错误:

 lsoda--  at current t (=r1), mxstep (=i1) steps   
       taken on this call before reaching tout     
      In above message,  I1 =       500
      In above message,  R1 =  0.7818108252072E+04
Excess work done on this call (perhaps wrong Dfun type).
Run with full_output = 1 to get quantitative information.

通常,当我遇到这样的问题时,我建立的方程系统会出现最终问题,但我都看不出它有什么问题。奇怪的是,如果您将 mu 更改为 1/15550 之类的东西,它也会起作用。如果系统出现问题,我在 R 中实现了模型,如下所示:

require(deSolve)

sir.model <- function (t, x, params) 
  S <- x[1]
  I <- x[2]
  R <- x[3]
  with (
    as.list(params),

    dS <- -beta*S*I/(S+I+R) - mu*S + mu*(S+I+R)
    dI <- beta*S*I/(S+I+R) - gamma*I - mu*I
    dR <- gamma*I - mu*R
  res <- c(dS,dI,dR)
  list(res)

  )


times <- seq(0,15000,by=1)
params <- c(
 beta <- 0.50,
 gamma <- 1/10,
 mu <- 1/25550
)

xstart <- c(S = 99, I = 1, R= 0)

out <- as.data.frame(lsoda(xstart,times,sir.model,params))

这个使用了 lsoda,但似乎顺利进行。谁能看到 Python 代码出了什么问题?

【问题讨论】:

【参考方案1】:

我认为对于您选择的参数,您遇到了stiffness 的问题 - 由于数值不稳定性,求解器的步长在求解曲线斜率实际为的区域变得非常小很浅。 Fortran 求解器 lsoda,由 scipy.integrate.odeint 包装,尝试在适合“刚性”和“非刚性”系统的方法之间自适应地切换,但在这种情况下,它似乎无法切换到刚性方法。

您可以非常粗略地大幅增加允许的最大步数,求解器最终会到达那里:

SIR = spi.odeint(eq_system, PopIn, t_interval,mxstep=5000000)

更好的选择是使用面向对象的 ODE 求解器 scipy.integrate.ode,它允许您明确选择是使用刚性方法还是非刚性方法:

import numpy as np
from pylab import *
import scipy.integrate as spi

def run():
    #Parameter Values
    S0 = 99.
    I0 = 1.
    R0 = 0.
    PopIn= (S0, I0, R0)
    beta= 0.50     
    gamma=1/10.  
    mu = 1/25550.
    t_end = 15000.
    t_start = 1.
    t_step = 1.
    t_interval = np.arange(t_start, t_end, t_step)

    #Solving the differential equation. Solves over t for initial conditions PopIn
    def eq_system(t,PopIn):
        '''Defining SIR System of Equations'''
        #Creating an array of equations
        Eqs= np.zeros((3))
        Eqs[0]= -beta * (PopIn[0]*PopIn[1]/(PopIn[0]+PopIn[1]+PopIn[2])) - mu*PopIn[0] + mu*(PopIn[0]+PopIn[1]+PopIn[2])
        Eqs[1]= (beta * (PopIn[0]*PopIn[1]/(PopIn[0]+PopIn[1]+PopIn[2])) - gamma*PopIn[1] - mu*PopIn[1])
        Eqs[2]= gamma*PopIn[1] - mu*PopIn[2]
        return Eqs

    ode =  spi.ode(eq_system)

    # BDF method suited to stiff systems of ODEs
    ode.set_integrator('vode',nsteps=500,method='bdf')
    ode.set_initial_value(PopIn,t_start)

    ts = []
    ys = []

    while ode.successful() and ode.t < t_end:
        ode.integrate(ode.t + t_step)
        ts.append(ode.t)
        ys.append(ode.y)

    t = np.vstack(ts)
    s,i,r = np.vstack(ys).T

    fig,ax = subplots(1,1)
    ax.hold(True)
    ax.plot(t,s,label='Susceptible')
    ax.plot(t,i,label='Infected')
    ax.plot(t,r,label='Recovered')
    ax.set_xlim(t_start,t_end)
    ax.set_ylim(0,100)
    ax.set_xlabel('Time')
    ax.set_ylabel('Percent')
    ax.legend(loc=0,fancybox=True)

    return t,s,i,r,fig,ax

输出:

【讨论】:

这里的所有内容都是封装成 run() 以便更容易调用,还是出于机械原因? @Fomite 将 python 文件作为模块而不是脚本编写通常是一个好主意,因为它允许代码重用并鼓励更好的代码结构。【参考方案2】:

感染人数PopIn[1] 衰减为零。显然,(正常)数值不精确导致PopIn[1] 在 t=322.9 附近变为负数(约 -3.549e-12)。然后最终解决方案在 t=7818.093 附近爆炸,PopIn[0] 趋向 +infinity,PopIn[1] 趋向 -infinity。

编辑:我删除了我之前关于“快速修复”的建议。这是一个有问题的黑客攻击。

【讨论】:

以上是关于奇怪的 SciPy ODE 集成错误的主要内容,如果未能解决你的问题,请参考以下文章

离散值的 ODE 集成

scipy.integrate.ode 与两个耦合的 ODE?

多个 scipy.integrate.ode 实例

scipy.integrate.ode.set_solout 有效吗?

在找到本地最大值之前,我可以与 scipy 的 odeint 集成吗?

通过 scipy.integrate.ode 使用自适应步长