我想解决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=False
和fill_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仍引发异常。然后,我可以被警告不连贯的行为。但它实际上并不可读,截至目前我看起来有点武断。
也许我只是过度思考这些错误。请告诉我。
答案 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
的参数包含在内,并明确处理大于t
中func
值的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}}