关于如何将多步(例如 Runge-Kutta)方法应用于二阶或更高阶 ODE 或 ODE 系统,似乎存在相当多的困惑。一旦你理解了这个过程就非常简单,但如果没有很好的解释,它可能并不明显。下面的方法是我觉得最简单的一种。
在您的情况下,您要解决的微分方程是F=米X¨. 第一步是将这个二阶 ODE 编写为一阶 ODE 系统。这是这样做的
[X˙v˙] = [vF/米]
这个系统中的所有方程必须同时求解,也就是说你不应该前进v然后前进X,它们应该同时被推进。在支持不带循环的向量运算的语言中,这很容易通过在长度为 2 的代码向量中添加所有必要项来完成。计算 ODE 右侧(变化率)的函数应该返回长度为 2 的向量, k1
tok4
应该是长度为 2 的向量,以及你的状态变量( x , v )应该是一个长度为 2 的向量。在 MATLAB 中,时间步长的必要代码可以写成:
while (t<TMAX)
k1 = RHS( t, X );
k2 = RHS( t + dt / 2, X + dt / 2 * k1 );
k3 = RHS( t + dt / 2, X + dt / 2 * k2 );
k4 = RHS( t + dt, X + dt * k3 );
X = X + dt / 6 * ( k1 + 2 * k2 + 2 * k3 + k4 );
t = t + dt;
end
在哪里X
= ( x , v )并RHS( t, X )
返回一个包含(X˙( t ) ,v˙( t ) ). 如您所见,通过向量化事物,您甚至不需要更改 RK4 代码的语法,无论您的 ODE 系统中有多少方程。
不幸的是,C++ 本身并不支持这样的向量操作,因此您需要使用向量库、使用循环或手动写出单独的部分。在 C++ 中你可以使用std::valarray
来达到同样的效果。这是一个恒定加速度的简单工作示例。
#include <valarray>
#include <iostream>
const size_t NDIM = 2;
typedef std::valarray<double> Vector;
Vector RHS( const double t, const Vector X )
{
// Right hand side of the ODE to solve, in this case:
// d/dt(x) = v;
// d/dt(v) = 1;
Vector output(NDIM);
output[0] = X[1];
output[1] = 1;
return output;
}
int main()
{
//initialize values
// State variable X is [position, velocity]
double init[] = { 0., 0. };
Vector X( init, NDIM );
double t = 0.;
double tMax=5.;
double dt = 0.1;
//time loop
int nSteps = round( ( tMax - t ) / dt );
for (int stepNumber = 1; stepNumber<=nSteps; ++stepNumber)
{
Vector k1 = RHS( t, X );
Vector k2 = RHS( t + dt / 2.0, X + dt / 2.0 * k1 );
Vector k3 = RHS( t + dt / 2.0, X + dt / 2.0 * k2 );
Vector k4 = RHS( t + dt, X + dt * k3 );
X += dt / 6.0 * ( k1 + 2.0 * k2 + 2.0 * k3 + k4 );
t += dt;
}
std::cout<<"Final time: "<<t<<std::endl;
std::cout<<"Final position: "<<X[0]<<std::endl;
std::cout<<"Final velocity: "<<X[1]<<std::endl;
}