Уравнение теплопроводности и метод конечных разностей

Тепло течёт от горячего к холодному, и скорость этого течения определяется кривизной температурного профиля — именно это и зашифровано в уравнении теплопроводности.

Метод конечных разностей — способ численно решать дифференциальные уравнения, заменяя производные разностями значений функции в узлах сетки.

Уравнение теплопроводности — простейшее и самое важное параболическое ДУЧП. В одномерном случае оно выглядит так: du/dt = α·d^2u/dx^2. Здесь u(x, t) — температура в точке x в момент t, а α — коэффициент температуропроводности, описывающий, насколько легко тепло проходит через материал. Левая часть — скорость изменения температуры в данной точке. Правая — кривизна профиля, умноженная на α.

Физический смысл уравнения

Чтобы прочувствовать уравнение, посмотрите на знак второй производной. Если в некоторой точке профиль образует острый горб (выпуклость вверх), то d^2u/dx^2 там отрицательна, значит du/dt отрицательна — точка остывает. И наоборот: на дне впадины кривизна положительна, du/dt положительна — точка нагревается. Природа сглаживает: горбы оседают, ямы заполняются.

Эту же мысль можно выразить через соседей. Вторая разность u(x+h) − 2u(x) + u(x−h) — это, по сути, сравнение значения в точке со средним её соседей. Если точка холоднее среднего соседей, она нагревается; если горячее — остывает. Тепло перетекает от тёплого к холодному, стремясь всё уравнять. Скорость этого выравнивания тем выше, чем сильнее точка отличается от окружения, то есть чем больше кривизна.

Дискретизация: сетка по пространству и времени

Чтобы решать уравнение численно, разобьём стержень на узлы. Пусть узлы расположены через шаг dx, и их значения хранятся в списке. Время тоже разобьём на шаги dt. Обозначим значение в узле i на временном слое n как u_i^n. Теперь заменим производные конечными разностями.

Производную по времени заменяем разностью вперёд: du/dt ≈ (u_i^{n+1} − u_i^n) / dt. Вторую производную по пространству — центральной разностью: d^2u/dx^2 ≈ (u_{i+1}^n − 2·u_i^n + u_{i-1}^n) / dx^2. Подставив это в уравнение и выразив новое значение, получаем явную схему:

u_i^{n+1} = u_i^n + r * (u_{i+1}^n - 2*u_i^n + u_{i-1}^n),  где r = α*dt/dx^2

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

Расплывание пика: запускаем на практике

Возьмём стержень из 7 узлов, концы которого держим при нуле (краевые условия). В начальный момент поместим в центр единичный пик: [0, 0, 0, 1, 0, 0, 0]. Возьмём r = 0.4 (почему именно меньше 0.5 — тема следующего урока) и сделаем несколько шагов. Проследим, как острый пик расползается в пологий холм.

def step(u, r):
    new = u[:]
    for i in range(1, len(u) - 1):
        new[i] = u[i] + r * (u[i-1] - 2*u[i] + u[i+1])
    new[0] = 0.0
    new[-1] = 0.0
    return new

u = [0, 0, 0, 1.0, 0, 0, 0]
r = 0.4
for s in range(4):
    print('шаг', s, [round(x, 3) for x in u])
    u = step(u, r)
print('шаг 4', [round(x, 3) for x in u])

Вывод:

шаг 0 [0, 0, 0, 1.0, 0, 0, 0]
шаг 1 [0.0, 0.0, 0.4, 0.2, 0.4, 0.0, 0.0]
шаг 2 [0.0, 0.16, 0.16, 0.36, 0.16, 0.16, 0.0]
шаг 3 [0.0, 0.096, 0.24, 0.2, 0.24, 0.096, 0.0]
шаг 4 [0.0, 0.115, 0.166, 0.232, 0.166, 0.115, 0.0]

Проследим первый шаг руками для центрального узла (i=3): new = 1.0 + 0.4·(0 − 2·1.0 + 0) = 1.0 + 0.4·(−2.0) = 1.0 − 0.8 = 0.2. Для соседнего узла i=2: new = 0 + 0.4·(0 − 0 + 1.0) = 0.4. Совпадает с выводом. Видно главное: пик высотой 1.0 за один шаг просел до 0.2, а его энергия растеклась к соседям. К четвёртому шагу вместо острой иглы — широкий пологий бугор. Это и есть диффузия в действии.

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

Обратите внимание на функцию step: она строит новый список из старого, и это принципиально. Если бы мы обновляли список на месте, то, посчитав узел i, мы бы испортили значение u[i], которое нужно для расчёта узла i+1. Все узлы должны обновляться одновременно, исходя из одного и того же старого слоя. Поэтому мы копируем список и пишем результат в копию.

Концы списка мы каждый шаг принудительно зануляем — это краевое условие Дирихле: на границах температура фиксирована (равна нулю, концы погружены в лёд). Цикл идёт от 1 до len(u)−1, то есть внутренние узлы; крайние не пересчитываются формулой, а удерживаются условием.

Интересно проследить за суммой значений. Первые два шага сумма строго равна 1.0 — энергия сохраняется, тепло лишь перераспределяется. Но как только волна тепла доходит до предпоследних узлов, через занулённые концы начинается утечка: на третьем шаге сумма падает до 0.872. Физически это значит, что тепло уходит в ледяные концы. Если бы концы были теплоизолированы (другое краевое условие), сумма сохранялась бы дольше.

ASCII-визуализация профиля

Чтобы увидеть расплывание глазами, нарисуем профиль столбиками: длина бара пропорциональна значению.

def step(u, r):
    new = u[:]
    for i in range(1, len(u) - 1):
        new[i] = u[i] + r * (u[i-1] - 2*u[i] + u[i+1])
    new[0] = 0.0
    new[-1] = 0.0
    return new

u = [0, 0, 0, 1.0, 0, 0, 0]
r = 0.4
for s in range(3):
    print('шаг', s)
    for x in u:
        print('  %5.3f | %s' % (x, int(x * 40) * '#'))
    u = step(u, r)

Вывод:

шаг 0
  0.000 | 
  0.000 | 
  0.000 | 
  1.000 | ########################################
  0.000 | 
  0.000 | 
  0.000 | 
шаг 1
  0.000 | 
  0.000 | 
  0.400 | ################
  0.200 | #######
  0.400 | ################
  0.000 | 
  0.000 | 
шаг 2
  0.000 | 
  0.160 | ######
  0.160 | ######
  0.360 | ##############
  0.160 | ######
  0.160 | ######
  0.000 | 

Высокий одинокий столбик первого шага к третьему превращается в широкое плато — наглядная картина того, как тепло размывается по стержню.

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

Самая коварная ошибка — обновлять список на месте (new = u, а потом писать в u). Тогда расчёт правого узла использует уже изменённое значение левого, и схема даёт неверный результат. Всегда работайте с копией старого слоя.

Вторая ошибка — забыть переустановить краевые условия после шага. Формула не трогает концы, но если в начале вы их задали нулём, а потом случайно изменили, профиль поплывёт неправильно.

Третья — взять слишком большой r и удивиться, что вместо плавного остывания получаются растущие осцилляции. Это не баг кода, а нарушение условия устойчивости, которому посвящён отдельный урок.

Итоги

  • Уравнение теплопроводности du/dt = α·d^2u/dx^2: скорость изменения температуры равна кривизне профиля.
  • Метод конечных разностей заменяет производные разностями значений в узлах сетки.
  • Явная схема: u_i^{n+1} = u_i^n + r·(u_{i+1} − 2u_i + u_{i-1}), где r = α·dt/dx^2.
  • Все узлы обновляются одновременно — обязательно через копию старого слоя.
  • Острый пик за несколько шагов расплывается в пологий холм; через занулённые концы тепло утекает.
Проверьте себя
1. Что физически означает правая часть уравнения теплопроводности α·d^2u/dx^2?
AСкорость движения стержня
BКривизну температурного профиля: точка холоднее соседей нагревается, горячее — остывает
CПолную энергию системы
DРасстояние между узлами сетки
2. В явной схеме теплопроводности почему нужно обновлять значения через копию старого слоя, а не на месте?
AЧтобы код был короче
BЧтобы расчёт каждого узла использовал значения соседей из одного и того же старого слоя, а не уже изменённые
CКопия не нужна, можно обновлять на месте
DЧтобы сэкономить память
3. В примере с пиком [0,0,0,1,0,0,0] и r=0.4 чему равно центральное значение после первого шага?
A1.0 — не изменилось
B0.4
C0.2
D0.8
4. Почему в примере сумма значений профиля со временем начинает падать ниже исходной 1.0?
AИз-за ошибки округления
BТепло утекает через концы стержня, которые принудительно держатся при нуле (краевое условие)
CСхема всегда теряет энергию случайно
DСумма не должна меняться никогда