Mímisbrunnr知恵の泉

← シミュレーション 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:Schellingの分居モデル | 関連:マルコフ連鎖と定常分布

要点(BLUF)

1. エージェントベースの SIR モデル

感染症の古典は SIR(Susceptible / Infected / Recovered)です。各個体を格子上のエージェントとし、状態を持たせます。

平均化された ODE 版 SIR(稀少事象や疫学の本拠)と違い、エージェント版は空間的なクラスタ形成個体のばらつきを直接表現できます。

2. 実装と流行曲線

import numpy as np
from scipy.signal import convolve2d

def abm_sir(L, beta, gamma, steps, seed, init_inf=5):
    rng = np.random.default_rng(seed)
    state = np.zeros((L, L), int)               # 0=S, 1=I, 2=R
    for _ in range(init_inf):
        state[rng.integers(L), rng.integers(L)] = 1   # 初期感染者
    I_series = [(state == 1).sum()]
    for t in range(steps):
        new = state.copy()
        # 各セルの感染中の近傍数
        inf_nb = convolve2d((state==1).astype(int), np.ones((3,3),int),
                            mode='same', boundary='wrap') - (state==1)
        susceptible = (state == 0)
        p_infect = 1 - (1-beta)**inf_nb          # 近傍感染者からの感染確率
        infect = susceptible & (rng.random((L,L)) < p_infect)
        new[infect] = 1
        recover = (state==1) & (rng.random((L,L)) < gamma)   # 回復
        new[recover] = 2
        state = new
        I_series.append((state==1).sum())
        if (state==1).sum() == 0: break          # 感染者ゼロで終息
    attack_rate = (state==2).sum() / (L*L)        # 最終感染率
    return attack_rate, max(I_series)/(L*L), int(np.argmax(I_series))

ar, peak, t_peak = abm_sir(L=100, beta=0.07, gamma=0.1, steps=400, seed=3)
print(f"beta=0.07: 最終感染率={ar:.3f}  流行ピーク={peak:.1%} (t={t_peak})")

出力:

beta=0.07: 最終感染率=0.983  流行ピーク=10.3% (t=97)

出力の意味:感染確率 β=0.07\beta=0.07 では、5人の初期感染者から始まって最終的に人口の98%が感染(R になった)し、流行のピーク(同時感染者が最大)は t=97 で人口の10.3%。感染者数が一度盛り上がってから収束する典型的な流行曲線(エピデミックカーブ)が、局所接触ルールだけから生まれます。

3. 創発する閾値:流行は急に立ち上がる

最も重要な創発現象が閾値(threshold)です。β\beta を少しずつ上げると、流行は連続的にではなくある点で急に全体に広がります。

import numpy as np
from scipy.signal import convolve2d

def abm_sir(L, beta, gamma, steps, seed, init_inf=5):
    rng = np.random.default_rng(seed)
    state = np.zeros((L, L), int)
    for _ in range(init_inf):
        state[rng.integers(L), rng.integers(L)] = 1
    I_series = [(state==1).sum()]
    for t in range(steps):
        new = state.copy()
        inf_nb = convolve2d((state==1).astype(int), np.ones((3,3),int),
                            mode='same', boundary='wrap') - (state==1)
        infect = (state==0) & (rng.random((L,L)) < 1-(1-beta)**inf_nb)
        new[infect] = 1
        recover = (state==1) & (rng.random((L,L)) < gamma)
        new[recover] = 2
        state = new
        I_series.append((state==1).sum())
        if (state==1).sum() == 0: break
    return (state==2).sum()/(L*L), max(I_series)/(L*L), int(np.argmax(I_series))

print("beta を上げると流行が相転移する(gamma=0.1, 100x100, seed=3):")
for beta in [0.02, 0.03, 0.04, 0.05, 0.07, 0.10]:
    ar, peak, tp = abm_sir(100, beta, 0.1, 400, seed=3)
    print(f"  beta={beta:.2f}: 最終感染率={ar:.3f}  ピーク={peak:.1%} (t={tp})")

出力:

beta を上げると流行が相転移する(gamma=0.1, 100x100, seed=3):
  beta=0.02: 最終感染率=0.001  ピーク=0.1% (t=1)
  beta=0.03: 最終感染率=0.011  ピーク=0.2% (t=42)
  beta=0.04: 最終感染率=0.597  ピーク=2.6% (t=142)
  beta=0.05: 最終感染率=0.922  ピーク=4.7% (t=209)
  beta=0.07: 最終感染率=0.983  ピーク=10.3% (t=97)
  beta=0.10: 最終感染率=0.997  ピーク=16.3% (t=61)

出力の意味β=0.03\beta=0.03 では感染がほぼ広がらず(最終感染率1.1%、すぐ終息)、ところが β=0.04\beta=0.04 に上げると突然最終感染率が60%へ跳ね上がる。この β=0.030.04\beta=0.03 \to 0.04 の間に閾値があり、それを境に「くすぶって消える」か「全体に広がる」かが切り替わります。これはパーコレーション転移と同種の相転移で、感染ネットワークが「臨界点」を超えて繋がるかどうかで決まります。個々の感染確率はわずかしか変えていないのに、マクロな帰結が劇的に変わる——これが流行モデルの核心的な創発です。

数式の直観的意味

閾値現象は「局所的な繋がりが、ある密度を超えると大域的な連結成分を作る」というパーコレーションの幾何です。感染が広がるには、感染者が回復する(γ\gamma)までの間に平均1人以上の新規感染を生む必要がある——これが基本再生産数 R0>1R_0 > 1 の条件。格子上では近傍数が限られるので、β\beta がある臨界値を超えて初めて感染経路が空間を貫通して繋がり、流行が「点火」する。β=0.04\beta=0.04 付近のピークが t=142 と遅いのは、臨界点近傍では伝播がぎりぎりで、ゆっくりとしか広がらないため(臨界減速)。閾値を十分超えた β=0.10\beta=0.10 では一気に燃え広がりピークが t=61 と早い。Schelling の分居(Schellingの分居モデル)が「緩い選好→強い分居」だったように、ここでも「わずかな感染力の差→流行の有無」という、ミクロとマクロの非線形なギャップが現れます。平均場の ODE では見えにくい空間的な閾値を、エージェントモデルは直接見せてくれます。

⚠️ よくある誤解・落とし穴

対応シミュレーション参照

本文の格子 SIR(default_rng(3))と β\beta スイープによる閾値転移(最終感染率が0.03→0.04で1%→60%)。

関連ノート