将Runge-Kutta方法应用于二阶ODE


11

如何用Runge-Kutta第四阶代替Euler方法来确定自由落体运动的重力大小不是恒定的(例如,从地面10000 km处自由落体)?

到目前为止,我通过欧拉方法编写了简单的积分:

while()
{
    v += getMagnitude(x) * dt;
    x += v * dt;
    time += dt;
}

x变量表示当前位置,v表示速度,getMagnitude(x)返回x位置的加速度。

我尝试实现RK4:

while()
{
    v += rk4(x, dt) * dt; // rk4() instead of getMagintude()
    x += v * dt;
    time += dt;
}

rk4()函数体在哪里:

inline double rk4(double tx, double tdt)
{
   double k1 = getMagnitude(tx);
   double k2 = getMagnitude(tx + 0.5 * tdt * k1);
   double k3 = getMagnitude(tx + 0.5 * tdt * k2);
   double k4 = getMagnitude(tx + tdt * k3);

   return (k1 + 2*k2 + 2*k3 + k4)/6.0;
}

但是出了点问题,因为我只使用RK4(加速)进行了一次集成。使用RK4积分速度没有意义,因为它与v * dt相同。

您能告诉我如何使用Runge-Kutta积分求解二阶微分方程吗?我是否应该通过计算k1,l1,k2,l2 ... l4系数来实现RK4?我怎样才能做到这一点?


@Marcin,您好,我编辑了您的标题以更好地反映我认为您的问题所在。我认为我们可能会得到更多有用的答案,并且对于将来使用新标题看到此问题的其他人来说,搜索起来会更容易。如果您不同意,请随时将其更改。
Doug Lipinski'3

Answers:


17

对于如何将多步骤(例如Runge-Kutta)方法应用于二阶或更高阶ODE或ODE系统,似乎存在很多困惑。一旦理解,该过程将非常简单,但是如果没有很好的解释,该过程可能并不明显。以下方法是我发现最简单的方法。

F=mx¨

[x˙v˙]=[vF/m]

vxk1k4(x,v)

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))

不幸的是,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;

}

6
不幸的是C ++本身不支持矢量运算这样的:”我认为是这样,在标准库甚至,但不一定容易与其他线性代数库使用:en.cppreference.com/w/cpp/numeric/valarray我认为常见的线性代数库(例如Eigen)也应算作“支持”。
Kirill'3

1
@Kirill,谢谢。我对C ++还是比较陌生,以前没有使用过valarray,我也学到了一些有用的东西!编辑添加。
Doug Lipinski'3

1
也许此建议对您有帮助,然后:1)使用clang-format自动格式化代码,这确实是标准且统一的。2)typedef std::valarray<double> Vector用于常用类型。3)使用const int NDIM = 2而不是#define为了类型安全和正确性。4)从C ++ 11开始,您可以简单地用替换RHS的主体return {X[1], 1}。5)在C ++中(先于C语言),首先声明变量,然后对其进行初始化,然后更喜欢在初始化变量的同一位置(double t = 0.等)声明变量,这确实并不常见
Kirill

1
@MarcinW。RHS()计算微分方程的右侧。状态向量X为(x,v),因此dX / dt =(dx / dt,dv / dt)=(v,a)。对于您的问题(如果a = G * M / x ^ 2),RHS应该返回{ X[1], G*M/(X[0]*X[0]) }
Doug Lipinski'3

1
@Kirill我知道,但是只能从C ++ 11开始使用,这意味着它不能与大多数流行的编译器上的默认编译器选项一起使用。我选择将其保留,而不再使用那些也适用于旧标准的东西,并希望减少由于无法编译代码而引起的混乱。
Doug Lipinski'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.