Моделювання COVID-19 на графах контактів — побудова й симуляція реалістичних мереж
Припущення про однорідне змішування, закладене в класичні рівняння SIR (Сприйнятливі-Інфіковані-Одужалі) — усі однаково ймовірно зустрічають усіх — погано описує, як насправді SARS-CoV-2 поширювався в популяції: реальна передача рухалась уздовж сімей, робочих місць, шкіл і довгого хвоста випадкових контактів, з надзвичайно нерівномірною кількістю контактів на людину. Ця стаття — про інженерію самого графа — як побудувати реалістичну багатошарову мережу контактів і симулювати на ній поширення хвороби — а не про загальну теорію того, чому топологія мережі має значення, яку докладно розглядає наша супутня стаття про мережеву епідеміологію.
1. Чому COVID-19 потребував графа, а не компартмента
Класична компартментна модель SIR/SEIR припускає, що кожна сприйнятлива людина має рівний, малий шанс контакту з кожною інфекційною людиною — математично зручно, але хибно у конкретний спосіб, що мав величезне значення для COVID-19: реальні патерни людських контактів надзвичайно неоднорідні. Більшість людей мають жменьку близьких, повторюваних контактів (сім'я, близькі колеги); дехто має десятки чи сотні (працівники роздрібної торгівлі, вчителі, працівники транспорту). Граф контактів — вузли це люди, ребра — контакти, здатні передавати інфекцію — явно представляє цю неоднорідність, а не усереднює її.
Ця відмінність не була академічною. Геномні дослідження та дослідження відстеження контактів SARS-CoV-2 неодноразово виявляли, що невелика частка випадків спричиняла велику частку наступних заражень (події суперпоширення в хорах, на м'ясопереробних заводах, весіллях) — патерн, який модель однорідного змішування структурно не може відтворити, а графова модель відтворює природно, щойно розподіл ступенів вузлів стає реалістичним (§4).
2. Побудова багатошарового графа контактів
Реалістичні синтетичні мережі контактів (як ті, що використовуються моделями на кшталт CovidSim Імперського коледжу чи Covasim Інституту моделювання хвороб) будуються як об'єднання кількох окремих шарів, кожен зі своєю частотою контактів і ризиком передачі:
🏠 Сім'я
Малі повні підграфи (2-6 вузлів), найвища ймовірність передачі на контакт, завжди активні.
🏢 Робота
Кластеризовані групи по 5-50, помірно-високий ризик передачі, активні лише в робочі дні.
🏫 Школа
Кластери на основі класів плюс загальношкільний випадковий шар, вікозалежна сприйнятливість.
🎲 Спільнота
Розріджені випадкові чи "малий світ" ребра (магазини, транспорт), нижчий ризик на контакт, але дуже висока кількість вузлів.
Ключовий параметр кожного шару — розподіл ступенів — скільки контактів має кожен вузол у цьому шарі. Сім'ї природно малі й приблизно рівномірні; шари роботи та спільноти зазвичай беруться з розподілу з важким хвостом (напр. від'ємного біноміального чи степеневого), щоб відтворити спостережувану надзвичайну варіативність реальної кількості контактів.
Кожне ребро (i, j) у шарі L несе: β_L // ймовірність передачі на контакт, за день, специфічна для шару
3. Графовий SEIR: стани й правила переходу
Кожен вузол несе стан хвороби — сприйнятливий (S), контактований (E, заражений, але ще не інфекційний), інфекційний (I, далі поділений на безсимптомний/легкий/важкий для COVID-19), одужалий (R), і, за потреби, померлий. Замість рівняння швидкості для компартмента, переходи відбуваються по ребру, на день:
P(j стає E цього дня) = 1 − (1 − β_L)^(контакти в шарі L)
E → I після латентного періоду ~ ЛогНормальний(μ=1.6, σ=0.5) днів
I → R (або смерть) після інфекційного періоду, тривалість і результат обираються для кожного вузла // індивідуальна неоднорідність важкості/тривалості, а не лише популяційне середнє
Виконання цього стохастично, вузол за вузлом, день за днем, природно відтворює розгалуження, кластеризацію в межах сімей і застрягання в розріджено з'єднаних частинах графа — жодне з цього не може представити середньопольове ЗДР, оскільки воно не має поняття "конкретна ця сім'я" чи "конкретне це робоче місце".
4. Суперпоширення та надлишкова дисперсія (k)
Емпірично, кількість вторинних заражень, спричинених одним випадком COVID-19, добре описується від'ємним біноміальним розподілом із параметром дисперсії k ≈ 0.1-0.5 — набагато більш надлишково розсіяним, ніж розподіл Пуассона (k → ∞), який випливає з однорідного змішування. Мале k означає, що більшість заражених людей передають хворобу небагатьом або нікому, тоді як невелика частка передає її десяткам.
k → ∞ (Пуассон): усі однаково ймовірно передають — режим однорідного змішування
k ≈ 1: помірна варіативність
k ≈ 0.1-0.2 (оцінка для SARS-CoV-2): сильна надлишкова дисперсія — "правило 80/20", коли приблизно 10-20% випадків спричиняють ~80% передач
На графі контактів ця надлишкова дисперсія виникає природно, без жодного додаткового параметра, лише з поєднання (а) розподілу ступенів з важким хвостом (§2) і (б) варіації індивідуальної інфекційності/вірусного навантаження — обидва є явними, вимірюваними властивостями графа й вузлів, а не підігнаною статистичною надбудовою.
5. Симуляція втручань на графі
Представлення у вигляді графа дозволяє легко симулювати реальні непрямі втручання (NPI) як конкретні операції над графом, а не абстрактні зміни параметрів:
- Закриття шкіл/робочих місць — видалити всі ребра відповідного шару на період втручання.
- Відстеження контактів та ізоляція — при виявленні інфекційного вузла тимчасово видалити ребра його прямих сусідів (відстежених контактів) на період карантину.
- Фізичне дистанціювання — імовірнісно проріджувати ребра шару спільноти, знижуючи середній ступінь без повного видалення шару.
- Вакцинація — позначити частку вузлів як імунних (вилучивши їх із пулу сприйнятливих), цільово випадково чи переважно для вузлів з високим ступенем, щоб протестувати стратегії "кільцевої вакцинації".
Оскільки кожне втручання — це чітко визначена операція над графом, цей підхід дозволяє безпосередньо порівнювати, наприклад, "закрити школи" проти "відстежувати й ізолювати контакти підтверджених випадків" на одній і тій самій базовій структурі популяції — порівняння, яке важко зробити змістовно в компартментній моделі.
6. JavaScript: побудова й запуск симуляції
function buildContactGraph(n = 2000) {
const nodes = Array.from({ length: n }, (_, i) => ({ id: i, state: 'S', edges: [] }));
const addEdge = (i, j, layer, beta) => {
nodes[i].edges.push({ to: j, layer, beta });
nodes[j].edges.push({ to: i, layer, beta });
};
// Шар сім'ї: кластеризувати вузли в групи по 2-5
let idx = 0;
while (idx < n) {
const size = 2 + Math.floor(Math.random() * 4);
const members = [];
for (let k = 0; k < size && idx < n; k++) members.push(idx++);
for (let a = 0; a < members.length; a++)
for (let b = a + 1; b < members.length; b++)
addEdge(members[a], members[b], 'household', 0.25);
}
// Шар спільноти: важкий хвіст випадкових ребер (у дусі configuration model)
const extraEdges = Math.floor(n * 2.5);
for (let e = 0; e < extraEdges; e++) {
const i = Math.floor(Math.random() * n), j = Math.floor(Math.random() * n);
if (i !== j) addEdge(i, j, 'community', 0.04);
}
return nodes;
}
function stepDay(nodes) {
const newlyExposed = [];
for (const node of nodes) {
if (node.state !== 'I') continue;
for (const { to, beta } of node.edges) {
if (nodes[to].state === 'S' && Math.random() < beta) newlyExposed.push(to);
}
}
for (const id of newlyExposed) nodes[id].state = 'E';
// (переходи E→I та I→R із вибірковими тривалостями опущено для стислості)
return nodes;
}
// Посіяти один інфекційний вузол у хабі з високим ступенем і запустити на 120 днів
const graph = buildContactGraph(2000);
graph[0].state = 'I';
for (let day = 0; day < 120; day++) stepDay(graph);
Запуск багатьох стохастичних повторів цього самого графа безпосередньо відтворює надлишково розсіяний розподіл вторинних випадків із §4 — прямо зі структури ребер, без потреби вручну вводити параметр дисперсії k. Про теорію R₀, порогів колективного імунітету та про те, як топологія мережі загалом визначає розмір спалаху, див. Поширення епідемій у мережах.