Метод стрельбы: пристреливаемся к цели

Самый интуитивный способ решить краевую задачу — превратить её обратно в задачу Коши, которую мы уже умеем решать. Не хватает стартового наклона? Угадаем его и будем пристреливаться к цели, как артиллерист наводит орудие.

Метод стрельбы (shooting method) — метод решения краевой задачи, при котором недостающее начальное условие (обычно наклон y'(a)=s) подбирается так, чтобы решение соответствующей задачи Коши попало в заданное краевое значение y(b)=B.

Аналогия с пушкой

Представьте артиллериста. У пушки есть угол наклона ствола — это наш параметр s, стартовый наклон y'(a). Цель находится на расстоянии и на известной высоте — это краевое условие y(b)=B. Артиллерист задаёт угол, стреляет (решает задачу Коши вперёд до b) и смотрит, куда упал снаряд. Перелёт — уменьшить угол, недолёт — увеличить. Несколько выстрелов с поправками — и снаряд ложится точно в цель.

Численная стрельба устроена ровно так же. Угадываем s, интегрируем задачу Коши до конца отрезка, измеряем промах — разность между тем, куда попали, и тем, куда хотели:

F(s) = y(b; s) - B

Здесь y(b; s) — это значение на правом конце, полученное при стартовом наклоне s. Наша цель — найти такой s, при котором F(s)=0. То есть мы свели краевую задачу к поиску корня функции одной переменной F(s) — задаче, для которой есть готовые надёжные методы: бисекция и метод секущих.

Алгоритм по шагам

1. Выбрать два пробных наклона s0 и s1.
2. Для каждого: задать задачу Коши y(a)=A, y'(a)=s,
   проинтегрировать RK4 до b, получить y(b; s).
3. Вычислить промахи F(s0) и F(s1).
4. Подобрать новый наклон (секущая/бисекция),
   чтобы загнать промах к нулю.
5. Повторять, пока |F(s)| не станет меньше допуска.

Линейная задача: точно за один проход

Есть приятный факт: если краевая задача линейна, то промах F(s) — это линейная функция наклона s. А корень линейной функции находится по двум точкам сразу, без итераций. Метод секущих в этом случае попадает в ответ за один шаг. Возьмём именно такую задачу — её удобно проверять, потому что ответ известен аналитически.

Задача:  y'' = 2,   y(0) = 0,  y(1) = 0
Точное решение:  y(t) = t^2 - t
Проверка: y'' = 2 (верно), y(0)=0, y(1)=1-1=0 (верно)
Точный стартовый наклон: y'(t)=2t-1, y'(0) = -1

Мы знаем заранее: правильный наклон равен -1. Посмотрим, как метод стрельбы его найдёт, не зная ответа.

import math

# Краевая задача: y'' = 2, y(0)=0, y(1)=0. Точное y = t^2 - t.
# Сводим к системе первого порядка: y' = v, v' = 2.

def accel(t, y, v):
    return 2.0   # это и есть y'' = 2

def shoot(s, n=100):
    """Решаем задачу Коши y(0)=0, y'(0)=s методом RK4, вернём y(1)."""
    t, y, v = 0.0, 0.0, s
    h = 1.0 / n
    for _ in range(n):
        k1y = v
        k1v = accel(t, y, v)
        k2y = v + 0.5*h*k1v
        k2v = accel(t + 0.5*h, y + 0.5*h*k1y, v + 0.5*h*k1v)
        k3y = v + 0.5*h*k2v
        k3v = accel(t + 0.5*h, y + 0.5*h*k2y, v + 0.5*h*k2v)
        k4y = v + h*k3v
        k4v = accel(t + h, y + h*k3y, v + h*k3v)
        y += (h/6.0)*(k1y + 2*k2y + 2*k3y + k4y)
        v += (h/6.0)*(k1v + 2*k2v + 2*k3v + k4v)
        t += h
    return y

B = 0.0                      # требуемое y(1)
s_a, s_b = 0.0, 1.0          # два пробных наклона
f_a = shoot(s_a) - B
f_b = shoot(s_b) - B
print("наклон s=0.0  ->  y(1) =", round(shoot(s_a), 6))
print("наклон s=1.0  ->  y(1) =", round(shoot(s_b), 6))

# Метод секущих: подбираем наклон, загоняя промах к нулю
for i in range(4):
    s_c = s_b - f_b * (s_b - s_a) / (f_b - f_a)
    f_c = shoot(s_c) - B
    print("итерация", i + 1, "-> наклон s =", round(s_c, 6),
          " промах y(1)-B =", round(f_c, 8))
    s_a, f_a = s_b, f_b
    s_b, f_b = s_c, f_c

print("найденный наклон s =", round(s_b, 6), "(точный = -1)")

Вывод:

наклон s=0.0  ->  y(1) = 1.0
наклон s=1.0  ->  y(1) = 2.0
итерация 1 -> наклон s = -1.0  промах y(1)-B = 0.0
итерация 2 -> наклон s = -1.0  промах y(1)-B = -0.0
итерация 3 -> наклон s = -1.0  промах y(1)-B = 0.0
итерация 4 -> наклон s = -1.0  промах y(1)-B = -0.0
найденный наклон s = -1.0 (точный = -1)

Смотрите, как красиво: при наклоне 0 мы «перелетели» и попали в y(1)=1 вместо нуля; при наклоне 1 перелёт ещё больше — y(1)=2. Метод секущих по этим двум промахам мгновенно вычислил правильный наклон -1, и дальше промах уже нулевой. Для линейной задачи одного шага достаточно — остальные итерации лишь подтверждают попадание.

Как работает под капотом

Сердце метода — функция промаха F(s)=y(b; s)-B. Каждый её вызов — это полный прогон задачи Коши от a до b. То есть стрельба — это двухуровневый процесс: внешний цикл крутит наклон s (поиск корня), а внутри каждой итерации вложен полный интегратор RK4 по всему отрезку. Это дороже одного прохода, зато мы переиспользуем уже отлаженный код решения начальных задач.

Почему для линейной задачи всё сходится за шаг? Если уравнение линейно, то по принципу суперпозиции решение линейно зависит от стартового наклона: y(b; s) = alpha * s + beta для некоторых констант alpha и beta. Тогда F(s) = alpha*s + (beta - B) — прямая. Корень прямой однозначно определяется по двум любым точкам, что метод секущих и делает за один шаг. Для нелинейных задач F(s) уже кривая, и понадобится несколько итераций секущих или бисекции; если функция промаха меняет знак на концах интервала — надёжнее бисекция, она не расходится.

Частые ошибки

  • Слишком грубая сетка во внутреннем интеграторе. Стрельба не точнее, чем интегратор внутри неё. Если RK4 идёт большим шагом, вы пристреляетесь к искажённой цели и получите неверный наклон. Промах будет нулевым, а решение — кривым.
  • Использовать секущие там, где они расходятся. На нелинейных задачах секущие могут улететь в сторону. Если есть интервал наклонов, на концах которого промах разного знака, — берите бисекцию, она гарантированно сходится.
  • Неустойчивость прямого прогона. На «жёстких» задачах малое изменение наклона на старте даёт гигантский разброс на дальнем конце (экспоненциальный рост). Тогда F(s) ведёт себя дико, и стрельба практически непригодна — нужен метод конечных разностей.
  • Деление на ноль в секущей. Если два пробных наклона дали одинаковый промах (f_b == f_a), формула секущей делит на ноль. Берите заметно разные пробные значения s0 и s1.

Итоги

  • Метод стрельбы сводит краевую задачу к задаче Коши: угадываем стартовый наклон s и интегрируем вперёд.
  • Качество выстрела измеряет функция промаха F(s)=y(b; s)-B; цель — найти её корень.
  • Корень ищут бисекцией или секущими; аналогия — артиллерист, подбирающий угол ствола.
  • Для линейной задачи промах линеен по s, и секущая попадает в ответ за один шаг.
  • Стрельба переиспользует код RK4, но наследует его точность и плохо работает на жёстких задачах.
Проверьте себя
1. Что подбирает метод стрельбы в задаче y''=f с y(a)=A, y(b)=B?
AДлину отрезка [a, b]
BНедостающее начальное условие — стартовый наклон y'(a)=s
CСамо правое краевое значение B
DПорядок дифференциального уравнения
2. Что такое функция промаха F(s) в методе стрельбы?
AРазность y(b; s) - B между полученным и требуемым значением на правом конце
BПроизводная решения в точке a
CСумма всех узловых значений сетки
DШаг интегрирования RK4
3. Почему для линейной краевой задачи метод секущих находит наклон за один шаг?
AПотому что линейные задачи всегда имеют наклон, равный нулю
BПотому что RK4 для линейных задач даёт точный ответ за один шаг по времени
CПотому что промах F(s) линейно зависит от s, а корень прямой определяется по двум точкам
DПотому что у линейной задачи нет краевых условий на правом конце
4. В каком случае предпочтительнее бисекция, а не секущие?
AКогда задача линейна и решение известно заранее
BКогда функция промаха нелинейна и есть интервал, на концах которого промах разного знака
CКогда нужен максимально быстрый результат любой ценой
DКогда отрезок [a, b] очень короткий