Фізика · CFD · Кінетична теорія
📅 Березень 2026 ⏱ ≈ 11 хв читання 🎯 Середній–Просунутий · Останнє оновлення: 28 травня 2026 р.

Метод ґраткового Больцмана — гідродинаміка з кінетичної теорії

Більшість CFD-кодів розв'язують рівняння Нав'є-Стокса напряму. Метод ґраткового Больцмана (LBM) обирає інший шлях: він моделює популяції фіктивних частинок, що поширюються регулярною ґраткою, і відновлює макроскопічну поведінку рідини як емерджентний наслідок правил зіткнень. Результат — природно паралелізовний алгоритм, який працює в реальному часі у браузері.

1. Кінетична теорія — рівняння Больцмана

Класична гідродинаміка (Нав'є-Стокс) оперує макроскопічними полями: густиною ρ, швидкістю u та тиском p. Кінетична теорія дає мезоскопічний погляд: рідина описується функцією розподілу f(x, ξ, t) — густиною частинок у точці x з мікроскопічною швидкістю ξ у момент часу t.

Неперервне рівняння перенесення Больцмана керує еволюцією f:

∂f/∂t + ξ · ∇f = Ω(f)

Ліва частина: поширення (вільне розповсюдження частинок)
Права частина: Ω — оператор зіткнень (повертає f до рівноваги)

Ключова ідея LBM — це дискретизація і простору, і швидкості. Простір стає регулярною ґраткою; неперервний простір швидкостей зводиться до малого скінченного набору дозволених швидкостей ei. Член зіткнень апроксимується простою моделлю BGK. Попри ці радикальні спрощення, розклад Чепмена-Енскога показує, що отримані рівняння відтворюють рівняння Нав'є-Стокса для нестисливої рідини з точністю до другого порядку за числом Маха.

2. Ґратка D2Q9 — набір швидкостей і ваги

Назва «D2Q9» кодує вимірність і кількість швидкостей: 2 виміри, 9 напрямків швидкості. Кожен вузол ґратки поширює густину вздовж 9 напрямків (включно зі спокоєм).

f61/36
f21/9
f51/36
f31/9
·f04/9
f11/9
f71/36
f41/9
f81/36
Вектори швидкості e_i (у ґраткових одиницях):
e_0 = (0,0) спокій
e_1 = (1,0) e_2 = (0,1) e_3 = (-1,0) e_4 = (0,-1) уздовж осей
e_5 = (1,1) e_6 = (-1,1) e_7 = (-1,-1) e_8 = (1,-1) діагональні

Ваги w_i:
w_0 = 4/9 спокій
w_1..4 = 1/9 уздовж осей
w_5..8 = 1/36 діагональні

Швидкість звуку: c_s² = 1/3 (у ґраткових одиницях)

Ваги w_i підібрані так, щоб ґратка коректно відтворювала перший і другий моменти (масу й імпульс) рівноважного розподілу. Умова Σ w_i = 1 забезпечує збереження маси.

3. Оператор зіткнень BGK

Наближення Батнагара-Ґросса-Крука (BGK) замінює справжній інтеграл зіткнень релаксацією до локальної рівноваги. Кожна функція розподілу fi релаксує зі швидкістю 1/τ:

Крок зіткнення:
f_i*(x, t) = f_i(x, t) − (1/τ) · [f_i(x, t) − f_i^eq(x, t)]

Рівноважний розподіл:
f_i^eq = w_i · ρ · [1 + (e_i · u)/c_s² + (e_i · u)²/(2 c_s⁴) − u²/(2 c_s²)]

де c_s² = 1/3, тож:
f_i^eq = w_i · ρ · [1 + 3(e_i · u) + 9(e_i · u)²/2 − 3u²/2]

Кінематична в'язкість: ν = c_s² (τ − 1/2) = (τ − 0.5) / 3

Час релаксації τ керує в'язкістю: τ = 1 дає ν = 1/6 (помірно в'язка). Стійкість вимагає τ > 0.5. Коли τ → 0.5, в'язкість → 0 і симуляція стає нестійкою для високошвидкісних потоків.

Обмеження стійкості: τ > 0.5 необхідне, але не достатнє. Число Маха Ma = |u| / cs також має бути малим (Ma < 0.3), щоб припущення про малі числа Маха залишалося чинним. Для турбулентних потоків за вищих Re беріть τ ближче до 1.

4. Крок поширення

Після зіткнення кожен пост-зіткненнєвий розподіл fi* переходить до сусіднього вузла ґратки в напрямку ei:

Крок поширення:
f_i(x + e_i · Δt, t + Δt) = f_i*(x, t)

За один крок часу Δt = 1 (ґраткові одиниці) кожна f_i зсувається рівно на одну комірку у своєму напрямку.
Схема витягування (поширена в коді):
f_i(x, t+1) = f_i*(x − e_i, t) ← збираємо з вузла вище за потоком

Отже, повний цикл оновлення LBM за один крок часу такий:

  1. Обчислити макроскопічні ρ та u з поточної f
  2. Обчислити рівноважні розподіли feq
  3. Застосувати зіткнення BGK: f* = f − (1/τ)(f − feq)
  4. Поширити: зсунути кожну fi* на один крок у напрямку ei
  5. Застосувати граничні умови

5. Відновлення макроскопічних величин — ρ, u з f

Макроскопічні величини рідини — це моменти функції розподілу. Густина маси ρ — нульовий момент; імпульс ρu — перший:

ρ(x, t) = Σ_i f_i(x, t) ← сума по всіх 9 напрямках
ρu_x(x, t) = Σ_i f_i(x, t) · e_ix
ρu_y(x, t) = Σ_i f_i(x, t) · e_iy

Тиск (ідеальний газ у LBM-одиницях): p = ρ c_s² = ρ/3

Саме це локальне обчислення — що потребує лише 9 fi в одному вузлі — робить LBM добре паралелізовним. Кожен вузол незалежний під час кроку зіткнення; лише крок поширення потребує даних від сусідів.

6. Граничні умови

Стінки без проковзування — відбиття назад (Bounce-Back)

Найпростіша гранична умова без проковзування: будь-який розподіл, що прибуває у вузол твердої стінки, відбивається назад у протилежному напрямку — наче частинка відскочила від стінки.

f_ī(x_wall, t+1) = f_i*(x_wall, t)

ī = протилежний напрямок: opposite(0)=0, opposite(1)=3, opposite(3)=1 ...

Рухомі стінки — Зу-Хе

Для притоку/відтоку з відомою швидкістю u або відомим тиском ρ схема Зу-Хе аналітично екстраполює невідомі розподіли. Для лівого вхідного отвору зі швидкістю u = (u_x, 0):

ρ_in = (f_0 + f_2 + f_4 + 2(f_3 + f_6 + f_7)) / (1 − u_x)
f_1 = f_3 + (2/3) ρ_in u_x
f_5 = f_7 − (1/2)(f_2 − f_4) + (1/6) ρ_in u_x
f_8 = f_6 + (1/2)(f_2 − f_4) + (1/6) ρ_in u_x

7. Повна реалізація D2Q9 на JavaScript

// ── Константи D2Q9 ────────────────────────────────────────────────
const NX = 200, NY = 100;  // розмір сітки
const TAU = 0.6;            // час релаксації  (ν = (τ−0.5)/3 ≈ 0.033)
const OMEGA = 1 / TAU;      // швидкість релаксації
const N = NX * NY;

// Вектори швидкості: 0=спокій, 1–4 осі, 5–8 діагоналі
const EX = [0,  1,  0, -1,  0,  1, -1, -1,  1];
const EY = [0,  0,  1,  0, -1,  1,  1, -1, -1];
const W  = [4/9, 1/9, 1/9, 1/9, 1/9, 1/36, 1/36, 1/36, 1/36];
const OPP = [0, 3, 4, 1, 2, 7, 8, 5, 6]; // індекс протилежного напрямку

// Функції розподілу: f[i * N + x + y*NX]
const f    = new Float32Array(9 * N).fill(0);
const fTmp = new Float32Array(9 * N);
const solid = new Uint8Array(N); // 1 = перешкода

function idx(x, y) { return x + y * NX; }
function fi(i, x, y) { return i * N + idx(x, y); }

// ── Ініціалізація: однорідний потік u_x = u0 ─────────────────────────────
function init(u0 = 0.1) {
  for (let y = 0; y < NY; y++) {
    for (let x = 0; x < NX; x++) {
      for (let i = 0; i < 9; i++) {
        f[fi(i, x, y)] = feq(i, 1.0, u0, 0.0);
      }
    }
  }
  // Додаємо круглу перешкоду ближче до лівого краю
  const cx = NX / 4, cy = NY / 2, r = NY / 8;
  for (let y = 0; y < NY; y++) {
    for (let x = 0; x < NX; x++) {
      if ((x-cx)**2 + (y-cy)**2 < r*r) solid[idx(x,y)] = 1;
    }
  }
}

// ── Рівноважний розподіл ──────────────────────────────────────
function feq(i, rho, ux, uy) {
  const eu  = EX[i] * ux + EY[i] * uy;
  const u2  = ux * ux + uy * uy;
  return W[i] * rho * (1 + 3*eu + 4.5*eu*eu - 1.5*u2);
}

// ── Один крок часу LBM ──────────────────────────────────────────
function step(u0 = 0.1) {
  // 1. Зіткнення + поширення (схема витягування)
  for (let y = 0; y < NY; y++) {
    for (let x = 0; x < NX; x++) {
      if (solid[idx(x, y)]) continue;

      // Обчислюємо макроскопічні величини
      let rho = 0, ux = 0, uy = 0;
      for (let i = 0; i < 9; i++) {
        const fi_val = f[fi(i, x, y)];
        rho += fi_val;
        ux  += fi_val * EX[i];
        uy  += fi_val * EY[i];
      }
      ux /= rho; uy /= rho;

      // Зіткнення BGK, поширення витягуванням
      for (let i = 0; i < 9; i++) {
        // Вузол-джерело для поширення
        const sx = x - EX[i], sy = y - EY[i];
        if (sx < 0 || sx >= NX || sy < 0 || sy >= NY) {
          fTmp[fi(i, x, y)] = feq(i, 1.0, u0, 0.0); // приток
          continue;
        }
        if (solid[idx(sx, sy)]) {
          // Відбиття назад від перешкоди
          fTmp[fi(i, x, y)] = f[fi(OPP[i], x, y)];
          continue;
        }
        const f_src = f[fi(i, sx, sy)];
        fTmp[fi(i, x, y)] = f_src - OMEGA * (f_src - feq(i, rho, ux, uy));
      }
    }
  }
  fTmp.copyTo ? fTmp.copyTo(f) : f.set(fTmp);
}

// ── Рендер: колір за величиною швидкості ─────────────────────────
function render(ctx, canvas) {
  const img = ctx.createImageData(NX, NY);
  const d   = img.data;
  for (let y = 0; y < NY; y++) {
    for (let x = 0; x < NX; x++) {
      const p = idx(x, y);
      if (solid[p]) { d[p*4]   = 200; d[p*4+1] = 200; d[p*4+2] = 200; d[p*4+3] = 255; continue; }
      let ux = 0, uy = 0, rho = 0;
      for (let i = 0; i < 9; i++) {
        const v = f[fi(i, x, y)]; rho += v; ux += v*EX[i]; uy += v*EY[i];
      }
      ux /= rho; uy /= rho;
      const spd = Math.hypot(ux, uy) / 0.25; // нормалізуємо до [0,1]
      const t   = Math.min(spd, 1);
      d[p*4]   = (t * 56)  | 0;  // r
      d[p*4+1] = (t * 189) | 0;  // g (→ блакитний)
      d[p*4+2] = (t * 248) | 0;  // b
      d[p*4+3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);
}
Порада щодо продуктивності: замініть внутрішні цикли типізованими масивами, дружніми до SIMD, і винесіть функцію кроку у Worker. За 200 × 100 вузлів однопотокова JS-реалізація легко працює на 60 FPS. За 512 × 256 обчислювальний прохід WebGL2 (з текстурами з плаваючою комою для ping-pong) у 30–100 разів швидший.

8. Застосування та розширення

💧 SPH-симуляція рідини

Відчуйте динаміку частинкової рідини в реальному часі — частинкове доповнення до ейлерового сіткового підходу LBM.

Відкрити симуляцію рідини →