Застосування методу Runge-Kutta до ODE другого порядку


11

Як я можу замінити метод Ейлера на Runge-Kutta 4-го порядку, щоб визначити рух вільного падіння в не постійній гравітаційній величині (наприклад, вільне падіння з 10 000 км над землею)?

Поки я писав просту інтеграцію методом Ейлера:

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.

Не могли б ви сказати мені, як розв’язати диференціальні рівняння другого порядку за допомогою інтеграції Рунге-Кутти? Чи слід реалізувати RK4 шляхом обчислення коефіцієнтів k1, l1, k2, l2 ... l4? Як я можу це зробити?


Привіт @Marcin, я відредагував твій заголовок, щоб краще відобразити те, на що я думаю, що ця проблема є насправді. Я думаю, що ми можемо отримати більш корисні відповіді, і буде іншим, хто побачить це питання в майбутньому з новою назвою, буде більше пошуку. Не соромтеся змінити його назад, якщо ви не погоджуєтесь.
Дуг Ліпінський

Відповіді:


17

Здається, існує досить велика плутанина щодо того, як застосувати багатоступінчасті (наприклад, Runge-Kutta) методи до ОРД 2-го чи вищого порядку або систем ОДЕ. Коли ви зрозумієте це, процес дуже простий, але, мабуть, не очевидний без хорошого пояснення. Наступний метод - той, який я вважаю найпростішим.

Ж=мх¨

[х˙v˙]=[vЖ/м]

vхk1k4(х,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=(х,v)RHS( t, X )(х˙(т),v˙(т))

На жаль, 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, також слід вважати "підтримкою".
Кирило

1
@Kirill, Дякую за пораду. Я все ще відносно новий в C ++ і раніше не користувався valarray, я просто навчився чогось корисного! Редагування для додання.
Дуг Ліпінський

1
Можливо, ця порада також буде корисною тоді: 1) Використовуйте кланг-формат для автоматичного форматування коду, він дійсно стандартний і рівномірний. 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.тощо)
Кирило

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]) }.
Дуг Ліпінський

1
@Kirill Я знаю, але це працює лише з C ++ 11, а це означає, що він не працює з типовими параметрами компілятора на найпопулярніших компіляторах. Я вирішив залишити це на користь того, що працює зі старими стандартами, і, сподіваюся, зменшить плутанину, спричинену неможливістю скласти код.
Дуг Ліпінський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.