#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] ) ] - 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] ) ] - Неявный Эйлер самый точный и устойчивый. Ошибка $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() ```