26

的(简要)文档scipy.integrate.ode说两种方法(dopri5dop853)具有步长控制和密集输出。查看示例和代码本身,我只能看到一种从集成器获取输出的非常简单的方法。即,看起来您只是将积分器向前推进某个固定的 dt,获取当时的函数值,然后重复。

我的问题有相当多变的时间尺度,所以我想在需要评估的任何时间步骤获取值以达到所需的容差。也就是说,在早期,事情正在缓慢变化,因此输出时间步长可能很大。但随着事情变得有趣,输出时间步长必须更小。我实际上并不想要等间隔的密集输出,我只想要自适应函数使用的时间步长。

编辑:密集输出

一个相关的概念(几乎相反)是“密集输出”,其中所采取的步数与步进器一样大,但函数的值被插值(通常精度与步进器的精度相当)到任何你要。fortran 底层scipy.integrate.ode显然能够做到这一点,但ode没有接口。 odeint另一方面,它基于不同的代码,并且显然会进行密集输出。(您可以在每次调用右侧时输出以查看何时发生,并查看它与输出时间无关。)

所以我仍然可以利用适应性,只要我可以提前决定我想要的输出时间步长。不幸的是,对于我最喜欢的系统,我什至不知道作为时间函数的大致时间尺度是什么,直到我运行集成。因此,我必须将采取积分器步骤的想法与密集输出的概念结合起来。

编辑2:再次密集输出

显然,scipy 1.0.0 通过一个新接口引入了对密集输出的支持。特别是,他们建议远离scipy.integrate.odeint和接近scipy.integrate.solve_ivp作为关键字的dense_output。如果设置为True,则返回的对象具有一个属性sol,您可以使用时间数组调用该属性,然后返回这些时间的集成函数值。这仍然不能解决这个问题的问题,但在许多情况下它很有用。

4

5 回答 5

16

SciPy 0.13.0 开始

dopri现在可以通过solout回调函数访问 ODE 求解器系列的中间结果。

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01
t0 = 0
y0 = 1e-5
t1 = 5000.0

backend = 'dopri5'
# backend = 'dop853'
solver = ode(logistic).set_integrator(backend)

sol = []
def solout(t, y):
    sol.append([t, *y])
solver.set_solout(solout)
solver.set_initial_value(y0, t0).set_f_params(r)
solver.integrate(t1)

sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

结果: 使用 DOPRI5 求解的逻辑函数

结果似乎与 Tim D 的略有不同,尽管他们都使用相同的后端。我怀疑这与dopri5. 在 Tim 的方法中,我认为第七阶段的结果 k7 被丢弃,因此重新计算 k1。

注意:如果在设置初始值之后设置 set_solout ,则存在一个已知错误自SciPy 0.17.0起已修复。

于 2016-01-25T17:42:40.637 回答
14

我一直在看这个试图得到相同的结果。事实证明,您可以通过在 ode 实例化中设置 nsteps=1 来使用 hack 来获得逐步结果。它将在每一步生成一个 UserWarning(这可以被捕获和抑制)。

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt
import warnings


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01
t0 = 0
y0 = 1e-5
t1 = 5000.0

#backend = 'vode'
backend = 'dopri5'
#backend = 'dop853'

solver = ode(logistic).set_integrator(backend, nsteps=1)
solver.set_initial_value(y0, t0).set_f_params(r)
# suppress Fortran-printed warning
solver._integrator.iwork[2] = -1

sol = []
warnings.filterwarnings("ignore", category=UserWarning)
while solver.t < t1:
    solver.integrate(t1, step=True)
    sol.append([solver.t, solver.y])
warnings.resetwarnings()
sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

结果:

于 2013-01-22T22:01:50.330 回答
8

integrate方法接受一个布尔参数,该参数step告诉该方法返回单个内部步骤。但是,“dopri5”和“dop853”求解器似乎不支持它。

以下代码显示了在使用“vode”求解器时如何获取求解器所采取的内部步骤:

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt


def logistic(t, y, r):
    return r * y * (1.0 - y)

r = .01

t0 = 0
y0 = 1e-5
t1 = 5000.0

backend = 'vode'
#backend = 'dopri5'
#backend = 'dop853'
solver = ode(logistic).set_integrator(backend)
solver.set_initial_value(y0, t0).set_f_params(r)

sol = []
while solver.successful() and solver.t < t1:
    solver.integrate(t1, step=True)
    sol.append([solver.t, solver.y])

sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()

结果: 解的情节

于 2012-10-17T13:36:31.257 回答
2

仅供参考,虽然答案已经被接受,但我应该指出历史记录,PyDSTool 原生支持从计算轨迹的任何地方进行密集输出和任意采样。这还包括求解器内部使用的所有自适应确定的时间步长的记录。这与dopri853 和 radau5接口,并自动生成与它们接口所需的 C 代码,而不是依赖(慢得多)python 函数回调来进行右侧定义。据我所知,任何其他以 python 为中心的求解器都没有原生或有效地提供这些功能。

于 2015-11-04T01:54:21.800 回答
-1

这是另一个也应该与dopri5and一起使用的选项dop853。基本上,求解器将logistic()根据需要经常调用该函数来计算中间值,以便我们存储结果:

import numpy as np
from scipy.integrate import ode
import matplotlib.pyplot as plt

sol = []
def logistic(t, y, r):
    sol.append([t, y])
    return r * y * (1.0 - y)

r = .01

t0 = 0
y0 = 1e-5
t1 = 5000.0
# Maximum number of steps that the integrator is allowed 
# to do along the whole interval [t0, t1].
N = 10000

#backend = 'vode'
backend = 'dopri5'
#backend = 'dop853'
solver = ode(logistic).set_integrator(backend, nsteps=N)
solver.set_initial_value(y0, t0).set_f_params(r)

# Single call to solver.integrate()
solver.integrate(t1)
sol = np.array(sol)

plt.plot(sol[:,0], sol[:,1], 'b.-')
plt.show()
于 2014-12-01T17:10:49.800 回答