Идея многошаговых методов

До сих пор каждый наш шаг начинался «с чистого листа»: метод смотрел только на текущую точку и забывал всё, что было вычислено раньше. Многошаговые методы устроены иначе — они помнят прошлое и используют его, чтобы шагнуть точнее и дешевле.

Многошаговый метод — метод численного решения ОДУ, который вычисляет следующее значение решения, опираясь не только на текущую точку, но и на несколько предыдущих уже посчитанных точек и наклонов.

Вспомним, как работает обычный метод Эйлера. Чтобы шагнуть из точки 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 — старые значения вытесняются автоматически.
  • Плата: нужен разгон стартовыми точками от одношагового метода и сложнее менять шаг.
Проверьте себя
1. Чем многошаговый метод принципиально отличается от одношагового?
AОн работает только с линейными уравнениями
BОн использует несколько предыдущих точек и наклонов, а не только текущую
CОн не требует вычислять правую часть f
DОн всегда точнее любого метода Рунге-Кутты
2. В чём состоит экономия многошаговых методов по сравнению с RK4?
AОни не хранят промежуточные значения в памяти
BИм нужен один-два новых вызова f на шаг вместо четырёх, так как прошлые f переиспользуются
CОни не нуждаются в начальном условии
DОни автоматически подбирают шаг без затрат
3. Почему многошаговому методу нужен «разгон»?
AЧтобы прогреть процессор перед расчётом
BПотому что в самом начале известна только одна точка, а формуле нужна история из нескольких прошлых точек
CЧтобы выбрать оптимальный шаг интегрирования
DПотому что метод Эйлера запрещено применять к гладким задачам