Хімія · Клітинні автомати
📅 Березень 2026 ⏱ ≈ 11 хв читання 🎯 Середньо

Реакція-дифузія: як хімія малює плями леопарда

Аніщенко турбулентність, плями зебри, спіральні хвилі — усе це результат роботи простих рівнянь реакції та дифузії. Модель Грея-Скотта породжує нескінченне різноманіття паттернів з двох хімічних компонент та лише чотирьох параметрів.

1. Реакція, дифузія та само-організація

У 1952 році Алан Тюрінг опублікував статтю «Хімічна основа морфогенезу», де показав, що дифузна нестабільність може виникати навіть у системі, яка однорідна та стабільна без дифузії. Це здається парадоксом: дифузія зазвичай вирівнює концентрації — але якщо два реагенти дифундують з різними швидкостями, може виникнути просторовий паттерн.

Загальний механізм: активатор (A) стимулює своє власне виробництво та виробництво інгібітора (B); інгібітор пригнічує активатор; при цьому інгібітор дифундує швидше. Результат — локальне збудження активатора, оточене зоною придушення: плями, смуги, спіралі.

Приклади у природі: пігментація шкіри риб та ссавців, розподіл волосяних фолікулів, паттерни вигибання пальців ембріона, дендрити кристалів, серцева аритмія.

2. Загальні рівняння реакції-дифузії

Загальна форма для двох компонент — це система двох PDE (рівнянь у частинних похідних):

∂A/∂t = Dₐ · ∇²A + f(A, B)
∂B/∂t = D_b · ∇²B + g(A, B)

∇² — лапласіан (просторова дифузія)
Dₐ, D_b — коефіцієнти дифузії для A і B
f, g — функції реакції (конкретні для кожної моделі)

Різні вибори f і g дають різні моделі: Бріссель (Prigogine), Гіерера-Майнгардта, Фіцгью-Нагумо (збудливі нейрони), Ленгтон (цифровий антропоморфізм) — і нашу найпростішу, найзручнішу для симуляції: модель Грея-Скотта.

3. Модель Грея-Скотта

Грей та Скотт (1984, 1985) описали реакцію у відкритому реакторі, де речовина A подається ззовні і реагує з B за схемою: A + 2B → 3B, B → P (P — неактивний продукт).

∂A/∂t = Dₐ · ∇²A − A·B² + F·(1 − A)
∂B/∂t = D_b · ∇²B + A·B² − (F + k)·B

F — feed rate: швидкість подачі речовини A ззовні
k — kill rate: швидкість виведення B
Dₐ — типово 0.2100, D_b = 0.1050 (співвідношення 2:1)

Розберемо доданки:

Початкові умови: A = 1, B = 0 скрізь, крім кількох квадратів-«сід» у центрі, де A = 0.5, B = 0.25 + невеликий шум. Шум запускає симетрію-зламаний ріст паттернів.

4. Параметри F та k — зоопарк паттернів

Невеликі зміни у F та k кардинально змінюють морфологію паттернів. Нижче — класифікація основних режимів:

Плями
F=0.035, k=0.065
Ізольовані острівці B, схожі на плями леопарда або чорну крапчасту рибу.
Смуги
F=0.060, k=0.062
Лабіринтоподібні смуги, схожі на зебру або зебрану рибу.
Мітоз
F=0.028, k=0.062
Плями самовідтворюються — ділення подібне до мітозу клітин.
Мандала
F=0.025, k=0.060
Концентричні кільцеві хвилі, що розповсюджуються від центру.
Лабіринт
F=0.040, k=0.060
Пов'язані складні лабіринтні структури без clear ізоляції.
Вимирання
F=0.014, k=0.054
B швидко гине; A заповнює поле рівномірно — немає живих паттернів.
FkТип паттернуАналог у природі
0.0290.057Worms (хробаки)Шкіра морського коника
0.0350.065Spots (плями)Леопард, ягуар
0.0460.065Holes (дірки)Інвертовані плями
0.0600.062Stripes (смуги)Зебра, тигровий вугор
0.0140.047Moving spotsРухомі інфузорії

5. Дискретизація FTCS

FTCS (Forward Time, Central Space) — найпростіша явна схема різниць. Лапласіан апроксимується п'ятиточковим шаблоном (4-зв'язність):

∇²A[i,j] ≈ (A[i+1,j] + A[i−1,j] + A[i,j+1] + A[i,j−1] − 4·A[i,j]) / h²

h = 1.0 (просторовий крок), dt = 1.0 (часовий крок)

A_new[i,j] = A[i,j] + dt · (Dₐ·∇²A − A·B² + F·(1−A))
B_new[i,j] = B[i,j] + dt · (D_b·∇²B + A·B² − (F+k)·B)

Умова стійкості Куранта: dt · D / h² ≤ 0.5. При Dₐ=0.21, h=1, dt=1 — 0.21·1/1 = 0.21 < 0.5. Але для більш стійкого результату часто роблять кілька кроків FTCS за один "відображений" кадр.

Граничні умови: найпростіші — wrap-around (тороїдальна область). Без цього країв полотна паттерни відрізняються. Реалізується через % W у JS.

6. JavaScript реалізація на Canvas

const W = 256, H = 256;
const Da = 0.21, Db = 0.105;
const F  = 0.035, k  = 0.065;  // ← spots
const dt = 1.0;

// Ініціалізація сіток
let A = new Float32Array(W * H).fill(1);
let B = new Float32Array(W * H).fill(0);
let nA = new Float32Array(W * H);
let nB = new Float32Array(W * H);

// Початкове збудження
for (let seed = 0; seed < 5; seed++) {
  const cx = Math.floor(Math.random()*W);
  const cy = Math.floor(Math.random()*H);
  for (let dy = -5; dy <= 5; dy++) {
    for (let dx = -5; dx <= 5; dx++) {
      const x = (cx+dx+W) % W, y = (cy+dy+H) % H;
      A[y*W+x] = 0.5;
      B[y*W+x] = 0.25 + Math.random()*0.1;
    }
  }
}

function step() {
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const i = y*W + x;

      // Лапласіан (wrap-around)
      const la = A[((y-1+H)%H)*W+x] + A[((y+1)%H)*W+x]
               + A[y*W+(x-1+W)%W] + A[y*W+(x+1)%W] - 4*A[i];
      const lb = B[((y-1+H)%H)*W+x] + B[((y+1)%H)*W+x]
               + B[y*W+(x-1+W)%W] + B[y*W+(x+1)%W] - 4*B[i];

      const ab2 = A[i] * B[i] * B[i];
      nA[i] = A[i] + dt*(Da*la - ab2 + F*(1-A[i]));
      nB[i] = B[i] + dt*(Db*lb + ab2 - (F+k)*B[i]);

      // Затискання [0,1]
      if (nA[i] < 0) nA[i] = 0;
      if (nB[i] < 0) nB[i] = 0;
    }
  }
  [A, nA] = [nA, A]; [B, nB] = [nB, B]; // swap буферів
}

function draw(ctx, imageData) {
  const d = imageData.data;
  for (let i = 0, p = 0; i < W*H; i++, p+=4) {
    const v = Math.floor(B[i] * 255);
    d[p]=v; d[p+1]=v>>1; d[p+2]=255-v; d[p+3]=255;
  }
  ctx.putImageData(imageData, 0, 0);
}

// Цикл: 8 кроків за кадр для видимого прогресу
function loop() {
  for (let s = 0; s < 8; s++) step();
  draw(ctx, imageData);
  requestAnimationFrame(loop);
}

При 256×256 і 8 кроків за кадр отримуємо ~350 000 оновлень клітин за кадр. На сучасному браузері це виконується за <8 мс (менше ніж бюджет 16 мс для 60 FPS).

7. WebGL2: симуляція на GPU

Для роздільної здатності 512×512+ CPU-підхід повільний. WebGL2 дозволяє виконати кроки FTCS у фрагментному шейдері паралельно для всіх пікселів:

// Fragment shader (GLSL ES 3.0)
#version 300 es
precision highp float;
uniform sampler2D u_state;   // rgba: r=A, g=B
uniform vec2      u_res;
out vec4 fragColor;

void main() {
  vec2 uv = gl_FragCoord.xy / u_res;
  vec2 texel = 1.0 / u_res;

  vec2 cur = texture(u_state, uv).rg;
  vec2 top = texture(u_state, uv + vec2(0, texel.y)).rg;
  vec2 bot = texture(u_state, uv - vec2(0, texel.y)).rg;
  vec2 lft = texture(u_state, uv - vec2(texel.x, 0)).rg;
  vec2 rgt = texture(u_state, uv + vec2(texel.x, 0)).rg;

  vec2 lap = top + bot + lft + rgt - 4.0 * cur;

  float F = 0.035, k = 0.065;
  float Da = 0.21, Db = 0.105;
  float ab2 = cur.r * cur.g * cur.g;

  float newA = cur.r + Da*lap.r - ab2 + F*(1.0 - cur.r);
  float newB = cur.g + Db*lap.g + ab2 - (F+k)*cur.g;

  fragColor = vec4(clamp(newA, 0.0, 1.0),
                   clamp(newB, 0.0, 1.0),
                   0.0, 1.0);
}

Техніка ping-pong буферів: два framebuffer-и (FBO) чергуються — один є вхідним texture, інший — вихідним render target. Це дозволяє робити 30–50 кроків за кадр при 512×512 без будь-якого гальмування.

8. Зв'язок із Тюрінгом та біологією

Тюрінг (1952) довів: якщо інгібітор дифундує швидше за активатора, однорідний стан стає нестійким і виникає просторовий паттерн. Gray-Scott реалізує саме такий механізм: Dₐ > D_b і A грає роль «активатора» (само-каталітично утворює B).

Реальні приклади «тюрінгових паттернів»:

2012, Science: науковці з Гельсінкі продемонстрували, що позиція вусів (вібрис) у мишей визначається саме тюрінговим паттерном. Це перший прямий доказ у ссавця.

9. Розширення

⚗️ Реакція-дифузія (скоро)

Симуляція Gray-Scott у реальному часі зі зміною параметрів F та k з'явиться в розділі Хімія.

Розділ Хімія →