Туторіал · Хаос · Чисельні методи · JavaScript
📅 Липень 2026 ⏱ ≈ 15 хв 🎯 Початківець – Середній рівень

RK4 у JavaScript: 30-рядковий інтегратор

Кожна хаотична симуляція на цьому сайті — атрактор Лоренца, подвійний маятник, система Ресслера — керується одним і тим самим крихітним шматком коду: кроком методу Рунге-Кутта 4-го порядку. Напишіть його один раз, приблизно у 30 рядках, і використовуйте для будь-якої системи звичайних диференціальних рівнянь.

1. Чому Ейлера недостатньо

Найпростіший спосіб чисельно проінтегрувати dy/dt = f(t, y) — це явний (прямий) метод Ейлера: взяти поточний нахил і зробити крок уздовж нього.

y += f(t, y) * dt;
t += dt;

Це один рядок, але локальна похибка становить O(dt²) на крок — метод використовує похідну лише на початку інтервалу, тож будь-яка кривизна справжньої траєкторії протягом цього кроку повністю пропускається. Для хаотичної системи на кшталт рівнянь Лоренца ця похибка не просто трохи спотворює траєкторію — оскільки близькі траєкторії розходяться експоненціально, невелика похибка інтегрування швидко перетворюється на зовсім іншу орбіту. При більшому кроці метод Ейлера також може бути нестабільним: енергія чи амплітуда дрейфують, доки симуляція візуально не «вибухає».

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

2. Чотири стадії RK4

Метод Рунге-Кутта 4-го порядку (RK4) обчислює похідну чотири рази за крок — на початку, у двох оцінках середини та в кінці — а потім комбінує їх із вагами 1 : 2 : 2 : 1:

k1 = f(t, y) k2 = f(t + dt/2, y + k1 * dt/2) k3 = f(t + dt/2, y + k2 * dt/2) k4 = f(t + dt, y + k3 * dt) y(t + dt) = y(t) + (k1 + 2*k2 + 2*k3 + k4) * dt/6

Інтуїтивно: k1 — це той самий нахил, що дав би звичайний Ейлер. k2 та k3 уточнюють цю оцінку, обчислюючи нахил у середині інтервалу — використовуючи оцінку попередньої стадії, щоб туди дістатися. k4 обчислює нахил на дальньому кінці, використовуючи оцінку середини для проекції вперед. Усереднення всіх чотирьох — з подвійною вагою для оцінок середини — скасовує головні члени похибки, даючи локальну похибку O(dt⁵) на крок (глобальну — O(dt⁴)) лише за 4-кратну вартість одного кроку Ейлера.

3. 30-рядковий інтегратор

Секрет перевикористовуваної реалізації RK4 — тримати стан як звичайний масив і написати два крихітних векторних помічники (add, scale) замість того, щоб жорстко прописувати x, y, z. Тоді степер працює без змін, незалежно від того, чи y має 2 компоненти (маятник), 3 (Лоренц) чи 40 (ланцюжок зв'язаних осциляторів).

// --- векторні помічники -------------------------------------------------
function add(a, b) {
  return a.map((v, i) => v + b[i]);
}
function scale(a, s) {
  return a.map((v) => v * s);
}

// --- один крок RK4 -----------------------------------------------------
// f(t, y) -> масив похідних, тієї ж форми, що й y
function rk4Step(f, t, y, dt) {
  const k1 = f(t,              y);
  const k2 = f(t + dt / 2, add(y, scale(k1, dt / 2)));
  const k3 = f(t + dt / 2, add(y, scale(k2, dt / 2)));
  const k4 = f(t + dt,     add(y, scale(k3, dt)));

  const sum = k1.map(
    (_, i) => k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]
  );
  return add(y, scale(sum, dt / 6));
}

// --- запуск симуляції ---------------------------------------------
function integrate(f, y0, dt, steps) {
  let t = 0, y = y0, trail = [y0];
  for (let i = 0; i < steps; i++) {
    y = rk4Step(f, t, y, dt);
    t += dt;
    trail.push(y);
  }
  return trail;
}

Підрахуємо: два 3-рядкові векторні помічники, 11-рядковий rk4Step і 8-рядковий цикл-драйвер — рівно близько 30 рядків для повністю універсального ЗДР-інтегратора, без жодних залежностей.

4. Перевірка на системі Лоренца

Щоб проінтегрувати систему Лоренца, потрібна лише функція похідних, що відповідає сигнатурі f(t, y) вище — сам rk4Step не змінюється:

function lorenz(t, [x, y, z]) {
  const sigma = 10, rho = 28, beta = 8 / 3;
  return [
    sigma * (y - x),
    x * (rho - z) - y,
    x * y - beta * z,
  ];
}

const trail = integrate(lorenz, [0.1, 0, 0], 0.01, 20000);
// trail[i] = [x, y, z] на кроці i — подавайте прямо у WebGL line strip

Замініть lorenz на функцію похідних маятника ([θ, ω] → [ω, -g/L·sin θ]) чи систему Ресслера — та сама пара rk4Step/integrate продовжить працювати без змін — саме в цьому й полягає сенс зберігання стану як універсального масиву.

5. Вибір кроку інтегрування

Локальна похибка RK4 масштабується як dt⁵, тому зменшення dt удвічі скорочує похибку кроку приблизно до 1/32 попереднього значення — але подвоює кількість кроків, потрібних для покриття того самого проміжку часу. На практиці:

6. Типові пастки

Не мутуйте вхідний масив. Якщо f(t, y) записує щось у y на місці, обчислення середини (k2, k3) використають пошкоджений стан. Завжди повертайте новий масив із f, або зробіть add/scale такими, що виділяють нові масиви (як вище), а не змінюють на місці.
Форсування, залежне від часу, працює без проблем. Оскільки rk4Step передає у f проміжні моменти часу (t, t + dt/2, t + dt), системи з явною залежністю від часу (керовані осцилятори, періодичне форсування) інтегруються коректно без жодного додаткового коду.
RK4 не є симплектичним. Для довготривалих орбітальних чи гамільтонових симуляцій, де точне збереження енергії важливіше за суху точність, симплектичний інтегратор (напівнеявний Ейлер, leapfrog, Верле) зазвичай підходить краще, ніж RK4 — див. туторіал про фізичний движок для напівнеявного Ейлера на практиці.
▶ Демо наживо

Часті запитання

Чого я навчуся в цьому уроці?

Напишіть універсальний інтегратор Рунге-Кутта 4-го порядку (RK4) приблизно у 30 рядках JavaScript і застосуйте його до системи Лоренца, маятника та будь-якого ЗДР.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Чому Ейлера недостатньо, Чотири стадії RK4, 30-рядковий інтегратор, Перевірка на системі Лоренца, Вибір кроку інтегрування, Типові пастки.

Скільки часу займає цей урок?

Цей урок займає приблизно 15 хв.

Які попередні знання потрібні?

Це урок рівня «Початківець – Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.