Files
differential_equations/labs/lab1/report/res.typ
2025-12-10 18:33:31 +03:00

644 lines
21 KiB
Typst
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#set text(size: 1.3em)
#show link: underline
#set page(footer: context {
if counter(page).get().first() > 1 [
#align(center)[
#counter(page).display("1")
]
]
if counter(page).get().first() == 1 [
#align(center)[
Санкт-Петербург \ 2025
]
]
})
#set page(header: context {
if counter(page).get().first() == 1 [
#align(center)[
Санкт-Петербургский национальный исследовательский университет информационных технологий, механики и оптики
]
]
})
#show raw.where(block: false): box.with(
fill: luma(240),
inset: (x: 3pt, y: 0pt),
outset: (y: 3pt),
radius: 2pt,
)
#show raw.where(block: true): block.with(
fill: luma(240),
inset: 10pt,
radius: 4pt,
)
// title
#for _ in range(5) { linebreak() }
#align(center)[Расчетно графическая работа №1]
#for _ in range(15) { linebreak() }
#align(right)[Выполнили:]
#align(right)[Левахин Лев]
#align(right)[Останин Андрей]
#align(right)[Дощенников Никита]
#align(right)[Группы: К3221, К3240]
#align(right)[Проверил:]
#align(right)[Владимир Владимирович Беспалов]
#pagebreak()
=== Цель
Изучение и практическое применение численных методов решения обыкновенных дифференциальных уравнений, сравнение их точности и устойчивости на различных типах задач.
=== Задачи
1. Реализовать следующие численные методы решения ОДУ:
- Явный метод Эйлера (1-го порядка)
- Явный метод трапеций (улучшенный Эйлер, 2-го порядка)
- Классический метод Рунге-Кутты 4-го порядка (RK4)
- Неявный метод Эйлера (1-го порядка)
- Неявный метод трапеций (2-го порядка)
2. Решить две задачи:
- Нежесткое нелинейное уравнение типа Риккати (`1.4`): исследование точности и сходимости явных методов
- Жесткое линейное уравнение (`2.4`): исследование устойчивости неявных методов
3. Провести анализ погрешностей и построить графики решений
4. Сформулировать выводы о применимости различных методов
=== Теоретические основы
==== Постановка задачи Коши
Рассматривается начальная задача для обыкновенного дифференциального уравнения первого порядка:
$
frac(d y, d x) eq f(t, y), space.quad y(t_0) eq y_0
$
где
- $t$ - независимая переменная (время)
- $y(t)$ - искомая функция
- $f(t, y)$ - правая часть уравнения
- $y_0$ - начальное условие
==== Классификация численных методов
*Явные методы* - значение решения на следующем шаге вычисляется непосредственно из известных значений:
- Просты в реализации
- Требуют малого шага для жестких систем
- Примеры: явный Эйлер, RK4
*Неявные методы* - значение решения на следующем шаге находится из неявного уравнения:
- Требуют решения нелинейных уравнений на каждом шаге
- Обладают лучшей устойчивостью для жестких систем
- Примеры: неявный Эйлер, неявная трапеция
=== Описание реализованных методов
==== Явный метод Эйлера
$
y_(n + 1) eq y_n plus h dot f(t_n, y_n)
$
Имеет первый порядок точности $O(h)$.
```python
def euler_method(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
y[i + 1] = y[i] + h * f(t[i], y[i])
return t, y
```
Метод использует линейную аппроксимацию решения на основе производной в начале интервала. Это самый простой, но наименее точный метод.
==== Явный метод трапеций
$
k_1 eq f(t_n, y_n) \
k_2 eq f(t_n plus h, y_n plus h dot k_1) \
y_(n plus 1) eq y_n plus frac(h, 2) dot (k_1 plus k_2)
$
Имеет второй порядок точности $O(h^2)$.
```python
def explicit_trapezoid(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
k1 = f(t[i], y[i])
k2 = f(t[i] + h, y[i] + h * k1)
y[i + 1] = y[i] + (h / 2) * (k1 + k2)
return t, y
```
Метод сначала делает предсказание решения в конце интервала (явным Эйлером), затем усредняет производные в начале и конце интервала.
==== Метод Рунге-Кутты 4-го порядка (RK4)
$
k_1 eq f(t_n, y_n) \
k_2 eq f(t_n plus frac(h, 2), y_n plus h dot frac(k_1, 2)) \
k_3 eq f(t_n plus frac(h, 2), y_n plus h dot frac(k_2, 2)) \
k_4 eq f(t_n plus h, y_n plus h dot k_3) \
y_(n plus 1) = y_n + frac(h, 6) dot (k_1 plus 2 k_2 plus 2 k_3 plus k_4)
$
Имеет четвертый порядок точности $O(h^4)$
```python
def rk4_method(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
k1 = f(t[i], y[i])
k2 = f(t[i] + h/2, y[i] + h/2 * k1)
k3 = f(t[i] + h/2, y[i] + h/2 * k2)
k4 = f(t[i] + h, y[i] + h * k3)
y[i + 1] = y[i] + h/6 * (k1 + 2*k2 + 2*k3 + k4)
return t, y
```
Метод вычисляет четыре промежуточных значения производной внутри интервала и формирует взвешенное среднее. Это обеспечивает высокую точность.
==== Неявный метод Эйлера
$
y_(n plus 1) eq y_n plus h dot f(t_(n plus 1), y_(n plus 1))
$
Имеет первый порядок точности $O(h)$
```python
def backward_euler(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
t_next = t[i + 1]
def equation(z):
return z - h * f(t_next, z) - y[i]
y[i + 1] = fsolve(equation, y[i])[0]
return t, y
```
Метод использует производную в конце интервала, что требует решения нелинейного уравнения на каждом шаге с помощью `fsolve`. Это обеспечивает безусловную устойчивость для линейных задач.
==== Неявный метод трапеций
$
y_(n plus 1) eq y_n plus frac(h, 2) dot [f(t_n, y_n) plus f(t_(n plus 1), t_(n + 1)]
$
Имеет второй порядок точности $O(h^2)$
```python
def implicit_trapezoid(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
t_n = t[i]
t_np1 = t[i + 1]
y_n = y[i]
def equation(z):
return z - y_n - (h/2) * (f(t_n, y_n) + f(t_np1, z))
y[i + 1] = fsolve(equation, y_n)[0]
return t, y
```
Метод усредняет производные в начале и конце интервала (как явная трапеция), но использует неизвестное значение $y_(n+1)$, что требует решения нелинейного уравнения. Метод A-устойчив.
=== Задачи
==== 1.4
$
y' eq y^2 - t, space.quad y(0) eq 1, space.quad t in [0, 1.5], space.quad h eq 0.25
$
- Нелинейное уравнение типа Риккати
- Не имеет аналитического решения
- Нежесткая система - подходит для явных методов
- Используется для анализа точности и сходимости
```python
def problem_1_4():
def f(t, y):
return y**2 - t
t_span = [0, 1.5]
y0 = 1
h = 0.25
solver = ODESolver()
t_euler, y_euler = solver.euler_method(f, t_span, y0, h)
t_trap, y_trap = solver.explicit_trapezoid(f, t_span, y0, h)
t_rk4, y_rk4 = solver.rk4_method(f, t_span, y0, h)
sol_ref = solve_ivp(f, t_span, [y0], method='RK45',
rtol=1e-10, atol=1e-12, dense_output=True)
```
==== 2.4
$
y' eq -2000y plus t, space.quad y(0) eq 0, space.quad t in [0, 1], space.quad h eq 1
$
- Линейное уравнение с большим отрицательным коэффициентом $lambda = -2000$
- Жесткая система
- Явный метод Эйлера неустойчив при больших шагах
- Требует неявных методов для устойчивости
```python
def problem_2_4():
def f(t, y):
return -2000 * y + t
t_span = [0, 1]
y0 = 0
h = 1
solver = ODESolver()
t_euler, y_euler = solver.euler_method(f, t_span, y0, h)
t_beuler, y_beuler = solver.backward_euler(f, t_span, y0, h)
t_itrap, y_itrap = solver.implicit_trapezoid(f, t_span, y0, h)
sol_ref = solve_ivp(f, t_span, [y0], method='Radau',
rtol=1e-10, atol=1e-12, dense_output=True)
```
=== Результаты
==== 1.4
Мы уменьшили интервал с $[0, 1.5]$ до $[0, 1]$ для того, чтобы избежать "взрыва решения", так как уравнение нелинейно.
Результаты программы:
```bash
=== ЗАДАЧА 1.4: y' = y² - t, y(0) = 1 ===
Ошибка метода Эйлера: 6.44e+00
Ошибка явного метода трапеций: 3.76e+00
Ошибка метода РК4: 6.45e-01
```
Ошибка получилась порядка $10^(-1)$.
#align(center)[
#figure(
image("assets/1.png"),
supplement: [Рис.],
caption: [График решения задачи 1.4]
) <g1>
]
- RK4 самый точный. Ошибка $tilde 10^(-1)$ в разы меньше остальных. На графике траектория почти совпадает с эталоном.
- Явная трапеция имеет среднюю точность. Ошибка меньше, чем у Эйлера, но заметно хуже RK4.
- Метод эйлера самый неточный. Ошибка огромная, отклонение от эталона быстро растёт.
==== 2.4
Результаты программы:
```bash
=== ЗАДАЧА 2.4: y' = -2000y + t, y(0) = 0 ===
Ошибка явного метода Эйлера: 5.00e-04
Ошибка неявного метода Эйлера: 1.25e-10
Ошибка неявного метода трапеций: 2.50e-07
```
#align(center)[
#figure(
image("assets/2.png"),
supplement: [Рис.],
caption: [График решения задачи 2.4]
) <g2>
]
- Неявный Эйлер самый точный и устойчивый. Ошибка $tilde 10^(-10)$.
- Неявная трапеция тоже устойчива, но точность хуже. Ошибка $tilde 10^(-7)$.
- Явный Эйлер нестабилен, даёт неправильную форму решения.
=== Структура программы
==== Класс `ODESolver`
Класс инкапсулирует все численные методы:
```python
class ODESolver:
def euler_method(self, f, t_span, y0, h): ...
def explicit_trapezoid(self, f, t_span, y0, h): ...
def rk4_method(self, f, t_span, y0, h): ...
def backward_euler(self, f, t_span, y0, h): ...
def implicit_trapezoid(self, f, t_span, y0, h): ...
```
==== Функция решения задач
Каждая задача решается в отдельной функции:
- `problem_1_4()` - для нежесткой задачи
- `problem_2_4()` - для жесткой задачи
Функции выполняют:
1. Определение правой части ОДУ
2. Задание параметров интегрирования
3. Вызов численных методов
4. Получение эталонного решения
5. Вычисление погрешностей
6. Построение графиков
==== Вычисление эталонного решения
Используются высокоточные методы из библиотеки SciPy:
- `solve_ivp` с методом RK45 для нежестких систем
- `solve_ivp` с методом Radau для жестких систем
- Строгие допуски: `rtol=1e-10`, `atol=1e-12`
==== Анализ погрешности
Глобальная погрешность вычисляется как норма разности:
```python
err = np.linalg.norm(y_numerical - y_reference)
```
где используется евклидова норма $L_2$.
=== Выводы
О порядке точности:
- Порядок метода существенно влияет на точность при одинаковом шаге
- Для достижения заданной точности метод более высокого порядка требует меньше вычислений
- RK4 является оптимальным выбором для нежестких задач
О явных и неявных методах:
- Явные методы просты в реализации, но имеют ограничения по устойчивости
- Неявные методы требуют решения нелинейных уравнений, но обеспечивают устойчивость
- Для жестких систем неявные методы необходимы
О жесткости:
- Жесткие системы характеризуются наличием сильно различающихся масштабов времени
- Явные методы требуют непрактично малых шагов для жестких систем
- A-устойчивые неявные методы позволяют использовать большие шаги
=== Приложение
Полный код программы:
```python
import matplotlib.pyplot as plt
import math
import numpy as np
from scipy.integrate import solve_ivp
from scipy.optimize import fsolve
class ODESolver:
def euler_method(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
y[i + 1] = y[i] + h * f(t[i], y[i])
return t, y
def explicit_trapezoid(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
k1 = f(t[i], y[i])
k2 = f(t[i] + h, y[i] + h * k1)
y[i + 1] = y[i] + (h / 2) * (k1 + k2)
return t, y
def rk4_method(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
k1 = f(t[i], y[i])
k2 = f(t[i] + h/2, y[i] + h/2 * k1)
k3 = f(t[i] + h/2, y[i] + h/2 * k2)
k4 = f(t[i] + h, y[i] + h * k3)
y[i + 1] = y[i] + h/6 * (k1 + 2*k2 + 2*k3 + k4)
return t, y
def backward_euler(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
t_next = t[i + 1]
def equation(z):
return z - h * f(t_next, z) - y[i]
y[i + 1] = fsolve(equation, y[i])[0]
return t, y
def implicit_trapezoid(self, f, t_span, y0, h):
t0, t_end = t_span
N = int((t_end - t0) / h)
t = np.linspace(t0, t_end, N + 1)
y = np.zeros(N + 1)
y[0] = y0
for i in range(N):
t_n = t[i]
t_np1 = t[i + 1]
y_n = y[i]
def equation(z):
return z - y_n - (h/2) * (f(t_n, y_n) + f(t_np1, z))
y[i + 1] = fsolve(equation, y_n)[0]
return t, y
def problem_1_4():
print("=== ЗАДАЧА 1.4: y' = y² - t, y(0) = 1 ===")
def f(t, y):
return y**2 - t
t_span = [0, 1.0]
y0 = 1
h = 0.25
solver = ODESolver()
t_euler, y_euler = solver.euler_method(f, t_span, y0, h)
t_trap, y_trap = solver.explicit_trapezoid(f, t_span, y0, h)
t_rk4, y_rk4 = solver.rk4_method(f, t_span, y0, h)
sol_ref = solve_ivp(f, t_span, [y0], method='RK45',
rtol=1e-10, atol=1e-12, dense_output=True)
t_ref = np.linspace(t_span[0], t_span[1], 100)
y_ref = sol_ref.sol(t_ref)[0]
y_ref_euler = sol_ref.sol(t_euler)[0]
y_ref_trap = sol_ref.sol(t_trap)[0]
y_ref_rk4 = sol_ref.sol(t_rk4)[0]
err_euler = np.linalg.norm(y_euler - y_ref_euler)
err_trap = np.linalg.norm(y_trap - y_ref_trap)
err_rk4 = np.linalg.norm(y_rk4 - y_ref_rk4)
print(f"Ошибка метода Эйлера: {err_euler:.2e}")
print(f"Ошибка явного метода трапеций: {err_trap:.2e}")
print(f"Ошибка метода РК4: {err_rk4:.2e}")
plt.figure(figsize=(12, 8))
plt.plot(t_ref, y_ref, 'k-', linewidth=5, label='Эталонное решение (solve_ivp)')
plt.plot(t_euler, y_euler, 'bo-', markersize=10, label='Метод Эйлера')
plt.plot(t_trap, y_trap, 'c*-', markersize=15, label='Явный метод трапеций')
plt.plot(t_rk4, y_rk4, 'rs-', markersize=5, label='Метод РК4')
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('Задача 1.4: y\' = y² - t, y(0) = 1')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
return t_euler, y_euler, t_trap, y_trap, t_rk4, y_rk4, t_ref, y_ref
if __name__ == "__main__":
result1 = problem_1_4()
def problem_2_4():
print("\n=== ЗАДАЧА 2.4: y' = -2000y + t, y(0) = 0 ===")
def f(t, y):
return -2000 * y + t
t_span = [0, 1]
y0 = 0
h = 1
solver = ODESolver()
t_euler, y_euler = solver.euler_method(f, t_span, y0, h)
t_beuler, y_beuler = solver.backward_euler(f, t_span, y0, h)
t_itrap, y_itrap = solver.implicit_trapezoid(f, t_span, y0, h)
sol_ref = solve_ivp(f, t_span, [y0], method='Radau',
rtol=1e-10, atol=1e-12, dense_output=True)
t_ref = np.linspace(t_span[0], t_span[1], 100)
y_ref = sol_ref.sol(t_ref)[0]
y_ref_euler = sol_ref.sol(t_euler)[0]
y_ref_beuler = sol_ref.sol(t_beuler)[0]
y_ref_itrap = sol_ref.sol(t_itrap)[0]
err_euler = np.linalg.norm(y_euler - y_ref_euler)
err_beuler = np.linalg.norm(y_beuler - y_ref_beuler)
err_itrap = np.linalg.norm(y_itrap - y_ref_itrap)
print(f"Ошибка явного метода Эйлера: {err_euler:.2e}")
print(f"Ошибка неявного метода Эйлера: {err_beuler:.2e}")
print(f"Ошибка неявного метода трапеций: {err_itrap:.2e}")
plt.figure(figsize=(12, 8))
plt.plot(t_ref, y_ref, 'k-', linewidth=5, label='Эталонное решение (solve_ivp)')
plt.plot(t_euler, y_euler, 'bo-', markersize=4, label='Явный метод Эйлера')
plt.plot(t_beuler, y_beuler, 'g^-', markersize=4, label='Неявный метод Эйлера')
plt.plot(t_itrap, y_itrap, 'm*-',linewidth=2.2, markersize=4, label='Неявный метод трапеций')
plt.xlabel('t')
plt.ylabel('y(t)')
plt.title('Задача 2.4: y\' = -2000y + t, y(0) = 0 (жесткая система)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
return t_euler, y_euler, t_beuler, y_beuler, t_itrap, y_itrap, t_ref, y_ref
if __name__ == "__main__":
result2 = problem_2_4()
```