Astrophysics · Algorithms
📅 March 2026 ⏱ ≈ 13 min read 🎯 Intermediate–Advanced · Last updated: 22 June 2026

N-body gravity: from O(N²) to Barnes-Hut

Newton's law of gravity is simple to write down, but naively simulating gravity between 100,000 bodies costs O(N²) per step and grinds a browser to a halt. Discover how the Barnes-Hut octree algorithm cuts that complexity down to O(N log N) by grouping distant clusters of bodies into single approximate points, while a leapfrog integrator keeps orbits stable without accumulating energy error over time.

TL;DR: Newton's law of gravity is simple, but simulating gravity among 100,000 bodies naively costs O(N²) per step and grinds a browser to a halt. The Barnes-Hut octree algorithm approximates distant clusters of bodies as single points, cutting the cost to O(N log N), while the leapfrog integrator keeps orbits stable over long timescales without energy drift.

Newton's law of gravity

Гравітаційна сила між двома матеріальними точками масами m₁ та m₂, розділеними відстанню r, описується формулою Ньютона:

F = G · m₁ · m₂ / r²
G ≈ 6.674 × 10⁻¹¹ N·m²/kg² — gravitational constant

У симуляціях часто використовують безрозмірні одиниці або нормалізують G = 1, щоб уникнути проблем із числовою точністю float32. Прискорення тіла i від решти N–1 тіл:

aᵢ = Σⱼ≠ᵢ G · mⱼ · (rⱼ − rᵢ) / |rⱼ − rᵢ|³

Навіть проста сумація вимагає O(N²) обчислень за крок — для 10 000 тіл це вже 10⁸ операцій, що виводить за межі реального часу.

Softening: avoiding the singularity

Якщо два тіла близько зближуються, знаменник підходить до нуля, і прискорення стає нескінченним — числова катастрофа. Рішення: додати softening-параметр ε (зазвичай позначають ε або ε²):

aᵢ = Σⱼ≠ᵢ G · mⱼ · (rⱼ − rᵢ) / (|rⱼ − rᵢ|² + ε²)^(3/2)

Фізичний сенс: тіла поводяться як м'які об'єкти зі «скругленим» потенціалом. Типові значення ε — від 0.01 до 1.0 у безрозмірних одиницях, залежно від масштабу задачі.

Choosing ε: занадто велике — гравітація ослаблена, тіла не утворюють щільні скупчення. Занадто мале — часті близькі зближення повертають чисельну нестабільність. Для галактичних симуляцій зазвичай ε ≈ 0.01–0.1 від середньої відстані між тілами.

Naïve O(N²) algorithm

Базовий алгоритм — подвійний цикл: для кожної пари (i, j) обчислити силу і додати до вектора прискорення обох тіл. Завдяки 3-му закону Ньютона (actio = reactio) достатньо перебирати лише унікальні пари j > i:

N bodies Pairs Steps/sec @60fps Status
100 4 950 ≈ 297 000 Easy ✓
1 000 499 500 ≈ 30 000 000 Slow ⚠️
10 000 49 995 000 ≈ 3×10⁹ Unreachable ✗
O(N²) optimisation: навіть у наївному алгоритмі виграш дає кешування відстані, SIMD-операції (Float32Array) та відмова від Math.sqrt там, де достатньо r² (порівняння). Але асимптотичну складність це не зменшує.

Leapfrog integrator

Для N-body задач симплектичний Leapfrog (Störmer-Verlet DKD) є стандартом: він зберігає енергію набагато краще за метод Ейлера і дешевший за RK4.

Kick: v(t + Δt/2) = v(t) + a(t) · Δt/2
Drift: x(t + Δt) = x(t) + v(t + Δt/2) · Δt
Force: compute a(t + Δt) on new positions
Kick: v(t + Δt) = v(t + Δt/2) + a(t + Δt) · Δt/2

Key property: symplecticity — Leapfrog exactly preserves the symplectic structure of phase space. This means orbits neither unwind nor spiral inward even after millions of steps.

Comparison with RK4: Leapfrog — 2 оцінки сили (або 1 якщо "kick" ділять), RK4 — 4. Але для довгих симуляцій Leapfrog виграє через симплектичність, тоді як RK4 поступово "дрейфує" у фазовому просторі.

Oct-Tree and Barnes-Hut

Алгоритм Barnes-Hut (1986) — ключова ідея: далекі тіла можна апроксимувати одним центром мас, а не підсумовувати кожне окремо.

Building the Oct-Tree

3D простір рекурсивно ділиться на 8 октантів доти, доки кожна клітина містить не більше одного тіла. Одночасно в кожній внутрішній ноді зберігають:

r̄ = Σᵢ mᵢ · rᵢ / M

Building Oct-Tree for N bodies: O(N log N)

Tree traversal and the θ criterion

Для кожного тіла i дерево обходять рекурсивно. Для поточної ноди з розміром s і відстанню d від тіла перевіряють умову:

if s / d < θ → approximate node as one body with mass M and coordinate r̄
else → recursively traverse 8 child nodes

Де θ ≈ 0.5–1.0 — параметр точності. При θ = 0 отримуємо точний O(N²) алгоритм. При θ = 1.0 — максимальна швидкість, але помітна похибка в щільних регіонах.

θ criterion and accuracy

θ Complexity Relative force error Typical use
0.0 O(N²) 0% (точно) Verification
0.3 ~O(N^1.2) < 0.1% Scientific calculations
0.5 ~O(N^1.1) ~0.5% Standard (cosmology)
1.0 ≈O(N log N) ~5% Real time, games

Для браузерної симуляції в реальному часі θ = 0.7–1.0 дає непомітну для ока похибку й достатню продуктивність. Наукові гідродинамічні коди зазвичай використовують θ = 0.5–0.7.

Complexity and benchmarks

N тіл Direct O(N²) Barnes-Hut O(N log N) Прискорення ×
1 000 ~0.5 мс ~0.1 мс ×5
10 000 ~50 мс ~1.5 мс ×33
100 000 ~5 000 мс ~20 мс ×250

Для 100 000 тіл Barnes-Hut дозволяє утримувати 50+ FPS, тоді як O(N²) займає 5 секунд на крок — повний гальм. Побудова дерева додає ~O(N log N) накладних витрат, але вони окупаються вже від ~500 тіл.

JavaScript implementation

Naïve O(N²) with Float32Array

// Float32Array for CPU cache efficiency:
const pos = new Float32Array(N * 3);   // x, y, z
const vel = new Float32Array(N * 3);
const acc = new Float32Array(N * 3);
const mass = new Float32Array(N);

function computeForces() {
  acc.fill(0);
  const eps2 = EPS * EPS;
  for (let i = 0; i < N; i++) {
    const ix = i * 3, iy = ix + 1, iz = ix + 2;
    for (let j = i + 1; j < N; j++) {
      const jx = j * 3, jy = jx + 1, jz = jx + 2;
      const dx = pos[jx] - pos[ix];
      const dy = pos[jy] - pos[iy];
      const dz = pos[jz] - pos[iz];
      const r2 = dx*dx + dy*dy + dz*dz + eps2;
      const inv = G * mass[j] / (r2 * Math.sqrt(r2));  // 1/r³
      acc[ix] += dx * inv;  acc[iy] += dy * inv;  acc[iz] += dz * inv;
      // Newton 3: force j on i is opposite
      const invI = G * mass[i] / (r2 * Math.sqrt(r2));
      acc[jx] -= dx * invI; acc[jy] -= dy * invI; acc[jz] -= dz * invI;
    }
  }
}

function leapfrogStep(dt) {
  const hdt = dt * 0.5;
  // Kick: v += a * dt/2
  for (let k = 0; k < N * 3; k++) vel[k] += acc[k] * hdt;
  // Drift: x += v * dt
  for (let k = 0; k < N * 3; k++) pos[k] += vel[k] * dt;
  // New forces at new positions
  computeForces();
  // Kick: v += a * dt/2
  for (let k = 0; k < N * 3; k++) vel[k] += acc[k] * hdt;
}

Oct-Tree node

class OctNode {
  constructor(cx, cy, cz, halfSize) {
    this.cx = cx; this.cy = cy; this.cz = cz;  // cube centre
    this.halfSize = halfSize;
    this.mass = 0;
    this.comX = 0; this.comY = 0; this.comZ = 0;   // centre of mass
    this.bodyIdx = -1;    // -1 = internal node
    this.children = null; // null or Array(8)
  }

  insert(i) {
    if (this.mass === 0) {          // empty node
      this.bodyIdx = i;
      this._updateCOM(i);
      return;
    }
    if (this.children === null) {  // leaf node with 1 body
      this._subdivide();
      this._insertIn(this.bodyIdx); // relocate existing body
      this.bodyIdx = -1;
    }
    this._insertIn(i);
    this._updateCOM(i);
  }

  calcAcc(i, ax, ay, az, theta) {
    if (this.mass === 0 || this.bodyIdx === i) return;
    const dx = this.comX - pos[i*3];
    const dy = this.comY - pos[i*3+1];
    const dz = this.comZ - pos[i*3+2];
    const d2 = dx*dx + dy*dy + dz*dz + EPS*EPS;
    // Barnes-Hut criterion: s/d < θ
    if ((this.bodyIdx >= 0) || (4 * this.halfSize * this.halfSize) / d2 < theta * theta) {
      const inv = G * this.mass / (d2 * Math.sqrt(d2));
      ax[i] += dx * inv;
      ay[i] += dy * inv;
      az[i] += dz * inv;
    } else {
      for (const child of this.children) {
        if (child) child.calcAcc(i, ax, ay, az, theta);
      }
    }
  }
}

Extensions and GPU

WebGPU and compute shaders

Наступний крок після Barnes-Hut — перенесення обчислень на GPU через WebGPU Compute Shaders. Пряме O(N²) на GPU (тисячі ядер) обганяє CPU Barnes-Hut до N ≈ 50 000. Вище — більш складні GPU-BVH або FMM алгоритми.

Fast Multipole Method (FMM)

FMM — наступник Barnes-Hut із справжньою O(N) складністю. Замість однієї апроксимації (маса + COM), кожна нода зберігає мультипольний розклад, що дозволяє точніше і швидше оцінювати далекі взаємодії. Однак реалізація значно складніша.

Adaptive time stepping

Тіла в щільних скупченнях потребують малого dt, тоді як далекі — великого. Block-based адаптивний крок часу, популярний у Gadget-2 та аналогах, може ще у 10–50 разів пришвидшити симуляцію без втрати точності в ключових регіонах.

▶ Live Demo

🪐 Run N-Body simulation

Leapfrog integrator, thousands of particles, galaxy formation in real time

Open simulation →

🔗 Related Simulations

🪐Solar System 🌌Galaxy Binary Stars 🔭Orbital