Метод Рунге-Кутта 4-го порядка для численного решения дифференциальных уравнений.

Автор: Петров А.
E-MAILspm111@yandex.ru

Алгоритм реализован в виде нескольких программ на языке С.
Опишем сначала, что такое дифференциальные уравнения на примере из механики и введем несколько обозначений.
Представьте себе, что вы бросили камень (можно бросать и гранату, но это негуманно) по направлению вверх и вперед. Из житейского опыта известно, что он полетит вперед по гладкой дуге, сначала вверх, потом вниз. На уроках физики вы может быть решали эту задачу аналитически и узнали, что камень летит по параболе. Естественно, для простоты будем считать, что воздух не оказывает никакого сопротивления камню.

На камень в полете действует только одна сила - сила тяжести. Если масса камня m, то сила тяжести, равна F=mg, где g-ускорение свободного падения, вблизи Земли g=9.81 (м с2)-1

Введем координаты камня. Пусть x - расстояние по горизонтали от места, с которого кидали камень, пусть y - расстояние от поверхности Земли до камня. Условно примем, что в начальный момент времени x=0, y=0. Если мы хотим узнать поведение камня в любой момент времени, естественно, что эти координаты зависят от времени: x(t), y(t). Пусть vx(t), vy(t) - скорость камня в горизонтальном и вертикальном направлении, соответственно. Пусть ax(t), ay(t) - ускорение камня в горизонтальном и вертикальном направлении соответственно.

Воспользуемся вторым законом Ньютона, который связывает ускорение движущегося тела и силы, действующие на него:

F = ma

Здесь F - сумма всех сил, действующих на тело, a - полное ускорение. Обе эти величины векторные. т.е. имеют направление.

Это же уравнение можно записать в проекциях на оси x, y.

Fx = m ax - сила, действующая в горизонтальном направлении.
Fy = m ay - сила действующая в вертикальном направлении.

Сила тяжести действует на тело в вертикальном направлении и направлена вниз. То есть

Fx = m ax = 0
Fy = m ay = - m g

Как известно из курса кинематики скорость, ускорение и координата тела связаны друг с другом. Скорость равна производной координаты по времени. Ускорение равно производной скорости по времени, т.е. ускорение равно второй производной координаты по времени. Далее штрих (') означает производную по времени.

vx = x'
vy = y'
ax = x''
ay = y''

Заменим в уравнениях Ньютона ускорения на скорости и запишем все уравнения в виде системы, где в левой части стоят производные, а в правой части сами переменные.

m v'x = 0
m v'y = - m g
x' = vx
y' = vy

Поделим первое и второе уравнение на m:

x' = vx
y' = vy
v'x = 0
v'y = - g

Полученная система является системой линейных уравнений первой степени. Неизвестными в ней являются x(t), y(t), vx(t), vy(t). Для того, чтобы решение этой системы существовало и было единственным необходимо и достаточно, чтобы были заданы значения неизвестных в начальный момент времени t=0. Действительно, мы их чаще всего знаем. В нашей задаче мы так ввели оси координат, что x(0)=0, y(0)=0. Пусть мы знаем и проекции начальной скорости тела vx(0), vy(0). Чаще всего задают не проекции, а полную скорость тела v и угол к горизонту, под которым тело бросали.

Введем четырехмерный вектор U(x, y, vx, vy). Это может показаться странным, что для такой простой задачи нам понадобились какие-то четырехмерные вектора. Не пугайтесь. Четырехмерных векторов в природе не бывает. Это лишь абстракция, которая поможет нам проще записать решение этой задачи и программу, которая этот метод реализует. Итак, координатами этого вектора являются координаты камня и проекции его скорости. Нашу систему уравнений можно записать в виде:

U' = F(U,t)

где F(U,t) = (vx, vy, 0, -g)

Перейдем к описанию метода решения таких задач. Метода Рунге-Кутта.

Это численный метод. Суть метода состоит в том, чтобы разбить интервал времени, в течение которого нам надо узнать координаты и скорости на некоторое большое количество отрезков времени dt.

Пусть мы знаем значение вектора U в некоторый момент времени t и хотим узнать его в момент времени t+dt.

Методом Эйлера называется метод, в котором
U(t+dt) = U(t) + F(U,t)dt.

Этот метод действительно работает. Посмотрите покомпонентно расписанную эту же запись:

x(t+dt) = x(t) + vxdt
y(t+dt) = y(t) + vydt
vx(t+dt) = vx(t) + axdt
vy(t+dt) = vy(t) + aydt

Эти равенства являются приближенными и следуют из определения производной, Чем меньше dt, тем точнее эти равенства.

Методом Рунге-Кутта 4-го порядка называется метод, в котором
U(t+dt) = U(t) + (k1 + 2k2 + 2k3 + k4)/6.
где

k1 = F(U,t)dt
k2 = F(U+k1/2, t+dt/2)dt
k3 = F(U+k2/2, t+dt/2)dt
k4 = F(U+k3, t+dt)dt

Понимаю, что это довольно абстрактное выражение, непонятное интуитивно, но можете считать, что это тот же метод Эйлера, только уточненный. Более подробно о методе Рунге-Кутта (с доказательствами) можно почитать в книгах по численным методам (например, Калиткин Н.Н., Численные методы).

Напишем часть кода на С++, который позволит нам реализовать этот метод:

class vector{
   public:
   float x, y, vx, vy;
   vector(){
       x = 0;
       y = 0;
       vx = 0;
       vy = 0;
   }
   vector(float ax, float ay, float avx, float avy){
       x = ax;
       y = ay;
       vx = avx;
       vy = avy;
   }
   void out(){
       printf("x=%f\t", x);
       printf("y=%f\t", y);
       printf("vx=%f\t", vx);
       printf("vy=%f\t", vy);
   }
};
vector operator +(vector a, vector b){
   vector c;
   c.x = a.x + b.x;
   c.y = a.y + b.y;
   c.vx = a.vx + b.vx;
   c.vy = a.vy + b.vy;
   return(c);
}
vector operator -(vector a, vector b){
   vector c;
   c.x = a.x - b.x;
   c.y = a.y - b.y;
   c.vx = a.vx - b.vx;
   c.vy = a.vy - b.vy;
   return(c);
}
vector operator *(float a, vector b){
   vector c;
   c.x = a * b.x;
   c.y = a * b.y;
   c.vx = a * b.vx;
   c.vy = a * b.vy;
   return(c);
}
vector operator *(vector b, float a){
   vector c;
   c.x = a * b.x;
   c.y = a * b.y;
   c.vx = a * b.vx;
   c.vy = a * b.vy;
   return(c);
}

В данном участке кода мы ввели класс под названием vector. У него есть 4 компонента: x, y, vx, vy. Далее мы ввели операции сложения и вычитания таких векторов (покомпонентное сложение или вычитание) а также операции умножения вектора на число и числа на вектор (которые совпадают с точностью до перестановки множителей местами).

Напишем функцию F(U,t) для нашей задачи с камнем:

vector F(vector U, float t){
   vector res;
   float g = 9.81;
   res.x = U.vx;
   res.y = U.vy;
   res.vx = 0;
   res.vy = - g;
   return(res);
}

Дальше нам осталось только написать главную часть программы, которая будет использовать этот код:

#include <stdio.h>
void main(){
   float t=0, dt = 0.05;
   float vx = 1, vy = 2;
   float x=0, y=0;
   vector U(x, y, vx, vy);
   vector k1, k2, k3, k4;
   while(U.y >= 0){
       k1 = F(U, t)*dt;
       k2 = F(U + 0.5*k1, t+0.5*dt)*dt;
       k3 = F(U + 0.5*k2, t+0.5*dt)*dt;
       k4 = F(U + k3, t+dt)*dt;
       U = U + 1.0/6.0 * (k1 + 2*k2 + 2*k3 + k4);
       t += dt;       
       printf("t=%f   x=%f  y=%f\n", t, U.x, U.y);
   }
}

Здесь мы бросаем камень со скоростью 1 м/с по горизонтали и 2 м/с по вертикали. Интервал времени равен 0.05 секунды. Программа печатает время, прошедшее со старта камня и его координаты x, y.

При заданных условиях программа напишет следующее:

t=0.050000   x=0.050000  y=0.087738
t=0.100000   x=0.100000  y=0.150950
t=0.150000   x=0.150000  y=0.189637
t=0.200000   x=0.200000  y=0.203800
t=0.250000   x=0.250000  y=0.193437
t=0.300000   x=0.300000  y=0.158550
t=0.350000   x=0.350000  y=0.099137
t=0.400000   x=0.400000  y=0.015200
t=0.450000   x=0.450000  y=-0.093263

Мы видим, что за 0.4 секунды камень пролетит на 0.4 метра вперед и упадет на землю (в следующий момент времени его координата y является отрицательной, что означает, что камень упал на землю). Максимальная высота подъема камня над землей составит приблизительно 0.2 метра. Эти результаты в очень хорошо совпадают с расчетными.

Надеюсь, вам все стало понятно, и вы сможете применить эти методы в своих расчетах.

Назад