Рассмотрим задачу Коши для системы обыкновенных дифференциальных уравнений $$ \begin{align} \tag{1} \frac{d u_i}{d t} &= f_i (t, u_1, u_2, \ldots, u_n), \quad t > 0\\ \tag{2} u_i(0) &= u_i^0, \quad i = 1, 2, \ldots, m. \end{align} $$
Используя векторные обозначения, задачу (1), (2) можно записать как задачу Коши $$ \begin{align} \tag{3} \frac{d \pmb{u}}{d t} &= \pmb{F}(t, \pmb{u}), \quad t > 0, \\ \tag{4} \pmb{u}(0) &= \pmb{u}_0 \end{align} $$ В задаче Коши необходимо по известному решению в точке \( t = 0 \) необходимо найти из уравнения (3) решение при других \( t \).
Существует большое количество методов численного решения задачи (3), (4). Вначале рассмотрим простейший явный метод Эйлера и его программную реализацию. Затем будут представлены методы Рунге—Кутта и многошаговые методы.
При построении численных алгоритмов будем считать, что решение этой дифференциальной задачи существует, оно единственно и обладает необходимыми свойствами гладкости.
Идея численных методов решения задачи (3), (4) состоит из четырех частей:
1. Вводится расчетная сетка по переменной \( t \) (время) из \( N_t + 1 \) точки \( t_0 \), \( t_1 \), \( \ldots \), \( t_{N_t} \). Нужно найти значения неизвестной функции \( \pmb{u} \) в узлах сетки \( t_n \). Обозначим через \( \pmb{y}^n \) приближенное значение \( \pmb{u}(t_n) \).
2. Предполагаем, что дифференциальное уравнение выполнено в узлах сетки.
3. Аппроксимируем производные конечными разностями.
4. Формулируем алгоритм, который вычисляет новые значения \( \pmb{y}^{n+1} \) на основе предыдущих вычисленных значений \( \pmb{y}^k \), \( k < n \).
Метод сходится в точке \( t_n \) , если \( |\pmb{y}^n - \pmb{u}(t_n)| \to 0 \) при \( \tau \to 0 \). Метод имеет \( p \)-ый порядок точности, если \( |\pmb{y}^n - \pmb{u}(t_n)| = O(\tau^p) \), \( p > 0 \) при \( \tau\to 0 \).
Проиллюстрируем указанные шаги. Для начала введем расчетную сетку. Очень часто сетка является равномерной, т.е. имеет одинаковое расстояние между узлами \( t_n \) и \( t_{n+1} \): $$ \omega_\tau = \{ t_n = n \tau, n = 0, 1, \ldots, N_t \}. $$
Затем, предполагаем, что уравнение выполнено в узлах сетки, т.е.: $$ \pmb{u}^\prime (t_n) = \pmb{F}(t_n, u(t_n)), \quad t_n \in \omega_\tau. $$
Заменяем производные конечными разностями. С этой целью, нам нужно знать конкретные формулы, как производные могут быть аппроксимированы конечными разностями. Простейший подход заключается в использовании определения производной: $$ \pmb{u}^\prime(t) = \lim_{\tau \to 0} \frac{\pmb{u}(t+\tau) - \pmb{u}(t)}{\tau}. $$
В произвольном узле сетки \( t_n \) это определение можно переписать в виде: $$ \begin{equation*} \pmb{u}^\prime(t_n) = \lim_{\tau \to 0} \frac{\pmb{u}(t_n+\tau) - \pmb{u}(t_n)}{\tau}. \end{equation*} $$ Вместо того, чтобы устремлять шаг сетки к нулю, мы можем использовать малый шаг \( \tau \), который даст численное приближение \( u^\prime(t_n) \): $$ \begin{equation*} \pmb{u}^\prime(t_n) \approx \frac{\pmb{u}^{n+1} - \pmb{u}^{n}}{\tau}. \end{equation*} $$ Такая аппроксимация известна как разностная производная вперед и имеет первый порядок по \( \tau \), т.е. \( O(\tau) \). Теперь можно использовать аппроксимацию производной. Таким образом получим явный метод Эйлера: $$ \begin{equation} \tag{5} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \pmb{F}(t_n, \pmb{y}^{n}). \end{equation} $$
Четвертый шаг заключается в получении численного алгоритма. Из (5) следует, что мы должны знать значение \( y^n \) для того, чтобы решить уравнение (5) относительно \( y^{n+1} \) и получить формулу для нахождения приближенного значения искомой функции на следующем временном слое \( t_{n+1} \): $$ \begin{equation} \tag{6} \pmb{y}^{n+1} = \pmb{y}^n + \tau \pmb{F}(t_n, \pmb{y}^{n}) \end{equation} $$
При условии, что у нас известно начальное значение \( \pmb{y}^0 = \pmb{u}_0 \), мы можем использовать (6) для нахождения решений на последующих временных слоях.
Выражение (6) может быть как скалярным так и векторным уравнением. И в скалярном и в векторном случае на языке Python его можно реализовать следующим образом
u[n+1] = u[n] + tau*F_(t[n], u[n])
При решении системы (векторный случай), u[n]
— одномерный массив
numpy
длины \( m+1 \) (\( m \) — размерность задачи), а функция F
должна
возвращать numpy
-массив размерности \( m+1 \), t[n]
— значение в
момент времени \( t_n \).
Таким образом численное решение на отрезке \( [0, T] \) должно быть
представлено двумерным массивом, инициализируемым нулями
u = np.zeros((N_t+1, m+1))
. Первый индекс соответствует временному
слою, а второй компоненте вектора решения на соответствующем временном
слое. Использование только одного индекса, u[n]
или, что то же
самое, u[n, :]
, соответствует всем компонентам вектора решения.
Функция euler
решения системы уравнений реализована в файле
euler.py:
import numpy as np
import matplotlib.pyplot as plt
def euler(F, u0, tau, T):
N_t = int(round(T/tau))
F_ = lambda t, u: np.asarray(F(t, u))
t = np.linspace(0, N_t*tau, N_t+1)
u = np.zeros((N_t+1, len(u0)))
u[0] = np.array(u0)
for n in range(N_t):
u[n+1] = u[n] + tau*F_(t[n], u[n])
return u, t
Строка F_ = lambda ...
требует пояснений. Для пользователя,
решающего систему ОДУ, удобно задавать функцию правой части в виде
списка компонент. Можно, конечно, требовать чтобы пользователь
возвращал из функции массив numpy
, но очень легко осуществлять
преобразование в самой функции решателе. Чтобы быть уверенным, что
результат F
будет нужным массивом, который можно использовать в
векторных вычислениях, мы вводим новую функцию F_
, которая вызывает
пользовательскую функцию F
«прогоняет» результат через функцию
assaray
модуля numpy
.
При построении неявного метода Эйлера значение функции \( F \) берется на новом временном слое, т.е. для решении задачи (5) используется следующий метод: $$ \begin{equation*} \tag{7} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \pmb{F}(t_{n+1}, \pmb{y}^{n+1}). \end{equation*} $$
Таким образом для нахождения приближенного значения искомой функции на новом временном слое \( t_{n+1} \) нужно решить нелинейное уравнение относительно \( \pmb{y}^{n+1} \): $$ \begin{equation} \tag{8} \pmb{y}^{n+1} - \tau \pmb{F}(t_{n+1}, \pmb{y}^{n+1}) - y^n = 0. \end{equation} $$
Для решения уравнения (8) можно использовать, например, метод Ньютона.
Функция backward_euler
решения системы уравнений реализована в файле
euler.py:
def backward_euler(F, u0, tau, T):
from scipy import optimize
N_t = int(round(T/tau))
F_ = lambda t, u: np.asarray(F(t, u))
t = np.linspace(0, N_t*tau, N_t+1)
u = np.zeros((N_t+1, len(u0)))
u[0] = np.array(u0)
def Phi(z, t, v):
return z - tau*F_(t, z) - v
for n in range(N_t):
u[n+1] = optimize.fsolve(Phi, u[n], args=(t[n], u[n]))
return u, t
Отметим, что для нахождения значения u[n+1]
используется функция
fsolve
модуля optimize
библиотеки scipy
. В качестве начального
приближения для решения нелинейного уравнения используется значение
искомой функции с предыдущего слоя u[n]
.
Одношаговый метод Рунге—Кутта в общем виде записывается следующим образом: $$ \begin{equation} \tag{9} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \sum_{i = 1}^s b_i \pmb{k}_i, \end{equation} $$ где $$ \begin{equation} \tag{10} \pmb{k}_i = \pmb{F}\left( t_n + c_i\tau, \pmb{y}^n + \tau \sum_{j=1}^s a_{ij}\pmb{k}_j \right), \quad i = 1, 2, \ldots, s. \end{equation} $$ Формула (9) основана на \( s \) вычислениях функции \( \pmb{F} \) и называется \( s \)-стадийной. Если \( a_{ij} = 0 \) при \( j \geq i \) имеем явный метод Рунге—Кутта. Если \( a_{ij} = 0 \) при \( j > i \) и \( a_{ii} \ne 0 \), то \( \pmb{k}_i \) определяется неявно из уравнения $$ \begin{equation} \tag{11} \pmb{k}_i = \pmb{F}\left( t_n + c_i\tau, \pmb{y}^n + \tau \sum_{j=1}^{i-1} a_{ij}\pmb{k}_j + \tau a_{ii} \pmb{k}_i \right), \quad i = 1, 2, \ldots, s. \end{equation} $$ О таком методе Рунге—Кутта говорят как о диагонально-неявном.
Одним из наиболее распространенных является явный метод Рунге-Кутта четвертого порядка: $$ \begin{align*} \tag{12} \pmb{k}_1 & = \pmb{F}(t_n, \pmb{y}^n), &\quad \pmb{k}_2 &= \pmb{F}\left( t_n + \frac{\tau}{2}, \pmb{y}^n + \tau \frac{\pmb{k}_1}{2} \right),\\ \pmb{k}_3 &= \pmb{F}\left( t_n + \frac{\tau}{2}, \pmb{y}^n + \tau \frac{\pmb{k}_2}{2} \right), &\quad \pmb{k}_4 &= \pmb{F}\left( t_n + \tau, \pmb{y}^n + \tau \pmb{k}_3 \right),\\ \frac{\pmb{y}^{n+1} -\pmb{y}^n}{\tau} &= \frac{1}{6} (\pmb{k}_1 + 2\pmb{k}_2 + 2\pmb{k}_3 + \pmb{k}_4) & & \end{align*} $$
В методах Рунге—Кутта в вычислениях участвуют значения приближенного решения только в двух соседних узлах \( \pmb{y}^n \) и \( \pmb{y}^{n+1} \) — один шаг по переменной \( t \). Линейный \( m \)-шаговый разностный метод записывается в виде $$ \begin{equation} \tag{13} \frac{1}{\tau} \sum_{i=0}^m a_i \pmb{y}^{n+1-i} = \sum_{i=0}^{m} b_i \pmb{F}(t_{n+1-i}, \pmb{y}^{n+1-i}), \quad n = m-1, m, \ldots \end{equation} $$ Вариант численного метода определяется заданием коэффициентов \( a_i \), \( b_i \), \( i = 0, 1, \ldots, m \), причем \( a_0 \ne 0 \). Для начала расчетов по рекуррентной формуле (13) необходимо задать \( m \) начальных значений \( \pmb{y}^0 \), \( \pmb{y}^1 \), \( \dots \), \( \pmb{y}^{m-1} \) (например, можно использовать для их вычисления метод Эйлера).
Различные варианты многошаговых методов (методы Адамса) решения задачи с начальными условиями для систем обыкновенных дифференциальных уравнений могут быть получены на основе использования квадратурных формул для правой части равенства $$ \begin{equation} \tag{14} \pmb{u}(t_{n+1}) - \pmb{u}(t_n) = \int_{t_n}^{t_{n+1}} \pmb{F}(t, \pmb{u}) dt \end{equation} $$
Для получения неявного многошагового метода используем для подынтегральной функции интерполяционную формулу по значениям функции \( \pmb{F}^{n+1} = \pmb{F}(t_{n+1}, \pmb{y}^{n+1}) \), \( \pmb{F}^n \), \( \dots \), \( \pmb{F}^{n+1-m} \), т.е. $$ \begin{equation} \tag{15} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \sum_{i=0}^{m} b_i \pmb{F}(t_{n+1-i}, \pmb{y}^{n+1-i}) \end{equation} $$
Для интерполяционного метода Адамса (15) наивысший порядок аппроксимации равен \( m+1 \).
Для построения явных многошаговых методов можно использовать процедуру экстраполяции подынтегральной функции в правой части (14). В этом случае приближение осуществляется по значениям \( \pmb{F}^n \), \( \pmb{F}^{n-1} \), \( \dots \), \( \pmb{F}^{n+1-m} \) и поэтому $$ \begin{equation} \tag{16} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \sum_{i=1}^{m} b_i \pmb{F}(t_{n+1-i}, \pmb{y}^{n+1-i}) \end{equation} $$
Для экстраполяционного метода Адамса (16) погрешность аппроксимации имеет \( m \)-ый порядок.
Примерами методов Адамса (15), (16) при \( m=3 \) являются $$ \begin{align} \tag{17} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} &= \frac{1}{24} (9\pmb{F}^{n+1} + 19\pmb{F}^{n} - 5\pmb{F}^{n-1} + \pmb{F}^{n-2}),\\ \tag{18} \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} &= \frac{1}{12} (23 \pmb{F}^{n} -16\pmb{F}^{n-1} + 5\pmb{F}^{n-2}), \end{align} $$ соответственно.
На основе методов Адамса строятся и схемы предиктор–корректор. На этапе предиктор используется явный метод Адамса, на этапе корректора — аналог неявного метода Адамса. Например, при использовании методов третьего порядка аппроксимации в соответствии с (18) для предсказания решения положим $$ \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \frac{1}{12} (23 \pmb{F}^{n} -16\pmb{F}^{n-1} + 5\pmb{F}^{n-2}). $$ Для уточнеия решения (см. (17)) используется схема $$ \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} = \frac{1}{24} (9\pmb{F}^{n+1} + 19\pmb{F}^{n} - 5\pmb{F}^{n-1} + \pmb{F}^{n-2}). $$ Аналогично строятся и другие классы многошаговых методов.
При численном решении задачи Коши для систем обыкновенных дифференциальных уравнений (3), (4) могут возникнуть дополнительные трудности, порожденные жесткостью системы. Локальные особенности поведения решения в точке \( u = w \) передаются линейной системой $$ \begin{equation*} \frac{d v_i}{dt} = \sum_{j=1}^{m} \frac{\partial f_i}{\partial u_j} (t, w) v + \bar{f_i}(t), \quad t > 0. \end{equation*} $$
Пусть \( \lambda_i(t) \), \( i = 1, 2, \ldots, m \) — собственные числа матрицы $$ \begin{equation*} A(t) = \{ a_{ij}(t) \}, \quad a_{ij}(t) = \frac{\partial f_i}{\partial u_j}(t, w). \end{equation*} $$ Система уравнений (3) является жесткой, если число $$ \begin{equation*} S(t) = \frac{\max_{1 \leq i \leq m} |Re \lambda_i(t)|}{\min_{1 \leq i \leq m} |Re \lambda_i(t)|} \end{equation*} $$ велико. Это означает, что в решении присутствуют составляющие с сильно различающимися масштабами изменения по переменной \( t \).
Для численное решения жестких задач используются вычислительные алгоритмы, которые имеют повышенный запас устойчивости. Необходимо ориентироваться на использование \( A \)-устойчивых или \( A(\alpha) \)-устойчивых методов.
Метод называется \( A \)-устойчивым, если при решении задачи Коши для системы (3) область его устойчивости содержит угол $$ \begin{equation*} |\arg(-\mu)| < \alpha, \quad \mu = \lambda \tau. \end{equation*} $$
Среди \( A \)-устойчивых методов можно выделить чисто неявные многошаговые методы (методы Гира), когда $$ \begin{equation} \tag{19} \frac{1}{\tau} \sum_{i=0}^{k} a_i \pmb{y}^{n+1-i} = \pmb{F}(t_n, \pmb{y}^{n+1}) \end{equation} $$
Написать программу для численного решения задачи Коши для системы обыкновенных дифференциальных уравнений явным методом Рунге—Кутта четвертого порядка. Продемонстрировать работоспособность этой программы при решении задачи Коши (построить график зависимости решения от \( t \)) $$ \frac{d^2 u}{d t^2} = -\sin u, \quad 0 < t < 4\pi, $$ $$ u(0) = 1, \quad \frac{d u}{d t} = 0. $$
Написать программу, которая реализует метод предиктор–корректор: $$ \begin{align*} \frac{\bar{\pmb{y}}^{n+1} - \pmb{y}^n}{\tau} &= \frac{1}{24} (55 \pmb{F}(t_n, \pmb{y}^{n}) -59\pmb{F}(t_{n-1}, \pmb{y}^{n-1}) + 37 \pmb{F}(t_{n-2}, \pmb{y}^{n-2}) - 9 \pmb{F}(t_{n-3}, \pmb{y}^{n-3})), \\ \frac{\pmb{y}^{n+1} - \pmb{y}^n}{\tau} &= \frac{1}{24} (9\pmb{F}(t_{n+1}, \bar{\pmb{y}}^{n+1}) + 19\pmb{F}(t_n, \pmb{y}^{n}) - 5\pmb{F}(t_{n-1}, \pmb{y}^{n-1}) + \pmb{F}(t_{n-2}, \pmb{y}^{n-2}). \end{align*} $$ С помощью этой программы решить задачу $$ \begin{align*} \frac{d u_1}{dt} &= u_2 - u_3, \\ \frac{d u_2}{dt} &= u_1 + a u_2,\\ \frac{d u_3}{dt} &= b + u_3(u_1 - c), \quad 0 < t \leq 100,\\ u_1(0) &= 1, \quad u_2(0) = 1, \quad u_3(0) = 1. \end{align*} $$ при \( a, b, c = 0.2, 0.2, 2.5 \) и \( a, b, c = 0.2, 0.2, 5 \).
Написать программу для приближенного решения задачи Коши для системы обыкновенных дифференциальных уравнений первого порядка методом Гира четвертого порядка точности: $$ \begin{equation*} \frac{25 \pmb{y}^{n+1} - 48 \pmb{y}^n + 36 \pmb{y}^{n-1} - 16 \pmb{y}^{n-2} + 3\pmb{y}^{n-3}}{12\tau} = \pmb{F}(t_{n+1}, \pmb{y}^{n+1}) \end{equation*} $$ С ее помощью найти решение задачи $$ \begin{align*} \frac{du_1}{dt} &= u_2, \\ \frac{d u_2}{dt} &= \mu(1-u_1^2)u_2 - u_1, \quad 0 < t \leq 100,\\ u_1(0) &= 2, \quad u_2(0) = 0. \end{align*} $$ при \( \mu = 50 \).