问题
I would like to solve the ODE dy/dt = -2y + data(t), between t=0..3, for y(t=0)=1.
I wrote the following code:
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)
When I run this code, I get several errors like this:
ValueError: A value in x_new is above the interpolation range.
odepack.error: Error occurred while calling the Python function named func
My interpolation range is between 0.0 and 3.0.
Printing the value of t0
in func, I realized that t0
is actually sometimes above my interpolation range: 3.07634612585, 3.0203768998, 3.00638459329, ... It's why linear_interpolation(t0)
raises the ValueError
exceptions.
I have a few questions:
how does
integrate.ode
makest0
vary? Why does it maket0
exceed the infimum (3.0) of my interpolation range?in spite of these errors,
integrate.ode
returns an array which seems to contain correct value. So, should I just catch and ignore these errors? Should I ignore them whatever the differential equation(s), thet
range and the initial condition(s)?if I shouldn't ignore these errors, what is the best way to avoid them? 2 suggestions:
in
interp1d
, I could setbounds_error=False
andfill_value=data[-1]
since thet0
outside of my interpolation range seem to be closed tot[-1]
:linear_interpolation = interp1d(t, data, bounds_error=False, fill_value=data[-1])
But first I would like to be sure that with any other
func
and any otherdata
thet0
will always remain closed tot[-1]
. For example, ifintegrate.ode
chooses at0
below my interpolation range, the fill_value would be stilldata[-1]
, which would not be correct. Maybe to know howintegrate.ode
makest0
vary would help me to be sure of that (see my first question).in
func
, I could enclose thelinear_interpolation
call in a try/except block, and, when I catch aValueError
, I recalllinear_interpolation
but witht0
truncated: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
At least this solution permits linear_interpolation to still raise an exception if
integrate.ode
makest0
>= 4.0 ort0
<= -1.0. I can then be alerted of incoherent behavior. But it is not really readable and the truncation seems to me a little arbitrary by now.
Maybe I'm just over-thinking about these errors. Please let me know.
回答1:
It is normal for the odeint
solver to evaluate your function at time values past the last requested time. Most ODE solvers work this way--they take internal time steps with sizes determined by their error control algorithm, and then use their own interpolation to evaluate the solution at the times requested by the user. Some solvers (e.g. the CVODE solver in the Sundials library) allow you to specify a hard bound on the time, beyond which the solver is not allowed to evaluate your equations, but odeint
does not have such an option.
If you don't mind switching from scipy.integrate.odeint
to scipy.integrate.ode
, it looks like the "dopri5"
and "dop853"
solvers don't evaluate your function at times beyond the requested time. Two caveats:
- The
ode
solvers use a different convention for the order of the arguments that define the differential equation. In theode
solvers,t
is the first argument. (Yeah, I know, grumble, grumble...) - The
"dopri5"
and"dop853"
solvers are for non-stiff systems. If your problem is stiff, they should still give correct answers, but they will do a lot more work than a stiff solver would do.
Here's a script that shows how to solve your example. To emphasize the change in the arguments, I renamed func
to 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)
The rest of this answer looks at how you could continue to use odeint
.
If you are only interested in linear interpolation, you could simply extend your data linearly, using the last two points of the data. A simple way to extend the data
array is to append the value 2*data[-1] - data[-2]
to the end of the array, and do the same for the t
array. If the last time step in t
is small, this might not be a sufficiently long extension to avoid the problem, so in the following, I've used a more general extension.
Example:
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)
If simply using the last two data points to extend the interpolator linearly is too crude, then you'll have to use some other method to extrapolate a little beyond the final t
value given to odeint
.
Another alternative is to include the final t
value as an argument to func
, and explicitly handle t
values larger than it in func
. Something like this, where extrapolation
is something you'll have to figure out:
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],))
来源:https://stackoverflow.com/questions/25031966/integrate-ode-sets-t0-values-outside-of-my-data-range