🎓 レベル:基礎 | 重要度:A(必須)
📎 関連:Schellingの分居モデル
要点(BLUF)
- セルオートマトン(CA):格子上の各セルが、近傍の状態だけを見て同時に次の状態を更新する離散モデル。
- 単純な局所ルールから、振動・移動・複製などの複雑な構造が創発します。
- コンウェイのライフゲームで、周期2で振動する「ブリンカー」と、形を保って斜めに移動する「グライダー」をコードで確認します。
1. セルオートマトンとは
セルオートマトンは、最もシンプルなエージェントベースモデルです。
- 格子(grid):セルが規則正しく並ぶ空間(1次元の列、2次元の方眼など)。
- 状態:各セルが取る値(生/死、0/1 など有限個)。
- 近傍:次状態を決めるとき参照する周囲のセル(上下左右の4近傍、周囲8近傍など)。
- 遷移規則:自分と近傍の状態から次の自分の状態を決める局所ルール。全セルが同時に更新される。
全エージェントが同じ単純ルールに従うだけなのに、全体としては設計していないパターンが現れる——これが CA の魅力であり、創発(emergence)の最小実例です。
2. ライフゲームのルール
コンウェイのライフゲーム(Game of Life)は2次元・2状態(生=1/死=0)・周囲8近傍の CA で、ルールはたった4つ:
- 誕生:死セルの生きた近傍がちょうど3個 → 次は生
- 生存:生セルの生きた近傍が2または3個 → 次も生
- 過疎:生セルの生きた近傍が1個以下 → 死
- 過密:生セルの生きた近傍が4個以上 → 死
まとめると「生きた近傍が3個なら誕生/生存、生セルで2個なら生存、それ以外は死」。畳み込みで近傍の生数を一気に数えられます。
import numpy as np
from scipy.signal import convolve2d
def step_life(grid):
"""ライフゲームの1ステップ(周期境界)"""
# 各セルの生きた近傍の数(自分を除く8近傍)
neighbors = convolve2d(grid, np.ones((3,3), int), mode='same', boundary='wrap') - grid
born_or_survive = (neighbors == 3) | ((grid == 1) & (neighbors == 2))
return born_or_survive.astype(int)
3. ブリンカー:周期2の振動子
3つ並んだ生セルは、縦横に交互に振動します(周期2)。
import numpy as np
from scipy.signal import convolve2d
def step_life(grid):
neighbors = convolve2d(grid, np.ones((3,3), int), mode='same', boundary='wrap') - grid
return ((neighbors == 3) | ((grid == 1) & (neighbors == 2))).astype(int)
g = np.zeros((5,5), int); g[2, 1:4] = 1 # 横3つのブリンカー
g1 = step_life(g)
g2 = step_life(g1)
print(f"1ステップ後は変化する:{not np.array_equal(g, g1)}")
print(f"2ステップ後に元へ戻る:{np.array_equal(g, g2)}")
出力:
1ステップ後は変化する:True
2ステップ後に元へ戻る:True
出力の意味:横並び3つが1ステップで縦並び3つになり、もう1ステップで横に戻る——周期2の振動子です。「振動する」という性質は、誰もそうプログラムしていません。4つの生死ルールから自然に出てきた構造です。
4. グライダー:形を保って移動する
5セルの「グライダー」は、4ステップごとに形を保ったまま斜めに1マス移動します。
import numpy as np
from scipy.signal import convolve2d
def step_life(grid):
neighbors = convolve2d(grid, np.ones((3,3), int), mode='same', boundary='wrap') - grid
return ((neighbors == 3) | ((grid == 1) & (neighbors == 2))).astype(int)
G = np.zeros((20,20), int)
glider = np.array([[0,1,0],[0,0,1],[1,1,1]])
G[1:4, 1:4] = glider
G4 = G.copy()
for _ in range(4):
G4 = step_life(G4)
shifted = np.roll(np.roll(G, 1, axis=0), 1, axis=1) # 元を(1,1)平行移動
print(f"生セル数:開始 {G.sum()} → 4ステップ後 {G4.sum()}")
print(f"4ステップで(1,1)だけ平行移動:{np.array_equal(G4, shifted)}")
出力:
生セル数:開始 5 → 4ステップ後 5
4ステップで(1,1)だけ平行移動:True
出力の意味:5つの生セルが、4ステップ後に形をそっくり保ったまま斜め下に1マス動いた(元を (1,1) 平行移動したものと完全一致)。これは「動く物体」が局所ルールから創発した例で、ライフゲームでは情報を運ぶ「信号」として使えます。ライフゲームはこうした部品を組み合わせると**万能計算(チューリング完全)**を実現でき、「単純な局所規則が任意の計算を生む」ことの象徴になっています。
数式の直観的意味
CA の本質は「大域的な設計図なしに、局所ルールの反復だけで大域構造が決まる」点です。グライダーが動いて見えるのは、実際には各セルが点滅しているだけ——「移動する物体」は私たちがパターンに見出す解釈で、物理的に動いているものはありません。それでもパターンは安定して伝播し、衝突し、相互作用する。畳み込み convolve2d が近傍の生数を数えるのは、各セルが「自分の周りしか見ていない(局所性)」ことの数学的表現です。複雑さは個々のセルではなく、多数の局所相互作用の積み重ねから生まれる——これが ABM 全体を貫く創発の原理で、Schelling や流行でも同じ構図が現れます。
⚠️ よくある誤解・落とし穴
- 「更新は順番に行う」ではない:全セルを同時に更新します。逐次更新すると別物(非同期 CA)になり結果が変わります。古い状態のコピーから新状態を計算すること。
- 「境界は気にしなくていい」ではない:端の扱い(周期境界・固定境界)で結果が変わります。本コードは
boundary='wrap'(トーラス)。 - 「ランダム要素がある」ではない:ライフゲームは決定的。初期配置が決まれば未来は一意。乱数を使う ABM(次トピック以降)とは対照的です。
- 「複雑な振る舞いには複雑なルールが要る」ではない:4つの単純ルールで万能計算まで到達します。複雑さはルールでなく相互作用から生まれます。
- 「セルオートマトンは現実離れした遊び」ではない:交通流・森林火災・結晶成長・反応拡散など、現実現象のモデルとして広く使われます。
対応シミュレーション参照
本文のライフゲーム(ブリンカー周期2・グライダー移動の確認)。決定的なので乱数シードは不要(初期配置で再現)。
関連ノート
- Schellingの分居モデル(次のトピック・乱数を含む ABM)
- 群集・流行のエージェントモデル(局所接触からの創発)
- モデル化の流れ(系・状態・入力・出力)(状態と局所ルールの設計)
- 第7章 エージェントベースモデル 目次
- シミュレーション・モンテカルロ法 全体目次