integrate.ode将t0值设置在我的数据范围之外

时间:2014-07-30 08:22:41

标签: python scipy ode

我想解决ODE dy / dt = -2y + data(t),在t = 0..3之间,y(t = 0)= 1。

我写了以下代码:

import numpy as np
from scipy.integrate import odeint
from scipy.interpolate import interp1d

t = np.linspace(0, 3, 4)

data = [1, 2, 3, 4]

linear_interpolation = interp1d(t, data)

def func(y, t0):
    print 't0', t0
    return -2*y + linear_interpolation(t0)

soln = odeint(func, 1, t)

当我运行此代码时,我会收到以下几个错误:

  

ValueError:x_new中的值高于插值范围   odepack.error:调用名为的Python函数时发生错误   FUNC

我的插值范围介于0.0和3.0之间。 在func中打印t0的值,我意识到t0实际上有时高于我的插值范围:3.07634612585,3.0203768998,3.00638459329,...这就是为什么linear_interpolation(t0)提升了ValueError例外。

我有几个问题:

  • integrate.ode如何使t0变化?为什么t0超出插值范围的下限(3.0)?

  • 尽管存在这些错误,integrate.ode仍返回一个似乎包含正确值的数组。那么,我应该抓住并忽略这些错误吗?无论微分方程,t范围和初始条件是什么,我都应该忽略它们吗?

  • 如果我不能忽视这些错误,避免它们的最佳方法是什么? 2意见建议:

      interp1d中的
    • ,我可以设置bounds_error=Falsefill_value=data[-1],因为我的插值范围之外的t0似乎已关闭到t[-1]

      linear_interpolation = interp1d(t, data, bounds_error=False, fill_value=data[-1])
      

      但首先我想确保与任何其他func和任何其他data t0保持一致t[-1]。例如,如果integrate.ode选择低于我的插值范围t0,则fill_value仍然是data[-1],这是不正确的。也许知道integrate.ode如何使t0变化有助于我确定(请参阅我的第一个问题)。

    • func中的
    • ,我可以将linear_interpolation调用包含在try / except块中,当我抓住ValueError时,我会回想起linear_interpolation但是t0被截断:

      def func(y, t0):    
          try:
              interpolated_value = linear_interpolation(t0)
          except ValueError:
              interpolated_value = linear_interpolation(int(t0)) # truncate t0
          return -2*y + interpolated_value
      

      如果integrate.ode使t0> = 4.0或t0< = -1.0,则至少此解决方案允许linear_interpolation仍引发异常。然后,我可以被警告不连贯的行为。但它实际上并不可读,截至目前我看起来有点武断。

也许我只是过度思考这些错误。请告诉我。

1 个答案:

答案 0 :(得分:3)

odeint求解程序在上次请求的时间之后的时间值评估函数是正常的。大多数ODE求解器以这种方式工作 - 它们采用内部时间步长,其大小由其错误控制算法确定,然后使用它们自己的插值在用户请求的时间评估解决方案。一些求解器(例如Sundials库中的CVODE求解器)允许您指定时间上的硬约束,超出此范围,求解器不允许求您的等式,但odeint没有这样的选项。

如果您不介意从scipy.integrate.odeint切换到scipy.integrate.ode"dopri5""dop853"解算器似乎不会在超出要求的时间内评估您的功能时间。两个警告:

  • ode解算器对定义微分方程的参数的顺序使用不同的约定。在ode求解器中,t是第一个参数。 (是的,我知道,抱怨,发牢骚......)
  • "dopri5""dop853"求解器适用于非刚性系统。如果你的问题是stiff,他们仍然应该给出正确的答案,但他们会比僵硬的求解者做更多的工作。

这是一个显示如何解决您的示例的脚本。为了强调参数的变化,我将func重命名为rhs

import numpy as np
from scipy.integrate import ode
from scipy.interpolate import interp1d


t = np.linspace(0, 3, 4)
data = [1, 2, 3, 4]
linear_interpolation = interp1d(t, data)

def rhs(t, y):
    """The "right-hand side" of the differential equation."""
    #print 't', t
    return -2*y + linear_interpolation(t)


# Initial condition
y0 = 1

solver = ode(rhs).set_integrator("dop853")
solver.set_initial_value(y0)

k = 0
soln = [y0]
while solver.successful() and solver.t < t[-1]:
    k += 1
    solver.integrate(t[k])
    soln.append(solver.y)

# Convert the list to a numpy array.
soln = np.array(soln)

本答案的其余部分将介绍如何继续使用odeint

如果您只对线性插值感兴趣,可以使用数据的最后两点简单地线性扩展数据。扩展data数组的一种简单方法是将值2*data[-1] - data[-2]附加到数组的末尾,并对t数组执行相同的操作。如果t中的最后一个步骤很小,这可能不是一个足够长的扩展来避免问题,所以在下面,我使用了更一般的扩展。

示例:

import numpy as np
from scipy.integrate import odeint
from scipy.interpolate import interp1d

t = np.linspace(0, 3, 4)

data = [1, 2, 3, 4]

# Slope of the last segment.
m = (data[-1] - data[-2]) / (t[-1] - t[-2])
# Amount of time by which to extend the interpolation.
dt = 3.0
# Extended final time.
t_ext = t[-1] + dt
# Extended final data value.
data_ext = data[-1] + m*dt

# Extended arrays.
extended_t = np.append(t, t_ext)
extended_data = np.append(data, data_ext)

linear_interpolation = interp1d(extended_t, extended_data)

def func(y, t0):
    print 't0', t0
    return -2*y + linear_interpolation(t0)

soln = odeint(func, 1, t)

如果仅仅使用最后两个数据点来线性扩展插值器太粗糙,那么你将不得不使用其他一些方法来推断一些超出给t的最终odeint值的方法。

另一种方法是将最终t值作为func的参数包含在内,并明确处理大于tfunc值的extrapolation值。像这样的东西,def func(y, t0, tmax): if t0 > tmax: f = -2*y + extrapolation(t0) else: f = -2*y + linear_interpolation(t0) return f soln = odeint(func, 1, t, args=(t[-1],)) 是你必须要弄清楚的东西:

{{1}}