Идея многошаговых методов
До сих пор каждый наш шаг начинался «с чистого листа»: метод смотрел только на текущую точку и забывал всё, что было вычислено раньше. Многошаговые методы устроены иначе — они помнят прошлое и используют его, чтобы шагнуть точнее и дешевле.
Многошаговый метод — метод численного решения ОДУ, который вычисляет следующее значение решения, опираясь не только на текущую точку, но и на несколько предыдущих уже посчитанных точек и наклонов.
Вспомним, как работает обычный метод Эйлера. Чтобы шагнуть из точки t_n в t_{n+1}, мы берём текущее значение y_n, вычисляем наклон f(t_n, y_n) и идём по нему. На следующем шаге мы вычисляем f(t_{n+1}, y_{n+1}) — и снова забываем всё предыдущее. Метод Рунге-Кутты четвёртого порядка делает то же самое, но честнее: внутри одного шага он вычисляет правую часть f целых четыре раза в промежуточных точках, усредняет наклоны и за счёт этого получает порядок 4. Оба метода роднит одно свойство: чтобы сделать шаг, им достаточно знать ровно одну точку — текущую. Поэтому их называют одношаговыми.
Теперь зададим неудобный вопрос. Мы прошли уже сотню шагов. У нас на руках сотня вычисленных значений f в прошлых точках. Эти числа честно посчитаны, они описывают поведение нашей функции в недавнем прошлом — и мы их выбрасываем. А ведь если правая часть f меняется плавно (а у гладких задач это почти всегда так), то наклон в недавнем прошлом многое говорит о наклоне в ближайшем будущем. Идея многошаговых методов ровно в этом: не выбрасывать историю, а использовать её.
Одношаговые против многошаговых
Представим, что нам нужно оценить, куда повернёт траектория на следующем шаге. Одношаговый метод высокого порядка добывает информацию о кривизне «на месте»: он тратит дополнительные вызовы f внутри текущего шага, прощупывая промежуточные точки. RK4 делает четыре вызова f на каждый шаг — это его плата за точность. Многошаговый метод поступает экономнее: информацию о кривизне он берёт из уже имеющихся прошлых наклонов f_{n-1}, f_{n-2} и так далее. Эти числа уже посчитаны на предыдущих шагах, переиспользовать их бесплатно.
Отсюда главное преимущество. Многошаговому методового того же порядка точности нужен один-два новых вызова f на шаг вместо четырёх у RK4. Если правая часть f дорогая — например, она сама требует решения системы уравнений, обращения к базе данных или тяжёлого физического расчёта — то экономия вызовов превращается в экономию реального времени. На длинном интегрировании с дорогой правой частью многошаговый метод может оказаться в разы быстрее RK при сопоставимой точности.
Одношаговый (RK4): на каждый шаг 4 свежих вызова f
t_n ----[ f, f, f, f ]----> t_{n+1}
Многошаговый (AB): на каждый шаг 1 свежий вызов f
... f_{n-2} f_{n-1} f_n ----[ +1 вызов ]----> t_{n+1}
\________ переиспользуем ________/Как работает под капотом
Чтобы метод мог опираться на историю, эту историю нужно где-то хранить. На практике хранят список (или очередь) последних значений наклона f. Каждый шаг устроен так: мы берём из истории несколько последних f, по формуле метода вычисляем новую точку, затем считаем наклон в этой новой точке и добавляем его в историю, а самый старый элемент выбрасываем. Получается «скользящее окно» фиксированной длины, едущее вдоль решения.
В Python для такого окна идеально подходит collections.deque с параметром maxlen. Это двусторонняя очередь: когда вы добавляете элемент в конец, а длина уже достигла maxlen, самый старый элемент с другого конца удаляется автоматически. Вам не нужно вручную следить за размером — структура данных делает это за вас. Посмотрим на крошечную модель: будем хранить два последних наклона для задачи y' = y.
from collections import deque
import math
h = 0.2
f = lambda t, y: y # правая часть y' = y
hist = deque(maxlen=2) # окно из двух последних наклонов
t, y = 0.0, 1.0
hist.append(f(t, y)) # стартовый наклон
print(f'после старта: история наклонов = {list(hist)}')
# шаг 1 (простой Эйлер ради демонстрации хранения)
y = y + h * f(t, y); t = t + h
hist.append(f(t, y))
print(f'после шага 1: t={t:.1f}, y={y:.4f}, история = {[round(x, 4) for x in hist]}')
# шаг 2
y = y + h * f(t, y); t = t + h
hist.append(f(t, y))
print(f'после шага 2: t={t:.1f}, y={y:.4f}, история = {[round(x, 4) for x in hist]}')
print(f'старое значение вытеснено, длина истории = {len(hist)}')Вывод:
после старта: история наклонов = [1.0] после шага 1: t=0.2, y=1.2000, история = [1.0, 1.2] после шага 2: t=0.4, y=1.4400, история = [1.2, 1.44] старое значение вытеснено, длина истории = 2
Обратите внимание: после второго шага число 1.0 (самый старый наклон) исчезло само — deque вытеснил его, как только окно переполнилось. Именно такой «конвейер наклонов» лежит в основе всех многошаговых схем; различаются они только формулой, по которой из истории получают следующую точку.
Цена удобства: разгон и переменный шаг
За экономию приходится платить, и важно понимать чем. Первая проблема — разгон (старт). Метод, которому нужны два прошлых наклона, не может сделать самый первый шаг: у него ещё нет истории, в начале известна только одна точка y_0. Откуда взять y_1? Ответ: первые несколько точек считают одношаговым методом — обычно тем же RK4 или методом Хойна, у которых порядок не ниже, чем у основного многошагового метода. Эти стартовые шаги называют разгоном. Только набрав нужное число точек в истории, мы переключаемся на дешёвую многошаговую формулу.
Вторая проблема — смена шага. У одношагового метода поменять шаг h посреди интегрирования легко: следующий шаг просто берёт другую длину, истории-то нет. У многошагового метода вся история наклонов посчитана со старым шагом и при равномерной сетке. Стоит изменить h — и прошлые точки больше не лежат на нужных расстояниях, формула перестаёт быть корректной. Менять шаг можно, но это требует либо пересчёта истории, либо специальных формул для неравномерной сетки — заметное усложнение. Поэтому адаптивные по шагу решатели часто остаются одношаговыми (как RK45), а многошаговые методы любят равномерную сетку и длинные «спокойные» участки.
Частые ошибки
- Забыть про разгон. Попытка стартовать многошаговый метод там, где истории ещё нет, приводит к обращению к несуществующим индексам или к мусорным начальным наклонам. Первые точки обязаны прийти от корректного одношагового метода.
- Стартовый метод ниже порядком. Если разогнать метод порядка 4 грубым методом Эйлера (порядок 1), ошибка разгона «отравит» всю историю, и высокий порядок основной формулы будет потерян. Порядок стартового метода должен быть не ниже порядка основного.
- Перепутать индексы истории. f_n — это наклон в текущей точке, f_{n-1} — в предыдущей. Поменять их местами в формуле — типичная ошибка, дающая внешне правдоподобный, но неверный результат.
- Менять шаг как ни в чём не бывало. Изменение h без пересчёта истории молча портит точность: формула продолжает считать, будто точки равноотстоят.
Итоги
- Многошаговые методы используют историю — несколько предыдущих точек и наклонов, а не только текущую.
- Одношаговые (Эйлер, RK) забывают прошлое и добывают точность дополнительными вызовами f внутри шага; многошаговые переиспользуют уже посчитанные f.
- Экономия: один-два новых вызова f на шаг вместо четырёх у RK4 — выигрыш ощутим, когда f дорогая.
- Историю наклонов удобно хранить в collections.deque с maxlen — старые значения вытесняются автоматически.
- Плата: нужен разгон стартовыми точками от одношагового метода и сложнее менять шаг.