====== Методы решения дифференциальных уравнений ====== Данная статья посвящена численным методам решения дифференциальных уравнений. Мы будем рассматривать методы решения одного обыкновенного дифференциального уравнения первого порядка с одним начальным условием: y' = f(x,y) (1) y(x[0]) = y[0] (2) Методы, которые здесь рассматриваются, легко обобщаются для системы уравнений первого порядка. А уравнения высших порядков можно свести к системе уравнений первого порядка. === В основном существуют два широких класса методов === * Одноступенчатые методы, в которых используется только информация о самой кривой в одной точке и не производятся итерации. Один из методов - решение с помощью рядов Тейлора, но на практике он не слишком удобен для использования.\\ Практически удобные методы этого класса - методы //Рунге-Кутта//. Эти методы прямые (без итераций), но требуют многократных повторных вычислений функции.\\ При использовании данных методов трудно оценивать допускаемую ошибку. * Многоступенчатые методы, в которых следующую точку кривой можно найти, не производя так много повторных вычислений, но для достижения достаточной точности требуются итерации. Большинство методов этого класса называются методами прогноза и коррекции (метод //Адамса-Бошфора//).\\ Некоторые трудности, связанные с использованием итерационной процедуры и с использованием нескольких начальных точек уравновешиваются тем фактом, что оценку ошибки при использовании этого метода легко получить в качестве побочного продукта вычислений. Как и во многих других случаях, эти два класса методов придется сочетать разумным образом, учитывая их достоинства и недостатки. ==== 1. Методы Рунге-Кутта ==== Методы Рунге-Кутта обладают следующими отличительными свойствами: * Эти методы одноступенчатые: чтобы найти //y[m+1]//, нужна информация только о предыдущей точке //x[m],y[m]//. * Они согласуются с рядом Тейлора вплоть до членов порядка //h^p//, где //p// - различна для разных методов и называется порядком метода. * Они не требуют вычисления производных от //f(x,y)//, а требуют только вычисления самой функции. Именно благодаря //3)// эти методы удобны для практических вычислений, однако для вычисления одной последующей точки решения нам придется вычислять //f(x,y)// несколько раз при различных x и y. === Метод Эйлера === Этот метод, один из самых старых и широко известных, описывается формулой: y[m+1] = y[m] + h*f(x[m],y[m]). (3) Найденное по формуле (3) решение согласуется с разложением в ряд Тейлора вплоть до членов порядка h, т.е. данный метод является методом Рунге-Кутта первого порядка.\\ Этот метод имеет довольно большую ошибку приближения, кроме того, он очень часто оказывается неустойчивым - изначально малая ошибка (происходящая от приближения, округления или исходных данных) увеличивается с ростом x.\\ Для вычисления //y[m+1]// метод Эйлера использует наклон касательной только в точке //x[m],y[m]//. Этот метод можно усовершенствовать множеством различных способов.\\ Рассмотрим два из них. === Исправленный метод Эйлера === В исправленном методе Эйлера мы находим средний tg угла наклона касательной для двух точек: ////x[m],y[m]// и //x[m+1],y[m]+h*y'[m]////. Соотношения, описывающие данный метод, имеют вид: y[m+1] = y[m] + h*F(x[m],y[m],h) (4) F(x[m],y[m],h) = ( y'[m] + f(x[m]+h, y[m]+h*y'[m]) )/2 (5) y'[m] = f(x[m],y[m]) (6) Исправленный метод Эйлера согласуется с разложением в ряд Тейлора вплоть до членов степени //h^2//, являясь, т.о. методом Рунге-Кутта второго порядка. === Модифицированный метод Эйлера === В данном методе мы находим tg угла наклона касательной в точке: x = x[m] + h/2; y = y[m] + (h/2)*y'[m] Соотношения, описывающие модифицированный метод Эйлера имеют вид: y[m+1] = y[m] + h*F(x[m],y[m],h) (7) F(x[m],y[m],h) = f( x[m]+h/2, y[m]+(h/2)*y'[m] ) (8) y'[m] = f(x[m],y[m]) (9) Модифицированный метод Эйлера также согласуется с разложением в ряд Тейлора вплоть до членов степени //h^2//, и также является методом Рунге-Кутта второго порядка. === Метод Рунге-Кутта четвертого порядка === Данный метод является одним из самых употребительных методов интегрирования дифференциальных уравнений. Этот метод применяется настолько широко, что в литературе его просто называют "методом Рунге-Кутта" без всяких указаний на его тип или порядок. Этот классический метод описывается системой следующих пяти соотношений: y[m+1] = y[m] + h*(k[1] + 2*k[2] + 2*k[3] + k[4])/6 (10) k[1] = f(x[m], y[m]) (11) k[2] = f(x[m]+h/2, y[m]+h*k[1]/2) (12) k[3] = f(x[m]+h/2, y[m]+h*k[2]/2) (13) k[4] = f(x[m]+h, y[m]+h*k[3]) (14) Ошибка приближения для этого метода равна //e[t]=k*h^5//. Заметим, что при использовании этого метода функцию необходимо вычислять четыре раза. Один из серьезных недостатков методов Рунге-Кутта состоит в отсутствии простых способов оценки их ошибки. ==== 2. Методы прогноза и коррекции ==== Отличительной чертой методов Рунге-Кутта является то, что при вычислении следующей точки //(x[m+1],y[m+1])// используется информация только об одной предыдущей точке //(x[m],y[m])//, но не нескольких. Кроме того, для методов Рунге-Кутта отсутствуют достаточно простые способы оценки ошибки, что приводит к необходимости рассмотрения некоторых дополнительных методов решения ДУ.\\ Отличительное свойство этих методов состоит в том, что с их помощью нельзя начать решение уравнения т.к. в них необходимо использовать информацию о предыдущих точках решения. Чтобы начать решение, имея только одну точку, определяемую начальными условиями, или для того, чтобы изменить шаг //(h)//, необходим метод типа Рунге-Кутта. Поэтому приходится использовать разумное сочетание этих двух методов.\\ Методы, которые мы рассмотрим, известны под общим названием методов прогноза и корректировки. Как ясно из названия вначале "предсказывается" значение //y[m+1]//, а затем используется тот или иной метод его "корректировки".\\ Среди множества возможных формул прогноза и коррекции выберем по одному примеру, применимому ко многим практическим задачам. === Метод Адамса-Бошфора === Для прогноза используем формулу второго порядка y^(0)_[m+1] = y[m-1] + 2*h*f(x[m],y[m]) (15) , где (0) - означает исходное приближение //y[m+1]//, т.е. предсказанное значение. Непосредственно из написанной формулы следует, что с ее помощью нельзя вычислить //y[1]//, т.к. для его вычисления потребовалась бы точка, расположенная перед начальной точкой //y[0]//. Чтобы начать решение с помощью метода прогноза и коррекции, для нахождения //y[1]// необходимо использовать метод типа Рунге-Кутта. Для коррекции возьмем формулу, похожую на исправленный метод Эйлера (4)-(6): y^(i)_[m+1] = y[m] + h(f(x[m],y[m])+f(x[m+1],y^(i-1)_[m+1]))/2 (16) для i=1,2,3, ... Итерационный процесс прекращается, когда | y^(i+1)_[m+1] - y^(i)_[m+1] | < Eps (17) для некоторого //Eps>0//. {$E+,N+,X+} const eps = 1e-5; x0 = 3; y0 = 3; type TFunc = function(x,y: extended): extended; function func(x,y: extended): extended; far; begin func := y*cos(x) - 2*sin(2*x); end; function Euler(f: TFunc; x0_,x_end,y0_: extended; n: word): extended; { где x0_ и y0_ - начальное условие x_end - точка, в которой необходимо вычислить результат n - количество шагов для вычисления результата } var i : word; { счетчик цикла } x,h : extended; { текущая точка и длина шага } res : extended; { переменная для накопления конечного результата функции} begin h:= (x_end - x0_)/n; { Находим длину шага } res:= y0_; { устанавливаем начальные значения} x:=x0_; for i:=1 to n do begin { вычисляем результат по методу Эйлера } res:=res+h*f(x,res); x:=x+h; { переходим к следующей точке } end; Euler:=res; { присваиваем конечный результат функции } end; function Euler2(f: TFunc; x0_,x_end,y0_: extended; n: word): extended; { где x0_ и y0_ - начальное условие x_end - точка, в которой необходимо вычислить результат n - количество шагов для вычисления результата } var i : word; { счетчик цикла } x,h : extended; { текущая точка и длина шага } res : extended; { переменная для накопления конечного результата функции} begin h:= (x_end - x0_)/n; { Находим длину шага } res:= y0_; { устанавливаем начальные значения} x:=x0_; for i:=1 to n do begin { вычисляем результат по исправленному методу Эйлера } res:=res+h*(f(x,res)+f(x+h,res+h*f(x,res)))/2; x:=x+h; { переходим к следующей точке } end; Euler2:=res; { присваиваем конечный результат функции } end; function Euler3(f: TFunc; x0_,x_end,y0_: extended; n: word): extended; { где x0_ и y0_ - начальное условие x_end - точка, в которой необходимо вычислить результат n - количество шагов для вычисления результата } var i : word; { счетчик цикла } x,h : extended; { текущая точка и длина шага } res : extended; { переменная для накопления конечного результата функции} begin h:= (x_end - x0_)/n; { Находим длину шага } res:= y0_; { устанавливаем начальные значения} x:=x0_; for i:=1 to n do begin { вычисляем результат по модифицированному методу Эйлера } res:=res+h*f(x+h/2,res+(h/2)*f(x,res)); x:=x+h; { переходим к следующей точке } end; Euler3:=res; { присваиваем конечный результат функции } end; function RungeKutt(f: TFunc; x0_,x_end,y0_: extended; n: word): extended; { где x0_ и y0_ - начальное условие x_end - точка, в которой необходимо вычислить результат n - количество шагов для вычисления результата } var i : word; { счетчик цикла } x,h : extended; { текущая точка и длина шага } res : extended; { переменная для накопления конечного результата функции } k1,k2,k3,k4: extended; { вспомогательные переменные вычисления результата } begin h:= (x_end - x0_)/n; { Находим длину шага } res:= y0_; { устанавливаем начальные значения} x:=x0_; for i:=1 to n do begin { вычисляем результат по методу Рунге-Кутта 4го порядка } k1:=f(x,res); k2:=f(x+h/2,res+h*k1/2); k3:=f(x+h/2,res+h*k2/2); k4:=f(x+h,res+h*k3); res:=res+h*(k1+2*k2+2*k3+k4)/6; x:=x+h; { переходим к следующей точке } end; RungeKutt:=res; { присваиваем конечный результат функции } end; function AdmsBoshf(f: TFunc; x0_,x_end,y0_: extended; n: word): extended; { где x0_ и y0_ - начальное условие x_end - точка, в которой необходимо вычислить результат n - количество шагов для вычисления результата } var i : word; { счетчик цикла } x,h : extended; { текущая точка и длина шага } y1,y2 : extended; { переменные для вычисления следующей точки } tmp : extended; { временная переменная для уточнения результата } begin h:= (x_end - x0_)/n; { Находим длину шага } x:=x0_; { устанавливаем текущую точку } y1:=y0_+h*f(x+h/2,y0_+(h/2)*f(x,y0_)); { находим начальное значение модифицированным методом Эйлера } repeat { уточняем(корректируем) начальное значение } tmp:=y1; y1:=y0_+h*(f(x,y0_)+f(x+h,y1))/2; until abs(y1-tmp) < eps; x:=x+h; { переходим к следующей точке } y2:=y0_+2*h*f(x,y1); { прогнозируенм следующее значение } repeat { уточнаяем наш прогноз } tmp:=y2; y2:=y1+h*(f(x,y1)+f(x+h,y2))/2; until abs(y2-tmp) < eps; for i:=3 to n do { начинаем счет с 3 т.к. две точки уже найдены } begin x0_:=x; { переходим к следующей точке } x:=x+h; y0_:=y1; y1:=y2; y2:=y0_+2*h*f(x,y1); { прогнозируенм следующее значение } repeat { уточнаяем наш прогноз } tmp:=y2; y2:=y1+h*(f(x,y1)+f(x+h,y2))/2; until abs(y2-tmp) < eps; end; AdmsBoshf:=y2; { присваиваем конечный результат функции } end; begin writeln('Численное решение дифференциальных уравнений:'); writeln(#10,' y'' = y*cos(x) - 2*sin(2*x); y(0)=3; x_end=',(5*x0+3.5):5:5); writeln(#10,'Метод Эйлера:'); writeln('n=5 000, result: ',Euler(func,x0,5*x0+3.5,y0,5000):5:5); writeln('n=10 000, result: ',Euler(func,x0,5*x0+3.5,y0,10000):5:5); writeln('n=25 000, result: ',Euler(func,x0,5*x0+3.5,y0,25000):5:5); writeln(#10,'Исправленный метод Эйлера:'); writeln('n=50, result: ',Euler2(func,x0,5*x0+3.5,y0,50):5:5); writeln('n=100, result: ',Euler2(func,x0,5*x0+3.5,y0,100):5:5); writeln('n=250, result: ',Euler2(func,x0,5*x0+3.5,y0,250):5:5); writeln(#10,'Модифицированный метод Эйлера:'); writeln('n=50, result: ',Euler3(func,x0,5*x0+3.5,y0,50):5:5); writeln('n=100, result: ',Euler3(func,x0,5*x0+3.5,y0,100):5:5); writeln('n=250, result: ',Euler3(func,x0,5*x0+3.5,y0,250):5:5); writeln(#10,'Метод Рунге-Кутта:'); writeln('n=50, result: ',RungeKutt(func,x0,5*x0+3.5,y0,50):5:5); writeln('n=100, result: ',RungeKutt(func,x0,5*x0+3.5,y0,100):5:5); writeln('n=250, result: ',RungeKutt(func,x0,5*x0+3.5,y0,250):5:5); write(#10,'Press Enter to continue.'); readln; writeln(#10,'Метод Адамса-Бошфора:'); writeln('n=50, result: ',AdmsBoshf(func,x0,5*x0+3.5,y0,50):5:5); writeln('n=100, result: ',AdmsBoshf(func,x0,5*x0+3.5,y0,100):5:5); writeln('n=250, result: ',AdmsBoshf(func,x0,5*x0+3.5,y0,250):5:5); end. Автор статьи: Labinskiy Nikolay aka e-moe (c) 2005