Решение ОДУ#
Подмодуль scipy.integrate содержит в себе множество методов решения систем обыкновенных дифференциальных уравнений (ОДУ, ordinary differential equation , ODE ).
Т.к. везде ниже будут строиться графики полученного и точного решений уравнения, определим функцию для построения графиков этих решений.
from matplotlib import pyplot as plt def plot_solution(ax, x, exact_sol, sol_x, sol_y, xlabel="$t$"): ax.plot(x, exact_sol(x), color="green", label="Точное решение") ax.scatter(sol_x, sol_y, color="red", marker=".", label="Приближенное решение") ax.set_xlabel(xlabel) ax.set_ylabel("$y$") ax.legend()
Задача Коши#
Функция scipy.integrate.solve_ivp (solve initial value problem) позволяет численно решать задачу Коши вида
Уравнение первого порядка#
В качестве самого простого примера решим уравнение
точное решение которого \(y=e^t\) .
Чтобы численно решить это уравнение средствами SciPy , необходимо определить функцию правой части ( f(t, y) в ячейке ниже), задать начальное значение \(y(0)\) в виде списка из одного элемента ( y_0 в ячейке ниже), а также задать интервал независимой переменной, на которой необходимо решить дифференциальное уравнение.
import numpy as np from scipy import integrate from matplotlib import pyplot as plt def f(t, y): return y def exact_solution(t): return np.exp(t) y_0 = [1] t_0 = 0 t_final = 3 solution = integrate.solve_ivp(f, (t_0, t_final), y_0) print(solution)
message: 'The solver successfully reached the end of the integration interval.' nfev: 26 njev: 0 nlu: 0 sol: None status: 0 success: True t: array([0. , 0.10001999, 1.06609106, 2.30431769, 3. ]) t_events: None y: array([[ 1. , 1.10519301, 2.9040598 , 10.01740317, 20.08580546]]) y_events: None
Метод возвращает структуру со сравнительно большим количеством полей, из которых можно получить отладочную информацию о сходимости алгоритма, а также получить приближенную оценку \(\tilde(t)\) в наборе точек \(\
fig, ax = plt.subplots(figsize=(8, 8), layout="tight") x = np.linspace(t_0, t_final, 100) plot_solution(ax, x, exact_solution, solution.t, solution.y[0])
Хоть правая часть уравнения и не зависит явным образом от \(t\) , функция f(t, x) все равно объявляется с первым параметром t .
Для данного уравнения функция scipy.integrate.solve_ivp выдала решение, содержащее совсем небольшое количество точек, так как метод быстро сошелся. Параметром t_eval можно в явно виде задать сетку, в узлах которой требуется получить оценку точного решения.
solution = integrate.solve_ivp(f, (t_0, t_final), y_0, t_eval=np.linspace(t_0, t_final, 50)) fig, ax = plt.subplots(figsize=(8, 8), layout="tight") plot_solution(ax, x, exact_solution, solution.t, solution.y[0])
Уравнения высшего порядка#
Предполагается, что в общем случае \(y=y(t)\) является векторной и решается система обыкновенных дифференциальных уравнений.
\[\begin
Если имеется дифференциальное уравнение высшего порядка разрешенное относительно старшей переменной вида
то необходимо свести его к системе из \(n\) уравнение первого порядка.
В качестве примера решим уравнение
точное решение которого — \(z = \sin t\) . Чтобы его решить, необходимо свести уравнение к системе двух дифференциальных уравнений первого порядка. Введём обозначения \(y_1(t) = z(t)\) и \(y_2(t) = z'(t)\) , тогда систему выше можно переписать в виде
В таком случае функция правой части уравнения должна принимать на вход вектор \(y\) и возвращать вектор производны \(y’=f(t, y)\) в виде списка или массива NumPy .
def exact_solution(t): return np.sin(t) def f(t, y): return [ y[1], -y[0] ] y_0 = [0, 1] t_0 = 0 t_final = np.pi t_eval = np.linspace(0, np.pi, 10) solution = integrate.solve_ivp(f, (t_0, t_final), y_0, t_eval=t_eval) print(solution)
message: 'The solver successfully reached the end of the integration interval.' nfev: 44 njev: 0 nlu: 0 sol: None status: 0 success: True t: array([0. , 0.34906585, 0.6981317 , 1.04719755, 1.3962634 , 1.74532925, 2.0943951 , 2.44346095, 2.7925268 , 3.14159265]) t_events: None y: array([[ 0.00000000e+00, 3.42054448e-01, 6.42796558e-01, 8.66269432e-01, 9.85009559e-01, 9.84744759e-01, 8.66190242e-01, 6.42630108e-01, 3.41454338e-01, -5.87748431e-04], [ 1.00000000e+00, 9.39714990e-01, 7.66028577e-01, 5.00009935e-01, 1.73634556e-01, -1.73906214e-01, -5.00472507e-01, -7.66382176e-01, -9.39862784e-01, -9.99961909e-01]]) y_events: None
В решении \(y\) тоже возвращается в виде массива из \(n\) строк и \(m\) столбцов, где \(n\) — порядок системы уравнений, а \(m\) — количество точек, в которых построена оценка решения. В данном примере первая строка массива соответствует искомой зависимости \(y_1(t)=z(t)\) , а вторая строка — \(y_2(t)\) , которая соответствует производной решения \(z'(t)\) .
x = np.linspace(0, np.pi, 50) fig, ax = plt.subplots(figsize=(8, 8), layout="tight") plot_solution(ax, x, exact_solution, solution.t, solution.y[0])
Выбор метода решения ОДУ#
Решение таких систем ОДУ отнюдь не тривиально. Разработано множество методов их решения и ряд из них “зашит” в подмодуле scipy.integrate , среди которых:
- Явные ме́тоды Ру́нге — Ку́тты 2-го ( RK23 ), 4-го ( RK45 ) и 8-го ( DOP853 ) порядков;
- Неявный метод Руне — Кутты 5-го порядка Radau ;
- Неявные методы BDF и LSODA .
Основным критерием выбора является жесткость системы ОДУ: явные методы плохо проявляют себя на жестких системах. SciPy рекомендует использовать по умолчанию метод RK45 и если он плохо/долго сходится переключаться на метод Radau иди BDF .
Краевая задача#
Функция scipy.integrate.solve_bvp (solve boundary value problem`) предназначена для решения системы ОДУ с краевыми условиями
В качестве аргументов функция solve_bvp принимает:
- функцию f , задающую правую часть уравнения,
- функцию bc (boundary condition), задающую невязку для граничных условий,
- массив x , определяющий сетку значений независимой переменной \(x\) ,
- массив y , задающий “догадку” об итоговом решении \(y = y(x)\) .
Функция bc должна принимать на вход \(y(a)\) и \(y(b)\) (векторные величины в общем случае) и возвращать вектор невязки на граничных условиях.
В качестве примера решим последнюю систему,
но с граничными условиями на отрезке \(x \in [0, \frac<\pi>]\) . Как и в случае задачи Коши здесь потребуется свести уравнение второго порядка к системе из двух уравнений первого порядка.
import numpy as np from scipy import integrate from matplotlib import pyplot as plt def boundary_residual(ya, yb): return np.array([ ya[0] - 0, yb[0] - 1 ]) a, b = 0, np.pi/2 N = 30 x = np.linspace(a, b, N) y_guess = np.zeros((2, N), dtype=float) sol = integrate.solve_bvp(f, boundary_residual, x, y_guess) fig, ax = plt.subplots(figsize=(8, 8), layout="tight") plot_solution(ax, x, exact_solution, sol.x, sol.y[0])
Интерполяция и аппроксимация