我正在寻找一种设置固定步长的方法,以通过 Python 中的 Runge-Kutta 方法解决我的初始值问题。因此,我如何告诉scipy.integrate.RK45它的集成过程保持不断更新(步长)?
非常感谢。
我正在寻找一种设置固定步长的方法,以通过 Python 中的 Runge-Kutta 方法解决我的初始值问题。因此,我如何告诉scipy.integrate.RK45它的集成过程保持不断更新(步长)?
非常感谢。
为 Dormand-Prince RK45 方法编写 Butcher 画面非常容易。
0
1/5 | 1/5
3/10 | 3/40 9/40
4/5 | 44/45 −56/15 32/9
8/9 | 19372/6561 −25360/2187 64448/6561 −212/729
1 | 9017/3168 −355/33 46732/5247 49/176 −5103/18656
1 | 35/384 0 500/1113 125/192 −2187/6784 11/84
-----------------------------------------------------------------------------------------
| 35/384 0 500/1113 125/192 −2187/6784 11/84 0
| 5179/57600 0 7571/16695 393/640 −92097/339200 187/2100 1/40
首先在一个函数中进行单步 import numpy as np
def DoPri45Step(f,t,x,h):
k1 = f(t,x)
k2 = f(t + 1./5*h, x + h*(1./5*k1) )
k3 = f(t + 3./10*h, x + h*(3./40*k1 + 9./40*k2) )
k4 = f(t + 4./5*h, x + h*(44./45*k1 - 56./15*k2 + 32./9*k3) )
k5 = f(t + 8./9*h, x + h*(19372./6561*k1 - 25360./2187*k2 + 64448./6561*k3 - 212./729*k4) )
k6 = f(t + h, x + h*(9017./3168*k1 - 355./33*k2 + 46732./5247*k3 + 49./176*k4 - 5103./18656*k5) )
v5 = 35./384*k1 + 500./1113*k3 + 125./192*k4 - 2187./6784*k5 + 11./84*k6
k7 = f(t + h, x + h*v5)
v4 = 5179./57600*k1 + 7571./16695*k3 + 393./640*k4 - 92097./339200*k5 + 187./2100*k6 + 1./40*k7;
return v4,v5
然后在标准的固定步长循环中
def DoPri45integrate(f, t, x0):
N = len(t)
x = [x0]
for k in range(N-1):
v4, v5 = DoPri45Step(f,t[k],x[k],t[k+1]-t[k])
x.append(x[k] + (t[k+1]-t[k])*v5)
return np.array(x)
然后用已知的精确解决方案测试一些玩具示例y(t)=sin(t)
def mms_ode(t,y): return np.array([ y[1], sin(sin(t))-sin(t)-sin(y[0]) ])
mms_x0 = [0.0, 1.0]
并绘制按比例缩放的误差h^5
for h in [0.2, 0.1, 0.08, 0.05, 0.01][::-1]:
t = np.arange(0,20,h);
y = DoPri45integrate(mms_ode,t,mms_x0)
plt.plot(t, (y[:,0]-np.sin(t))/h**5, 'o', ms=3, label = "h=%.4f"%h);
plt.grid(); plt.legend(); plt.show()
确认这确实是 5 阶方法,因为误差系数的图表非常接近。
Scipy.integrate 通常与可变步长方法一起使用,通过在数值积分时控制 TOL(一步误差)。TOL 通常通过用另一种数值方法检查来计算。例如 RK45 使用 5 阶 Runge-Kutta 来检查 4 阶 Runge-Kutta 方法的 TOL 以确定积分步长。
因此,如果您必须将 ODE 与固定步长集成,只需通过将 atol、rtol 设置为相当大的常数来关闭 TOL 检查。例如,像这样的形式:
solve_ivp(your function, t_span=[0, 10], y0=..., method="RK45", max_step=0.01, atol = 1, rtol = 1)
TOL 检查设置得如此之大,以至于积分步长将是您选择的 max_step。
通过查看step 的实现,您会发现最好的方法是通过在h_abs调用之前设置属性来控制初始步长(在最小和最大步长设置的范围内) RK45.step:
In [27]: rk = RK45(lambda t, y: t, 0, [0], 1e6)
In [28]: rk.h_abs = 30
In [29]: rk.step()
In [30]: rk.step_size
Out[30]: 30.0
如果您对数据修复步长感兴趣,那么我强烈建议您使用该scipy.integrate.solve_ivp函数及其t_eval参数。
此函数将所有 ode 求解器封装scipy.integrate在一个函数中,因此您必须通过为其method参数赋值来选择方法。幸运的是,默认方法是 RK45,因此您不必为此烦恼。
对你来说更有趣的是t_eval参数,你必须给出一个平面数组。该函数在值处对解曲线进行采样,t_eval并仅返回这些点。因此,如果您想要按步长进行统一采样,那么只需给出以下t_eval参数:numpy.linspace(t0, tf, samplingResolution),其中 t0 是模拟的开始,tf 是模拟的结束。因此,您可以进行统一采样,而不必采用会导致某些 ODE 不稳定的固定步长。
我建议在py中编写自己的rk4定步程序。有许多互联网示例可以提供帮助。这可以保证您准确地知道每个值是如何计算的。此外,通常不会有 0/0 计算,如果是这样,它们将很容易追踪并提示再次查看正在求解的 ode。
你说过你想要一个固定时间步的行为,而不仅仅是一个固定的评估时间步。因此,如果您不想自己重新实现求解器,则必须“破解”您的方式。只需将积分公差 atol 和 rtol 设置为 1e90,并将 max_step 和 first_step 设置为您要使用的时间步长的值 dt。这样,估计的积分误差总是很小,从而欺骗求解器不动态地缩小时间步长。
但是,只能将此技巧与 EXPLICIT 算法(RK23,RK45,DOP853) 一起使用!来自“solve_ivp”的隐式算法(Radau、BDF,也可能是 LSODA)根据 atol 和 rtol 调整非线性牛顿求解器的精度,因此您最终可能会得到一个没有任何意义的解决方案......